diff --git a/.gitignore b/.gitignore index e867d77..e2f17aa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ /.idea /.vscode -/vendor *.log .env /tests/tmp diff --git a/app/api/controller/IndexController.php b/app/api/controller/IndexController.php index a4b5eb1..7d3967e 100644 --- a/app/api/controller/IndexController.php +++ b/app/api/controller/IndexController.php @@ -6,6 +6,7 @@ use support\Request; use plugin\admin\app\model\Admin; use plugin\admin\app\controller\Base; use plugin\admin\app\common\Util; +use support\Redis; class IndexController extends Base { @@ -26,9 +27,9 @@ class IndexController extends Base $admin->login_at = date('Y-m-d H:i:s'); $admin->save(); $admin = $admin->toArray(); - $session = $request->session(); unset($admin['password']); - $session->set('admin', $admin); + $key=rand(); + $a=Redis::set($key, json_encode($admin,true), 2592000); return $this->json(200, '登录成功', [ "userinfo" => [ "id" => 1, @@ -39,7 +40,7 @@ class IndexController extends Base ], 'token' => [ "tokenName" => 'satoken', - "tokenValue" => $request->sessionId(), + "tokenValue" => $key, "isLogin" => "true", "loginId" => "1", "loginType" => "login", @@ -59,8 +60,7 @@ class IndexController extends Base public function logout(Request $request) { - $session = $request->session(); - $session->delete('admin'); + Redis::del($request->post('satoken')); return $this->json(200, '退出成功'); } } diff --git a/app/api/middleware/Auth.php b/app/api/middleware/Auth.php index a197a8b..baa78c2 100644 --- a/app/api/middleware/Auth.php +++ b/app/api/middleware/Auth.php @@ -18,6 +18,7 @@ namespace app\api\middleware; use Webman\MiddlewareInterface; use Webman\Http\Response; use Webman\Http\Request; +use support\Redis; class Auth implements MiddlewareInterface { @@ -33,12 +34,11 @@ class Auth implements MiddlewareInterface 'Access-Control-Allow-Methods' => $request->header('access-control-request-method', '*'), 'Access-Control-Allow-Headers' => $request->header('access-control-request-headers', '*'), ]); - $session = $request->session(); - $admin=$session->get('admin'); + $admin=Redis::get($request->header('satoken')); if(!$admin){ return json(['code'=>-1,'msg'=>'登录失效']); } - $request->admin=$admin; + $request->admin=json_decode($admin,true); return $handler($request); } diff --git a/composer.json b/composer.json index dfe53bc..64fce49 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,9 @@ "monolog/monolog": "^2.0", "webman/admin": "^0.6.13", "webman/think-orm": "^1.1", - "vlucas/phpdotenv": "^5.5" + "vlucas/phpdotenv": "^5.5", + "illuminate/redis": "^8.83", + "illuminate/events": "^8.83" }, "suggest": { "ext-event": "For better performance. " diff --git a/composer.lock b/composer.lock index 5496f16..aba0608 100644 --- a/composer.lock +++ b/composer.lock @@ -4,32 +4,32 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "d30f94cf75d6d6c6c07bb12adefb9a1f", + "content-hash": "ed766318d6b5fdc02c786ab7f27bc874", "packages": [ { "name": "doctrine/inflector", - "version": "2.0.6", + "version": "2.0.8", "source": { "type": "git", "url": "https://github.com/doctrine/inflector.git", - "reference": "d9d313a36c872fd6ee06d9a6cbcf713eaa40f024" + "reference": "f9301a5b2fb1216b2b08f02ba04dc45423db6bff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/inflector/zipball/d9d313a36c872fd6ee06d9a6cbcf713eaa40f024", - "reference": "d9d313a36c872fd6ee06d9a6cbcf713eaa40f024", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/f9301a5b2fb1216b2b08f02ba04dc45423db6bff", + "reference": "f9301a5b2fb1216b2b08f02ba04dc45423db6bff", "shasum": "" }, "require": { "php": "^7.2 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^10", + "doctrine/coding-standard": "^11.0", "phpstan/phpstan": "^1.8", "phpstan/phpstan-phpunit": "^1.1", "phpstan/phpstan-strict-rules": "^1.3", "phpunit/phpunit": "^8.5 || ^9.5", - "vimeo/psalm": "^4.25" + "vimeo/psalm": "^4.25 || ^5.4" }, "type": "library", "autoload": { @@ -79,7 +79,7 @@ ], "support": { "issues": "https://github.com/doctrine/inflector/issues", - "source": "https://github.com/doctrine/inflector/tree/2.0.6" + "source": "https://github.com/doctrine/inflector/tree/2.0.8" }, "funding": [ { @@ -95,7 +95,7 @@ "type": "tidelift" } ], - "time": "2022-10-20T09:10:12+00:00" + "time": "2023-06-16T13:40:37+00:00" }, { "name": "graham-campbell/result-type", @@ -957,6 +957,60 @@ }, "time": "2021-03-26T18:39:16+00:00" }, + { + "name": "illuminate/redis", + "version": "v8.83.27", + "source": { + "type": "git", + "url": "https://github.com/illuminate/redis.git", + "reference": "0fee121324054226823a59623fab3d98ad88fbd5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/redis/zipball/0fee121324054226823a59623fab3d98ad88fbd5", + "reference": "0fee121324054226823a59623fab3d98ad88fbd5", + "shasum": "" + }, + "require": { + "illuminate/collections": "^8.0", + "illuminate/contracts": "^8.0", + "illuminate/macroable": "^8.0", + "illuminate/support": "^8.0", + "php": "^7.3|^8.0" + }, + "suggest": { + "ext-redis": "Required to use the phpredis connector (^4.0|^5.0).", + "predis/predis": "Required to use the predis connector (^1.1.9)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "8.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Redis\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Redis package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2022-01-24T02:32:30+00:00" + }, { "name": "illuminate/support", "version": "v8.83.27", diff --git a/config/redis.php b/config/redis.php index 2f9757a..dd05854 100644 --- a/config/redis.php +++ b/config/redis.php @@ -17,6 +17,6 @@ return [ 'host' => '127.0.0.1', 'password' => null, 'port' => 6379, - 'database' => 0, + 'database' => 2, ], ]; diff --git a/vendor/autoload.php b/vendor/autoload.php new file mode 100644 index 0000000..6efe868 --- /dev/null +++ b/vendor/autoload.php @@ -0,0 +1,25 @@ +realpath = realpath($opened_path) ?: $opened_path; + $opened_path = $this->realpath; + $this->handle = fopen($this->realpath, $mode); + $this->position = 0; + + return (bool) $this->handle; + } + + public function stream_read($count) + { + $data = fread($this->handle, $count); + + if ($this->position === 0) { + $data = preg_replace('{^#!.*\r?\n}', '', $data); + } + + $this->position += strlen($data); + + return $data; + } + + public function stream_cast($castAs) + { + return $this->handle; + } + + public function stream_close() + { + fclose($this->handle); + } + + public function stream_lock($operation) + { + return $operation ? flock($this->handle, $operation) : true; + } + + public function stream_seek($offset, $whence) + { + if (0 === fseek($this->handle, $offset, $whence)) { + $this->position = ftell($this->handle); + return true; + } + + return false; + } + + public function stream_tell() + { + return $this->position; + } + + public function stream_eof() + { + return feof($this->handle); + } + + public function stream_stat() + { + return array(); + } + + public function stream_set_option($option, $arg1, $arg2) + { + return true; + } + + public function url_stat($path, $flags) + { + $path = substr($path, 17); + if (file_exists($path)) { + return stat($path); + } + + return false; + } + } + } + + if ( + (function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true)) + || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) + ) { + return include("phpvfscomposer://" . __DIR__ . '/..'.'/nesbot/carbon/bin/carbon'); + } +} + +return include __DIR__ . '/..'.'/nesbot/carbon/bin/carbon'; diff --git a/vendor/composer/ClassLoader.php b/vendor/composer/ClassLoader.php new file mode 100644 index 0000000..a72151c --- /dev/null +++ b/vendor/composer/ClassLoader.php @@ -0,0 +1,585 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see https://www.php-fig.org/psr/psr-0/ + * @see https://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + /** @var \Closure(string):void */ + private static $includeFile; + + /** @var ?string */ + private $vendorDir; + + // PSR-4 + /** + * @var array[] + * @psalm-var array> + */ + private $prefixLengthsPsr4 = array(); + /** + * @var array[] + * @psalm-var array> + */ + private $prefixDirsPsr4 = array(); + /** + * @var array[] + * @psalm-var array + */ + private $fallbackDirsPsr4 = array(); + + // PSR-0 + /** + * @var array[] + * @psalm-var array> + */ + private $prefixesPsr0 = array(); + /** + * @var array[] + * @psalm-var array + */ + private $fallbackDirsPsr0 = array(); + + /** @var bool */ + private $useIncludePath = false; + + /** + * @var string[] + * @psalm-var array + */ + private $classMap = array(); + + /** @var bool */ + private $classMapAuthoritative = false; + + /** + * @var bool[] + * @psalm-var array + */ + private $missingClasses = array(); + + /** @var ?string */ + private $apcuPrefix; + + /** + * @var self[] + */ + private static $registeredLoaders = array(); + + /** + * @param ?string $vendorDir + */ + public function __construct($vendorDir = null) + { + $this->vendorDir = $vendorDir; + self::initializeIncludeClosure(); + } + + /** + * @return string[] + */ + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); + } + + return array(); + } + + /** + * @return array[] + * @psalm-return array> + */ + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + /** + * @return array[] + * @psalm-return array + */ + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + /** + * @return array[] + * @psalm-return array + */ + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + /** + * @return string[] Array of classname => path + * @psalm-return array + */ + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param string[] $classMap Class to filename map + * @psalm-param array $classMap + * + * @return void + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param string[]|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + * + * @return void + */ + public function add($prefix, $paths, $prepend = false) + { + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + (array) $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + (array) $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = (array) $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + (array) $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param string[]|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + (array) $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + (array) $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + (array) $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param string[]|string $paths The PSR-0 base directories + * + * @return void + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param string[]|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + * + * @return void + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + * + * @return void + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + * + * @return void + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + * + * @return void + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + + if (null === $this->vendorDir) { + return; + } + + if ($prepend) { + self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; + } else { + unset(self::$registeredLoaders[$this->vendorDir]); + self::$registeredLoaders[$this->vendorDir] = $this; + } + } + + /** + * Unregisters this instance as an autoloader. + * + * @return void + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + + if (null !== $this->vendorDir) { + unset(self::$registeredLoaders[$this->vendorDir]); + } + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return true|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + $includeFile = self::$includeFile; + $includeFile($file); + + return true; + } + + return null; + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + /** + * Returns the currently registered loaders indexed by their corresponding vendor directories. + * + * @return self[] + */ + public static function getRegisteredLoaders() + { + return self::$registeredLoaders; + } + + /** + * @param string $class + * @param string $ext + * @return string|false + */ + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath . '\\'; + if (isset($this->prefixDirsPsr4[$search])) { + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); + foreach ($this->prefixDirsPsr4[$search] as $dir) { + if (file_exists($file = $dir . $pathEnd)) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } + + /** + * @return void + */ + private static function initializeIncludeClosure() + { + if (self::$includeFile !== null) { + return; + } + + /** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + * + * @param string $file + * @return void + */ + self::$includeFile = \Closure::bind(static function($file) { + include $file; + }, null, null); + } +} diff --git a/vendor/composer/InstalledVersions.php b/vendor/composer/InstalledVersions.php new file mode 100644 index 0000000..51e734a --- /dev/null +++ b/vendor/composer/InstalledVersions.php @@ -0,0 +1,359 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer; + +use Composer\Autoload\ClassLoader; +use Composer\Semver\VersionParser; + +/** + * This class is copied in every Composer installed project and available to all + * + * See also https://getcomposer.org/doc/07-runtime.md#installed-versions + * + * To require its presence, you can require `composer-runtime-api ^2.0` + * + * @final + */ +class InstalledVersions +{ + /** + * @var mixed[]|null + * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null + */ + private static $installed; + + /** + * @var bool|null + */ + private static $canGetVendors; + + /** + * @var array[] + * @psalm-var array}> + */ + private static $installedByVendor = array(); + + /** + * Returns a list of all package names which are present, either by being installed, replaced or provided + * + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackages() + { + $packages = array(); + foreach (self::getInstalled() as $installed) { + $packages[] = array_keys($installed['versions']); + } + + if (1 === \count($packages)) { + return $packages[0]; + } + + return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); + } + + /** + * Returns a list of all package names with a specific type e.g. 'library' + * + * @param string $type + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackagesByType($type) + { + $packagesByType = array(); + + foreach (self::getInstalled() as $installed) { + foreach ($installed['versions'] as $name => $package) { + if (isset($package['type']) && $package['type'] === $type) { + $packagesByType[] = $name; + } + } + } + + return $packagesByType; + } + + /** + * Checks whether the given package is installed + * + * This also returns true if the package name is provided or replaced by another package + * + * @param string $packageName + * @param bool $includeDevRequirements + * @return bool + */ + public static function isInstalled($packageName, $includeDevRequirements = true) + { + foreach (self::getInstalled() as $installed) { + if (isset($installed['versions'][$packageName])) { + return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false; + } + } + + return false; + } + + /** + * Checks whether the given package satisfies a version constraint + * + * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: + * + * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') + * + * @param VersionParser $parser Install composer/semver to have access to this class and functionality + * @param string $packageName + * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package + * @return bool + */ + public static function satisfies(VersionParser $parser, $packageName, $constraint) + { + $constraint = $parser->parseConstraints((string) $constraint); + $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); + + return $provided->matches($constraint); + } + + /** + * Returns a version constraint representing all the range(s) which are installed for a given package + * + * It is easier to use this via isInstalled() with the $constraint argument if you need to check + * whether a given version of a package is installed, and not just whether it exists + * + * @param string $packageName + * @return string Version constraint usable with composer/semver + */ + public static function getVersionRanges($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + $ranges = array(); + if (isset($installed['versions'][$packageName]['pretty_version'])) { + $ranges[] = $installed['versions'][$packageName]['pretty_version']; + } + if (array_key_exists('aliases', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); + } + if (array_key_exists('replaced', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); + } + if (array_key_exists('provided', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); + } + + return implode(' || ', $ranges); + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['version'])) { + return null; + } + + return $installed['versions'][$packageName]['version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getPrettyVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['pretty_version'])) { + return null; + } + + return $installed['versions'][$packageName]['pretty_version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference + */ + public static function getReference($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['reference'])) { + return null; + } + + return $installed['versions'][$packageName]['reference']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path. + */ + public static function getInstallPath($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @return array + * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool} + */ + public static function getRootPackage() + { + $installed = self::getInstalled(); + + return $installed[0]['root']; + } + + /** + * Returns the raw installed.php data for custom implementations + * + * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. + * @return array[] + * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} + */ + public static function getRawData() + { + @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + self::$installed = include __DIR__ . '/installed.php'; + } else { + self::$installed = array(); + } + } + + return self::$installed; + } + + /** + * Returns the raw data of all installed.php which are currently loaded for custom implementations + * + * @return array[] + * @psalm-return list}> + */ + public static function getAllRawData() + { + return self::getInstalled(); + } + + /** + * Lets you reload the static array from another file + * + * This is only useful for complex integrations in which a project needs to use + * this class but then also needs to execute another project's autoloader in process, + * and wants to ensure both projects have access to their version of installed.php. + * + * A typical case would be PHPUnit, where it would need to make sure it reads all + * the data it needs from this class, then call reload() with + * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure + * the project in which it runs can then also use this class safely, without + * interference between PHPUnit's dependencies and the project's dependencies. + * + * @param array[] $data A vendor/composer/installed.php data set + * @return void + * + * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $data + */ + public static function reload($data) + { + self::$installed = $data; + self::$installedByVendor = array(); + } + + /** + * @return array[] + * @psalm-return list}> + */ + private static function getInstalled() + { + if (null === self::$canGetVendors) { + self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); + } + + $installed = array(); + + if (self::$canGetVendors) { + foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { + if (isset(self::$installedByVendor[$vendorDir])) { + $installed[] = self::$installedByVendor[$vendorDir]; + } elseif (is_file($vendorDir.'/composer/installed.php')) { + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ + $required = require $vendorDir.'/composer/installed.php'; + $installed[] = self::$installedByVendor[$vendorDir] = $required; + if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { + self::$installed = $installed[count($installed) - 1]; + } + } + } + } + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ + $required = require __DIR__ . '/installed.php'; + self::$installed = $required; + } else { + self::$installed = array(); + } + } + + if (self::$installed !== array()) { + $installed[] = self::$installed; + } + + return $installed; + } +} diff --git a/vendor/composer/LICENSE b/vendor/composer/LICENSE new file mode 100644 index 0000000..f27399a --- /dev/null +++ b/vendor/composer/LICENSE @@ -0,0 +1,21 @@ + +Copyright (c) Nils Adermann, Jordi Boggiano + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php new file mode 100644 index 0000000..351cc2f --- /dev/null +++ b/vendor/composer/autoload_classmap.php @@ -0,0 +1,17 @@ + $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', + 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', + 'JsonException' => $vendorDir . '/symfony/polyfill-php73/Resources/stubs/JsonException.php', + 'Normalizer' => $vendorDir . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php', + 'PhpToken' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php', + 'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', + 'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', + 'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', +); diff --git a/vendor/composer/autoload_files.php b/vendor/composer/autoload_files.php new file mode 100644 index 0000000..1bd5219 --- /dev/null +++ b/vendor/composer/autoload_files.php @@ -0,0 +1,27 @@ + $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', + 'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php', + '6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php', + '60799491728b879e74601d83e38b2cad' => $vendorDir . '/illuminate/collections/helpers.php', + 'a1105708a18b76903365ca1c4aa61b02' => $vendorDir . '/symfony/translation/Resources/functions.php', + '72579e7bd17821bb1321b87411366eae' => $vendorDir . '/illuminate/support/helpers.php', + '7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php', + '320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php', + '8825ede83f2f289127722d4e842cf7e8' => $vendorDir . '/symfony/polyfill-intl-grapheme/bootstrap.php', + 'e69f7f6ee287b969198c3c9d6777bd38' => $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php', + '253c157292f75eb38082b5acb06f3f01' => $vendorDir . '/nikic/fast-route/src/functions.php', + '0d59ee240a4cd96ddbb4ff164fccea4d' => $vendorDir . '/symfony/polyfill-php73/bootstrap.php', + 'b6b991a57620e2fb6b2f66f03fe9ddc2' => $vendorDir . '/symfony/string/Resources/functions.php', + '9b552a3cc426e3287cc811caefa3cf53' => $vendorDir . '/topthink/think-helper/src/helper.php', + '37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php', + 'ef65a1626449d89d0811cf9befce46f0' => $vendorDir . '/illuminate/events/functions.php', + '35fab96057f1bf5e7aba31a8a6d5fdde' => $vendorDir . '/topthink/think-orm/stubs/load_stubs.php', + 'da5b71a9ad8465d48da441e2f36823b6' => $baseDir . '/support/helpers.php', +); diff --git a/vendor/composer/autoload_namespaces.php b/vendor/composer/autoload_namespaces.php new file mode 100644 index 0000000..15a2ff3 --- /dev/null +++ b/vendor/composer/autoload_namespaces.php @@ -0,0 +1,9 @@ + array($vendorDir . '/voku/portable-ascii/src/voku'), + 'think\\' => array($vendorDir . '/topthink/think-helper/src', $vendorDir . '/topthink/think-orm/src'), + 'support\\' => array($vendorDir . '/workerman/webman-framework/src/support'), + 'app\\View\\Components\\' => array($baseDir . '/app/view/components'), + 'app\\' => array($baseDir . '/app'), + 'Workerman\\' => array($vendorDir . '/workerman/workerman'), + 'Webman\\ThinkOrm\\' => array($vendorDir . '/webman/think-orm/src'), + 'Webman\\Event\\' => array($vendorDir . '/webman/event/src'), + 'Webman\\Captcha\\' => array($vendorDir . '/webman/captcha/src'), + 'Webman\\Admin\\' => array($vendorDir . '/webman/admin/src'), + 'Webman\\' => array($vendorDir . '/workerman/webman-framework/src'), + 'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'), + 'Symfony\\Polyfill\\Php73\\' => array($vendorDir . '/symfony/polyfill-php73'), + 'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'), + 'Symfony\\Polyfill\\Intl\\Normalizer\\' => array($vendorDir . '/symfony/polyfill-intl-normalizer'), + 'Symfony\\Polyfill\\Intl\\Grapheme\\' => array($vendorDir . '/symfony/polyfill-intl-grapheme'), + 'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'), + 'Symfony\\Contracts\\Translation\\' => array($vendorDir . '/symfony/translation-contracts'), + 'Symfony\\Contracts\\Service\\' => array($vendorDir . '/symfony/service-contracts'), + 'Symfony\\Component\\Translation\\' => array($vendorDir . '/symfony/translation'), + 'Symfony\\Component\\String\\' => array($vendorDir . '/symfony/string'), + 'Symfony\\Component\\Console\\' => array($vendorDir . '/symfony/console'), + 'Support\\View\\' => array($vendorDir . '/workerman/webman-framework/src/support/view'), + 'Support\\Exception\\' => array($vendorDir . '/workerman/webman-framework/src/support/exception'), + 'Support\\Bootstrap\\' => array($vendorDir . '/workerman/webman-framework/src/support/bootstrap'), + 'Support\\' => array($vendorDir . '/workerman/webman-framework/src/support'), + 'Psr\\SimpleCache\\' => array($vendorDir . '/psr/simple-cache/src'), + 'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'), + 'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-factory/src', $vendorDir . '/psr/http-message/src'), + 'Psr\\Http\\Client\\' => array($vendorDir . '/psr/http-client/src'), + 'Psr\\Container\\' => array($vendorDir . '/psr/container/src'), + 'PhpOption\\' => array($vendorDir . '/phpoption/phpoption/src/PhpOption'), + 'Monolog\\' => array($vendorDir . '/monolog/monolog/src/Monolog'), + 'Intervention\\Image\\' => array($vendorDir . '/intervention/image/src/Intervention/Image'), + 'Illuminate\\Support\\' => array($vendorDir . '/illuminate/collections', $vendorDir . '/illuminate/macroable', $vendorDir . '/illuminate/support'), + 'Illuminate\\Redis\\' => array($vendorDir . '/illuminate/redis'), + 'Illuminate\\Pipeline\\' => array($vendorDir . '/illuminate/pipeline'), + 'Illuminate\\Pagination\\' => array($vendorDir . '/illuminate/pagination'), + 'Illuminate\\Events\\' => array($vendorDir . '/illuminate/events'), + 'Illuminate\\Database\\' => array($vendorDir . '/illuminate/database'), + 'Illuminate\\Contracts\\' => array($vendorDir . '/illuminate/contracts'), + 'Illuminate\\Container\\' => array($vendorDir . '/illuminate/container'), + 'Illuminate\\Bus\\' => array($vendorDir . '/illuminate/bus'), + 'GuzzleHttp\\Psr7\\' => array($vendorDir . '/guzzlehttp/psr7/src'), + 'GuzzleHttp\\Promise\\' => array($vendorDir . '/guzzlehttp/promises/src'), + 'GuzzleHttp\\' => array($vendorDir . '/guzzlehttp/guzzle/src'), + 'GrahamCampbell\\ResultType\\' => array($vendorDir . '/graham-campbell/result-type/src'), + 'FastRoute\\' => array($vendorDir . '/nikic/fast-route/src'), + 'Dotenv\\' => array($vendorDir . '/vlucas/phpdotenv/src'), + 'Doctrine\\Inflector\\' => array($vendorDir . '/doctrine/inflector/lib/Doctrine/Inflector'), + 'Carbon\\' => array($vendorDir . '/nesbot/carbon/src/Carbon'), + 'App\\' => array($baseDir . '/app'), + '' => array($baseDir . '/'), +); diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php new file mode 100644 index 0000000..79d9af9 --- /dev/null +++ b/vendor/composer/autoload_real.php @@ -0,0 +1,50 @@ +register(true); + + $filesToLoad = \Composer\Autoload\ComposerStaticInitd74e1fcdbb076080cce04a84d4bbcd73::$files; + $requireFile = \Closure::bind(static function ($fileIdentifier, $file) { + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + + require $file; + } + }, null, null); + foreach ($filesToLoad as $fileIdentifier => $file) { + $requireFile($fileIdentifier, $file); + } + + return $loader; + } +} diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php new file mode 100644 index 0000000..fd1cb8d --- /dev/null +++ b/vendor/composer/autoload_static.php @@ -0,0 +1,367 @@ + __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', + 'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php', + '6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php', + '60799491728b879e74601d83e38b2cad' => __DIR__ . '/..' . '/illuminate/collections/helpers.php', + 'a1105708a18b76903365ca1c4aa61b02' => __DIR__ . '/..' . '/symfony/translation/Resources/functions.php', + '72579e7bd17821bb1321b87411366eae' => __DIR__ . '/..' . '/illuminate/support/helpers.php', + '7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php', + '320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php', + '8825ede83f2f289127722d4e842cf7e8' => __DIR__ . '/..' . '/symfony/polyfill-intl-grapheme/bootstrap.php', + 'e69f7f6ee287b969198c3c9d6777bd38' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/bootstrap.php', + '253c157292f75eb38082b5acb06f3f01' => __DIR__ . '/..' . '/nikic/fast-route/src/functions.php', + '0d59ee240a4cd96ddbb4ff164fccea4d' => __DIR__ . '/..' . '/symfony/polyfill-php73/bootstrap.php', + 'b6b991a57620e2fb6b2f66f03fe9ddc2' => __DIR__ . '/..' . '/symfony/string/Resources/functions.php', + '9b552a3cc426e3287cc811caefa3cf53' => __DIR__ . '/..' . '/topthink/think-helper/src/helper.php', + '37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php', + 'ef65a1626449d89d0811cf9befce46f0' => __DIR__ . '/..' . '/illuminate/events/functions.php', + '35fab96057f1bf5e7aba31a8a6d5fdde' => __DIR__ . '/..' . '/topthink/think-orm/stubs/load_stubs.php', + 'da5b71a9ad8465d48da441e2f36823b6' => __DIR__ . '/../..' . '/support/helpers.php', + ); + + public static $prefixLengthsPsr4 = array ( + 'v' => + array ( + 'voku\\' => 5, + ), + 't' => + array ( + 'think\\' => 6, + ), + 's' => + array ( + 'support\\' => 8, + ), + 'a' => + array ( + 'app\\View\\Components\\' => 20, + 'app\\' => 4, + ), + 'W' => + array ( + 'Workerman\\' => 10, + 'Webman\\ThinkOrm\\' => 16, + 'Webman\\Event\\' => 13, + 'Webman\\Captcha\\' => 15, + 'Webman\\Admin\\' => 13, + 'Webman\\' => 7, + ), + 'S' => + array ( + 'Symfony\\Polyfill\\Php80\\' => 23, + 'Symfony\\Polyfill\\Php73\\' => 23, + 'Symfony\\Polyfill\\Mbstring\\' => 26, + 'Symfony\\Polyfill\\Intl\\Normalizer\\' => 33, + 'Symfony\\Polyfill\\Intl\\Grapheme\\' => 31, + 'Symfony\\Polyfill\\Ctype\\' => 23, + 'Symfony\\Contracts\\Translation\\' => 30, + 'Symfony\\Contracts\\Service\\' => 26, + 'Symfony\\Component\\Translation\\' => 30, + 'Symfony\\Component\\String\\' => 25, + 'Symfony\\Component\\Console\\' => 26, + 'Support\\View\\' => 13, + 'Support\\Exception\\' => 18, + 'Support\\Bootstrap\\' => 18, + 'Support\\' => 8, + ), + 'P' => + array ( + 'Psr\\SimpleCache\\' => 16, + 'Psr\\Log\\' => 8, + 'Psr\\Http\\Message\\' => 17, + 'Psr\\Http\\Client\\' => 16, + 'Psr\\Container\\' => 14, + 'PhpOption\\' => 10, + ), + 'M' => + array ( + 'Monolog\\' => 8, + ), + 'I' => + array ( + 'Intervention\\Image\\' => 19, + 'Illuminate\\Support\\' => 19, + 'Illuminate\\Redis\\' => 17, + 'Illuminate\\Pipeline\\' => 20, + 'Illuminate\\Pagination\\' => 22, + 'Illuminate\\Events\\' => 18, + 'Illuminate\\Database\\' => 20, + 'Illuminate\\Contracts\\' => 21, + 'Illuminate\\Container\\' => 21, + 'Illuminate\\Bus\\' => 15, + ), + 'G' => + array ( + 'GuzzleHttp\\Psr7\\' => 16, + 'GuzzleHttp\\Promise\\' => 19, + 'GuzzleHttp\\' => 11, + 'GrahamCampbell\\ResultType\\' => 26, + ), + 'F' => + array ( + 'FastRoute\\' => 10, + ), + 'D' => + array ( + 'Dotenv\\' => 7, + 'Doctrine\\Inflector\\' => 19, + ), + 'C' => + array ( + 'Carbon\\' => 7, + ), + 'A' => + array ( + 'App\\' => 4, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'voku\\' => + array ( + 0 => __DIR__ . '/..' . '/voku/portable-ascii/src/voku', + ), + 'think\\' => + array ( + 0 => __DIR__ . '/..' . '/topthink/think-helper/src', + 1 => __DIR__ . '/..' . '/topthink/think-orm/src', + ), + 'support\\' => + array ( + 0 => __DIR__ . '/..' . '/workerman/webman-framework/src/support', + ), + 'app\\View\\Components\\' => + array ( + 0 => __DIR__ . '/../..' . '/app/view/components', + ), + 'app\\' => + array ( + 0 => __DIR__ . '/../..' . '/app', + ), + 'Workerman\\' => + array ( + 0 => __DIR__ . '/..' . '/workerman/workerman', + ), + 'Webman\\ThinkOrm\\' => + array ( + 0 => __DIR__ . '/..' . '/webman/think-orm/src', + ), + 'Webman\\Event\\' => + array ( + 0 => __DIR__ . '/..' . '/webman/event/src', + ), + 'Webman\\Captcha\\' => + array ( + 0 => __DIR__ . '/..' . '/webman/captcha/src', + ), + 'Webman\\Admin\\' => + array ( + 0 => __DIR__ . '/..' . '/webman/admin/src', + ), + 'Webman\\' => + array ( + 0 => __DIR__ . '/..' . '/workerman/webman-framework/src', + ), + 'Symfony\\Polyfill\\Php80\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php80', + ), + 'Symfony\\Polyfill\\Php73\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php73', + ), + 'Symfony\\Polyfill\\Mbstring\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring', + ), + 'Symfony\\Polyfill\\Intl\\Normalizer\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer', + ), + 'Symfony\\Polyfill\\Intl\\Grapheme\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-intl-grapheme', + ), + 'Symfony\\Polyfill\\Ctype\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-ctype', + ), + 'Symfony\\Contracts\\Translation\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/translation-contracts', + ), + 'Symfony\\Contracts\\Service\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/service-contracts', + ), + 'Symfony\\Component\\Translation\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/translation', + ), + 'Symfony\\Component\\String\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/string', + ), + 'Symfony\\Component\\Console\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/console', + ), + 'Support\\View\\' => + array ( + 0 => __DIR__ . '/..' . '/workerman/webman-framework/src/support/view', + ), + 'Support\\Exception\\' => + array ( + 0 => __DIR__ . '/..' . '/workerman/webman-framework/src/support/exception', + ), + 'Support\\Bootstrap\\' => + array ( + 0 => __DIR__ . '/..' . '/workerman/webman-framework/src/support/bootstrap', + ), + 'Support\\' => + array ( + 0 => __DIR__ . '/..' . '/workerman/webman-framework/src/support', + ), + 'Psr\\SimpleCache\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/simple-cache/src', + ), + 'Psr\\Log\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/log/Psr/Log', + ), + 'Psr\\Http\\Message\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/http-factory/src', + 1 => __DIR__ . '/..' . '/psr/http-message/src', + ), + 'Psr\\Http\\Client\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/http-client/src', + ), + 'Psr\\Container\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/container/src', + ), + 'PhpOption\\' => + array ( + 0 => __DIR__ . '/..' . '/phpoption/phpoption/src/PhpOption', + ), + 'Monolog\\' => + array ( + 0 => __DIR__ . '/..' . '/monolog/monolog/src/Monolog', + ), + 'Intervention\\Image\\' => + array ( + 0 => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image', + ), + 'Illuminate\\Support\\' => + array ( + 0 => __DIR__ . '/..' . '/illuminate/collections', + 1 => __DIR__ . '/..' . '/illuminate/macroable', + 2 => __DIR__ . '/..' . '/illuminate/support', + ), + 'Illuminate\\Redis\\' => + array ( + 0 => __DIR__ . '/..' . '/illuminate/redis', + ), + 'Illuminate\\Pipeline\\' => + array ( + 0 => __DIR__ . '/..' . '/illuminate/pipeline', + ), + 'Illuminate\\Pagination\\' => + array ( + 0 => __DIR__ . '/..' . '/illuminate/pagination', + ), + 'Illuminate\\Events\\' => + array ( + 0 => __DIR__ . '/..' . '/illuminate/events', + ), + 'Illuminate\\Database\\' => + array ( + 0 => __DIR__ . '/..' . '/illuminate/database', + ), + 'Illuminate\\Contracts\\' => + array ( + 0 => __DIR__ . '/..' . '/illuminate/contracts', + ), + 'Illuminate\\Container\\' => + array ( + 0 => __DIR__ . '/..' . '/illuminate/container', + ), + 'Illuminate\\Bus\\' => + array ( + 0 => __DIR__ . '/..' . '/illuminate/bus', + ), + 'GuzzleHttp\\Psr7\\' => + array ( + 0 => __DIR__ . '/..' . '/guzzlehttp/psr7/src', + ), + 'GuzzleHttp\\Promise\\' => + array ( + 0 => __DIR__ . '/..' . '/guzzlehttp/promises/src', + ), + 'GuzzleHttp\\' => + array ( + 0 => __DIR__ . '/..' . '/guzzlehttp/guzzle/src', + ), + 'GrahamCampbell\\ResultType\\' => + array ( + 0 => __DIR__ . '/..' . '/graham-campbell/result-type/src', + ), + 'FastRoute\\' => + array ( + 0 => __DIR__ . '/..' . '/nikic/fast-route/src', + ), + 'Dotenv\\' => + array ( + 0 => __DIR__ . '/..' . '/vlucas/phpdotenv/src', + ), + 'Doctrine\\Inflector\\' => + array ( + 0 => __DIR__ . '/..' . '/doctrine/inflector/lib/Doctrine/Inflector', + ), + 'Carbon\\' => + array ( + 0 => __DIR__ . '/..' . '/nesbot/carbon/src/Carbon', + ), + 'App\\' => + array ( + 0 => __DIR__ . '/../..' . '/app', + ), + ); + + public static $fallbackDirsPsr4 = array ( + 0 => __DIR__ . '/../..' . '/', + ); + + public static $classMap = array ( + 'Attribute' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', + 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', + 'JsonException' => __DIR__ . '/..' . '/symfony/polyfill-php73/Resources/stubs/JsonException.php', + 'Normalizer' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php', + 'PhpToken' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php', + 'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', + 'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', + 'ValueError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInitd74e1fcdbb076080cce04a84d4bbcd73::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInitd74e1fcdbb076080cce04a84d4bbcd73::$prefixDirsPsr4; + $loader->fallbackDirsPsr4 = ComposerStaticInitd74e1fcdbb076080cce04a84d4bbcd73::$fallbackDirsPsr4; + $loader->classMap = ComposerStaticInitd74e1fcdbb076080cce04a84d4bbcd73::$classMap; + + }, null, ClassLoader::class); + } +} diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json new file mode 100644 index 0000000..9b430f4 --- /dev/null +++ b/vendor/composer/installed.json @@ -0,0 +1,3545 @@ +{ + "packages": [ + { + "name": "doctrine/inflector", + "version": "2.0.8", + "version_normalized": "2.0.8.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/inflector.git", + "reference": "f9301a5b2fb1216b2b08f02ba04dc45423db6bff" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/f9301a5b2fb1216b2b08f02ba04dc45423db6bff", + "reference": "f9301a5b2fb1216b2b08f02ba04dc45423db6bff", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^11.0", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.1", + "phpstan/phpstan-strict-rules": "^1.3", + "phpunit/phpunit": "^8.5 || ^9.5", + "vimeo/psalm": "^4.25 || ^5.4" + }, + "time": "2023-06-16T13:40:37+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Doctrine\\Inflector\\": "lib/Doctrine/Inflector" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.", + "homepage": "https://www.doctrine-project.org/projects/inflector.html", + "keywords": [ + "inflection", + "inflector", + "lowercase", + "manipulation", + "php", + "plural", + "singular", + "strings", + "uppercase", + "words" + ], + "support": { + "issues": "https://github.com/doctrine/inflector/issues", + "source": "https://github.com/doctrine/inflector/tree/2.0.8" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finflector", + "type": "tidelift" + } + ], + "install-path": "../doctrine/inflector" + }, + { + "name": "graham-campbell/result-type", + "version": "v1.1.1", + "version_normalized": "1.1.1.0", + "source": { + "type": "git", + "url": "https://github.com/GrahamCampbell/Result-Type.git", + "reference": "672eff8cf1d6fe1ef09ca0f89c4b287d6a3eb831" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/672eff8cf1d6fe1ef09ca0f89c4b287d6a3eb831", + "reference": "672eff8cf1d6fe1ef09ca0f89c4b287d6a3eb831", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9.1" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.32 || ^9.6.3 || ^10.0.12" + }, + "time": "2023-02-25T20:23:15+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "GrahamCampbell\\ResultType\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "An Implementation Of The Result Type", + "keywords": [ + "Graham Campbell", + "GrahamCampbell", + "Result Type", + "Result-Type", + "result" + ], + "support": { + "issues": "https://github.com/GrahamCampbell/Result-Type/issues", + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.1" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type", + "type": "tidelift" + } + ], + "install-path": "../graham-campbell/result-type" + }, + { + "name": "guzzlehttp/guzzle", + "version": "7.7.0", + "version_normalized": "7.7.0.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "fb7566caccf22d74d1ab270de3551f72a58399f5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/fb7566caccf22d74d1ab270de3551f72a58399f5", + "reference": "fb7566caccf22d74d1ab270de3551f72a58399f5", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.5.3 || ^2.0", + "guzzlehttp/psr7": "^1.9.1 || ^2.4.5", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.1", + "ext-curl": "*", + "php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999", + "php-http/message-factory": "^1.1", + "phpunit/phpunit": "^8.5.29 || ^9.5.23", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "time": "2023-05-21T14:04:53+00:00", + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.7.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } + ], + "install-path": "../guzzlehttp/guzzle" + }, + { + "name": "guzzlehttp/promises", + "version": "2.0.0", + "version_normalized": "2.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "3a494dc7dc1d7d12e511890177ae2d0e6c107da6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/3a494dc7dc1d7d12e511890177ae2d0e6c107da6", + "reference": "3a494dc7dc1d7d12e511890177ae2d0e6c107da6", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.1", + "phpunit/phpunit": "^8.5.29 || ^9.5.23" + }, + "time": "2023-05-21T13:50:22+00:00", + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/2.0.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "install-path": "../guzzlehttp/promises" + }, + { + "name": "guzzlehttp/psr7", + "version": "2.5.0", + "version_normalized": "2.5.0.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "b635f279edd83fc275f822a1188157ffea568ff6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/b635f279edd83fc275f822a1188157ffea568ff6", + "reference": "b635f279edd83fc275f822a1188157ffea568ff6", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0", + "ralouphie/getallheaders": "^3.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.1", + "http-interop/http-factory-tests": "^0.9", + "phpunit/phpunit": "^8.5.29 || ^9.5.23" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "time": "2023-04-17T16:11:26+00:00", + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.5.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "install-path": "../guzzlehttp/psr7" + }, + { + "name": "illuminate/bus", + "version": "v8.83.27", + "version_normalized": "8.83.27.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/bus.git", + "reference": "d2a8ae4bfd881086e55455e470776358eab27eae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/bus/zipball/d2a8ae4bfd881086e55455e470776358eab27eae", + "reference": "d2a8ae4bfd881086e55455e470776358eab27eae", + "shasum": "" + }, + "require": { + "illuminate/collections": "^8.0", + "illuminate/contracts": "^8.0", + "illuminate/pipeline": "^8.0", + "illuminate/support": "^8.0", + "php": "^7.3|^8.0" + }, + "suggest": { + "illuminate/queue": "Required to use closures when chaining jobs (^7.0)." + }, + "time": "2022-03-07T15:02:42+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "8.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Illuminate\\Bus\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Bus package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "install-path": "../illuminate/bus" + }, + { + "name": "illuminate/collections", + "version": "v8.83.27", + "version_normalized": "8.83.27.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/collections.git", + "reference": "705a4e1ef93cd492c45b9b3e7911cccc990a07f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/collections/zipball/705a4e1ef93cd492c45b9b3e7911cccc990a07f4", + "reference": "705a4e1ef93cd492c45b9b3e7911cccc990a07f4", + "shasum": "" + }, + "require": { + "illuminate/contracts": "^8.0", + "illuminate/macroable": "^8.0", + "php": "^7.3|^8.0" + }, + "suggest": { + "symfony/var-dumper": "Required to use the dump method (^5.4)." + }, + "time": "2022-06-23T15:29:49+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "8.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "helpers.php" + ], + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Collections package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "install-path": "../illuminate/collections" + }, + { + "name": "illuminate/container", + "version": "v8.83.27", + "version_normalized": "8.83.27.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/container.git", + "reference": "14062628d05f75047c5a1360b9350028427d568e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/container/zipball/14062628d05f75047c5a1360b9350028427d568e", + "reference": "14062628d05f75047c5a1360b9350028427d568e", + "shasum": "" + }, + "require": { + "illuminate/contracts": "^8.0", + "php": "^7.3|^8.0", + "psr/container": "^1.0" + }, + "provide": { + "psr/container-implementation": "1.0" + }, + "time": "2022-02-02T21:03:35+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "8.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Illuminate\\Container\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Container package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "install-path": "../illuminate/container" + }, + { + "name": "illuminate/contracts", + "version": "v8.83.27", + "version_normalized": "8.83.27.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/contracts.git", + "reference": "5e0fd287a1b22a6b346a9f7cd484d8cf0234585d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/contracts/zipball/5e0fd287a1b22a6b346a9f7cd484d8cf0234585d", + "reference": "5e0fd287a1b22a6b346a9f7cd484d8cf0234585d", + "shasum": "" + }, + "require": { + "php": "^7.3|^8.0", + "psr/container": "^1.0", + "psr/simple-cache": "^1.0" + }, + "time": "2022-01-13T14:47:47+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "8.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Illuminate\\Contracts\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Contracts package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "install-path": "../illuminate/contracts" + }, + { + "name": "illuminate/database", + "version": "v8.83.27", + "version_normalized": "8.83.27.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/database.git", + "reference": "1a5b0e4e6913415464fa2aab554a38b9e6fa44b1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/database/zipball/1a5b0e4e6913415464fa2aab554a38b9e6fa44b1", + "reference": "1a5b0e4e6913415464fa2aab554a38b9e6fa44b1", + "shasum": "" + }, + "require": { + "ext-json": "*", + "illuminate/collections": "^8.0", + "illuminate/container": "^8.0", + "illuminate/contracts": "^8.0", + "illuminate/macroable": "^8.0", + "illuminate/support": "^8.0", + "php": "^7.3|^8.0", + "symfony/console": "^5.4" + }, + "suggest": { + "doctrine/dbal": "Required to rename columns and drop SQLite columns (^2.13.3|^3.1.4).", + "fakerphp/faker": "Required to use the eloquent factory builder (^1.9.1).", + "illuminate/console": "Required to use the database commands (^8.0).", + "illuminate/events": "Required to use the observers with Eloquent (^8.0).", + "illuminate/filesystem": "Required to use the migrations (^8.0).", + "illuminate/pagination": "Required to paginate the result set (^8.0).", + "symfony/finder": "Required to use Eloquent model factories (^5.4)." + }, + "time": "2022-08-31T16:16:06+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "8.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Illuminate\\Database\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Database package.", + "homepage": "https://laravel.com", + "keywords": [ + "database", + "laravel", + "orm", + "sql" + ], + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "install-path": "../illuminate/database" + }, + { + "name": "illuminate/events", + "version": "v8.83.27", + "version_normalized": "8.83.27.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/events.git", + "reference": "b7f06cafb6c09581617f2ca05d69e9b159e5a35d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/events/zipball/b7f06cafb6c09581617f2ca05d69e9b159e5a35d", + "reference": "b7f06cafb6c09581617f2ca05d69e9b159e5a35d", + "shasum": "" + }, + "require": { + "illuminate/bus": "^8.0", + "illuminate/collections": "^8.0", + "illuminate/container": "^8.0", + "illuminate/contracts": "^8.0", + "illuminate/macroable": "^8.0", + "illuminate/support": "^8.0", + "php": "^7.3|^8.0" + }, + "time": "2021-09-15T14:32:50+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "8.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "functions.php" + ], + "psr-4": { + "Illuminate\\Events\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Events package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "install-path": "../illuminate/events" + }, + { + "name": "illuminate/macroable", + "version": "v8.83.27", + "version_normalized": "8.83.27.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/macroable.git", + "reference": "aed81891a6e046fdee72edd497f822190f61c162" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/macroable/zipball/aed81891a6e046fdee72edd497f822190f61c162", + "reference": "aed81891a6e046fdee72edd497f822190f61c162", + "shasum": "" + }, + "require": { + "php": "^7.3|^8.0" + }, + "time": "2021-11-16T13:57:03+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "8.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Macroable package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "install-path": "../illuminate/macroable" + }, + { + "name": "illuminate/pagination", + "version": "v8.83.27", + "version_normalized": "8.83.27.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/pagination.git", + "reference": "16fe8dc35f9d18c58a3471469af656a02e9ab692" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/pagination/zipball/16fe8dc35f9d18c58a3471469af656a02e9ab692", + "reference": "16fe8dc35f9d18c58a3471469af656a02e9ab692", + "shasum": "" + }, + "require": { + "ext-json": "*", + "illuminate/collections": "^8.0", + "illuminate/contracts": "^8.0", + "illuminate/support": "^8.0", + "php": "^7.3|^8.0" + }, + "time": "2022-06-27T13:26:06+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "8.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Illuminate\\Pagination\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Pagination package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "install-path": "../illuminate/pagination" + }, + { + "name": "illuminate/pipeline", + "version": "v8.83.27", + "version_normalized": "8.83.27.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/pipeline.git", + "reference": "23aeff5b26ae4aee3f370835c76bd0f4e93f71d2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/pipeline/zipball/23aeff5b26ae4aee3f370835c76bd0f4e93f71d2", + "reference": "23aeff5b26ae4aee3f370835c76bd0f4e93f71d2", + "shasum": "" + }, + "require": { + "illuminate/contracts": "^8.0", + "illuminate/support": "^8.0", + "php": "^7.3|^8.0" + }, + "time": "2021-03-26T18:39:16+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "8.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Illuminate\\Pipeline\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Pipeline package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "install-path": "../illuminate/pipeline" + }, + { + "name": "illuminate/redis", + "version": "v8.83.27", + "version_normalized": "8.83.27.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/redis.git", + "reference": "0fee121324054226823a59623fab3d98ad88fbd5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/redis/zipball/0fee121324054226823a59623fab3d98ad88fbd5", + "reference": "0fee121324054226823a59623fab3d98ad88fbd5", + "shasum": "" + }, + "require": { + "illuminate/collections": "^8.0", + "illuminate/contracts": "^8.0", + "illuminate/macroable": "^8.0", + "illuminate/support": "^8.0", + "php": "^7.3|^8.0" + }, + "suggest": { + "ext-redis": "Required to use the phpredis connector (^4.0|^5.0).", + "predis/predis": "Required to use the predis connector (^1.1.9)." + }, + "time": "2022-01-24T02:32:30+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "8.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Illuminate\\Redis\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Redis package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "install-path": "../illuminate/redis" + }, + { + "name": "illuminate/support", + "version": "v8.83.27", + "version_normalized": "8.83.27.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/support.git", + "reference": "1c79242468d3bbd9a0f7477df34f9647dde2a09b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/support/zipball/1c79242468d3bbd9a0f7477df34f9647dde2a09b", + "reference": "1c79242468d3bbd9a0f7477df34f9647dde2a09b", + "shasum": "" + }, + "require": { + "doctrine/inflector": "^1.4|^2.0", + "ext-json": "*", + "ext-mbstring": "*", + "illuminate/collections": "^8.0", + "illuminate/contracts": "^8.0", + "illuminate/macroable": "^8.0", + "nesbot/carbon": "^2.53.1", + "php": "^7.3|^8.0", + "voku/portable-ascii": "^1.6.1" + }, + "conflict": { + "tightenco/collect": "<5.5.33" + }, + "suggest": { + "illuminate/filesystem": "Required to use the composer class (^8.0).", + "league/commonmark": "Required to use Str::markdown() and Stringable::markdown() (^1.3|^2.0.2).", + "ramsey/uuid": "Required to use Str::uuid() (^4.2.2).", + "symfony/process": "Required to use the composer class (^5.4).", + "symfony/var-dumper": "Required to use the dd function (^5.4).", + "vlucas/phpdotenv": "Required to use the Env class and env helper (^5.4.1)." + }, + "time": "2022-09-21T21:30:03+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "8.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "helpers.php" + ], + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Support package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "install-path": "../illuminate/support" + }, + { + "name": "intervention/image", + "version": "2.7.2", + "version_normalized": "2.7.2.0", + "source": { + "type": "git", + "url": "https://github.com/Intervention/image.git", + "reference": "04be355f8d6734c826045d02a1079ad658322dad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Intervention/image/zipball/04be355f8d6734c826045d02a1079ad658322dad", + "reference": "04be355f8d6734c826045d02a1079ad658322dad", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "guzzlehttp/psr7": "~1.1 || ^2.0", + "php": ">=5.4.0" + }, + "require-dev": { + "mockery/mockery": "~0.9.2", + "phpunit/phpunit": "^4.8 || ^5.7 || ^7.5.15" + }, + "suggest": { + "ext-gd": "to use GD library based image processing.", + "ext-imagick": "to use Imagick based image processing.", + "intervention/imagecache": "Caching extension for the Intervention Image library" + }, + "time": "2022-05-21T17:30:32+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + }, + "laravel": { + "providers": [ + "Intervention\\Image\\ImageServiceProvider" + ], + "aliases": { + "Image": "Intervention\\Image\\Facades\\Image" + } + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Intervention\\Image\\": "src/Intervention/Image" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Oliver Vogel", + "email": "oliver@intervention.io", + "homepage": "https://intervention.io/" + } + ], + "description": "Image handling and manipulation library with support for Laravel integration", + "homepage": "http://image.intervention.io/", + "keywords": [ + "gd", + "image", + "imagick", + "laravel", + "thumbnail", + "watermark" + ], + "support": { + "issues": "https://github.com/Intervention/image/issues", + "source": "https://github.com/Intervention/image/tree/2.7.2" + }, + "funding": [ + { + "url": "https://paypal.me/interventionio", + "type": "custom" + }, + { + "url": "https://github.com/Intervention", + "type": "github" + } + ], + "install-path": "../intervention/image" + }, + { + "name": "monolog/monolog", + "version": "2.9.1", + "version_normalized": "2.9.1.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "f259e2b15fb95494c83f52d3caad003bbf5ffaa1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/f259e2b15fb95494c83f52d3caad003bbf5ffaa1", + "reference": "f259e2b15fb95494c83f52d3caad003bbf5ffaa1", + "shasum": "" + }, + "require": { + "php": ">=7.2", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "provide": { + "psr/log-implementation": "1.0.0 || 2.0.0 || 3.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^2.4.9 || ^3.0", + "doctrine/couchdb": "~1.0@dev", + "elasticsearch/elasticsearch": "^7 || ^8", + "ext-json": "*", + "graylog2/gelf-php": "^1.4.2 || ^2@dev", + "guzzlehttp/guzzle": "^7.4", + "guzzlehttp/psr7": "^2.2", + "mongodb/mongodb": "^1.8", + "php-amqplib/php-amqplib": "~2.4 || ^3", + "phpspec/prophecy": "^1.15", + "phpstan/phpstan": "^0.12.91", + "phpunit/phpunit": "^8.5.14", + "predis/predis": "^1.1 || ^2.0", + "rollbar/rollbar": "^1.3 || ^2 || ^3", + "ruflin/elastica": "^7", + "swiftmailer/swiftmailer": "^5.3|^6.0", + "symfony/mailer": "^5.4 || ^6", + "symfony/mime": "^5.4 || ^6" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler", + "ext-mbstring": "Allow to work properly with unicode symbols", + "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", + "ext-openssl": "Required to send log messages using SSL", + "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server" + }, + "time": "2023-02-06T13:44:46+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "https://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "support": { + "issues": "https://github.com/Seldaek/monolog/issues", + "source": "https://github.com/Seldaek/monolog/tree/2.9.1" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" + } + ], + "install-path": "../monolog/monolog" + }, + { + "name": "nesbot/carbon", + "version": "2.67.0", + "version_normalized": "2.67.0.0", + "source": { + "type": "git", + "url": "https://github.com/briannesbitt/Carbon.git", + "reference": "c1001b3bc75039b07f38a79db5237c4c529e04c8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/c1001b3bc75039b07f38a79db5237c4c529e04c8", + "reference": "c1001b3bc75039b07f38a79db5237c4c529e04c8", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^7.1.8 || ^8.0", + "symfony/polyfill-mbstring": "^1.0", + "symfony/polyfill-php80": "^1.16", + "symfony/translation": "^3.4 || ^4.0 || ^5.0 || ^6.0" + }, + "require-dev": { + "doctrine/dbal": "^2.0 || ^3.1.4", + "doctrine/orm": "^2.7", + "friendsofphp/php-cs-fixer": "^3.0", + "kylekatarnls/multi-tester": "^2.0", + "ondrejmirtes/better-reflection": "*", + "phpmd/phpmd": "^2.9", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12.99 || ^1.7.14", + "phpunit/php-file-iterator": "^2.0.5 || ^3.0.6", + "phpunit/phpunit": "^7.5.20 || ^8.5.26 || ^9.5.20", + "squizlabs/php_codesniffer": "^3.4" + }, + "time": "2023-05-25T22:09:47+00:00", + "bin": [ + "bin/carbon" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-3.x": "3.x-dev", + "dev-master": "2.x-dev" + }, + "laravel": { + "providers": [ + "Carbon\\Laravel\\ServiceProvider" + ] + }, + "phpstan": { + "includes": [ + "extension.neon" + ] + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Carbon\\": "src/Carbon/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brian Nesbitt", + "email": "brian@nesbot.com", + "homepage": "https://markido.com" + }, + { + "name": "kylekatarnls", + "homepage": "https://github.com/kylekatarnls" + } + ], + "description": "An API extension for DateTime that supports 281 different languages.", + "homepage": "https://carbon.nesbot.com", + "keywords": [ + "date", + "datetime", + "time" + ], + "support": { + "docs": "https://carbon.nesbot.com/docs", + "issues": "https://github.com/briannesbitt/Carbon/issues", + "source": "https://github.com/briannesbitt/Carbon" + }, + "funding": [ + { + "url": "https://github.com/sponsors/kylekatarnls", + "type": "github" + }, + { + "url": "https://opencollective.com/Carbon#sponsor", + "type": "opencollective" + }, + { + "url": "https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&utm_medium=referral&utm_campaign=readme", + "type": "tidelift" + } + ], + "install-path": "../nesbot/carbon" + }, + { + "name": "nikic/fast-route", + "version": "v1.3.0", + "version_normalized": "1.3.0.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "time": "2018-02-13T20:26:39+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/master" + }, + "install-path": "../nikic/fast-route" + }, + { + "name": "phpoption/phpoption", + "version": "1.9.1", + "version_normalized": "1.9.1.0", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/php-option.git", + "reference": "dd3a383e599f49777d8b628dadbb90cae435b87e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/dd3a383e599f49777d8b628dadbb90cae435b87e", + "reference": "dd3a383e599f49777d8b628dadbb90cae435b87e", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.32 || ^9.6.3 || ^10.0.12" + }, + "time": "2023-02-25T19:38:58+00:00", + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": true + }, + "branch-alias": { + "dev-master": "1.9-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "PhpOption\\": "src/PhpOption/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com", + "homepage": "https://github.com/schmittjoh" + }, + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "Option Type for PHP", + "keywords": [ + "language", + "option", + "php", + "type" + ], + "support": { + "issues": "https://github.com/schmittjoh/php-option/issues", + "source": "https://github.com/schmittjoh/php-option/tree/1.9.1" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption", + "type": "tidelift" + } + ], + "install-path": "../phpoption/phpoption" + }, + { + "name": "psr/container", + "version": "1.1.2", + "version_normalized": "1.1.2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "time": "2021-11-05T16:50:12+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, + "install-path": "../psr/container" + }, + { + "name": "psr/http-client", + "version": "1.0.2", + "version_normalized": "1.0.2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "0955afe48220520692d2d09f7ab7e0f93ffd6a31" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/0955afe48220520692d2d09f7ab7e0f93ffd6a31", + "reference": "0955afe48220520692d2d09f7ab7e0f93ffd6a31", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "time": "2023-04-10T20:12:12+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client/tree/1.0.2" + }, + "install-path": "../psr/http-client" + }, + { + "name": "psr/http-factory", + "version": "1.0.2", + "version_normalized": "1.0.2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "e616d01114759c4c489f93b099585439f795fe35" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35", + "reference": "e616d01114759c4c489f93b099585439f795fe35", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "time": "2023-04-10T20:10:41+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/1.0.2" + }, + "install-path": "../psr/http-factory" + }, + { + "name": "psr/http-message", + "version": "2.0", + "version_normalized": "2.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "time": "2023-04-04T09:54:51+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "install-path": "../psr/http-message" + }, + { + "name": "psr/log", + "version": "1.1.4", + "version_normalized": "1.1.4.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2021-05-03T11:20:27+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/1.1.4" + }, + "install-path": "../psr/log" + }, + { + "name": "psr/simple-cache", + "version": "1.0.1", + "version_normalized": "1.0.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2017-10-23T01:57:42+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "support": { + "source": "https://github.com/php-fig/simple-cache/tree/master" + }, + "install-path": "../psr/simple-cache" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "version_normalized": "3.0.3.0", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "time": "2019-03-08T08:55:37+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "install-path": "../ralouphie/getallheaders" + }, + { + "name": "symfony/console", + "version": "v5.4.24", + "version_normalized": "5.4.24.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "560fc3ed7a43e6d30ea94a07d77f9a60b8ed0fb8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/560fc3ed7a43e6d30ea94a07d77f9a60b8ed0fb8", + "reference": "560fc3ed7a43e6d30ea94a07d77f9a60b8ed0fb8", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.9", + "symfony/polyfill-php80": "^1.16", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/string": "^5.1|^6.0" + }, + "conflict": { + "psr/log": ">=3", + "symfony/dependency-injection": "<4.4", + "symfony/dotenv": "<5.1", + "symfony/event-dispatcher": "<4.4", + "symfony/lock": "<4.4", + "symfony/process": "<4.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0" + }, + "require-dev": { + "psr/log": "^1|^2", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/event-dispatcher": "^4.4|^5.0|^6.0", + "symfony/lock": "^4.4|^5.0|^6.0", + "symfony/process": "^4.4|^5.0|^6.0", + "symfony/var-dumper": "^4.4|^5.0|^6.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "time": "2023-05-26T05:13:16+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v5.4.24" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/console" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v2.5.2", + "version_normalized": "2.5.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e8b495ea28c1d97b5e0c121748d6f9b53d075c66", + "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "time": "2022-01-02T09:53:40+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/deprecation-contracts" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.27.0", + "version_normalized": "1.27.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "5bbc823adecdae860bb64756d639ecfec17b050a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a", + "reference": "5bbc823adecdae860bb64756d639ecfec17b050a", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "time": "2022-11-03T14:55:06+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-ctype" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.27.0", + "version_normalized": "1.27.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "511a08c03c1960e08a883f4cffcacd219b758354" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/511a08c03c1960e08a883f4cffcacd219b758354", + "reference": "511a08c03c1960e08a883f4cffcacd219b758354", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "time": "2022-11-03T14:55:06+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-intl-grapheme" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.27.0", + "version_normalized": "1.27.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/19bd1e4fcd5b91116f14d8533c57831ed00571b6", + "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "time": "2022-11-03T14:55:06+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-intl-normalizer" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.27.0", + "version_normalized": "1.27.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534", + "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "time": "2022-11-03T14:55:06+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-mbstring" + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.27.0", + "version_normalized": "1.27.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "9e8ecb5f92152187c4799efd3c96b78ccab18ff9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/9e8ecb5f92152187c4799efd3c96b78ccab18ff9", + "reference": "9e8ecb5f92152187c4799efd3c96b78ccab18ff9", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "time": "2022-11-03T14:55:06+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php73/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-php73" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.27.0", + "version_normalized": "1.27.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", + "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "time": "2022-11-03T14:55:06+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-php80" + }, + { + "name": "symfony/service-contracts", + "version": "v2.5.2", + "version_normalized": "2.5.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/4b426aac47d6427cc1a1d0f7e2ac724627f5966c", + "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.1", + "symfony/deprecation-contracts": "^2.1|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "time": "2022-05-30T19:17:29+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v2.5.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/service-contracts" + }, + { + "name": "symfony/string", + "version": "v5.4.22", + "version_normalized": "5.4.22.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "8036a4c76c0dd29e60b6a7cafcacc50cf088ea62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/8036a4c76c0dd29e60b6a7cafcacc50cf088ea62", + "reference": "8036a4c76c0dd29e60b6a7cafcacc50cf088ea62", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "~1.15" + }, + "conflict": { + "symfony/translation-contracts": ">=3.0" + }, + "require-dev": { + "symfony/error-handler": "^4.4|^5.0|^6.0", + "symfony/http-client": "^4.4|^5.0|^6.0", + "symfony/translation-contracts": "^1.1|^2", + "symfony/var-exporter": "^4.4|^5.0|^6.0" + }, + "time": "2023-03-14T06:11:53+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v5.4.22" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/string" + }, + { + "name": "symfony/translation", + "version": "v5.4.24", + "version_normalized": "5.4.24.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "de237e59c5833422342be67402d487fbf50334ff" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/de237e59c5833422342be67402d487fbf50334ff", + "reference": "de237e59c5833422342be67402d487fbf50334ff", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "^1.16", + "symfony/translation-contracts": "^2.3" + }, + "conflict": { + "symfony/config": "<4.4", + "symfony/console": "<5.3", + "symfony/dependency-injection": "<5.0", + "symfony/http-kernel": "<5.0", + "symfony/twig-bundle": "<5.0", + "symfony/yaml": "<4.4" + }, + "provide": { + "symfony/translation-implementation": "2.3" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/console": "^5.4|^6.0", + "symfony/dependency-injection": "^5.0|^6.0", + "symfony/finder": "^4.4|^5.0|^6.0", + "symfony/http-client-contracts": "^1.1|^2.0|^3.0", + "symfony/http-kernel": "^5.0|^6.0", + "symfony/intl": "^4.4|^5.0|^6.0", + "symfony/polyfill-intl-icu": "^1.21", + "symfony/service-contracts": "^1.1.2|^2|^3", + "symfony/yaml": "^4.4|^5.0|^6.0" + }, + "suggest": { + "psr/log-implementation": "To use logging capability in translator", + "symfony/config": "", + "symfony/yaml": "" + }, + "time": "2023-05-19T12:34:17+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools to internationalize your application", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/translation/tree/v5.4.24" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/translation" + }, + { + "name": "symfony/translation-contracts", + "version": "v2.5.2", + "version_normalized": "2.5.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "136b19dd05cdf0709db6537d058bcab6dd6e2dbe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/136b19dd05cdf0709db6537d058bcab6dd6e2dbe", + "reference": "136b19dd05cdf0709db6537d058bcab6dd6e2dbe", + "shasum": "" + }, + "require": { + "php": ">=7.2.5" + }, + "suggest": { + "symfony/translation-implementation": "" + }, + "time": "2022-06-27T16:58:25+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Translation\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to translation", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/translation-contracts/tree/v2.5.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/translation-contracts" + }, + { + "name": "topthink/think-helper", + "version": "v3.1.6", + "version_normalized": "3.1.6.0", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-helper.git", + "reference": "769acbe50a4274327162f9c68ec2e89a38eb2aff" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-helper/zipball/769acbe50a4274327162f9c68ec2e89a38eb2aff", + "reference": "769acbe50a4274327162f9c68ec2e89a38eb2aff", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "time": "2021-12-15T04:27:55+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "src/helper.php" + ], + "psr-4": { + "think\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "description": "The ThinkPHP6 Helper Package", + "support": { + "issues": "https://github.com/top-think/think-helper/issues", + "source": "https://github.com/top-think/think-helper/tree/v3.1.6" + }, + "install-path": "../topthink/think-helper" + }, + { + "name": "topthink/think-orm", + "version": "v2.0.61", + "version_normalized": "2.0.61.0", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-orm.git", + "reference": "10528ebf4a5106b19c3bac9c6deae7a67ff49de6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-orm/zipball/10528ebf4a5106b19c3bac9c6deae7a67ff49de6", + "reference": "10528ebf4a5106b19c3bac9c6deae7a67ff49de6", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-pdo": "*", + "php": ">=7.1.0", + "psr/log": "^1.0|^2.0", + "psr/simple-cache": "^1.0|^2.0", + "topthink/think-helper": "^3.1" + }, + "require-dev": { + "phpunit/phpunit": "^7|^8|^9.5" + }, + "time": "2023-04-20T14:27:51+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "stubs/load_stubs.php" + ], + "psr-4": { + "think\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "description": "think orm", + "keywords": [ + "database", + "orm" + ], + "support": { + "issues": "https://github.com/top-think/think-orm/issues", + "source": "https://github.com/top-think/think-orm/tree/v2.0.61" + }, + "install-path": "../topthink/think-orm" + }, + { + "name": "vlucas/phpdotenv", + "version": "v5.5.0", + "version_normalized": "5.5.0.0", + "source": { + "type": "git", + "url": "https://github.com/vlucas/phpdotenv.git", + "reference": "1a7ea2afc49c3ee6d87061f5a233e3a035d0eae7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/1a7ea2afc49c3ee6d87061f5a233e3a035d0eae7", + "reference": "1a7ea2afc49c3ee6d87061f5a233e3a035d0eae7", + "shasum": "" + }, + "require": { + "ext-pcre": "*", + "graham-campbell/result-type": "^1.0.2", + "php": "^7.1.3 || ^8.0", + "phpoption/phpoption": "^1.8", + "symfony/polyfill-ctype": "^1.23", + "symfony/polyfill-mbstring": "^1.23.1", + "symfony/polyfill-php80": "^1.23.1" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "ext-filter": "*", + "phpunit/phpunit": "^7.5.20 || ^8.5.30 || ^9.5.25" + }, + "suggest": { + "ext-filter": "Required to use the boolean validator." + }, + "time": "2022-10-16T01:01:54+00:00", + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": true + }, + "branch-alias": { + "dev-master": "5.5-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Dotenv\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Vance Lucas", + "email": "vance@vancelucas.com", + "homepage": "https://github.com/vlucas" + } + ], + "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "support": { + "issues": "https://github.com/vlucas/phpdotenv/issues", + "source": "https://github.com/vlucas/phpdotenv/tree/v5.5.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv", + "type": "tidelift" + } + ], + "install-path": "../vlucas/phpdotenv" + }, + { + "name": "voku/portable-ascii", + "version": "1.6.1", + "version_normalized": "1.6.1.0", + "source": { + "type": "git", + "url": "https://github.com/voku/portable-ascii.git", + "reference": "87337c91b9dfacee02452244ee14ab3c43bc485a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/voku/portable-ascii/zipball/87337c91b9dfacee02452244ee14ab3c43bc485a", + "reference": "87337c91b9dfacee02452244ee14ab3c43bc485a", + "shasum": "" + }, + "require": { + "php": ">=7.0.0" + }, + "require-dev": { + "phpunit/phpunit": "~6.0 || ~7.0 || ~9.0" + }, + "suggest": { + "ext-intl": "Use Intl for transliterator_transliterate() support" + }, + "time": "2022-01-24T18:55:24+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "voku\\": "src/voku/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Lars Moelleken", + "homepage": "http://www.moelleken.org/" + } + ], + "description": "Portable ASCII library - performance optimized (ascii) string functions for php.", + "homepage": "https://github.com/voku/portable-ascii", + "keywords": [ + "ascii", + "clean", + "php" + ], + "support": { + "issues": "https://github.com/voku/portable-ascii/issues", + "source": "https://github.com/voku/portable-ascii/tree/1.6.1" + }, + "funding": [ + { + "url": "https://www.paypal.me/moelleken", + "type": "custom" + }, + { + "url": "https://github.com/voku", + "type": "github" + }, + { + "url": "https://opencollective.com/portable-ascii", + "type": "open_collective" + }, + { + "url": "https://www.patreon.com/voku", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/voku/portable-ascii", + "type": "tidelift" + } + ], + "install-path": "../voku/portable-ascii" + }, + { + "name": "webman/admin", + "version": "v0.6.13", + "version_normalized": "0.6.13.0", + "source": { + "type": "git", + "url": "https://github.com/webman-php/admin.git", + "reference": "b16fb7d75bf65dd2de781f35b1938b22076d405e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webman-php/admin/zipball/b16fb7d75bf65dd2de781f35b1938b22076d405e", + "reference": "b16fb7d75bf65dd2de781f35b1938b22076d405e", + "shasum": "" + }, + "require": { + "guzzlehttp/guzzle": "^7.5", + "illuminate/database": ">=7.30", + "illuminate/events": ">=7.30", + "illuminate/pagination": ">=7.30", + "intervention/image": "^2.7", + "webman/captcha": "^1.0.0", + "webman/event": "^1.0", + "workerman/webman-framework": ">=1.4" + }, + "time": "2023-05-21T13:30:31+00:00", + "type": "project", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Webman\\Admin\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Webman Admin", + "support": { + "issues": "https://github.com/webman-php/admin/issues", + "source": "https://github.com/webman-php/admin/tree/v0.6.13" + }, + "install-path": "../webman/admin" + }, + { + "name": "webman/captcha", + "version": "v1.0.2", + "version_normalized": "1.0.2.0", + "source": { + "type": "git", + "url": "https://github.com/webman-php/captcha.git", + "reference": "00ea47505c5aa92b116ca914f5733e2eb326719a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webman-php/captcha/zipball/00ea47505c5aa92b116ca914f5733e2eb326719a", + "reference": "00ea47505c5aa92b116ca914f5733e2eb326719a", + "shasum": "" + }, + "require": { + "ext-gd": "*", + "ext-mbstring": "*", + "php": ">=5.6.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.4" + }, + "time": "2023-03-30T06:08:19+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Webman\\Captcha\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "walkor", + "email": "walkor@workerman.net" + }, + { + "name": "Grégoire Passault", + "email": "g.passault@gmail.com", + "homepage": "http://www.gregwar.com/" + }, + { + "name": "Jeremy Livingston", + "email": "jeremy.j.livingston@gmail.com" + } + ], + "description": "Captcha generator", + "keywords": [ + "bot", + "captcha", + "spam" + ], + "support": { + "source": "https://github.com/webman-php/captcha/tree/v1.0.2" + }, + "install-path": "../webman/captcha" + }, + { + "name": "webman/event", + "version": "v1.0.4", + "version_normalized": "1.0.4.0", + "source": { + "type": "git", + "url": "https://github.com/webman-php/event.git", + "reference": "f4478941c3b7efa4d7e9c063f2f6efd7ee3071a7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webman-php/event/zipball/f4478941c3b7efa4d7e9c063f2f6efd7ee3071a7", + "reference": "f4478941c3b7efa4d7e9c063f2f6efd7ee3071a7", + "shasum": "" + }, + "time": "2023-03-28T04:01:23+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Webman\\Event\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Webman event plugin", + "support": { + "issues": "https://github.com/webman-php/event/issues", + "source": "https://github.com/webman-php/event/tree/v1.0.4" + }, + "install-path": "../webman/event" + }, + { + "name": "webman/think-orm", + "version": "v1.1.1", + "version_normalized": "1.1.1.0", + "source": { + "type": "git", + "url": "https://github.com/webman-php/think-orm.git", + "reference": "9f1e525c5c4b5a2e1eee6a4f82ef5d23c69139a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webman-php/think-orm/zipball/9f1e525c5c4b5a2e1eee6a4f82ef5d23c69139a2", + "reference": "9f1e525c5c4b5a2e1eee6a4f82ef5d23c69139a2", + "shasum": "" + }, + "require": { + "topthink/think-orm": "^2.0.53 || ^3.0.0", + "workerman/webman-framework": "^1.2.1" + }, + "time": "2023-04-23T14:40:18+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Webman\\ThinkOrm\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "support": { + "issues": "https://github.com/webman-php/think-orm/issues", + "source": "https://github.com/webman-php/think-orm/tree/v1.1.1" + }, + "install-path": "../webman/think-orm" + }, + { + "name": "workerman/webman-framework", + "version": "v1.5.6", + "version_normalized": "1.5.6.0", + "source": { + "type": "git", + "url": "https://github.com/walkor/webman-framework.git", + "reference": "99a57690f3a9e0694a00b97154c4448dd630c622" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/walkor/webman-framework/zipball/99a57690f3a9e0694a00b97154c4448dd630c622", + "reference": "99a57690f3a9e0694a00b97154c4448dd630c622", + "shasum": "" + }, + "require": { + "ext-json": "*", + "nikic/fast-route": "^1.3", + "php": ">=7.2", + "psr/container": ">=1.0", + "workerman/workerman": "^4.0.4 || ^5.0.0" + }, + "suggest": { + "ext-event": "For better performance. " + }, + "time": "2023-05-27T12:27:43+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Webman\\": "./src", + "Support\\": "./src/support", + "support\\": "./src/support", + "Support\\View\\": "./src/support/view", + "Support\\Bootstrap\\": "./src/support/bootstrap", + "Support\\Exception\\": "./src/support/exception" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "walkor", + "email": "walkor@workerman.net", + "homepage": "https://www.workerman.net", + "role": "Developer" + } + ], + "description": "High performance HTTP Service Framework.", + "homepage": "https://www.workerman.net", + "keywords": [ + "High Performance", + "http service" + ], + "support": { + "email": "walkor@workerman.net", + "forum": "https://wenda.workerman.net/", + "issues": "https://github.com/walkor/webman/issues", + "source": "https://github.com/walkor/webman-framework", + "wiki": "https://doc.workerman.net/" + }, + "install-path": "../workerman/webman-framework" + }, + { + "name": "workerman/workerman", + "version": "v4.1.10", + "version_normalized": "4.1.10.0", + "source": { + "type": "git", + "url": "https://github.com/walkor/workerman.git", + "reference": "e967b79f95b9251a72acb971be05623ec1a51e83" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/walkor/workerman/zipball/e967b79f95b9251a72acb971be05623ec1a51e83", + "reference": "e967b79f95b9251a72acb971be05623ec1a51e83", + "shasum": "" + }, + "require": { + "php": ">=7.0" + }, + "suggest": { + "ext-event": "For better performance. " + }, + "time": "2023-05-01T02:12:20+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Workerman\\": "./" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "walkor", + "email": "walkor@workerman.net", + "homepage": "http://www.workerman.net", + "role": "Developer" + } + ], + "description": "An asynchronous event driven PHP framework for easily building fast, scalable network applications.", + "homepage": "http://www.workerman.net", + "keywords": [ + "asynchronous", + "event-loop" + ], + "support": { + "email": "walkor@workerman.net", + "forum": "http://wenda.workerman.net/", + "issues": "https://github.com/walkor/workerman/issues", + "source": "https://github.com/walkor/workerman", + "wiki": "http://doc.workerman.net/" + }, + "funding": [ + { + "url": "https://opencollective.com/workerman", + "type": "open_collective" + }, + { + "url": "https://www.patreon.com/walkor", + "type": "patreon" + } + ], + "install-path": "../workerman/workerman" + } + ], + "dev": true, + "dev-package-names": [] +} diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php new file mode 100644 index 0000000..1a73884 --- /dev/null +++ b/vendor/composer/installed.php @@ -0,0 +1,510 @@ + array( + 'name' => 'workerman/webman', + 'pretty_version' => '1.0.0+no-version-set', + 'version' => '1.0.0.0', + 'reference' => NULL, + 'type' => 'project', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev' => true, + ), + 'versions' => array( + 'doctrine/inflector' => array( + 'pretty_version' => '2.0.8', + 'version' => '2.0.8.0', + 'reference' => 'f9301a5b2fb1216b2b08f02ba04dc45423db6bff', + 'type' => 'library', + 'install_path' => __DIR__ . '/../doctrine/inflector', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'graham-campbell/result-type' => array( + 'pretty_version' => 'v1.1.1', + 'version' => '1.1.1.0', + 'reference' => '672eff8cf1d6fe1ef09ca0f89c4b287d6a3eb831', + 'type' => 'library', + 'install_path' => __DIR__ . '/../graham-campbell/result-type', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'guzzlehttp/guzzle' => array( + 'pretty_version' => '7.7.0', + 'version' => '7.7.0.0', + 'reference' => 'fb7566caccf22d74d1ab270de3551f72a58399f5', + 'type' => 'library', + 'install_path' => __DIR__ . '/../guzzlehttp/guzzle', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'guzzlehttp/promises' => array( + 'pretty_version' => '2.0.0', + 'version' => '2.0.0.0', + 'reference' => '3a494dc7dc1d7d12e511890177ae2d0e6c107da6', + 'type' => 'library', + 'install_path' => __DIR__ . '/../guzzlehttp/promises', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'guzzlehttp/psr7' => array( + 'pretty_version' => '2.5.0', + 'version' => '2.5.0.0', + 'reference' => 'b635f279edd83fc275f822a1188157ffea568ff6', + 'type' => 'library', + 'install_path' => __DIR__ . '/../guzzlehttp/psr7', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'illuminate/bus' => array( + 'pretty_version' => 'v8.83.27', + 'version' => '8.83.27.0', + 'reference' => 'd2a8ae4bfd881086e55455e470776358eab27eae', + 'type' => 'library', + 'install_path' => __DIR__ . '/../illuminate/bus', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'illuminate/collections' => array( + 'pretty_version' => 'v8.83.27', + 'version' => '8.83.27.0', + 'reference' => '705a4e1ef93cd492c45b9b3e7911cccc990a07f4', + 'type' => 'library', + 'install_path' => __DIR__ . '/../illuminate/collections', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'illuminate/container' => array( + 'pretty_version' => 'v8.83.27', + 'version' => '8.83.27.0', + 'reference' => '14062628d05f75047c5a1360b9350028427d568e', + 'type' => 'library', + 'install_path' => __DIR__ . '/../illuminate/container', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'illuminate/contracts' => array( + 'pretty_version' => 'v8.83.27', + 'version' => '8.83.27.0', + 'reference' => '5e0fd287a1b22a6b346a9f7cd484d8cf0234585d', + 'type' => 'library', + 'install_path' => __DIR__ . '/../illuminate/contracts', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'illuminate/database' => array( + 'pretty_version' => 'v8.83.27', + 'version' => '8.83.27.0', + 'reference' => '1a5b0e4e6913415464fa2aab554a38b9e6fa44b1', + 'type' => 'library', + 'install_path' => __DIR__ . '/../illuminate/database', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'illuminate/events' => array( + 'pretty_version' => 'v8.83.27', + 'version' => '8.83.27.0', + 'reference' => 'b7f06cafb6c09581617f2ca05d69e9b159e5a35d', + 'type' => 'library', + 'install_path' => __DIR__ . '/../illuminate/events', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'illuminate/macroable' => array( + 'pretty_version' => 'v8.83.27', + 'version' => '8.83.27.0', + 'reference' => 'aed81891a6e046fdee72edd497f822190f61c162', + 'type' => 'library', + 'install_path' => __DIR__ . '/../illuminate/macroable', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'illuminate/pagination' => array( + 'pretty_version' => 'v8.83.27', + 'version' => '8.83.27.0', + 'reference' => '16fe8dc35f9d18c58a3471469af656a02e9ab692', + 'type' => 'library', + 'install_path' => __DIR__ . '/../illuminate/pagination', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'illuminate/pipeline' => array( + 'pretty_version' => 'v8.83.27', + 'version' => '8.83.27.0', + 'reference' => '23aeff5b26ae4aee3f370835c76bd0f4e93f71d2', + 'type' => 'library', + 'install_path' => __DIR__ . '/../illuminate/pipeline', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'illuminate/redis' => array( + 'pretty_version' => 'v8.83.27', + 'version' => '8.83.27.0', + 'reference' => '0fee121324054226823a59623fab3d98ad88fbd5', + 'type' => 'library', + 'install_path' => __DIR__ . '/../illuminate/redis', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'illuminate/support' => array( + 'pretty_version' => 'v8.83.27', + 'version' => '8.83.27.0', + 'reference' => '1c79242468d3bbd9a0f7477df34f9647dde2a09b', + 'type' => 'library', + 'install_path' => __DIR__ . '/../illuminate/support', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'intervention/image' => array( + 'pretty_version' => '2.7.2', + 'version' => '2.7.2.0', + 'reference' => '04be355f8d6734c826045d02a1079ad658322dad', + 'type' => 'library', + 'install_path' => __DIR__ . '/../intervention/image', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'monolog/monolog' => array( + 'pretty_version' => '2.9.1', + 'version' => '2.9.1.0', + 'reference' => 'f259e2b15fb95494c83f52d3caad003bbf5ffaa1', + 'type' => 'library', + 'install_path' => __DIR__ . '/../monolog/monolog', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'nesbot/carbon' => array( + 'pretty_version' => '2.67.0', + 'version' => '2.67.0.0', + 'reference' => 'c1001b3bc75039b07f38a79db5237c4c529e04c8', + 'type' => 'library', + 'install_path' => __DIR__ . '/../nesbot/carbon', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'nikic/fast-route' => array( + 'pretty_version' => 'v1.3.0', + 'version' => '1.3.0.0', + 'reference' => '181d480e08d9476e61381e04a71b34dc0432e812', + 'type' => 'library', + 'install_path' => __DIR__ . '/../nikic/fast-route', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'phpoption/phpoption' => array( + 'pretty_version' => '1.9.1', + 'version' => '1.9.1.0', + 'reference' => 'dd3a383e599f49777d8b628dadbb90cae435b87e', + 'type' => 'library', + 'install_path' => __DIR__ . '/../phpoption/phpoption', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'psr/container' => array( + 'pretty_version' => '1.1.2', + 'version' => '1.1.2.0', + 'reference' => '513e0666f7216c7459170d56df27dfcefe1689ea', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/container', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'psr/container-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '1.0', + ), + ), + 'psr/http-client' => array( + 'pretty_version' => '1.0.2', + 'version' => '1.0.2.0', + 'reference' => '0955afe48220520692d2d09f7ab7e0f93ffd6a31', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/http-client', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'psr/http-client-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '1.0', + ), + ), + 'psr/http-factory' => array( + 'pretty_version' => '1.0.2', + 'version' => '1.0.2.0', + 'reference' => 'e616d01114759c4c489f93b099585439f795fe35', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/http-factory', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'psr/http-factory-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '1.0', + ), + ), + 'psr/http-message' => array( + 'pretty_version' => '2.0', + 'version' => '2.0.0.0', + 'reference' => '402d35bcb92c70c026d1a6a9883f06b2ead23d71', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/http-message', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'psr/http-message-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '1.0', + ), + ), + 'psr/log' => array( + 'pretty_version' => '1.1.4', + 'version' => '1.1.4.0', + 'reference' => 'd49695b909c3b7628b6289db5479a1c204601f11', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/log', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'psr/log-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '1.0.0 || 2.0.0 || 3.0.0', + 1 => '1.0|2.0', + ), + ), + 'psr/simple-cache' => array( + 'pretty_version' => '1.0.1', + 'version' => '1.0.1.0', + 'reference' => '408d5eafb83c57f6365a3ca330ff23aa4a5fa39b', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/simple-cache', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'ralouphie/getallheaders' => array( + 'pretty_version' => '3.0.3', + 'version' => '3.0.3.0', + 'reference' => '120b605dfeb996808c31b6477290a714d356e822', + 'type' => 'library', + 'install_path' => __DIR__ . '/../ralouphie/getallheaders', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/console' => array( + 'pretty_version' => 'v5.4.24', + 'version' => '5.4.24.0', + 'reference' => '560fc3ed7a43e6d30ea94a07d77f9a60b8ed0fb8', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/console', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/deprecation-contracts' => array( + 'pretty_version' => 'v2.5.2', + 'version' => '2.5.2.0', + 'reference' => 'e8b495ea28c1d97b5e0c121748d6f9b53d075c66', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/deprecation-contracts', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/polyfill-ctype' => array( + 'pretty_version' => 'v1.27.0', + 'version' => '1.27.0.0', + 'reference' => '5bbc823adecdae860bb64756d639ecfec17b050a', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-ctype', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/polyfill-intl-grapheme' => array( + 'pretty_version' => 'v1.27.0', + 'version' => '1.27.0.0', + 'reference' => '511a08c03c1960e08a883f4cffcacd219b758354', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-intl-grapheme', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/polyfill-intl-normalizer' => array( + 'pretty_version' => 'v1.27.0', + 'version' => '1.27.0.0', + 'reference' => '19bd1e4fcd5b91116f14d8533c57831ed00571b6', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-intl-normalizer', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/polyfill-mbstring' => array( + 'pretty_version' => 'v1.27.0', + 'version' => '1.27.0.0', + 'reference' => '8ad114f6b39e2c98a8b0e3bd907732c207c2b534', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-mbstring', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/polyfill-php73' => array( + 'pretty_version' => 'v1.27.0', + 'version' => '1.27.0.0', + 'reference' => '9e8ecb5f92152187c4799efd3c96b78ccab18ff9', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-php73', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/polyfill-php80' => array( + 'pretty_version' => 'v1.27.0', + 'version' => '1.27.0.0', + 'reference' => '7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-php80', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/service-contracts' => array( + 'pretty_version' => 'v2.5.2', + 'version' => '2.5.2.0', + 'reference' => '4b426aac47d6427cc1a1d0f7e2ac724627f5966c', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/service-contracts', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/string' => array( + 'pretty_version' => 'v5.4.22', + 'version' => '5.4.22.0', + 'reference' => '8036a4c76c0dd29e60b6a7cafcacc50cf088ea62', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/string', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/translation' => array( + 'pretty_version' => 'v5.4.24', + 'version' => '5.4.24.0', + 'reference' => 'de237e59c5833422342be67402d487fbf50334ff', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/translation', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/translation-contracts' => array( + 'pretty_version' => 'v2.5.2', + 'version' => '2.5.2.0', + 'reference' => '136b19dd05cdf0709db6537d058bcab6dd6e2dbe', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/translation-contracts', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/translation-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '2.3', + ), + ), + 'topthink/think-helper' => array( + 'pretty_version' => 'v3.1.6', + 'version' => '3.1.6.0', + 'reference' => '769acbe50a4274327162f9c68ec2e89a38eb2aff', + 'type' => 'library', + 'install_path' => __DIR__ . '/../topthink/think-helper', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'topthink/think-orm' => array( + 'pretty_version' => 'v2.0.61', + 'version' => '2.0.61.0', + 'reference' => '10528ebf4a5106b19c3bac9c6deae7a67ff49de6', + 'type' => 'library', + 'install_path' => __DIR__ . '/../topthink/think-orm', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'vlucas/phpdotenv' => array( + 'pretty_version' => 'v5.5.0', + 'version' => '5.5.0.0', + 'reference' => '1a7ea2afc49c3ee6d87061f5a233e3a035d0eae7', + 'type' => 'library', + 'install_path' => __DIR__ . '/../vlucas/phpdotenv', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'voku/portable-ascii' => array( + 'pretty_version' => '1.6.1', + 'version' => '1.6.1.0', + 'reference' => '87337c91b9dfacee02452244ee14ab3c43bc485a', + 'type' => 'library', + 'install_path' => __DIR__ . '/../voku/portable-ascii', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'webman/admin' => array( + 'pretty_version' => 'v0.6.13', + 'version' => '0.6.13.0', + 'reference' => 'b16fb7d75bf65dd2de781f35b1938b22076d405e', + 'type' => 'project', + 'install_path' => __DIR__ . '/../webman/admin', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'webman/captcha' => array( + 'pretty_version' => 'v1.0.2', + 'version' => '1.0.2.0', + 'reference' => '00ea47505c5aa92b116ca914f5733e2eb326719a', + 'type' => 'library', + 'install_path' => __DIR__ . '/../webman/captcha', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'webman/event' => array( + 'pretty_version' => 'v1.0.4', + 'version' => '1.0.4.0', + 'reference' => 'f4478941c3b7efa4d7e9c063f2f6efd7ee3071a7', + 'type' => 'library', + 'install_path' => __DIR__ . '/../webman/event', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'webman/think-orm' => array( + 'pretty_version' => 'v1.1.1', + 'version' => '1.1.1.0', + 'reference' => '9f1e525c5c4b5a2e1eee6a4f82ef5d23c69139a2', + 'type' => 'library', + 'install_path' => __DIR__ . '/../webman/think-orm', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'workerman/webman' => array( + 'pretty_version' => '1.0.0+no-version-set', + 'version' => '1.0.0.0', + 'reference' => NULL, + 'type' => 'project', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'workerman/webman-framework' => array( + 'pretty_version' => 'v1.5.6', + 'version' => '1.5.6.0', + 'reference' => '99a57690f3a9e0694a00b97154c4448dd630c622', + 'type' => 'library', + 'install_path' => __DIR__ . '/../workerman/webman-framework', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'workerman/workerman' => array( + 'pretty_version' => 'v4.1.10', + 'version' => '4.1.10.0', + 'reference' => 'e967b79f95b9251a72acb971be05623ec1a51e83', + 'type' => 'library', + 'install_path' => __DIR__ . '/../workerman/workerman', + 'aliases' => array(), + 'dev_requirement' => false, + ), + ), +); diff --git a/vendor/composer/platform_check.php b/vendor/composer/platform_check.php new file mode 100644 index 0000000..580fa96 --- /dev/null +++ b/vendor/composer/platform_check.php @@ -0,0 +1,26 @@ += 70400)) { + $issues[] = 'Your Composer dependencies require a PHP version ">= 7.4.0". You are running ' . PHP_VERSION . '.'; +} + +if ($issues) { + if (!headers_sent()) { + header('HTTP/1.1 500 Internal Server Error'); + } + if (!ini_get('display_errors')) { + if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { + fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL); + } elseif (!headers_sent()) { + echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL; + } + } + trigger_error( + 'Composer detected issues in your platform: ' . implode(' ', $issues), + E_USER_ERROR + ); +} diff --git a/vendor/doctrine/inflector/LICENSE b/vendor/doctrine/inflector/LICENSE new file mode 100644 index 0000000..8c38cc1 --- /dev/null +++ b/vendor/doctrine/inflector/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2006-2015 Doctrine Project + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/doctrine/inflector/README.md b/vendor/doctrine/inflector/README.md new file mode 100644 index 0000000..6e3a97f --- /dev/null +++ b/vendor/doctrine/inflector/README.md @@ -0,0 +1,7 @@ +# Doctrine Inflector + +Doctrine Inflector is a small library that can perform string manipulations +with regard to uppercase/lowercase and singular/plural forms of words. + +[![Build Status](https://github.com/doctrine/inflector/workflows/Continuous%20Integration/badge.svg)](https://github.com/doctrine/inflector/actions?query=workflow%3A%22Continuous+Integration%22+branch%3A4.0.x) +[![Code Coverage](https://codecov.io/gh/doctrine/inflector/branch/2.0.x/graph/badge.svg)](https://codecov.io/gh/doctrine/inflector/branch/2.0.x) diff --git a/vendor/doctrine/inflector/docs/en/index.rst b/vendor/doctrine/inflector/docs/en/index.rst new file mode 100644 index 0000000..29866f4 --- /dev/null +++ b/vendor/doctrine/inflector/docs/en/index.rst @@ -0,0 +1,226 @@ +Introduction +============ + +The Doctrine Inflector has methods for inflecting text. The features include pluralization, +singularization, converting between camelCase and under_score and capitalizing +words. + +Installation +============ + +You can install the Inflector with composer: + +.. code-block:: console + + $ composer require doctrine/inflector + +Usage +===== + +Using the inflector is easy, you can create a new ``Doctrine\Inflector\Inflector`` instance by using +the ``Doctrine\Inflector\InflectorFactory`` class: + +.. code-block:: php + + use Doctrine\Inflector\InflectorFactory; + + $inflector = InflectorFactory::create()->build(); + +By default it will create an English inflector. If you want to use another language, just pass the language +you want to create an inflector for to the ``createForLanguage()`` method: + +.. code-block:: php + + use Doctrine\Inflector\InflectorFactory; + use Doctrine\Inflector\Language; + + $inflector = InflectorFactory::createForLanguage(Language::SPANISH)->build(); + +The supported languages are as follows: + +- ``Language::ENGLISH`` +- ``Language::FRENCH`` +- ``Language::NORWEGIAN_BOKMAL`` +- ``Language::PORTUGUESE`` +- ``Language::SPANISH`` +- ``Language::TURKISH`` + +If you want to manually construct the inflector instead of using a factory, you can do so like this: + +.. code-block:: php + + use Doctrine\Inflector\CachedWordInflector; + use Doctrine\Inflector\RulesetInflector; + use Doctrine\Inflector\Rules\English; + + $inflector = new Inflector( + new CachedWordInflector(new RulesetInflector( + English\Rules::getSingularRuleset() + )), + new CachedWordInflector(new RulesetInflector( + English\Rules::getPluralRuleset() + )) + ); + +Adding Languages +---------------- + +If you are interested in adding support for your language, take a look at the other languages defined in the +``Doctrine\Inflector\Rules`` namespace and the tests located in ``Doctrine\Tests\Inflector\Rules``. You can copy +one of the languages and update the rules for your language. + +Once you have done this, send a pull request to the ``doctrine/inflector`` repository with the additions. + +Custom Setup +============ + +If you want to setup custom singular and plural rules, you can configure these in the factory: + +.. code-block:: php + + use Doctrine\Inflector\InflectorFactory; + use Doctrine\Inflector\Rules\Pattern; + use Doctrine\Inflector\Rules\Patterns; + use Doctrine\Inflector\Rules\Ruleset; + use Doctrine\Inflector\Rules\Substitution; + use Doctrine\Inflector\Rules\Substitutions; + use Doctrine\Inflector\Rules\Transformation; + use Doctrine\Inflector\Rules\Transformations; + use Doctrine\Inflector\Rules\Word; + + $inflector = InflectorFactory::create() + ->withSingularRules( + new Ruleset( + new Transformations( + new Transformation(new Pattern('/^(bil)er$/i'), '\1'), + new Transformation(new Pattern('/^(inflec|contribu)tors$/i'), '\1ta') + ), + new Patterns(new Pattern('singulars')), + new Substitutions(new Substitution(new Word('spins'), new Word('spinor'))) + ) + ) + ->withPluralRules( + new Ruleset( + new Transformations( + new Transformation(new Pattern('^(bil)er$'), '\1'), + new Transformation(new Pattern('^(inflec|contribu)tors$'), '\1ta') + ), + new Patterns(new Pattern('noflect'), new Pattern('abtuse')), + new Substitutions( + new Substitution(new Word('amaze'), new Word('amazable')), + new Substitution(new Word('phone'), new Word('phonezes')) + ) + ) + ) + ->build(); + +No operation inflector +---------------------- + +The ``Doctrine\Inflector\NoopWordInflector`` may be used to configure an inflector that doesn't perform any operation for +pluralization and/or singularization. If will simply return the input as output. + +This is an implementation of the `Null Object design pattern `_. + +.. code-block:: php + + use Doctrine\Inflector\Inflector; + use Doctrine\Inflector\NoopWordInflector; + + $inflector = new Inflector(new NoopWordInflector(), new NoopWordInflector()); + +Tableize +======== + +Converts ``ModelName`` to ``model_name``: + +.. code-block:: php + + echo $inflector->tableize('ModelName'); // model_name + +Classify +======== + +Converts ``model_name`` to ``ModelName``: + +.. code-block:: php + + echo $inflector->classify('model_name'); // ModelName + +Camelize +======== + +This method uses `Classify`_ and then converts the first character to lowercase: + +.. code-block:: php + + echo $inflector->camelize('model_name'); // modelName + +Capitalize +========== + +Takes a string and capitalizes all of the words, like PHP's built-in +``ucwords`` function. This extends that behavior, however, by allowing the +word delimiters to be configured, rather than only separating on +whitespace. + +Here is an example: + +.. code-block:: php + + $string = 'top-o-the-morning to all_of_you!'; + + echo $inflector->capitalize($string); // Top-O-The-Morning To All_of_you! + + echo $inflector->capitalize($string, '-_ '); // Top-O-The-Morning To All_Of_You! + +Pluralize +========= + +Returns a word in plural form. + +.. code-block:: php + + echo $inflector->pluralize('browser'); // browsers + +Singularize +=========== + +Returns a word in singular form. + +.. code-block:: php + + echo $inflector->singularize('browsers'); // browser + +Urlize +====== + +Generate a URL friendly string from a string of text: + +.. code-block:: php + + echo $inflector->urlize('My first blog post'); // my-first-blog-post + +Unaccent +======== + +You can unaccent a string of text using the ``unaccent()`` method: + +.. code-block:: php + + echo $inflector->unaccent('año'); // ano + +Legacy API +========== + +The API present in Inflector 1.x is still available, but will be deprecated in a future release and dropped for 3.0. +Support for languages other than English is available in the 2.0 API only. + +Acknowledgements +================ + +The language rules in this library have been adapted from several different sources, including but not limited to: + +- `Ruby On Rails Inflector `_ +- `ICanBoogie Inflector `_ +- `CakePHP Inflector `_ diff --git a/vendor/doctrine/inflector/lib/Doctrine/Inflector/CachedWordInflector.php b/vendor/doctrine/inflector/lib/Doctrine/Inflector/CachedWordInflector.php new file mode 100644 index 0000000..2d52908 --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Inflector/CachedWordInflector.php @@ -0,0 +1,24 @@ +wordInflector = $wordInflector; + } + + public function inflect(string $word): string + { + return $this->cache[$word] ?? $this->cache[$word] = $this->wordInflector->inflect($word); + } +} diff --git a/vendor/doctrine/inflector/lib/Doctrine/Inflector/GenericLanguageInflectorFactory.php b/vendor/doctrine/inflector/lib/Doctrine/Inflector/GenericLanguageInflectorFactory.php new file mode 100644 index 0000000..166061d --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Inflector/GenericLanguageInflectorFactory.php @@ -0,0 +1,66 @@ +singularRulesets[] = $this->getSingularRuleset(); + $this->pluralRulesets[] = $this->getPluralRuleset(); + } + + final public function build(): Inflector + { + return new Inflector( + new CachedWordInflector(new RulesetInflector( + ...$this->singularRulesets + )), + new CachedWordInflector(new RulesetInflector( + ...$this->pluralRulesets + )) + ); + } + + final public function withSingularRules(?Ruleset $singularRules, bool $reset = false): LanguageInflectorFactory + { + if ($reset) { + $this->singularRulesets = []; + } + + if ($singularRules instanceof Ruleset) { + array_unshift($this->singularRulesets, $singularRules); + } + + return $this; + } + + final public function withPluralRules(?Ruleset $pluralRules, bool $reset = false): LanguageInflectorFactory + { + if ($reset) { + $this->pluralRulesets = []; + } + + if ($pluralRules instanceof Ruleset) { + array_unshift($this->pluralRulesets, $pluralRules); + } + + return $this; + } + + abstract protected function getSingularRuleset(): Ruleset; + + abstract protected function getPluralRuleset(): Ruleset; +} diff --git a/vendor/doctrine/inflector/lib/Doctrine/Inflector/Inflector.php b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Inflector.php new file mode 100644 index 0000000..610a4cf --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Inflector.php @@ -0,0 +1,507 @@ + 'A', + 'Á' => 'A', + 'Â' => 'A', + 'Ã' => 'A', + 'Ä' => 'Ae', + 'Æ' => 'Ae', + 'Å' => 'Aa', + 'æ' => 'a', + 'Ç' => 'C', + 'È' => 'E', + 'É' => 'E', + 'Ê' => 'E', + 'Ë' => 'E', + 'Ì' => 'I', + 'Í' => 'I', + 'Î' => 'I', + 'Ï' => 'I', + 'Ñ' => 'N', + 'Ò' => 'O', + 'Ó' => 'O', + 'Ô' => 'O', + 'Õ' => 'O', + 'Ö' => 'Oe', + 'Ù' => 'U', + 'Ú' => 'U', + 'Û' => 'U', + 'Ü' => 'Ue', + 'Ý' => 'Y', + 'ß' => 'ss', + 'à' => 'a', + 'á' => 'a', + 'â' => 'a', + 'ã' => 'a', + 'ä' => 'ae', + 'å' => 'aa', + 'ç' => 'c', + 'è' => 'e', + 'é' => 'e', + 'ê' => 'e', + 'ë' => 'e', + 'ì' => 'i', + 'í' => 'i', + 'î' => 'i', + 'ï' => 'i', + 'ñ' => 'n', + 'ò' => 'o', + 'ó' => 'o', + 'ô' => 'o', + 'õ' => 'o', + 'ö' => 'oe', + 'ù' => 'u', + 'ú' => 'u', + 'û' => 'u', + 'ü' => 'ue', + 'ý' => 'y', + 'ÿ' => 'y', + 'Ā' => 'A', + 'ā' => 'a', + 'Ă' => 'A', + 'ă' => 'a', + 'Ą' => 'A', + 'ą' => 'a', + 'Ć' => 'C', + 'ć' => 'c', + 'Ĉ' => 'C', + 'ĉ' => 'c', + 'Ċ' => 'C', + 'ċ' => 'c', + 'Č' => 'C', + 'č' => 'c', + 'Ď' => 'D', + 'ď' => 'd', + 'Đ' => 'D', + 'đ' => 'd', + 'Ē' => 'E', + 'ē' => 'e', + 'Ĕ' => 'E', + 'ĕ' => 'e', + 'Ė' => 'E', + 'ė' => 'e', + 'Ę' => 'E', + 'ę' => 'e', + 'Ě' => 'E', + 'ě' => 'e', + 'Ĝ' => 'G', + 'ĝ' => 'g', + 'Ğ' => 'G', + 'ğ' => 'g', + 'Ġ' => 'G', + 'ġ' => 'g', + 'Ģ' => 'G', + 'ģ' => 'g', + 'Ĥ' => 'H', + 'ĥ' => 'h', + 'Ħ' => 'H', + 'ħ' => 'h', + 'Ĩ' => 'I', + 'ĩ' => 'i', + 'Ī' => 'I', + 'ī' => 'i', + 'Ĭ' => 'I', + 'ĭ' => 'i', + 'Į' => 'I', + 'į' => 'i', + 'İ' => 'I', + 'ı' => 'i', + 'IJ' => 'IJ', + 'ij' => 'ij', + 'Ĵ' => 'J', + 'ĵ' => 'j', + 'Ķ' => 'K', + 'ķ' => 'k', + 'ĸ' => 'k', + 'Ĺ' => 'L', + 'ĺ' => 'l', + 'Ļ' => 'L', + 'ļ' => 'l', + 'Ľ' => 'L', + 'ľ' => 'l', + 'Ŀ' => 'L', + 'ŀ' => 'l', + 'Ł' => 'L', + 'ł' => 'l', + 'Ń' => 'N', + 'ń' => 'n', + 'Ņ' => 'N', + 'ņ' => 'n', + 'Ň' => 'N', + 'ň' => 'n', + 'ʼn' => 'N', + 'Ŋ' => 'n', + 'ŋ' => 'N', + 'Ō' => 'O', + 'ō' => 'o', + 'Ŏ' => 'O', + 'ŏ' => 'o', + 'Ő' => 'O', + 'ő' => 'o', + 'Œ' => 'OE', + 'œ' => 'oe', + 'Ø' => 'O', + 'ø' => 'o', + 'Ŕ' => 'R', + 'ŕ' => 'r', + 'Ŗ' => 'R', + 'ŗ' => 'r', + 'Ř' => 'R', + 'ř' => 'r', + 'Ś' => 'S', + 'ś' => 's', + 'Ŝ' => 'S', + 'ŝ' => 's', + 'Ş' => 'S', + 'ş' => 's', + 'Š' => 'S', + 'š' => 's', + 'Ţ' => 'T', + 'ţ' => 't', + 'Ť' => 'T', + 'ť' => 't', + 'Ŧ' => 'T', + 'ŧ' => 't', + 'Ũ' => 'U', + 'ũ' => 'u', + 'Ū' => 'U', + 'ū' => 'u', + 'Ŭ' => 'U', + 'ŭ' => 'u', + 'Ů' => 'U', + 'ů' => 'u', + 'Ű' => 'U', + 'ű' => 'u', + 'Ų' => 'U', + 'ų' => 'u', + 'Ŵ' => 'W', + 'ŵ' => 'w', + 'Ŷ' => 'Y', + 'ŷ' => 'y', + 'Ÿ' => 'Y', + 'Ź' => 'Z', + 'ź' => 'z', + 'Ż' => 'Z', + 'ż' => 'z', + 'Ž' => 'Z', + 'ž' => 'z', + 'ſ' => 's', + '€' => 'E', + '£' => '', + ]; + + /** @var WordInflector */ + private $singularizer; + + /** @var WordInflector */ + private $pluralizer; + + public function __construct(WordInflector $singularizer, WordInflector $pluralizer) + { + $this->singularizer = $singularizer; + $this->pluralizer = $pluralizer; + } + + /** + * Converts a word into the format for a Doctrine table name. Converts 'ModelName' to 'model_name'. + */ + public function tableize(string $word): string + { + $tableized = preg_replace('~(?<=\\w)([A-Z])~u', '_$1', $word); + + if ($tableized === null) { + throw new RuntimeException(sprintf( + 'preg_replace returned null for value "%s"', + $word + )); + } + + return mb_strtolower($tableized); + } + + /** + * Converts a word into the format for a Doctrine class name. Converts 'table_name' to 'TableName'. + */ + public function classify(string $word): string + { + return str_replace([' ', '_', '-'], '', ucwords($word, ' _-')); + } + + /** + * Camelizes a word. This uses the classify() method and turns the first character to lowercase. + */ + public function camelize(string $word): string + { + return lcfirst($this->classify($word)); + } + + /** + * Uppercases words with configurable delimiters between words. + * + * Takes a string and capitalizes all of the words, like PHP's built-in + * ucwords function. This extends that behavior, however, by allowing the + * word delimiters to be configured, rather than only separating on + * whitespace. + * + * Here is an example: + * + * capitalize($string); + * // Top-O-The-Morning To All_of_you! + * + * echo $inflector->capitalize($string, '-_ '); + * // Top-O-The-Morning To All_Of_You! + * ?> + * + * + * @param string $string The string to operate on. + * @param string $delimiters A list of word separators. + * + * @return string The string with all delimiter-separated words capitalized. + */ + public function capitalize(string $string, string $delimiters = " \n\t\r\0\x0B-"): string + { + return ucwords($string, $delimiters); + } + + /** + * Checks if the given string seems like it has utf8 characters in it. + * + * @param string $string The string to check for utf8 characters in. + */ + public function seemsUtf8(string $string): bool + { + for ($i = 0; $i < strlen($string); $i++) { + if (ord($string[$i]) < 0x80) { + continue; // 0bbbbbbb + } + + if ((ord($string[$i]) & 0xE0) === 0xC0) { + $n = 1; // 110bbbbb + } elseif ((ord($string[$i]) & 0xF0) === 0xE0) { + $n = 2; // 1110bbbb + } elseif ((ord($string[$i]) & 0xF8) === 0xF0) { + $n = 3; // 11110bbb + } elseif ((ord($string[$i]) & 0xFC) === 0xF8) { + $n = 4; // 111110bb + } elseif ((ord($string[$i]) & 0xFE) === 0xFC) { + $n = 5; // 1111110b + } else { + return false; // Does not match any model + } + + for ($j = 0; $j < $n; $j++) { // n bytes matching 10bbbbbb follow ? + if (++$i === strlen($string) || ((ord($string[$i]) & 0xC0) !== 0x80)) { + return false; + } + } + } + + return true; + } + + /** + * Remove any illegal characters, accents, etc. + * + * @param string $string String to unaccent + * + * @return string Unaccented string + */ + public function unaccent(string $string): string + { + if (preg_match('/[\x80-\xff]/', $string) === false) { + return $string; + } + + if ($this->seemsUtf8($string)) { + $string = strtr($string, self::ACCENTED_CHARACTERS); + } else { + $characters = []; + + // Assume ISO-8859-1 if not UTF-8 + $characters['in'] = + chr(128) + . chr(131) + . chr(138) + . chr(142) + . chr(154) + . chr(158) + . chr(159) + . chr(162) + . chr(165) + . chr(181) + . chr(192) + . chr(193) + . chr(194) + . chr(195) + . chr(196) + . chr(197) + . chr(199) + . chr(200) + . chr(201) + . chr(202) + . chr(203) + . chr(204) + . chr(205) + . chr(206) + . chr(207) + . chr(209) + . chr(210) + . chr(211) + . chr(212) + . chr(213) + . chr(214) + . chr(216) + . chr(217) + . chr(218) + . chr(219) + . chr(220) + . chr(221) + . chr(224) + . chr(225) + . chr(226) + . chr(227) + . chr(228) + . chr(229) + . chr(231) + . chr(232) + . chr(233) + . chr(234) + . chr(235) + . chr(236) + . chr(237) + . chr(238) + . chr(239) + . chr(241) + . chr(242) + . chr(243) + . chr(244) + . chr(245) + . chr(246) + . chr(248) + . chr(249) + . chr(250) + . chr(251) + . chr(252) + . chr(253) + . chr(255); + + $characters['out'] = 'EfSZszYcYuAAAAAACEEEEIIIINOOOOOOUUUUYaaaaaaceeeeiiiinoooooouuuuyy'; + + $string = strtr($string, $characters['in'], $characters['out']); + + $doubleChars = []; + + $doubleChars['in'] = [ + chr(140), + chr(156), + chr(198), + chr(208), + chr(222), + chr(223), + chr(230), + chr(240), + chr(254), + ]; + + $doubleChars['out'] = ['OE', 'oe', 'AE', 'DH', 'TH', 'ss', 'ae', 'dh', 'th']; + + $string = str_replace($doubleChars['in'], $doubleChars['out'], $string); + } + + return $string; + } + + /** + * Convert any passed string to a url friendly string. + * Converts 'My first blog post' to 'my-first-blog-post' + * + * @param string $string String to urlize. + * + * @return string Urlized string. + */ + public function urlize(string $string): string + { + // Remove all non url friendly characters with the unaccent function + $unaccented = $this->unaccent($string); + + if (function_exists('mb_strtolower')) { + $lowered = mb_strtolower($unaccented); + } else { + $lowered = strtolower($unaccented); + } + + $replacements = [ + '/\W/' => ' ', + '/([A-Z]+)([A-Z][a-z])/' => '\1_\2', + '/([a-z\d])([A-Z])/' => '\1_\2', + '/[^A-Z^a-z^0-9^\/]+/' => '-', + ]; + + $urlized = $lowered; + + foreach ($replacements as $pattern => $replacement) { + $replaced = preg_replace($pattern, $replacement, $urlized); + + if ($replaced === null) { + throw new RuntimeException(sprintf( + 'preg_replace returned null for value "%s"', + $urlized + )); + } + + $urlized = $replaced; + } + + return trim($urlized, '-'); + } + + /** + * Returns a word in singular form. + * + * @param string $word The word in plural form. + * + * @return string The word in singular form. + */ + public function singularize(string $word): string + { + return $this->singularizer->inflect($word); + } + + /** + * Returns a word in plural form. + * + * @param string $word The word in singular form. + * + * @return string The word in plural form. + */ + public function pluralize(string $word): string + { + return $this->pluralizer->inflect($word); + } +} diff --git a/vendor/doctrine/inflector/lib/Doctrine/Inflector/InflectorFactory.php b/vendor/doctrine/inflector/lib/Doctrine/Inflector/InflectorFactory.php new file mode 100644 index 0000000..a0740a7 --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Inflector/InflectorFactory.php @@ -0,0 +1,52 @@ +getFlippedSubstitutions() + ); + } + + public static function getPluralRuleset(): Ruleset + { + return new Ruleset( + new Transformations(...Inflectible::getPlural()), + new Patterns(...Uninflected::getPlural()), + new Substitutions(...Inflectible::getIrregular()) + ); + } +} diff --git a/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/English/Uninflected.php b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/English/Uninflected.php new file mode 100644 index 0000000..02257de --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/English/Uninflected.php @@ -0,0 +1,189 @@ +getFlippedSubstitutions() + ); + } + + public static function getPluralRuleset(): Ruleset + { + return new Ruleset( + new Transformations(...Inflectible::getPlural()), + new Patterns(...Uninflected::getPlural()), + new Substitutions(...Inflectible::getIrregular()) + ); + } +} diff --git a/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/French/Uninflected.php b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/French/Uninflected.php new file mode 100644 index 0000000..9747f91 --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/French/Uninflected.php @@ -0,0 +1,28 @@ +getFlippedSubstitutions() + ); + } + + public static function getPluralRuleset(): Ruleset + { + return new Ruleset( + new Transformations(...Inflectible::getPlural()), + new Patterns(...Uninflected::getPlural()), + new Substitutions(...Inflectible::getIrregular()) + ); + } +} diff --git a/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/NorwegianBokmal/Uninflected.php b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/NorwegianBokmal/Uninflected.php new file mode 100644 index 0000000..5d8d3b3 --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/NorwegianBokmal/Uninflected.php @@ -0,0 +1,30 @@ +pattern = $pattern; + + if (isset($this->pattern[0]) && $this->pattern[0] === '/') { + $this->regex = $this->pattern; + } else { + $this->regex = '/' . $this->pattern . '/i'; + } + } + + public function getPattern(): string + { + return $this->pattern; + } + + public function getRegex(): string + { + return $this->regex; + } + + public function matches(string $word): bool + { + return preg_match($this->getRegex(), $word) === 1; + } +} diff --git a/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Patterns.php b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Patterns.php new file mode 100644 index 0000000..e8d45cb --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Patterns.php @@ -0,0 +1,34 @@ +patterns = $patterns; + + $patterns = array_map(static function (Pattern $pattern): string { + return $pattern->getPattern(); + }, $this->patterns); + + $this->regex = '/^(?:' . implode('|', $patterns) . ')$/i'; + } + + public function matches(string $word): bool + { + return preg_match($this->regex, $word, $regs) === 1; + } +} diff --git a/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Portuguese/Inflectible.php b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Portuguese/Inflectible.php new file mode 100644 index 0000000..0d41fe7 --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Portuguese/Inflectible.php @@ -0,0 +1,98 @@ +getFlippedSubstitutions() + ); + } + + public static function getPluralRuleset(): Ruleset + { + return new Ruleset( + new Transformations(...Inflectible::getPlural()), + new Patterns(...Uninflected::getPlural()), + new Substitutions(...Inflectible::getIrregular()) + ); + } +} diff --git a/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Portuguese/Uninflected.php b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Portuguese/Uninflected.php new file mode 100644 index 0000000..b8e988f --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Portuguese/Uninflected.php @@ -0,0 +1,32 @@ +regular = $regular; + $this->uninflected = $uninflected; + $this->irregular = $irregular; + } + + public function getRegular(): Transformations + { + return $this->regular; + } + + public function getUninflected(): Patterns + { + return $this->uninflected; + } + + public function getIrregular(): Substitutions + { + return $this->irregular; + } +} diff --git a/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Spanish/Inflectible.php b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Spanish/Inflectible.php new file mode 100644 index 0000000..9129460 --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Spanish/Inflectible.php @@ -0,0 +1,47 @@ +getFlippedSubstitutions() + ); + } + + public static function getPluralRuleset(): Ruleset + { + return new Ruleset( + new Transformations(...Inflectible::getPlural()), + new Patterns(...Uninflected::getPlural()), + new Substitutions(...Inflectible::getIrregular()) + ); + } +} diff --git a/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Spanish/Uninflected.php b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Spanish/Uninflected.php new file mode 100644 index 0000000..c26ebe9 --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Spanish/Uninflected.php @@ -0,0 +1,30 @@ +from = $from; + $this->to = $to; + } + + public function getFrom(): Word + { + return $this->from; + } + + public function getTo(): Word + { + return $this->to; + } +} diff --git a/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Substitutions.php b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Substitutions.php new file mode 100644 index 0000000..17ee296 --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Substitutions.php @@ -0,0 +1,57 @@ +substitutions[$substitution->getFrom()->getWord()] = $substitution; + } + } + + public function getFlippedSubstitutions(): Substitutions + { + $substitutions = []; + + foreach ($this->substitutions as $substitution) { + $substitutions[] = new Substitution( + $substitution->getTo(), + $substitution->getFrom() + ); + } + + return new Substitutions(...$substitutions); + } + + public function inflect(string $word): string + { + $lowerWord = strtolower($word); + + if (isset($this->substitutions[$lowerWord])) { + $firstLetterUppercase = $lowerWord[0] !== $word[0]; + + $toWord = $this->substitutions[$lowerWord]->getTo()->getWord(); + + if ($firstLetterUppercase) { + return strtoupper($toWord[0]) . substr($toWord, 1); + } + + return $toWord; + } + + return $word; + } +} diff --git a/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Transformation.php b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Transformation.php new file mode 100644 index 0000000..30dcd59 --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Transformation.php @@ -0,0 +1,39 @@ +pattern = $pattern; + $this->replacement = $replacement; + } + + public function getPattern(): Pattern + { + return $this->pattern; + } + + public function getReplacement(): string + { + return $this->replacement; + } + + public function inflect(string $word): string + { + return (string) preg_replace($this->pattern->getRegex(), $this->replacement, $word); + } +} diff --git a/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Transformations.php b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Transformations.php new file mode 100644 index 0000000..b6a48fa --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Transformations.php @@ -0,0 +1,29 @@ +transformations = $transformations; + } + + public function inflect(string $word): string + { + foreach ($this->transformations as $transformation) { + if ($transformation->getPattern()->matches($word)) { + return $transformation->inflect($word); + } + } + + return $word; + } +} diff --git a/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Turkish/Inflectible.php b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Turkish/Inflectible.php new file mode 100644 index 0000000..a2bda0d --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Turkish/Inflectible.php @@ -0,0 +1,34 @@ +getFlippedSubstitutions() + ); + } + + public static function getPluralRuleset(): Ruleset + { + return new Ruleset( + new Transformations(...Inflectible::getPlural()), + new Patterns(...Uninflected::getPlural()), + new Substitutions(...Inflectible::getIrregular()) + ); + } +} diff --git a/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Turkish/Uninflected.php b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Turkish/Uninflected.php new file mode 100644 index 0000000..ec1c37d --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Turkish/Uninflected.php @@ -0,0 +1,30 @@ +word = $word; + } + + public function getWord(): string + { + return $this->word; + } +} diff --git a/vendor/doctrine/inflector/lib/Doctrine/Inflector/RulesetInflector.php b/vendor/doctrine/inflector/lib/Doctrine/Inflector/RulesetInflector.php new file mode 100644 index 0000000..12b2ed5 --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Inflector/RulesetInflector.php @@ -0,0 +1,56 @@ +rulesets = array_merge([$ruleset], $rulesets); + } + + public function inflect(string $word): string + { + if ($word === '') { + return ''; + } + + foreach ($this->rulesets as $ruleset) { + if ($ruleset->getUninflected()->matches($word)) { + return $word; + } + + $inflected = $ruleset->getIrregular()->inflect($word); + + if ($inflected !== $word) { + return $inflected; + } + + $inflected = $ruleset->getRegular()->inflect($word); + + if ($inflected !== $word) { + return $inflected; + } + } + + return $word; + } +} diff --git a/vendor/doctrine/inflector/lib/Doctrine/Inflector/WordInflector.php b/vendor/doctrine/inflector/lib/Doctrine/Inflector/WordInflector.php new file mode 100644 index 0000000..b88b1d6 --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Inflector/WordInflector.php @@ -0,0 +1,10 @@ + + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/graham-campbell/result-type/src/Error.php b/vendor/graham-campbell/result-type/src/Error.php new file mode 100644 index 0000000..add9b2d --- /dev/null +++ b/vendor/graham-campbell/result-type/src/Error.php @@ -0,0 +1,120 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace GrahamCampbell\ResultType; + +use PhpOption\None; +use PhpOption\Some; + +/** + * @template T + * @template E + * @extends \GrahamCampbell\ResultType\Result + */ +final class Error extends Result +{ + /** + * @var E + */ + private $value; + + /** + * Internal constructor for an error value. + * + * @param E $value + * + * @return void + */ + private function __construct($value) + { + $this->value = $value; + } + + /** + * Create a new error value. + * + * @template F + * + * @param F $value + * + * @return \GrahamCampbell\ResultType\Result + */ + public static function create($value) + { + return new self($value); + } + + /** + * Get the success option value. + * + * @return \PhpOption\Option + */ + public function success() + { + return None::create(); + } + + /** + * Map over the success value. + * + * @template S + * + * @param callable(T):S $f + * + * @return \GrahamCampbell\ResultType\Result + */ + public function map(callable $f) + { + return self::create($this->value); + } + + /** + * Flat map over the success value. + * + * @template S + * @template F + * + * @param callable(T):\GrahamCampbell\ResultType\Result $f + * + * @return \GrahamCampbell\ResultType\Result + */ + public function flatMap(callable $f) + { + /** @var \GrahamCampbell\ResultType\Result */ + return self::create($this->value); + } + + /** + * Get the error option value. + * + * @return \PhpOption\Option + */ + public function error() + { + return Some::create($this->value); + } + + /** + * Map over the error value. + * + * @template F + * + * @param callable(E):F $f + * + * @return \GrahamCampbell\ResultType\Result + */ + public function mapError(callable $f) + { + return self::create($f($this->value)); + } +} diff --git a/vendor/graham-campbell/result-type/src/Result.php b/vendor/graham-campbell/result-type/src/Result.php new file mode 100644 index 0000000..8c67bcd --- /dev/null +++ b/vendor/graham-campbell/result-type/src/Result.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 GrahamCampbell\ResultType; + +/** + * @template T + * @template E + */ +abstract class Result +{ + /** + * Get the success option value. + * + * @return \PhpOption\Option + */ + abstract public function success(); + + /** + * Map over the success value. + * + * @template S + * + * @param callable(T):S $f + * + * @return \GrahamCampbell\ResultType\Result + */ + abstract public function map(callable $f); + + /** + * Flat map over the success value. + * + * @template S + * @template F + * + * @param callable(T):\GrahamCampbell\ResultType\Result $f + * + * @return \GrahamCampbell\ResultType\Result + */ + abstract public function flatMap(callable $f); + + /** + * Get the error option value. + * + * @return \PhpOption\Option + */ + abstract public function error(); + + /** + * Map over the error value. + * + * @template F + * + * @param callable(E):F $f + * + * @return \GrahamCampbell\ResultType\Result + */ + abstract public function mapError(callable $f); +} diff --git a/vendor/graham-campbell/result-type/src/Success.php b/vendor/graham-campbell/result-type/src/Success.php new file mode 100644 index 0000000..b68ad57 --- /dev/null +++ b/vendor/graham-campbell/result-type/src/Success.php @@ -0,0 +1,119 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace GrahamCampbell\ResultType; + +use PhpOption\None; +use PhpOption\Some; + +/** + * @template T + * @template E + * @extends \GrahamCampbell\ResultType\Result + */ +final class Success extends Result +{ + /** + * @var T + */ + private $value; + + /** + * Internal constructor for a success value. + * + * @param T $value + * + * @return void + */ + private function __construct($value) + { + $this->value = $value; + } + + /** + * Create a new error value. + * + * @template S + * + * @param S $value + * + * @return \GrahamCampbell\ResultType\Result + */ + public static function create($value) + { + return new self($value); + } + + /** + * Get the success option value. + * + * @return \PhpOption\Option + */ + public function success() + { + return Some::create($this->value); + } + + /** + * Map over the success value. + * + * @template S + * + * @param callable(T):S $f + * + * @return \GrahamCampbell\ResultType\Result + */ + public function map(callable $f) + { + return self::create($f($this->value)); + } + + /** + * Flat map over the success value. + * + * @template S + * @template F + * + * @param callable(T):\GrahamCampbell\ResultType\Result $f + * + * @return \GrahamCampbell\ResultType\Result + */ + public function flatMap(callable $f) + { + return $f($this->value); + } + + /** + * Get the error option value. + * + * @return \PhpOption\Option + */ + public function error() + { + return None::create(); + } + + /** + * Map over the error value. + * + * @template F + * + * @param callable(E):F $f + * + * @return \GrahamCampbell\ResultType\Result + */ + public function mapError(callable $f) + { + return self::create($this->value); + } +} diff --git a/vendor/guzzlehttp/guzzle/CHANGELOG.md b/vendor/guzzlehttp/guzzle/CHANGELOG.md new file mode 100644 index 0000000..1144eb7 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/CHANGELOG.md @@ -0,0 +1,1609 @@ +# Change Log + +Please refer to [UPGRADING](UPGRADING.md) guide for upgrading to a major version. + + +## 7.7.0 - 2023-05-21 + +### Added + +- Support `guzzlehttp/promises` v2 + + +## 7.6.1 - 2023-05-15 + +### Fixed + +- Fix `SetCookie::fromString` MaxAge deprecation warning and skip invalid MaxAge values + + +## 7.6.0 - 2023-05-14 + +### Added + +- Support for setting the minimum TLS version in a unified way +- Apply on request the version set in options parameters + + +## 7.5.2 - 2023-05-14 + +### Fixed + +- Fixed set cookie constructor validation +- Fixed handling of files with `'0'` body + +### Changed + +- Corrected docs and default connect timeout value to 300 seconds + + +## 7.5.1 - 2023-04-17 + +### Fixed + +- Fixed `NO_PROXY` settings so that setting the `proxy` option to `no` overrides the env variable + +### Changed + +- Adjusted `guzzlehttp/psr7` version constraint to `^1.9.1 || ^2.4.5` + + +## 7.5.0 - 2022-08-28 + +### Added + +- Support PHP 8.2 +- Add request to delay closure params + + +## 7.4.5 - 2022-06-20 + +### Fixed + +* Fix change in port should be considered a change in origin +* Fix `CURLOPT_HTTPAUTH` option not cleared on change of origin + + +## 7.4.4 - 2022-06-09 + +### Fixed + +* Fix failure to strip Authorization header on HTTP downgrade +* Fix failure to strip the Cookie header on change in host or HTTP downgrade + + +## 7.4.3 - 2022-05-25 + +### Fixed + +* Fix cross-domain cookie leakage + + +## 7.4.2 - 2022-03-20 + +### Fixed + +- Remove curl auth on cross-domain redirects to align with the Authorization HTTP header +- Reject non-HTTP schemes in StreamHandler +- Set a default ssl.peer_name context in StreamHandler to allow `force_ip_resolve` + + +## 7.4.1 - 2021-12-06 + +### Changed + +- Replaced implicit URI to string coercion [#2946](https://github.com/guzzle/guzzle/pull/2946) +- Allow `symfony/deprecation-contracts` version 3 [#2961](https://github.com/guzzle/guzzle/pull/2961) + +### Fixed + +- Only close curl handle if it's done [#2950](https://github.com/guzzle/guzzle/pull/2950) + + +## 7.4.0 - 2021-10-18 + +### Added + +- Support PHP 8.1 [#2929](https://github.com/guzzle/guzzle/pull/2929), [#2939](https://github.com/guzzle/guzzle/pull/2939) +- Support `psr/log` version 2 and 3 [#2943](https://github.com/guzzle/guzzle/pull/2943) + +### Fixed + +- Make sure we always call `restore_error_handler()` [#2915](https://github.com/guzzle/guzzle/pull/2915) +- Fix progress parameter type compatibility between the cURL and stream handlers [#2936](https://github.com/guzzle/guzzle/pull/2936) +- Throw `InvalidArgumentException` when an incorrect `headers` array is provided [#2916](https://github.com/guzzle/guzzle/pull/2916), [#2942](https://github.com/guzzle/guzzle/pull/2942) + +### Changed + +- Be more strict with types [#2914](https://github.com/guzzle/guzzle/pull/2914), [#2917](https://github.com/guzzle/guzzle/pull/2917), [#2919](https://github.com/guzzle/guzzle/pull/2919), [#2945](https://github.com/guzzle/guzzle/pull/2945) + + +## 7.3.0 - 2021-03-23 + +### Added + +- Support for DER and P12 certificates [#2413](https://github.com/guzzle/guzzle/pull/2413) +- Support the cURL (http://) scheme for StreamHandler proxies [#2850](https://github.com/guzzle/guzzle/pull/2850) +- Support for `guzzlehttp/psr7:^2.0` [#2878](https://github.com/guzzle/guzzle/pull/2878) + +### Fixed + +- Handle exceptions on invalid header consistently between PHP versions and handlers [#2872](https://github.com/guzzle/guzzle/pull/2872) + + +## 7.2.0 - 2020-10-10 + +### Added + +- Support for PHP 8 [#2712](https://github.com/guzzle/guzzle/pull/2712), [#2715](https://github.com/guzzle/guzzle/pull/2715), [#2789](https://github.com/guzzle/guzzle/pull/2789) +- Support passing a body summarizer to the http errors middleware [#2795](https://github.com/guzzle/guzzle/pull/2795) + +### Fixed + +- Handle exceptions during response creation [#2591](https://github.com/guzzle/guzzle/pull/2591) +- Fix CURLOPT_ENCODING not to be overwritten [#2595](https://github.com/guzzle/guzzle/pull/2595) +- Make sure the Request always has a body object [#2804](https://github.com/guzzle/guzzle/pull/2804) + +### Changed + +- The `TooManyRedirectsException` has a response [#2660](https://github.com/guzzle/guzzle/pull/2660) +- Avoid "functions" from dependencies [#2712](https://github.com/guzzle/guzzle/pull/2712) + +### Deprecated + +- Using environment variable GUZZLE_CURL_SELECT_TIMEOUT [#2786](https://github.com/guzzle/guzzle/pull/2786) + + +## 7.1.1 - 2020-09-30 + +### Fixed + +- Incorrect EOF detection for response body streams on Windows. + +### Changed + +- We dont connect curl `sink` on HEAD requests. +- Removed some PHP 5 workarounds + + +## 7.1.0 - 2020-09-22 + +### Added + +- `GuzzleHttp\MessageFormatterInterface` + +### Fixed + +- Fixed issue that caused cookies with no value not to be stored. +- On redirects, we allow all safe methods like GET, HEAD and OPTIONS. +- Fixed logging on empty responses. +- Make sure MessageFormatter::format returns string + +### Deprecated + +- All functions in `GuzzleHttp` has been deprecated. Use static methods on `Utils` instead. +- `ClientInterface::getConfig()` +- `Client::getConfig()` +- `Client::__call()` +- `Utils::defaultCaBundle()` +- `CurlFactory::LOW_CURL_VERSION_NUMBER` + + +## 7.0.1 - 2020-06-27 + +* Fix multiply defined functions fatal error [#2699](https://github.com/guzzle/guzzle/pull/2699) + + +## 7.0.0 - 2020-06-27 + +No changes since 7.0.0-rc1. + + +## 7.0.0-rc1 - 2020-06-15 + +### Changed + +* Use error level for logging errors in Middleware [#2629](https://github.com/guzzle/guzzle/pull/2629) +* Disabled IDN support by default and require ext-intl to use it [#2675](https://github.com/guzzle/guzzle/pull/2675) + + +## 7.0.0-beta2 - 2020-05-25 + +### Added + +* Using `Utils` class instead of functions in the `GuzzleHttp` namespace. [#2546](https://github.com/guzzle/guzzle/pull/2546) +* `ClientInterface::MAJOR_VERSION` [#2583](https://github.com/guzzle/guzzle/pull/2583) + +### Changed + +* Avoid the `getenv` function when unsafe [#2531](https://github.com/guzzle/guzzle/pull/2531) +* Added real client methods [#2529](https://github.com/guzzle/guzzle/pull/2529) +* Avoid functions due to global install conflicts [#2546](https://github.com/guzzle/guzzle/pull/2546) +* Use Symfony intl-idn polyfill [#2550](https://github.com/guzzle/guzzle/pull/2550) +* Adding methods for HTTP verbs like `Client::get()`, `Client::head()`, `Client::patch()` etc [#2529](https://github.com/guzzle/guzzle/pull/2529) +* `ConnectException` extends `TransferException` [#2541](https://github.com/guzzle/guzzle/pull/2541) +* Updated the default User Agent to "GuzzleHttp/7" [#2654](https://github.com/guzzle/guzzle/pull/2654) + +### Fixed + +* Various intl icu issues [#2626](https://github.com/guzzle/guzzle/pull/2626) + +### Removed + +* Pool option `pool_size` [#2528](https://github.com/guzzle/guzzle/pull/2528) + + +## 7.0.0-beta1 - 2019-12-30 + +The diff might look very big but 95% of Guzzle users will be able to upgrade without modification. +Please see [the upgrade document](UPGRADING.md) that describes all BC breaking changes. + +### Added + +* Implement PSR-18 and dropped PHP 5 support [#2421](https://github.com/guzzle/guzzle/pull/2421) [#2474](https://github.com/guzzle/guzzle/pull/2474) +* PHP 7 types [#2442](https://github.com/guzzle/guzzle/pull/2442) [#2449](https://github.com/guzzle/guzzle/pull/2449) [#2466](https://github.com/guzzle/guzzle/pull/2466) [#2497](https://github.com/guzzle/guzzle/pull/2497) [#2499](https://github.com/guzzle/guzzle/pull/2499) +* IDN support for redirects [2424](https://github.com/guzzle/guzzle/pull/2424) + +### Changed + +* Dont allow passing null as third argument to `BadResponseException::__construct()` [#2427](https://github.com/guzzle/guzzle/pull/2427) +* Use SAPI constant instead of method call [#2450](https://github.com/guzzle/guzzle/pull/2450) +* Use native function invocation [#2444](https://github.com/guzzle/guzzle/pull/2444) +* Better defaults for PHP installations with old ICU lib [2454](https://github.com/guzzle/guzzle/pull/2454) +* Added visibility to all constants [#2462](https://github.com/guzzle/guzzle/pull/2462) +* Dont allow passing `null` as URI to `Client::request()` and `Client::requestAsync()` [#2461](https://github.com/guzzle/guzzle/pull/2461) +* Widen the exception argument to throwable [#2495](https://github.com/guzzle/guzzle/pull/2495) + +### Fixed + +* Logging when Promise rejected with a string [#2311](https://github.com/guzzle/guzzle/pull/2311) + +### Removed + +* Class `SeekException` [#2162](https://github.com/guzzle/guzzle/pull/2162) +* `RequestException::getResponseBodySummary()` [#2425](https://github.com/guzzle/guzzle/pull/2425) +* `CookieJar::getCookieValue()` [#2433](https://github.com/guzzle/guzzle/pull/2433) +* `uri_template()` and `UriTemplate` [#2440](https://github.com/guzzle/guzzle/pull/2440) +* Request options `save_to` and `exceptions` [#2464](https://github.com/guzzle/guzzle/pull/2464) + + +## 6.5.2 - 2019-12-23 + +* idn_to_ascii() fix for old PHP versions [#2489](https://github.com/guzzle/guzzle/pull/2489) + + +## 6.5.1 - 2019-12-21 + +* Better defaults for PHP installations with old ICU lib [#2454](https://github.com/guzzle/guzzle/pull/2454) +* IDN support for redirects [#2424](https://github.com/guzzle/guzzle/pull/2424) + + +## 6.5.0 - 2019-12-07 + +* Improvement: Added support for reset internal queue in MockHandler. [#2143](https://github.com/guzzle/guzzle/pull/2143) +* Improvement: Added support to pass arbitrary options to `curl_multi_init`. [#2287](https://github.com/guzzle/guzzle/pull/2287) +* Fix: Gracefully handle passing `null` to the `header` option. [#2132](https://github.com/guzzle/guzzle/pull/2132) +* Fix: `RetryMiddleware` did not do exponential delay between retires due unit mismatch. [#2132](https://github.com/guzzle/guzzle/pull/2132) +* Fix: Prevent undefined offset when using array for ssl_key options. [#2348](https://github.com/guzzle/guzzle/pull/2348) +* Deprecated `ClientInterface::VERSION` + + +## 6.4.1 - 2019-10-23 + +* No `guzzle.phar` was created in 6.4.0 due expired API token. This release will fix that +* Added `parent::__construct()` to `FileCookieJar` and `SessionCookieJar` + + +## 6.4.0 - 2019-10-23 + +* Improvement: Improved error messages when using curl < 7.21.2 [#2108](https://github.com/guzzle/guzzle/pull/2108) +* Fix: Test if response is readable before returning a summary in `RequestException::getResponseBodySummary()` [#2081](https://github.com/guzzle/guzzle/pull/2081) +* Fix: Add support for GUZZLE_CURL_SELECT_TIMEOUT environment variable [#2161](https://github.com/guzzle/guzzle/pull/2161) +* Improvement: Added `GuzzleHttp\Exception\InvalidArgumentException` [#2163](https://github.com/guzzle/guzzle/pull/2163) +* Improvement: Added `GuzzleHttp\_current_time()` to use `hrtime()` if that function exists. [#2242](https://github.com/guzzle/guzzle/pull/2242) +* Improvement: Added curl's `appconnect_time` in `TransferStats` [#2284](https://github.com/guzzle/guzzle/pull/2284) +* Improvement: Make GuzzleException extend Throwable wherever it's available [#2273](https://github.com/guzzle/guzzle/pull/2273) +* Fix: Prevent concurrent writes to file when saving `CookieJar` [#2335](https://github.com/guzzle/guzzle/pull/2335) +* Improvement: Update `MockHandler` so we can test transfer time [#2362](https://github.com/guzzle/guzzle/pull/2362) + + +## 6.3.3 - 2018-04-22 + +* Fix: Default headers when decode_content is specified + + +## 6.3.2 - 2018-03-26 + +* Fix: Release process + + +## 6.3.1 - 2018-03-26 + +* Bug fix: Parsing 0 epoch expiry times in cookies [#2014](https://github.com/guzzle/guzzle/pull/2014) +* Improvement: Better ConnectException detection [#2012](https://github.com/guzzle/guzzle/pull/2012) +* Bug fix: Malformed domain that contains a "/" [#1999](https://github.com/guzzle/guzzle/pull/1999) +* Bug fix: Undefined offset when a cookie has no first key-value pair [#1998](https://github.com/guzzle/guzzle/pull/1998) +* Improvement: Support PHPUnit 6 [#1953](https://github.com/guzzle/guzzle/pull/1953) +* Bug fix: Support empty headers [#1915](https://github.com/guzzle/guzzle/pull/1915) +* Bug fix: Ignore case during header modifications [#1916](https://github.com/guzzle/guzzle/pull/1916) + ++ Minor code cleanups, documentation fixes and clarifications. + + +## 6.3.0 - 2017-06-22 + +* Feature: force IP resolution (ipv4 or ipv6) [#1608](https://github.com/guzzle/guzzle/pull/1608), [#1659](https://github.com/guzzle/guzzle/pull/1659) +* Improvement: Don't include summary in exception message when body is empty [#1621](https://github.com/guzzle/guzzle/pull/1621) +* Improvement: Handle `on_headers` option in MockHandler [#1580](https://github.com/guzzle/guzzle/pull/1580) +* Improvement: Added SUSE Linux CA path [#1609](https://github.com/guzzle/guzzle/issues/1609) +* Improvement: Use class reference for getting the name of the class instead of using hardcoded strings [#1641](https://github.com/guzzle/guzzle/pull/1641) +* Feature: Added `read_timeout` option [#1611](https://github.com/guzzle/guzzle/pull/1611) +* Bug fix: PHP 7.x fixes [#1685](https://github.com/guzzle/guzzle/pull/1685), [#1686](https://github.com/guzzle/guzzle/pull/1686), [#1811](https://github.com/guzzle/guzzle/pull/1811) +* Deprecation: BadResponseException instantiation without a response [#1642](https://github.com/guzzle/guzzle/pull/1642) +* Feature: Added NTLM auth [#1569](https://github.com/guzzle/guzzle/pull/1569) +* Feature: Track redirect HTTP status codes [#1711](https://github.com/guzzle/guzzle/pull/1711) +* Improvement: Check handler type during construction [#1745](https://github.com/guzzle/guzzle/pull/1745) +* Improvement: Always include the Content-Length if there's a body [#1721](https://github.com/guzzle/guzzle/pull/1721) +* Feature: Added convenience method to access a cookie by name [#1318](https://github.com/guzzle/guzzle/pull/1318) +* Bug fix: Fill `CURLOPT_CAPATH` and `CURLOPT_CAINFO` properly [#1684](https://github.com/guzzle/guzzle/pull/1684) +* Improvement: Use `\GuzzleHttp\Promise\rejection_for` function instead of object init [#1827](https://github.com/guzzle/guzzle/pull/1827) + ++ Minor code cleanups, documentation fixes and clarifications. + + +## 6.2.3 - 2017-02-28 + +* Fix deprecations with guzzle/psr7 version 1.4 + + +## 6.2.2 - 2016-10-08 + +* Allow to pass nullable Response to delay callable +* Only add scheme when host is present +* Fix drain case where content-length is the literal string zero +* Obfuscate in-URL credentials in exceptions + + +## 6.2.1 - 2016-07-18 + +* Address HTTP_PROXY security vulnerability, CVE-2016-5385: + https://httpoxy.org/ +* Fixing timeout bug with StreamHandler: + https://github.com/guzzle/guzzle/pull/1488 +* Only read up to `Content-Length` in PHP StreamHandler to avoid timeouts when + a server does not honor `Connection: close`. +* Ignore URI fragment when sending requests. + + +## 6.2.0 - 2016-03-21 + +* Feature: added `GuzzleHttp\json_encode` and `GuzzleHttp\json_decode`. + https://github.com/guzzle/guzzle/pull/1389 +* Bug fix: Fix sleep calculation when waiting for delayed requests. + https://github.com/guzzle/guzzle/pull/1324 +* Feature: More flexible history containers. + https://github.com/guzzle/guzzle/pull/1373 +* Bug fix: defer sink stream opening in StreamHandler. + https://github.com/guzzle/guzzle/pull/1377 +* Bug fix: do not attempt to escape cookie values. + https://github.com/guzzle/guzzle/pull/1406 +* Feature: report original content encoding and length on decoded responses. + https://github.com/guzzle/guzzle/pull/1409 +* Bug fix: rewind seekable request bodies before dispatching to cURL. + https://github.com/guzzle/guzzle/pull/1422 +* Bug fix: provide an empty string to `http_build_query` for HHVM workaround. + https://github.com/guzzle/guzzle/pull/1367 + + +## 6.1.1 - 2015-11-22 + +* Bug fix: Proxy::wrapSync() now correctly proxies to the appropriate handler + https://github.com/guzzle/guzzle/commit/911bcbc8b434adce64e223a6d1d14e9a8f63e4e4 +* Feature: HandlerStack is now more generic. + https://github.com/guzzle/guzzle/commit/f2102941331cda544745eedd97fc8fd46e1ee33e +* Bug fix: setting verify to false in the StreamHandler now disables peer + verification. https://github.com/guzzle/guzzle/issues/1256 +* Feature: Middleware now uses an exception factory, including more error + context. https://github.com/guzzle/guzzle/pull/1282 +* Feature: better support for disabled functions. + https://github.com/guzzle/guzzle/pull/1287 +* Bug fix: fixed regression where MockHandler was not using `sink`. + https://github.com/guzzle/guzzle/pull/1292 + + +## 6.1.0 - 2015-09-08 + +* Feature: Added the `on_stats` request option to provide access to transfer + statistics for requests. https://github.com/guzzle/guzzle/pull/1202 +* Feature: Added the ability to persist session cookies in CookieJars. + https://github.com/guzzle/guzzle/pull/1195 +* Feature: Some compatibility updates for Google APP Engine + https://github.com/guzzle/guzzle/pull/1216 +* Feature: Added support for NO_PROXY to prevent the use of a proxy based on + a simple set of rules. https://github.com/guzzle/guzzle/pull/1197 +* Feature: Cookies can now contain square brackets. + https://github.com/guzzle/guzzle/pull/1237 +* Bug fix: Now correctly parsing `=` inside of quotes in Cookies. + https://github.com/guzzle/guzzle/pull/1232 +* Bug fix: Cusotm cURL options now correctly override curl options of the + same name. https://github.com/guzzle/guzzle/pull/1221 +* Bug fix: Content-Type header is now added when using an explicitly provided + multipart body. https://github.com/guzzle/guzzle/pull/1218 +* Bug fix: Now ignoring Set-Cookie headers that have no name. +* Bug fix: Reason phrase is no longer cast to an int in some cases in the + cURL handler. https://github.com/guzzle/guzzle/pull/1187 +* Bug fix: Remove the Authorization header when redirecting if the Host + header changes. https://github.com/guzzle/guzzle/pull/1207 +* Bug fix: Cookie path matching fixes + https://github.com/guzzle/guzzle/issues/1129 +* Bug fix: Fixing the cURL `body_as_string` setting + https://github.com/guzzle/guzzle/pull/1201 +* Bug fix: quotes are no longer stripped when parsing cookies. + https://github.com/guzzle/guzzle/issues/1172 +* Bug fix: `form_params` and `query` now always uses the `&` separator. + https://github.com/guzzle/guzzle/pull/1163 +* Bug fix: Adding a Content-Length to PHP stream wrapper requests if not set. + https://github.com/guzzle/guzzle/pull/1189 + + +## 6.0.2 - 2015-07-04 + +* Fixed a memory leak in the curl handlers in which references to callbacks + were not being removed by `curl_reset`. +* Cookies are now extracted properly before redirects. +* Cookies now allow more character ranges. +* Decoded Content-Encoding responses are now modified to correctly reflect + their state if the encoding was automatically removed by a handler. This + means that the `Content-Encoding` header may be removed an the + `Content-Length` modified to reflect the message size after removing the + encoding. +* Added a more explicit error message when trying to use `form_params` and + `multipart` in the same request. +* Several fixes for HHVM support. +* Functions are now conditionally required using an additional level of + indirection to help with global Composer installations. + + +## 6.0.1 - 2015-05-27 + +* Fixed a bug with serializing the `query` request option where the `&` + separator was missing. +* Added a better error message for when `body` is provided as an array. Please + use `form_params` or `multipart` instead. +* Various doc fixes. + + +## 6.0.0 - 2015-05-26 + +* See the UPGRADING.md document for more information. +* Added `multipart` and `form_params` request options. +* Added `synchronous` request option. +* Added the `on_headers` request option. +* Fixed `expect` handling. +* No longer adding default middlewares in the client ctor. These need to be + present on the provided handler in order to work. +* Requests are no longer initiated when sending async requests with the + CurlMultiHandler. This prevents unexpected recursion from requests completing + while ticking the cURL loop. +* Removed the semantics of setting `default` to `true`. This is no longer + required now that the cURL loop is not ticked for async requests. +* Added request and response logging middleware. +* No longer allowing self signed certificates when using the StreamHandler. +* Ensuring that `sink` is valid if saving to a file. +* Request exceptions now include a "handler context" which provides handler + specific contextual information. +* Added `GuzzleHttp\RequestOptions` to allow request options to be applied + using constants. +* `$maxHandles` has been removed from CurlMultiHandler. +* `MultipartPostBody` is now part of the `guzzlehttp/psr7` package. + + +## 5.3.0 - 2015-05-19 + +* Mock now supports `save_to` +* Marked `AbstractRequestEvent::getTransaction()` as public. +* Fixed a bug in which multiple headers using different casing would overwrite + previous headers in the associative array. +* Added `Utils::getDefaultHandler()` +* Marked `GuzzleHttp\Client::getDefaultUserAgent` as deprecated. +* URL scheme is now always lowercased. + + +## 6.0.0-beta.1 + +* Requires PHP >= 5.5 +* Updated to use PSR-7 + * Requires immutable messages, which basically means an event based system + owned by a request instance is no longer possible. + * Utilizing the [Guzzle PSR-7 package](https://github.com/guzzle/psr7). + * Removed the dependency on `guzzlehttp/streams`. These stream abstractions + are available in the `guzzlehttp/psr7` package under the `GuzzleHttp\Psr7` + namespace. +* Added middleware and handler system + * Replaced the Guzzle event and subscriber system with a middleware system. + * No longer depends on RingPHP, but rather places the HTTP handlers directly + in Guzzle, operating on PSR-7 messages. + * Retry logic is now encapsulated in `GuzzleHttp\Middleware::retry`, which + means the `guzzlehttp/retry-subscriber` is now obsolete. + * Mocking responses is now handled using `GuzzleHttp\Handler\MockHandler`. +* Asynchronous responses + * No longer supports the `future` request option to send an async request. + Instead, use one of the `*Async` methods of a client (e.g., `requestAsync`, + `getAsync`, etc.). + * Utilizing `GuzzleHttp\Promise` instead of React's promise library to avoid + recursion required by chaining and forwarding react promises. See + https://github.com/guzzle/promises + * Added `requestAsync` and `sendAsync` to send request asynchronously. + * Added magic methods for `getAsync()`, `postAsync()`, etc. to send requests + asynchronously. +* Request options + * POST and form updates + * Added the `form_fields` and `form_files` request options. + * Removed the `GuzzleHttp\Post` namespace. + * The `body` request option no longer accepts an array for POST requests. + * The `exceptions` request option has been deprecated in favor of the + `http_errors` request options. + * The `save_to` request option has been deprecated in favor of `sink` request + option. +* Clients no longer accept an array of URI template string and variables for + URI variables. You will need to expand URI templates before passing them + into a client constructor or request method. +* Client methods `get()`, `post()`, `put()`, `patch()`, `options()`, etc. are + now magic methods that will send synchronous requests. +* Replaced `Utils.php` with plain functions in `functions.php`. +* Removed `GuzzleHttp\Collection`. +* Removed `GuzzleHttp\BatchResults`. Batched pool results are now returned as + an array. +* Removed `GuzzleHttp\Query`. Query string handling is now handled using an + associative array passed into the `query` request option. The query string + is serialized using PHP's `http_build_query`. If you need more control, you + can pass the query string in as a string. +* `GuzzleHttp\QueryParser` has been replaced with the + `GuzzleHttp\Psr7\parse_query`. + + +## 5.2.0 - 2015-01-27 + +* Added `AppliesHeadersInterface` to make applying headers to a request based + on the body more generic and not specific to `PostBodyInterface`. +* Reduced the number of stack frames needed to send requests. +* Nested futures are now resolved in the client rather than the RequestFsm +* Finishing state transitions is now handled in the RequestFsm rather than the + RingBridge. +* Added a guard in the Pool class to not use recursion for request retries. + + +## 5.1.0 - 2014-12-19 + +* Pool class no longer uses recursion when a request is intercepted. +* The size of a Pool can now be dynamically adjusted using a callback. + See https://github.com/guzzle/guzzle/pull/943. +* Setting a request option to `null` when creating a request with a client will + ensure that the option is not set. This allows you to overwrite default + request options on a per-request basis. + See https://github.com/guzzle/guzzle/pull/937. +* Added the ability to limit which protocols are allowed for redirects by + specifying a `protocols` array in the `allow_redirects` request option. +* Nested futures due to retries are now resolved when waiting for synchronous + responses. See https://github.com/guzzle/guzzle/pull/947. +* `"0"` is now an allowed URI path. See + https://github.com/guzzle/guzzle/pull/935. +* `Query` no longer typehints on the `$query` argument in the constructor, + allowing for strings and arrays. +* Exceptions thrown in the `end` event are now correctly wrapped with Guzzle + specific exceptions if necessary. + + +## 5.0.3 - 2014-11-03 + +This change updates query strings so that they are treated as un-encoded values +by default where the value represents an un-encoded value to send over the +wire. A Query object then encodes the value before sending over the wire. This +means that even value query string values (e.g., ":") are url encoded. This +makes the Query class match PHP's http_build_query function. However, if you +want to send requests over the wire using valid query string characters that do +not need to be encoded, then you can provide a string to Url::setQuery() and +pass true as the second argument to specify that the query string is a raw +string that should not be parsed or encoded (unless a call to getQuery() is +subsequently made, forcing the query-string to be converted into a Query +object). + + +## 5.0.2 - 2014-10-30 + +* Added a trailing `\r\n` to multipart/form-data payloads. See + https://github.com/guzzle/guzzle/pull/871 +* Added a `GuzzleHttp\Pool::send()` convenience method to match the docs. +* Status codes are now returned as integers. See + https://github.com/guzzle/guzzle/issues/881 +* No longer overwriting an existing `application/x-www-form-urlencoded` header + when sending POST requests, allowing for customized headers. See + https://github.com/guzzle/guzzle/issues/877 +* Improved path URL serialization. + + * No longer double percent-encoding characters in the path or query string if + they are already encoded. + * Now properly encoding the supplied path to a URL object, instead of only + encoding ' ' and '?'. + * Note: This has been changed in 5.0.3 to now encode query string values by + default unless the `rawString` argument is provided when setting the query + string on a URL: Now allowing many more characters to be present in the + query string without being percent encoded. See https://tools.ietf.org/html/rfc3986#appendix-A + + +## 5.0.1 - 2014-10-16 + +Bugfix release. + +* Fixed an issue where connection errors still returned response object in + error and end events event though the response is unusable. This has been + corrected so that a response is not returned in the `getResponse` method of + these events if the response did not complete. https://github.com/guzzle/guzzle/issues/867 +* Fixed an issue where transfer statistics were not being populated in the + RingBridge. https://github.com/guzzle/guzzle/issues/866 + + +## 5.0.0 - 2014-10-12 + +Adding support for non-blocking responses and some minor API cleanup. + +### New Features + +* Added support for non-blocking responses based on `guzzlehttp/guzzle-ring`. +* Added a public API for creating a default HTTP adapter. +* Updated the redirect plugin to be non-blocking so that redirects are sent + concurrently. Other plugins like this can now be updated to be non-blocking. +* Added a "progress" event so that you can get upload and download progress + events. +* Added `GuzzleHttp\Pool` which implements FutureInterface and transfers + requests concurrently using a capped pool size as efficiently as possible. +* Added `hasListeners()` to EmitterInterface. +* Removed `GuzzleHttp\ClientInterface::sendAll` and marked + `GuzzleHttp\Client::sendAll` as deprecated (it's still there, just not the + recommended way). + +### Breaking changes + +The breaking changes in this release are relatively minor. The biggest thing to +look out for is that request and response objects no longer implement fluent +interfaces. + +* Removed the fluent interfaces (i.e., `return $this`) from requests, + responses, `GuzzleHttp\Collection`, `GuzzleHttp\Url`, + `GuzzleHttp\Query`, `GuzzleHttp\Post\PostBody`, and + `GuzzleHttp\Cookie\SetCookie`. This blog post provides a good outline of + why I did this: https://ocramius.github.io/blog/fluent-interfaces-are-evil/. + This also makes the Guzzle message interfaces compatible with the current + PSR-7 message proposal. +* Removed "functions.php", so that Guzzle is truly PSR-4 compliant. Except + for the HTTP request functions from function.php, these functions are now + implemented in `GuzzleHttp\Utils` using camelCase. `GuzzleHttp\json_decode` + moved to `GuzzleHttp\Utils::jsonDecode`. `GuzzleHttp\get_path` moved to + `GuzzleHttp\Utils::getPath`. `GuzzleHttp\set_path` moved to + `GuzzleHttp\Utils::setPath`. `GuzzleHttp\batch` should now be + `GuzzleHttp\Pool::batch`, which returns an `objectStorage`. Using functions.php + caused problems for many users: they aren't PSR-4 compliant, require an + explicit include, and needed an if-guard to ensure that the functions are not + declared multiple times. +* Rewrote adapter layer. + * Removing all classes from `GuzzleHttp\Adapter`, these are now + implemented as callables that are stored in `GuzzleHttp\Ring\Client`. + * Removed the concept of "parallel adapters". Sending requests serially or + concurrently is now handled using a single adapter. + * Moved `GuzzleHttp\Adapter\Transaction` to `GuzzleHttp\Transaction`. The + Transaction object now exposes the request, response, and client as public + properties. The getters and setters have been removed. +* Removed the "headers" event. This event was only useful for changing the + body a response once the headers of the response were known. You can implement + a similar behavior in a number of ways. One example might be to use a + FnStream that has access to the transaction being sent. For example, when the + first byte is written, you could check if the response headers match your + expectations, and if so, change the actual stream body that is being + written to. +* Removed the `asArray` parameter from + `GuzzleHttp\Message\MessageInterface::getHeader`. If you want to get a header + value as an array, then use the newly added `getHeaderAsArray()` method of + `MessageInterface`. This change makes the Guzzle interfaces compatible with + the PSR-7 interfaces. +* `GuzzleHttp\Message\MessageFactory` no longer allows subclasses to add + custom request options using double-dispatch (this was an implementation + detail). Instead, you should now provide an associative array to the + constructor which is a mapping of the request option name mapping to a + function that applies the option value to a request. +* Removed the concept of "throwImmediately" from exceptions and error events. + This control mechanism was used to stop a transfer of concurrent requests + from completing. This can now be handled by throwing the exception or by + cancelling a pool of requests or each outstanding future request individually. +* Updated to "GuzzleHttp\Streams" 3.0. + * `GuzzleHttp\Stream\StreamInterface::getContents()` no longer accepts a + `maxLen` parameter. This update makes the Guzzle streams project + compatible with the current PSR-7 proposal. + * `GuzzleHttp\Stream\Stream::__construct`, + `GuzzleHttp\Stream\Stream::factory`, and + `GuzzleHttp\Stream\Utils::create` no longer accept a size in the second + argument. They now accept an associative array of options, including the + "size" key and "metadata" key which can be used to provide custom metadata. + + +## 4.2.2 - 2014-09-08 + +* Fixed a memory leak in the CurlAdapter when reusing cURL handles. +* No longer using `request_fulluri` in stream adapter proxies. +* Relative redirects are now based on the last response, not the first response. + +## 4.2.1 - 2014-08-19 + +* Ensuring that the StreamAdapter does not always add a Content-Type header +* Adding automated github releases with a phar and zip + +## 4.2.0 - 2014-08-17 + +* Now merging in default options using a case-insensitive comparison. + Closes https://github.com/guzzle/guzzle/issues/767 +* Added the ability to automatically decode `Content-Encoding` response bodies + using the `decode_content` request option. This is set to `true` by default + to decode the response body if it comes over the wire with a + `Content-Encoding`. Set this value to `false` to disable decoding the + response content, and pass a string to provide a request `Accept-Encoding` + header and turn on automatic response decoding. This feature now allows you + to pass an `Accept-Encoding` header in the headers of a request but still + disable automatic response decoding. + Closes https://github.com/guzzle/guzzle/issues/764 +* Added the ability to throw an exception immediately when transferring + requests in parallel. Closes https://github.com/guzzle/guzzle/issues/760 +* Updating guzzlehttp/streams dependency to ~2.1 +* No longer utilizing the now deprecated namespaced methods from the stream + package. + +## 4.1.8 - 2014-08-14 + +* Fixed an issue in the CurlFactory that caused setting the `stream=false` + request option to throw an exception. + See: https://github.com/guzzle/guzzle/issues/769 +* TransactionIterator now calls rewind on the inner iterator. + See: https://github.com/guzzle/guzzle/pull/765 +* You can now set the `Content-Type` header to `multipart/form-data` + when creating POST requests to force multipart bodies. + See https://github.com/guzzle/guzzle/issues/768 + +## 4.1.7 - 2014-08-07 + +* Fixed an error in the HistoryPlugin that caused the same request and response + to be logged multiple times when an HTTP protocol error occurs. +* Ensuring that cURL does not add a default Content-Type when no Content-Type + has been supplied by the user. This prevents the adapter layer from modifying + the request that is sent over the wire after any listeners may have already + put the request in a desired state (e.g., signed the request). +* Throwing an exception when you attempt to send requests that have the + "stream" set to true in parallel using the MultiAdapter. +* Only calling curl_multi_select when there are active cURL handles. This was + previously changed and caused performance problems on some systems due to PHP + always selecting until the maximum select timeout. +* Fixed a bug where multipart/form-data POST fields were not correctly + aggregated (e.g., values with "&"). + +## 4.1.6 - 2014-08-03 + +* Added helper methods to make it easier to represent messages as strings, + including getting the start line and getting headers as a string. + +## 4.1.5 - 2014-08-02 + +* Automatically retrying cURL "Connection died, retrying a fresh connect" + errors when possible. +* cURL implementation cleanup +* Allowing multiple event subscriber listeners to be registered per event by + passing an array of arrays of listener configuration. + +## 4.1.4 - 2014-07-22 + +* Fixed a bug that caused multi-part POST requests with more than one field to + serialize incorrectly. +* Paths can now be set to "0" +* `ResponseInterface::xml` now accepts a `libxml_options` option and added a + missing default argument that was required when parsing XML response bodies. +* A `save_to` stream is now created lazily, which means that files are not + created on disk unless a request succeeds. + +## 4.1.3 - 2014-07-15 + +* Various fixes to multipart/form-data POST uploads +* Wrapping function.php in an if-statement to ensure Guzzle can be used + globally and in a Composer install +* Fixed an issue with generating and merging in events to an event array +* POST headers are only applied before sending a request to allow you to change + the query aggregator used before uploading +* Added much more robust query string parsing +* Fixed various parsing and normalization issues with URLs +* Fixing an issue where multi-valued headers were not being utilized correctly + in the StreamAdapter + +## 4.1.2 - 2014-06-18 + +* Added support for sending payloads with GET requests + +## 4.1.1 - 2014-06-08 + +* Fixed an issue related to using custom message factory options in subclasses +* Fixed an issue with nested form fields in a multi-part POST +* Fixed an issue with using the `json` request option for POST requests +* Added `ToArrayInterface` to `GuzzleHttp\Cookie\CookieJar` + +## 4.1.0 - 2014-05-27 + +* Added a `json` request option to easily serialize JSON payloads. +* Added a `GuzzleHttp\json_decode()` wrapper to safely parse JSON. +* Added `setPort()` and `getPort()` to `GuzzleHttp\Message\RequestInterface`. +* Added the ability to provide an emitter to a client in the client constructor. +* Added the ability to persist a cookie session using $_SESSION. +* Added a trait that can be used to add event listeners to an iterator. +* Removed request method constants from RequestInterface. +* Fixed warning when invalid request start-lines are received. +* Updated MessageFactory to work with custom request option methods. +* Updated cacert bundle to latest build. + +4.0.2 (2014-04-16) +------------------ + +* Proxy requests using the StreamAdapter now properly use request_fulluri (#632) +* Added the ability to set scalars as POST fields (#628) + +## 4.0.1 - 2014-04-04 + +* The HTTP status code of a response is now set as the exception code of + RequestException objects. +* 303 redirects will now correctly switch from POST to GET requests. +* The default parallel adapter of a client now correctly uses the MultiAdapter. +* HasDataTrait now initializes the internal data array as an empty array so + that the toArray() method always returns an array. + +## 4.0.0 - 2014-03-29 + +* For information on changes and upgrading, see: + https://github.com/guzzle/guzzle/blob/master/UPGRADING.md#3x-to-40 +* Added `GuzzleHttp\batch()` as a convenience function for sending requests in + parallel without needing to write asynchronous code. +* Restructured how events are added to `GuzzleHttp\ClientInterface::sendAll()`. + You can now pass a callable or an array of associative arrays where each + associative array contains the "fn", "priority", and "once" keys. + +## 4.0.0.rc-2 - 2014-03-25 + +* Removed `getConfig()` and `setConfig()` from clients to avoid confusion + around whether things like base_url, message_factory, etc. should be able to + be retrieved or modified. +* Added `getDefaultOption()` and `setDefaultOption()` to ClientInterface +* functions.php functions were renamed using snake_case to match PHP idioms +* Added support for `HTTP_PROXY`, `HTTPS_PROXY`, and + `GUZZLE_CURL_SELECT_TIMEOUT` environment variables +* Added the ability to specify custom `sendAll()` event priorities +* Added the ability to specify custom stream context options to the stream + adapter. +* Added a functions.php function for `get_path()` and `set_path()` +* CurlAdapter and MultiAdapter now use a callable to generate curl resources +* MockAdapter now properly reads a body and emits a `headers` event +* Updated Url class to check if a scheme and host are set before adding ":" + and "//". This allows empty Url (e.g., "") to be serialized as "". +* Parsing invalid XML no longer emits warnings +* Curl classes now properly throw AdapterExceptions +* Various performance optimizations +* Streams are created with the faster `Stream\create()` function +* Marked deprecation_proxy() as internal +* Test server is now a collection of static methods on a class + +## 4.0.0-rc.1 - 2014-03-15 + +* See https://github.com/guzzle/guzzle/blob/master/UPGRADING.md#3x-to-40 + +## 3.8.1 - 2014-01-28 + +* Bug: Always using GET requests when redirecting from a 303 response +* Bug: CURLOPT_SSL_VERIFYHOST is now correctly set to false when setting `$certificateAuthority` to false in + `Guzzle\Http\ClientInterface::setSslVerification()` +* Bug: RedirectPlugin now uses strict RFC 3986 compliance when combining a base URL with a relative URL +* Bug: The body of a request can now be set to `"0"` +* Sending PHP stream requests no longer forces `HTTP/1.0` +* Adding more information to ExceptionCollection exceptions so that users have more context, including a stack trace of + each sub-exception +* Updated the `$ref` attribute in service descriptions to merge over any existing parameters of a schema (rather than + clobbering everything). +* Merging URLs will now use the query string object from the relative URL (thus allowing custom query aggregators) +* Query strings are now parsed in a way that they do no convert empty keys with no value to have a dangling `=`. + For example `foo&bar=baz` is now correctly parsed and recognized as `foo&bar=baz` rather than `foo=&bar=baz`. +* Now properly escaping the regular expression delimiter when matching Cookie domains. +* Network access is now disabled when loading XML documents + +## 3.8.0 - 2013-12-05 + +* Added the ability to define a POST name for a file +* JSON response parsing now properly walks additionalProperties +* cURL error code 18 is now retried automatically in the BackoffPlugin +* Fixed a cURL error when URLs contain fragments +* Fixed an issue in the BackoffPlugin retry event where it was trying to access all exceptions as if they were + CurlExceptions +* CURLOPT_PROGRESS function fix for PHP 5.5 (69fcc1e) +* Added the ability for Guzzle to work with older versions of cURL that do not support `CURLOPT_TIMEOUT_MS` +* Fixed a bug that was encountered when parsing empty header parameters +* UriTemplate now has a `setRegex()` method to match the docs +* The `debug` request parameter now checks if it is truthy rather than if it exists +* Setting the `debug` request parameter to true shows verbose cURL output instead of using the LogPlugin +* Added the ability to combine URLs using strict RFC 3986 compliance +* Command objects can now return the validation errors encountered by the command +* Various fixes to cache revalidation (#437 and 29797e5) +* Various fixes to the AsyncPlugin +* Cleaned up build scripts + +## 3.7.4 - 2013-10-02 + +* Bug fix: 0 is now an allowed value in a description parameter that has a default value (#430) +* Bug fix: SchemaFormatter now returns an integer when formatting to a Unix timestamp + (see https://github.com/aws/aws-sdk-php/issues/147) +* Bug fix: Cleaned up and fixed URL dot segment removal to properly resolve internal dots +* Minimum PHP version is now properly specified as 5.3.3 (up from 5.3.2) (#420) +* Updated the bundled cacert.pem (#419) +* OauthPlugin now supports adding authentication to headers or query string (#425) + +## 3.7.3 - 2013-09-08 + +* Added the ability to get the exception associated with a request/command when using `MultiTransferException` and + `CommandTransferException`. +* Setting `additionalParameters` of a response to false is now honored when parsing responses with a service description +* Schemas are only injected into response models when explicitly configured. +* No longer guessing Content-Type based on the path of a request. Content-Type is now only guessed based on the path of + an EntityBody. +* Bug fix: ChunkedIterator can now properly chunk a \Traversable as well as an \Iterator. +* Bug fix: FilterIterator now relies on `\Iterator` instead of `\Traversable`. +* Bug fix: Gracefully handling malformed responses in RequestMediator::writeResponseBody() +* Bug fix: Replaced call to canCache with canCacheRequest in the CallbackCanCacheStrategy of the CachePlugin +* Bug fix: Visiting XML attributes first before visiting XML children when serializing requests +* Bug fix: Properly parsing headers that contain commas contained in quotes +* Bug fix: mimetype guessing based on a filename is now case-insensitive + +## 3.7.2 - 2013-08-02 + +* Bug fix: Properly URL encoding paths when using the PHP-only version of the UriTemplate expander + See https://github.com/guzzle/guzzle/issues/371 +* Bug fix: Cookie domains are now matched correctly according to RFC 6265 + See https://github.com/guzzle/guzzle/issues/377 +* Bug fix: GET parameters are now used when calculating an OAuth signature +* Bug fix: Fixed an issue with cache revalidation where the If-None-Match header was being double quoted +* `Guzzle\Common\AbstractHasDispatcher::dispatch()` now returns the event that was dispatched +* `Guzzle\Http\QueryString::factory()` now guesses the most appropriate query aggregator to used based on the input. + See https://github.com/guzzle/guzzle/issues/379 +* Added a way to add custom domain objects to service description parsing using the `operation.parse_class` event. See + https://github.com/guzzle/guzzle/pull/380 +* cURL multi cleanup and optimizations + +## 3.7.1 - 2013-07-05 + +* Bug fix: Setting default options on a client now works +* Bug fix: Setting options on HEAD requests now works. See #352 +* Bug fix: Moving stream factory before send event to before building the stream. See #353 +* Bug fix: Cookies no longer match on IP addresses per RFC 6265 +* Bug fix: Correctly parsing header parameters that are in `<>` and quotes +* Added `cert` and `ssl_key` as request options +* `Host` header can now diverge from the host part of a URL if the header is set manually +* `Guzzle\Service\Command\LocationVisitor\Request\XmlVisitor` was rewritten to change from using SimpleXML to XMLWriter +* OAuth parameters are only added via the plugin if they aren't already set +* Exceptions are now thrown when a URL cannot be parsed +* Returning `false` if `Guzzle\Http\EntityBody::getContentMd5()` fails +* Not setting a `Content-MD5` on a command if calculating the Content-MD5 fails via the CommandContentMd5Plugin + +## 3.7.0 - 2013-06-10 + +* See UPGRADING.md for more information on how to upgrade. +* Requests now support the ability to specify an array of $options when creating a request to more easily modify a + request. You can pass a 'request.options' configuration setting to a client to apply default request options to + every request created by a client (e.g. default query string variables, headers, curl options, etc.). +* Added a static facade class that allows you to use Guzzle with static methods and mount the class to `\Guzzle`. + See `Guzzle\Http\StaticClient::mount`. +* Added `command.request_options` to `Guzzle\Service\Command\AbstractCommand` to pass request options to requests + created by a command (e.g. custom headers, query string variables, timeout settings, etc.). +* Stream size in `Guzzle\Stream\PhpStreamRequestFactory` will now be set if Content-Length is returned in the + headers of a response +* Added `Guzzle\Common\Collection::setPath($path, $value)` to set a value into an array using a nested key + (e.g. `$collection->setPath('foo/baz/bar', 'test'); echo $collection['foo']['bar']['bar'];`) +* ServiceBuilders now support storing and retrieving arbitrary data +* CachePlugin can now purge all resources for a given URI +* CachePlugin can automatically purge matching cached items when a non-idempotent request is sent to a resource +* CachePlugin now uses the Vary header to determine if a resource is a cache hit +* `Guzzle\Http\Message\Response` now implements `\Serializable` +* Added `Guzzle\Cache\CacheAdapterFactory::fromCache()` to more easily create cache adapters +* `Guzzle\Service\ClientInterface::execute()` now accepts an array, single command, or Traversable +* Fixed a bug in `Guzzle\Http\Message\Header\Link::addLink()` +* Better handling of calculating the size of a stream in `Guzzle\Stream\Stream` using fstat() and caching the size +* `Guzzle\Common\Exception\ExceptionCollection` now creates a more readable exception message +* Fixing BC break: Added back the MonologLogAdapter implementation rather than extending from PsrLog so that older + Symfony users can still use the old version of Monolog. +* Fixing BC break: Added the implementation back in for `Guzzle\Http\Message\AbstractMessage::getTokenizedHeader()`. + Now triggering an E_USER_DEPRECATED warning when used. Use `$message->getHeader()->parseParams()`. +* Several performance improvements to `Guzzle\Common\Collection` +* Added an `$options` argument to the end of the following methods of `Guzzle\Http\ClientInterface`: + createRequest, head, delete, put, patch, post, options, prepareRequest +* Added an `$options` argument to the end of `Guzzle\Http\Message\Request\RequestFactoryInterface::createRequest()` +* Added an `applyOptions()` method to `Guzzle\Http\Message\Request\RequestFactoryInterface` +* Changed `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $body = null)` to + `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $options = array())`. You can still pass in a + resource, string, or EntityBody into the $options parameter to specify the download location of the response. +* Changed `Guzzle\Common\Collection::__construct($data)` to no longer accepts a null value for `$data` but a + default `array()` +* Added `Guzzle\Stream\StreamInterface::isRepeatable` +* Removed `Guzzle\Http\ClientInterface::setDefaultHeaders(). Use + $client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. or + $client->getConfig()->setPath('request.options/headers', array('header_name' => 'value'))`. +* Removed `Guzzle\Http\ClientInterface::getDefaultHeaders(). Use $client->getConfig()->getPath('request.options/headers')`. +* Removed `Guzzle\Http\ClientInterface::expandTemplate()` +* Removed `Guzzle\Http\ClientInterface::setRequestFactory()` +* Removed `Guzzle\Http\ClientInterface::getCurlMulti()` +* Removed `Guzzle\Http\Message\RequestInterface::canCache` +* Removed `Guzzle\Http\Message\RequestInterface::setIsRedirect` +* Removed `Guzzle\Http\Message\RequestInterface::isRedirect` +* Made `Guzzle\Http\Client::expandTemplate` and `getUriTemplate` protected methods. +* You can now enable E_USER_DEPRECATED warnings to see if you are using a deprecated method by setting + `Guzzle\Common\Version::$emitWarnings` to true. +* Marked `Guzzle\Http\Message\Request::isResponseBodyRepeatable()` as deprecated. Use + `$request->getResponseBody()->isRepeatable()` instead. +* Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use + `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead. +* Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use + `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead. +* Marked `Guzzle\Http\Message\Request::setIsRedirect()` as deprecated. Use the HistoryPlugin instead. +* Marked `Guzzle\Http\Message\Request::isRedirect()` as deprecated. Use the HistoryPlugin instead. +* Marked `Guzzle\Cache\CacheAdapterFactory::factory()` as deprecated +* Marked 'command.headers', 'command.response_body' and 'command.on_complete' as deprecated for AbstractCommand. + These will work through Guzzle 4.0 +* Marked 'request.params' for `Guzzle\Http\Client` as deprecated. Use [request.options][params]. +* Marked `Guzzle\Service\Client::enableMagicMethods()` as deprecated. Magic methods can no longer be disabled on a Guzzle\Service\Client. +* Marked `Guzzle\Service\Client::getDefaultHeaders()` as deprecated. Use $client->getConfig()->getPath('request.options/headers')`. +* Marked `Guzzle\Service\Client::setDefaultHeaders()` as deprecated. Use $client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. +* Marked `Guzzle\Parser\Url\UrlParser` as deprecated. Just use PHP's `parse_url()` and percent encode your UTF-8. +* Marked `Guzzle\Common\Collection::inject()` as deprecated. +* Marked `Guzzle\Plugin\CurlAuth\CurlAuthPlugin` as deprecated. Use `$client->getConfig()->setPath('request.options/auth', array('user', 'pass', 'Basic|Digest');` +* CacheKeyProviderInterface and DefaultCacheKeyProvider are no longer used. All of this logic is handled in a + CacheStorageInterface. These two objects and interface will be removed in a future version. +* Always setting X-cache headers on cached responses +* Default cache TTLs are now handled by the CacheStorageInterface of a CachePlugin +* `CacheStorageInterface::cache($key, Response $response, $ttl = null)` has changed to `cache(RequestInterface + $request, Response $response);` +* `CacheStorageInterface::fetch($key)` has changed to `fetch(RequestInterface $request);` +* `CacheStorageInterface::delete($key)` has changed to `delete(RequestInterface $request);` +* Added `CacheStorageInterface::purge($url)` +* `DefaultRevalidation::__construct(CacheKeyProviderInterface $cacheKey, CacheStorageInterface $cache, CachePlugin + $plugin)` has changed to `DefaultRevalidation::__construct(CacheStorageInterface $cache, + CanCacheStrategyInterface $canCache = null)` +* Added `RevalidationInterface::shouldRevalidate(RequestInterface $request, Response $response)` + +## 3.6.0 - 2013-05-29 + +* ServiceDescription now implements ToArrayInterface +* Added command.hidden_params to blacklist certain headers from being treated as additionalParameters +* Guzzle can now correctly parse incomplete URLs +* Mixed casing of headers are now forced to be a single consistent casing across all values for that header. +* Messages internally use a HeaderCollection object to delegate handling case-insensitive header resolution +* Removed the whole changedHeader() function system of messages because all header changes now go through addHeader(). +* Specific header implementations can be created for complex headers. When a message creates a header, it uses a + HeaderFactory which can map specific headers to specific header classes. There is now a Link header and + CacheControl header implementation. +* Removed from interface: Guzzle\Http\ClientInterface::setUriTemplate +* Removed from interface: Guzzle\Http\ClientInterface::setCurlMulti() +* Removed Guzzle\Http\Message\Request::receivedRequestHeader() and implemented this functionality in + Guzzle\Http\Curl\RequestMediator +* Removed the optional $asString parameter from MessageInterface::getHeader(). Just cast the header to a string. +* Removed the optional $tryChunkedTransfer option from Guzzle\Http\Message\EntityEnclosingRequestInterface +* Removed the $asObjects argument from Guzzle\Http\Message\MessageInterface::getHeaders() +* Removed Guzzle\Parser\ParserRegister::get(). Use getParser() +* Removed Guzzle\Parser\ParserRegister::set(). Use registerParser(). +* All response header helper functions return a string rather than mixing Header objects and strings inconsistently +* Removed cURL blacklist support. This is no longer necessary now that Expect, Accept, etc. are managed by Guzzle + directly via interfaces +* Removed the injecting of a request object onto a response object. The methods to get and set a request still exist + but are a no-op until removed. +* Most classes that used to require a `Guzzle\Service\Command\CommandInterface` typehint now request a + `Guzzle\Service\Command\ArrayCommandInterface`. +* Added `Guzzle\Http\Message\RequestInterface::startResponse()` to the RequestInterface to handle injecting a response + on a request while the request is still being transferred +* The ability to case-insensitively search for header values +* Guzzle\Http\Message\Header::hasExactHeader +* Guzzle\Http\Message\Header::raw. Use getAll() +* Deprecated cache control specific methods on Guzzle\Http\Message\AbstractMessage. Use the CacheControl header object + instead. +* `Guzzle\Service\Command\CommandInterface` now extends from ToArrayInterface and ArrayAccess +* Added the ability to cast Model objects to a string to view debug information. + +## 3.5.0 - 2013-05-13 + +* Bug: Fixed a regression so that request responses are parsed only once per oncomplete event rather than multiple times +* Bug: Better cleanup of one-time events across the board (when an event is meant to fire once, it will now remove + itself from the EventDispatcher) +* Bug: `Guzzle\Log\MessageFormatter` now properly writes "total_time" and "connect_time" values +* Bug: Cloning an EntityEnclosingRequest now clones the EntityBody too +* Bug: Fixed an undefined index error when parsing nested JSON responses with a sentAs parameter that reference a + non-existent key +* Bug: All __call() method arguments are now required (helps with mocking frameworks) +* Deprecating Response::getRequest() and now using a shallow clone of a request object to remove a circular reference + to help with refcount based garbage collection of resources created by sending a request +* Deprecating ZF1 cache and log adapters. These will be removed in the next major version. +* Deprecating `Response::getPreviousResponse()` (method signature still exists, but it's deprecated). Use the + HistoryPlugin for a history. +* Added a `responseBody` alias for the `response_body` location +* Refactored internals to no longer rely on Response::getRequest() +* HistoryPlugin can now be cast to a string +* HistoryPlugin now logs transactions rather than requests and responses to more accurately keep track of the requests + and responses that are sent over the wire +* Added `getEffectiveUrl()` and `getRedirectCount()` to Response objects + +## 3.4.3 - 2013-04-30 + +* Bug fix: Fixing bug introduced in 3.4.2 where redirect responses are duplicated on the final redirected response +* Added a check to re-extract the temp cacert bundle from the phar before sending each request + +## 3.4.2 - 2013-04-29 + +* Bug fix: Stream objects now work correctly with "a" and "a+" modes +* Bug fix: Removing `Transfer-Encoding: chunked` header when a Content-Length is present +* Bug fix: AsyncPlugin no longer forces HEAD requests +* Bug fix: DateTime timezones are now properly handled when using the service description schema formatter +* Bug fix: CachePlugin now properly handles stale-if-error directives when a request to the origin server fails +* Setting a response on a request will write to the custom request body from the response body if one is specified +* LogPlugin now writes to php://output when STDERR is undefined +* Added the ability to set multiple POST files for the same key in a single call +* application/x-www-form-urlencoded POSTs now use the utf-8 charset by default +* Added the ability to queue CurlExceptions to the MockPlugin +* Cleaned up how manual responses are queued on requests (removed "queued_response" and now using request.before_send) +* Configuration loading now allows remote files + +## 3.4.1 - 2013-04-16 + +* Large refactoring to how CurlMulti handles work. There is now a proxy that sits in front of a pool of CurlMulti + handles. This greatly simplifies the implementation, fixes a couple bugs, and provides a small performance boost. +* Exceptions are now properly grouped when sending requests in parallel +* Redirects are now properly aggregated when a multi transaction fails +* Redirects now set the response on the original object even in the event of a failure +* Bug fix: Model names are now properly set even when using $refs +* Added support for PHP 5.5's CurlFile to prevent warnings with the deprecated @ syntax +* Added support for oauth_callback in OAuth signatures +* Added support for oauth_verifier in OAuth signatures +* Added support to attempt to retrieve a command first literally, then ucfirst, the with inflection + +## 3.4.0 - 2013-04-11 + +* Bug fix: URLs are now resolved correctly based on https://tools.ietf.org/html/rfc3986#section-5.2. #289 +* Bug fix: Absolute URLs with a path in a service description will now properly override the base URL. #289 +* Bug fix: Parsing a query string with a single PHP array value will now result in an array. #263 +* Bug fix: Better normalization of the User-Agent header to prevent duplicate headers. #264. +* Bug fix: Added `number` type to service descriptions. +* Bug fix: empty parameters are removed from an OAuth signature +* Bug fix: Revalidating a cache entry prefers the Last-Modified over the Date header +* Bug fix: Fixed "array to string" error when validating a union of types in a service description +* Bug fix: Removed code that attempted to determine the size of a stream when data is written to the stream +* Bug fix: Not including an `oauth_token` if the value is null in the OauthPlugin. +* Bug fix: Now correctly aggregating successful requests and failed requests in CurlMulti when a redirect occurs. +* The new default CURLOPT_TIMEOUT setting has been increased to 150 seconds so that Guzzle works on poor connections. +* Added a feature to EntityEnclosingRequest::setBody() that will automatically set the Content-Type of the request if + the Content-Type can be determined based on the entity body or the path of the request. +* Added the ability to overwrite configuration settings in a client when grabbing a throwaway client from a builder. +* Added support for a PSR-3 LogAdapter. +* Added a `command.after_prepare` event +* Added `oauth_callback` parameter to the OauthPlugin +* Added the ability to create a custom stream class when using a stream factory +* Added a CachingEntityBody decorator +* Added support for `additionalParameters` in service descriptions to define how custom parameters are serialized. +* The bundled SSL certificate is now provided in the phar file and extracted when running Guzzle from a phar. +* You can now send any EntityEnclosingRequest with POST fields or POST files and cURL will handle creating bodies +* POST requests using a custom entity body are now treated exactly like PUT requests but with a custom cURL method. This + means that the redirect behavior of POST requests with custom bodies will not be the same as POST requests that use + POST fields or files (the latter is only used when emulating a form POST in the browser). +* Lots of cleanup to CurlHandle::factory and RequestFactory::createRequest + +## 3.3.1 - 2013-03-10 + +* Added the ability to create PHP streaming responses from HTTP requests +* Bug fix: Running any filters when parsing response headers with service descriptions +* Bug fix: OauthPlugin fixes to allow for multi-dimensional array signing, and sorting parameters before signing +* Bug fix: Removed the adding of default empty arrays and false Booleans to responses in order to be consistent across + response location visitors. +* Bug fix: Removed the possibility of creating configuration files with circular dependencies +* RequestFactory::create() now uses the key of a POST file when setting the POST file name +* Added xmlAllowEmpty to serialize an XML body even if no XML specific parameters are set + +## 3.3.0 - 2013-03-03 + +* A large number of performance optimizations have been made +* Bug fix: Added 'wb' as a valid write mode for streams +* Bug fix: `Guzzle\Http\Message\Response::json()` now allows scalar values to be returned +* Bug fix: Fixed bug in `Guzzle\Http\Message\Response` where wrapping quotes were stripped from `getEtag()` +* BC: Removed `Guzzle\Http\Utils` class +* BC: Setting a service description on a client will no longer modify the client's command factories. +* BC: Emitting IO events from a RequestMediator is now a parameter that must be set in a request's curl options using + the 'emit_io' key. This was previously set under a request's parameters using 'curl.emit_io' +* BC: `Guzzle\Stream\Stream::getWrapper()` and `Guzzle\Stream\Stream::getSteamType()` are no longer converted to + lowercase +* Operation parameter objects are now lazy loaded internally +* Added ErrorResponsePlugin that can throw errors for responses defined in service description operations' errorResponses +* Added support for instantiating responseType=class responseClass classes. Classes must implement + `Guzzle\Service\Command\ResponseClassInterface` +* Added support for additionalProperties for top-level parameters in responseType=model responseClasses. These + additional properties also support locations and can be used to parse JSON responses where the outermost part of the + JSON is an array +* Added support for nested renaming of JSON models (rename sentAs to name) +* CachePlugin + * Added support for stale-if-error so that the CachePlugin can now serve stale content from the cache on error + * Debug headers can now added to cached response in the CachePlugin + +## 3.2.0 - 2013-02-14 + +* CurlMulti is no longer reused globally. A new multi object is created per-client. This helps to isolate clients. +* URLs with no path no longer contain a "/" by default +* Guzzle\Http\QueryString does no longer manages the leading "?". This is now handled in Guzzle\Http\Url. +* BadResponseException no longer includes the full request and response message +* Adding setData() to Guzzle\Service\Description\ServiceDescriptionInterface +* Adding getResponseBody() to Guzzle\Http\Message\RequestInterface +* Various updates to classes to use ServiceDescriptionInterface type hints rather than ServiceDescription +* Header values can now be normalized into distinct values when multiple headers are combined with a comma separated list +* xmlEncoding can now be customized for the XML declaration of a XML service description operation +* Guzzle\Http\QueryString now uses Guzzle\Http\QueryAggregator\QueryAggregatorInterface objects to add custom value + aggregation and no longer uses callbacks +* The URL encoding implementation of Guzzle\Http\QueryString can now be customized +* Bug fix: Filters were not always invoked for array service description parameters +* Bug fix: Redirects now use a target response body rather than a temporary response body +* Bug fix: The default exponential backoff BackoffPlugin was not giving when the request threshold was exceeded +* Bug fix: Guzzle now takes the first found value when grabbing Cache-Control directives + +## 3.1.2 - 2013-01-27 + +* Refactored how operation responses are parsed. Visitors now include a before() method responsible for parsing the + response body. For example, the XmlVisitor now parses the XML response into an array in the before() method. +* Fixed an issue where cURL would not automatically decompress responses when the Accept-Encoding header was sent +* CURLOPT_SSL_VERIFYHOST is never set to 1 because it is deprecated (see 5e0ff2ef20f839e19d1eeb298f90ba3598784444) +* Fixed a bug where redirect responses were not chained correctly using getPreviousResponse() +* Setting default headers on a client after setting the user-agent will not erase the user-agent setting + +## 3.1.1 - 2013-01-20 + +* Adding wildcard support to Guzzle\Common\Collection::getPath() +* Adding alias support to ServiceBuilder configs +* Adding Guzzle\Service\Resource\CompositeResourceIteratorFactory and cleaning up factory interface + +## 3.1.0 - 2013-01-12 + +* BC: CurlException now extends from RequestException rather than BadResponseException +* BC: Renamed Guzzle\Plugin\Cache\CanCacheStrategyInterface::canCache() to canCacheRequest() and added CanCacheResponse() +* Added getData to ServiceDescriptionInterface +* Added context array to RequestInterface::setState() +* Bug: Removing hard dependency on the BackoffPlugin from Guzzle\Http +* Bug: Adding required content-type when JSON request visitor adds JSON to a command +* Bug: Fixing the serialization of a service description with custom data +* Made it easier to deal with exceptions thrown when transferring commands or requests in parallel by providing + an array of successful and failed responses +* Moved getPath from Guzzle\Service\Resource\Model to Guzzle\Common\Collection +* Added Guzzle\Http\IoEmittingEntityBody +* Moved command filtration from validators to location visitors +* Added `extends` attributes to service description parameters +* Added getModels to ServiceDescriptionInterface + +## 3.0.7 - 2012-12-19 + +* Fixing phar detection when forcing a cacert to system if null or true +* Allowing filename to be passed to `Guzzle\Http\Message\Request::setResponseBody()` +* Cleaning up `Guzzle\Common\Collection::inject` method +* Adding a response_body location to service descriptions + +## 3.0.6 - 2012-12-09 + +* CurlMulti performance improvements +* Adding setErrorResponses() to Operation +* composer.json tweaks + +## 3.0.5 - 2012-11-18 + +* Bug: Fixing an infinite recursion bug caused from revalidating with the CachePlugin +* Bug: Response body can now be a string containing "0" +* Bug: Using Guzzle inside of a phar uses system by default but now allows for a custom cacert +* Bug: QueryString::fromString now properly parses query string parameters that contain equal signs +* Added support for XML attributes in service description responses +* DefaultRequestSerializer now supports array URI parameter values for URI template expansion +* Added better mimetype guessing to requests and post files + +## 3.0.4 - 2012-11-11 + +* Bug: Fixed a bug when adding multiple cookies to a request to use the correct glue value +* Bug: Cookies can now be added that have a name, domain, or value set to "0" +* Bug: Using the system cacert bundle when using the Phar +* Added json and xml methods to Response to make it easier to parse JSON and XML response data into data structures +* Enhanced cookie jar de-duplication +* Added the ability to enable strict cookie jars that throw exceptions when invalid cookies are added +* Added setStream to StreamInterface to actually make it possible to implement custom rewind behavior for entity bodies +* Added the ability to create any sort of hash for a stream rather than just an MD5 hash + +## 3.0.3 - 2012-11-04 + +* Implementing redirects in PHP rather than cURL +* Added PECL URI template extension and using as default parser if available +* Bug: Fixed Content-Length parsing of Response factory +* Adding rewind() method to entity bodies and streams. Allows for custom rewinding of non-repeatable streams. +* Adding ToArrayInterface throughout library +* Fixing OauthPlugin to create unique nonce values per request + +## 3.0.2 - 2012-10-25 + +* Magic methods are enabled by default on clients +* Magic methods return the result of a command +* Service clients no longer require a base_url option in the factory +* Bug: Fixed an issue with URI templates where null template variables were being expanded + +## 3.0.1 - 2012-10-22 + +* Models can now be used like regular collection objects by calling filter, map, etc. +* Models no longer require a Parameter structure or initial data in the constructor +* Added a custom AppendIterator to get around a PHP bug with the `\AppendIterator` + +## 3.0.0 - 2012-10-15 + +* Rewrote service description format to be based on Swagger + * Now based on JSON schema + * Added nested input structures and nested response models + * Support for JSON and XML input and output models + * Renamed `commands` to `operations` + * Removed dot class notation + * Removed custom types +* Broke the project into smaller top-level namespaces to be more component friendly +* Removed support for XML configs and descriptions. Use arrays or JSON files. +* Removed the Validation component and Inspector +* Moved all cookie code to Guzzle\Plugin\Cookie +* Magic methods on a Guzzle\Service\Client now return the command un-executed. +* Calling getResult() or getResponse() on a command will lazily execute the command if needed. +* Now shipping with cURL's CA certs and using it by default +* Added previousResponse() method to response objects +* No longer sending Accept and Accept-Encoding headers on every request +* Only sending an Expect header by default when a payload is greater than 1MB +* Added/moved client options: + * curl.blacklist to curl.option.blacklist + * Added ssl.certificate_authority +* Added a Guzzle\Iterator component +* Moved plugins from Guzzle\Http\Plugin to Guzzle\Plugin +* Added a more robust backoff retry strategy (replaced the ExponentialBackoffPlugin) +* Added a more robust caching plugin +* Added setBody to response objects +* Updating LogPlugin to use a more flexible MessageFormatter +* Added a completely revamped build process +* Cleaning up Collection class and removing default values from the get method +* Fixed ZF2 cache adapters + +## 2.8.8 - 2012-10-15 + +* Bug: Fixed a cookie issue that caused dot prefixed domains to not match where popular browsers did + +## 2.8.7 - 2012-09-30 + +* Bug: Fixed config file aliases for JSON includes +* Bug: Fixed cookie bug on a request object by using CookieParser to parse cookies on requests +* Bug: Removing the path to a file when sending a Content-Disposition header on a POST upload +* Bug: Hardening request and response parsing to account for missing parts +* Bug: Fixed PEAR packaging +* Bug: Fixed Request::getInfo +* Bug: Fixed cases where CURLM_CALL_MULTI_PERFORM return codes were causing curl transactions to fail +* Adding the ability for the namespace Iterator factory to look in multiple directories +* Added more getters/setters/removers from service descriptions +* Added the ability to remove POST fields from OAuth signatures +* OAuth plugin now supports 2-legged OAuth + +## 2.8.6 - 2012-09-05 + +* Added the ability to modify and build service descriptions +* Added the use of visitors to apply parameters to locations in service descriptions using the dynamic command +* Added a `json` parameter location +* Now allowing dot notation for classes in the CacheAdapterFactory +* Using the union of two arrays rather than an array_merge when extending service builder services and service params +* Ensuring that a service is a string before doing strpos() checks on it when substituting services for references + in service builder config files. +* Services defined in two different config files that include one another will by default replace the previously + defined service, but you can now create services that extend themselves and merge their settings over the previous +* The JsonLoader now supports aliasing filenames with different filenames. This allows you to alias something like + '_default' with a default JSON configuration file. + +## 2.8.5 - 2012-08-29 + +* Bug: Suppressed empty arrays from URI templates +* Bug: Added the missing $options argument from ServiceDescription::factory to enable caching +* Added support for HTTP responses that do not contain a reason phrase in the start-line +* AbstractCommand commands are now invokable +* Added a way to get the data used when signing an Oauth request before a request is sent + +## 2.8.4 - 2012-08-15 + +* Bug: Custom delay time calculations are no longer ignored in the ExponentialBackoffPlugin +* Added the ability to transfer entity bodies as a string rather than streamed. This gets around curl error 65. Set `body_as_string` in a request's curl options to enable. +* Added a StreamInterface, EntityBodyInterface, and added ftell() to Guzzle\Common\Stream +* Added an AbstractEntityBodyDecorator and a ReadLimitEntityBody decorator to transfer only a subset of a decorated stream +* Stream and EntityBody objects will now return the file position to the previous position after a read required operation (e.g. getContentMd5()) +* Added additional response status codes +* Removed SSL information from the default User-Agent header +* DELETE requests can now send an entity body +* Added an EventDispatcher to the ExponentialBackoffPlugin and added an ExponentialBackoffLogger to log backoff retries +* Added the ability of the MockPlugin to consume mocked request bodies +* LogPlugin now exposes request and response objects in the extras array + +## 2.8.3 - 2012-07-30 + +* Bug: Fixed a case where empty POST requests were sent as GET requests +* Bug: Fixed a bug in ExponentialBackoffPlugin that caused fatal errors when retrying an EntityEnclosingRequest that does not have a body +* Bug: Setting the response body of a request to null after completing a request, not when setting the state of a request to new +* Added multiple inheritance to service description commands +* Added an ApiCommandInterface and added `getParamNames()` and `hasParam()` +* Removed the default 2mb size cutoff from the Md5ValidatorPlugin so that it now defaults to validating everything +* Changed CurlMulti::perform to pass a smaller timeout to CurlMulti::executeHandles + +## 2.8.2 - 2012-07-24 + +* Bug: Query string values set to 0 are no longer dropped from the query string +* Bug: A Collection object is no longer created each time a call is made to `Guzzle\Service\Command\AbstractCommand::getRequestHeaders()` +* Bug: `+` is now treated as an encoded space when parsing query strings +* QueryString and Collection performance improvements +* Allowing dot notation for class paths in filters attribute of a service descriptions + +## 2.8.1 - 2012-07-16 + +* Loosening Event Dispatcher dependency +* POST redirects can now be customized using CURLOPT_POSTREDIR + +## 2.8.0 - 2012-07-15 + +* BC: Guzzle\Http\Query + * Query strings with empty variables will always show an equal sign unless the variable is set to QueryString::BLANK (e.g. ?acl= vs ?acl) + * Changed isEncodingValues() and isEncodingFields() to isUrlEncoding() + * Changed setEncodeValues(bool) and setEncodeFields(bool) to useUrlEncoding(bool) + * Changed the aggregation functions of QueryString to be static methods + * Can now use fromString() with querystrings that have a leading ? +* cURL configuration values can be specified in service descriptions using `curl.` prefixed parameters +* Content-Length is set to 0 before emitting the request.before_send event when sending an empty request body +* Cookies are no longer URL decoded by default +* Bug: URI template variables set to null are no longer expanded + +## 2.7.2 - 2012-07-02 + +* BC: Moving things to get ready for subtree splits. Moving Inflection into Common. Moving Guzzle\Http\Parser to Guzzle\Parser. +* BC: Removing Guzzle\Common\Batch\Batch::count() and replacing it with isEmpty() +* CachePlugin now allows for a custom request parameter function to check if a request can be cached +* Bug fix: CachePlugin now only caches GET and HEAD requests by default +* Bug fix: Using header glue when transferring headers over the wire +* Allowing deeply nested arrays for composite variables in URI templates +* Batch divisors can now return iterators or arrays + +## 2.7.1 - 2012-06-26 + +* Minor patch to update version number in UA string +* Updating build process + +## 2.7.0 - 2012-06-25 + +* BC: Inflection classes moved to Guzzle\Inflection. No longer static methods. Can now inject custom inflectors into classes. +* BC: Removed magic setX methods from commands +* BC: Magic methods mapped to service description commands are now inflected in the command factory rather than the client __call() method +* Verbose cURL options are no longer enabled by default. Set curl.debug to true on a client to enable. +* Bug: Now allowing colons in a response start-line (e.g. HTTP/1.1 503 Service Unavailable: Back-end server is at capacity) +* Guzzle\Service\Resource\ResourceIteratorApplyBatched now internally uses the Guzzle\Common\Batch namespace +* Added Guzzle\Service\Plugin namespace and a PluginCollectionPlugin +* Added the ability to set POST fields and files in a service description +* Guzzle\Http\EntityBody::factory() now accepts objects with a __toString() method +* Adding a command.before_prepare event to clients +* Added BatchClosureTransfer and BatchClosureDivisor +* BatchTransferException now includes references to the batch divisor and transfer strategies +* Fixed some tests so that they pass more reliably +* Added Guzzle\Common\Log\ArrayLogAdapter + +## 2.6.6 - 2012-06-10 + +* BC: Removing Guzzle\Http\Plugin\BatchQueuePlugin +* BC: Removing Guzzle\Service\Command\CommandSet +* Adding generic batching system (replaces the batch queue plugin and command set) +* Updating ZF cache and log adapters and now using ZF's composer repository +* Bug: Setting the name of each ApiParam when creating through an ApiCommand +* Adding result_type, result_doc, deprecated, and doc_url to service descriptions +* Bug: Changed the default cookie header casing back to 'Cookie' + +## 2.6.5 - 2012-06-03 + +* BC: Renaming Guzzle\Http\Message\RequestInterface::getResourceUri() to getResource() +* BC: Removing unused AUTH_BASIC and AUTH_DIGEST constants from +* BC: Guzzle\Http\Cookie is now used to manage Set-Cookie data, not Cookie data +* BC: Renaming methods in the CookieJarInterface +* Moving almost all cookie logic out of the CookiePlugin and into the Cookie or CookieJar implementations +* Making the default glue for HTTP headers ';' instead of ',' +* Adding a removeValue to Guzzle\Http\Message\Header +* Adding getCookies() to request interface. +* Making it easier to add event subscribers to HasDispatcherInterface classes. Can now directly call addSubscriber() + +## 2.6.4 - 2012-05-30 + +* BC: Cleaning up how POST files are stored in EntityEnclosingRequest objects. Adding PostFile class. +* BC: Moving ApiCommand specific functionality from the Inspector and on to the ApiCommand +* Bug: Fixing magic method command calls on clients +* Bug: Email constraint only validates strings +* Bug: Aggregate POST fields when POST files are present in curl handle +* Bug: Fixing default User-Agent header +* Bug: Only appending or prepending parameters in commands if they are specified +* Bug: Not requiring response reason phrases or status codes to match a predefined list of codes +* Allowing the use of dot notation for class namespaces when using instance_of constraint +* Added any_match validation constraint +* Added an AsyncPlugin +* Passing request object to the calculateWait method of the ExponentialBackoffPlugin +* Allowing the result of a command object to be changed +* Parsing location and type sub values when instantiating a service description rather than over and over at runtime + +## 2.6.3 - 2012-05-23 + +* [BC] Guzzle\Common\FromConfigInterface no longer requires any config options. +* [BC] Refactoring how POST files are stored on an EntityEnclosingRequest. They are now separate from POST fields. +* You can now use an array of data when creating PUT request bodies in the request factory. +* Removing the requirement that HTTPS requests needed a Cache-Control: public directive to be cacheable. +* [Http] Adding support for Content-Type in multipart POST uploads per upload +* [Http] Added support for uploading multiple files using the same name (foo[0], foo[1]) +* Adding more POST data operations for easier manipulation of POST data. +* You can now set empty POST fields. +* The body of a request is only shown on EntityEnclosingRequest objects that do not use POST files. +* Split the Guzzle\Service\Inspector::validateConfig method into two methods. One to initialize when a command is created, and one to validate. +* CS updates + +## 2.6.2 - 2012-05-19 + +* [Http] Better handling of nested scope requests in CurlMulti. Requests are now always prepares in the send() method rather than the addRequest() method. + +## 2.6.1 - 2012-05-19 + +* [BC] Removing 'path' support in service descriptions. Use 'uri'. +* [BC] Guzzle\Service\Inspector::parseDocBlock is now protected. Adding getApiParamsForClass() with cache. +* [BC] Removing Guzzle\Common\NullObject. Use https://github.com/mtdowling/NullObject if you need it. +* [BC] Removing Guzzle\Common\XmlElement. +* All commands, both dynamic and concrete, have ApiCommand objects. +* Adding a fix for CurlMulti so that if all of the connections encounter some sort of curl error, then the loop exits. +* Adding checks to EntityEnclosingRequest so that empty POST files and fields are ignored. +* Making the method signature of Guzzle\Service\Builder\ServiceBuilder::factory more flexible. + +## 2.6.0 - 2012-05-15 + +* [BC] Moving Guzzle\Service\Builder to Guzzle\Service\Builder\ServiceBuilder +* [BC] Executing a Command returns the result of the command rather than the command +* [BC] Moving all HTTP parsing logic to Guzzle\Http\Parsers. Allows for faster C implementations if needed. +* [BC] Changing the Guzzle\Http\Message\Response::setProtocol() method to accept a protocol and version in separate args. +* [BC] Moving ResourceIterator* to Guzzle\Service\Resource +* [BC] Completely refactored ResourceIterators to iterate over a cloned command object +* [BC] Moved Guzzle\Http\UriTemplate to Guzzle\Http\Parser\UriTemplate\UriTemplate +* [BC] Guzzle\Guzzle is now deprecated +* Moving Guzzle\Common\Guzzle::inject to Guzzle\Common\Collection::inject +* Adding Guzzle\Version class to give version information about Guzzle +* Adding Guzzle\Http\Utils class to provide getDefaultUserAgent() and getHttpDate() +* Adding Guzzle\Curl\CurlVersion to manage caching curl_version() data +* ServiceDescription and ServiceBuilder are now cacheable using similar configs +* Changing the format of XML and JSON service builder configs. Backwards compatible. +* Cleaned up Cookie parsing +* Trimming the default Guzzle User-Agent header +* Adding a setOnComplete() method to Commands that is called when a command completes +* Keeping track of requests that were mocked in the MockPlugin +* Fixed a caching bug in the CacheAdapterFactory +* Inspector objects can be injected into a Command object +* Refactoring a lot of code and tests to be case insensitive when dealing with headers +* Adding Guzzle\Http\Message\HeaderComparison for easy comparison of HTTP headers using a DSL +* Adding the ability to set global option overrides to service builder configs +* Adding the ability to include other service builder config files from within XML and JSON files +* Moving the parseQuery method out of Url and on to QueryString::fromString() as a static factory method. + +## 2.5.0 - 2012-05-08 + +* Major performance improvements +* [BC] Simplifying Guzzle\Common\Collection. Please check to see if you are using features that are now deprecated. +* [BC] Using a custom validation system that allows a flyweight implementation for much faster validation. No longer using Symfony2 Validation component. +* [BC] No longer supporting "{{ }}" for injecting into command or UriTemplates. Use "{}" +* Added the ability to passed parameters to all requests created by a client +* Added callback functionality to the ExponentialBackoffPlugin +* Using microtime in ExponentialBackoffPlugin to allow more granular backoff strategies. +* Rewinding request stream bodies when retrying requests +* Exception is thrown when JSON response body cannot be decoded +* Added configurable magic method calls to clients and commands. This is off by default. +* Fixed a defect that added a hash to every parsed URL part +* Fixed duplicate none generation for OauthPlugin. +* Emitting an event each time a client is generated by a ServiceBuilder +* Using an ApiParams object instead of a Collection for parameters of an ApiCommand +* cache.* request parameters should be renamed to params.cache.* +* Added the ability to set arbitrary curl options on requests (disable_wire, progress, etc.). See CurlHandle. +* Added the ability to disable type validation of service descriptions +* ServiceDescriptions and ServiceBuilders are now Serializable diff --git a/vendor/guzzlehttp/guzzle/LICENSE b/vendor/guzzlehttp/guzzle/LICENSE new file mode 100644 index 0000000..fd2375d --- /dev/null +++ b/vendor/guzzlehttp/guzzle/LICENSE @@ -0,0 +1,27 @@ +The MIT License (MIT) + +Copyright (c) 2011 Michael Dowling +Copyright (c) 2012 Jeremy Lindblom +Copyright (c) 2014 Graham Campbell +Copyright (c) 2015 Márk Sági-Kazár +Copyright (c) 2015 Tobias Schultze +Copyright (c) 2016 Tobias Nyholm +Copyright (c) 2016 George Mponos + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/guzzlehttp/guzzle/README.md b/vendor/guzzlehttp/guzzle/README.md new file mode 100644 index 0000000..0786462 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/README.md @@ -0,0 +1,94 @@ +![Guzzle](.github/logo.png?raw=true) + +# Guzzle, PHP HTTP client + +[![Latest Version](https://img.shields.io/github/release/guzzle/guzzle.svg?style=flat-square)](https://github.com/guzzle/guzzle/releases) +[![Build Status](https://img.shields.io/github/workflow/status/guzzle/guzzle/CI?label=ci%20build&style=flat-square)](https://github.com/guzzle/guzzle/actions?query=workflow%3ACI) +[![Total Downloads](https://img.shields.io/packagist/dt/guzzlehttp/guzzle.svg?style=flat-square)](https://packagist.org/packages/guzzlehttp/guzzle) + +Guzzle is a PHP HTTP client that makes it easy to send HTTP requests and +trivial to integrate with web services. + +- Simple interface for building query strings, POST requests, streaming large + uploads, streaming large downloads, using HTTP cookies, uploading JSON data, + etc... +- Can send both synchronous and asynchronous requests using the same interface. +- Uses PSR-7 interfaces for requests, responses, and streams. This allows you + to utilize other PSR-7 compatible libraries with Guzzle. +- Supports PSR-18 allowing interoperability between other PSR-18 HTTP Clients. +- Abstracts away the underlying HTTP transport, allowing you to write + environment and transport agnostic code; i.e., no hard dependency on cURL, + PHP streams, sockets, or non-blocking event loops. +- Middleware system allows you to augment and compose client behavior. + +```php +$client = new \GuzzleHttp\Client(); +$response = $client->request('GET', 'https://api.github.com/repos/guzzle/guzzle'); + +echo $response->getStatusCode(); // 200 +echo $response->getHeaderLine('content-type'); // 'application/json; charset=utf8' +echo $response->getBody(); // '{"id": 1420053, "name": "guzzle", ...}' + +// Send an asynchronous request. +$request = new \GuzzleHttp\Psr7\Request('GET', 'http://httpbin.org'); +$promise = $client->sendAsync($request)->then(function ($response) { + echo 'I completed! ' . $response->getBody(); +}); + +$promise->wait(); +``` + +## Help and docs + +We use GitHub issues only to discuss bugs and new features. For support please refer to: + +- [Documentation](https://docs.guzzlephp.org) +- [Stack Overflow](https://stackoverflow.com/questions/tagged/guzzle) +- [#guzzle](https://app.slack.com/client/T0D2S9JCT/CE6UAAKL4) channel on [PHP-HTTP Slack](https://slack.httplug.io/) +- [Gitter](https://gitter.im/guzzle/guzzle) + + +## Installing Guzzle + +The recommended way to install Guzzle is through +[Composer](https://getcomposer.org/). + +```bash +composer require guzzlehttp/guzzle +``` + + +## Version Guidance + +| Version | Status | Packagist | Namespace | Repo | Docs | PSR-7 | PHP Version | +|---------|---------------------|---------------------|--------------|---------------------|---------------------|-------|--------------| +| 3.x | EOL | `guzzle/guzzle` | `Guzzle` | [v3][guzzle-3-repo] | [v3][guzzle-3-docs] | No | >=5.3.3,<7.0 | +| 4.x | EOL | `guzzlehttp/guzzle` | `GuzzleHttp` | [v4][guzzle-4-repo] | N/A | No | >=5.4,<7.0 | +| 5.x | EOL | `guzzlehttp/guzzle` | `GuzzleHttp` | [v5][guzzle-5-repo] | [v5][guzzle-5-docs] | No | >=5.4,<7.4 | +| 6.x | Security fixes only | `guzzlehttp/guzzle` | `GuzzleHttp` | [v6][guzzle-6-repo] | [v6][guzzle-6-docs] | Yes | >=5.5,<8.0 | +| 7.x | Latest | `guzzlehttp/guzzle` | `GuzzleHttp` | [v7][guzzle-7-repo] | [v7][guzzle-7-docs] | Yes | >=7.2.5,<8.3 | + +[guzzle-3-repo]: https://github.com/guzzle/guzzle3 +[guzzle-4-repo]: https://github.com/guzzle/guzzle/tree/4.x +[guzzle-5-repo]: https://github.com/guzzle/guzzle/tree/5.3 +[guzzle-6-repo]: https://github.com/guzzle/guzzle/tree/6.5 +[guzzle-7-repo]: https://github.com/guzzle/guzzle +[guzzle-3-docs]: https://guzzle3.readthedocs.io/ +[guzzle-5-docs]: https://docs.guzzlephp.org/en/5.3/ +[guzzle-6-docs]: https://docs.guzzlephp.org/en/6.5/ +[guzzle-7-docs]: https://docs.guzzlephp.org/en/latest/ + + +## Security + +If you discover a security vulnerability within this package, please send an email to security@tidelift.com. All security vulnerabilities will be promptly addressed. Please do not disclose security-related issues publicly until a fix has been announced. Please see [Security Policy](https://github.com/guzzle/guzzle/security/policy) for more information. + +## License + +Guzzle is made available under the MIT License (MIT). Please see [License File](LICENSE) for more information. + +## For Enterprise + +Available as part of the Tidelift Subscription + +The maintainers of Guzzle and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/packagist-guzzlehttp-guzzle?utm_source=packagist-guzzlehttp-guzzle&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) diff --git a/vendor/guzzlehttp/guzzle/UPGRADING.md b/vendor/guzzlehttp/guzzle/UPGRADING.md new file mode 100644 index 0000000..45417a7 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/UPGRADING.md @@ -0,0 +1,1253 @@ +Guzzle Upgrade Guide +==================== + +6.0 to 7.0 +---------- + +In order to take advantage of the new features of PHP, Guzzle dropped the support +of PHP 5. The minimum supported PHP version is now PHP 7.2. Type hints and return +types for functions and methods have been added wherever possible. + +Please make sure: +- You are calling a function or a method with the correct type. +- If you extend a class of Guzzle; update all signatures on methods you override. + +#### Other backwards compatibility breaking changes + +- Class `GuzzleHttp\UriTemplate` is removed. +- Class `GuzzleHttp\Exception\SeekException` is removed. +- Classes `GuzzleHttp\Exception\BadResponseException`, `GuzzleHttp\Exception\ClientException`, + `GuzzleHttp\Exception\ServerException` can no longer be initialized with an empty + Response as argument. +- Class `GuzzleHttp\Exception\ConnectException` now extends `GuzzleHttp\Exception\TransferException` + instead of `GuzzleHttp\Exception\RequestException`. +- Function `GuzzleHttp\Exception\ConnectException::getResponse()` is removed. +- Function `GuzzleHttp\Exception\ConnectException::hasResponse()` is removed. +- Constant `GuzzleHttp\ClientInterface::VERSION` is removed. Added `GuzzleHttp\ClientInterface::MAJOR_VERSION` instead. +- Function `GuzzleHttp\Exception\RequestException::getResponseBodySummary` is removed. + Use `\GuzzleHttp\Psr7\get_message_body_summary` as an alternative. +- Function `GuzzleHttp\Cookie\CookieJar::getCookieValue` is removed. +- Request option `exception` is removed. Please use `http_errors`. +- Request option `save_to` is removed. Please use `sink`. +- Pool option `pool_size` is removed. Please use `concurrency`. +- We now look for environment variables in the `$_SERVER` super global, due to thread safety issues with `getenv`. We continue to fallback to `getenv` in CLI environments, for maximum compatibility. +- The `get`, `head`, `put`, `post`, `patch`, `delete`, `getAsync`, `headAsync`, `putAsync`, `postAsync`, `patchAsync`, and `deleteAsync` methods are now implemented as genuine methods on `GuzzleHttp\Client`, with strong typing. The original `__call` implementation remains unchanged for now, for maximum backwards compatibility, but won't be invoked under normal operation. +- The `log` middleware will log the errors with level `error` instead of `notice` +- Support for international domain names (IDN) is now disabled by default, and enabling it requires installing ext-intl, linked against a modern version of the C library (ICU 4.6 or higher). + +#### Native functions calls + +All internal native functions calls of Guzzle are now prefixed with a slash. This +change makes it impossible for method overloading by other libraries or applications. +Example: + +```php +// Before: +curl_version(); + +// After: +\curl_version(); +``` + +For the full diff you can check [here](https://github.com/guzzle/guzzle/compare/6.5.4..master). + +5.0 to 6.0 +---------- + +Guzzle now uses [PSR-7](https://www.php-fig.org/psr/psr-7/) for HTTP messages. +Due to the fact that these messages are immutable, this prompted a refactoring +of Guzzle to use a middleware based system rather than an event system. Any +HTTP message interaction (e.g., `GuzzleHttp\Message\Request`) need to be +updated to work with the new immutable PSR-7 request and response objects. Any +event listeners or subscribers need to be updated to become middleware +functions that wrap handlers (or are injected into a +`GuzzleHttp\HandlerStack`). + +- Removed `GuzzleHttp\BatchResults` +- Removed `GuzzleHttp\Collection` +- Removed `GuzzleHttp\HasDataTrait` +- Removed `GuzzleHttp\ToArrayInterface` +- The `guzzlehttp/streams` dependency has been removed. Stream functionality + is now present in the `GuzzleHttp\Psr7` namespace provided by the + `guzzlehttp/psr7` package. +- Guzzle no longer uses ReactPHP promises and now uses the + `guzzlehttp/promises` library. We use a custom promise library for three + significant reasons: + 1. React promises (at the time of writing this) are recursive. Promise + chaining and promise resolution will eventually blow the stack. Guzzle + promises are not recursive as they use a sort of trampolining technique. + Note: there has been movement in the React project to modify promises to + no longer utilize recursion. + 2. Guzzle needs to have the ability to synchronously block on a promise to + wait for a result. Guzzle promises allows this functionality (and does + not require the use of recursion). + 3. Because we need to be able to wait on a result, doing so using React + promises requires wrapping react promises with RingPHP futures. This + overhead is no longer needed, reducing stack sizes, reducing complexity, + and improving performance. +- `GuzzleHttp\Mimetypes` has been moved to a function in + `GuzzleHttp\Psr7\mimetype_from_extension` and + `GuzzleHttp\Psr7\mimetype_from_filename`. +- `GuzzleHttp\Query` and `GuzzleHttp\QueryParser` have been removed. Query + strings must now be passed into request objects as strings, or provided to + the `query` request option when creating requests with clients. The `query` + option uses PHP's `http_build_query` to convert an array to a string. If you + need a different serialization technique, you will need to pass the query + string in as a string. There are a couple helper functions that will make + working with query strings easier: `GuzzleHttp\Psr7\parse_query` and + `GuzzleHttp\Psr7\build_query`. +- Guzzle no longer has a dependency on RingPHP. Due to the use of a middleware + system based on PSR-7, using RingPHP and it's middleware system as well adds + more complexity than the benefits it provides. All HTTP handlers that were + present in RingPHP have been modified to work directly with PSR-7 messages + and placed in the `GuzzleHttp\Handler` namespace. This significantly reduces + complexity in Guzzle, removes a dependency, and improves performance. RingPHP + will be maintained for Guzzle 5 support, but will no longer be a part of + Guzzle 6. +- As Guzzle now uses a middleware based systems the event system and RingPHP + integration has been removed. Note: while the event system has been removed, + it is possible to add your own type of event system that is powered by the + middleware system. + - Removed the `Event` namespace. + - Removed the `Subscriber` namespace. + - Removed `Transaction` class + - Removed `RequestFsm` + - Removed `RingBridge` + - `GuzzleHttp\Subscriber\Cookie` is now provided by + `GuzzleHttp\Middleware::cookies` + - `GuzzleHttp\Subscriber\HttpError` is now provided by + `GuzzleHttp\Middleware::httpError` + - `GuzzleHttp\Subscriber\History` is now provided by + `GuzzleHttp\Middleware::history` + - `GuzzleHttp\Subscriber\Mock` is now provided by + `GuzzleHttp\Handler\MockHandler` + - `GuzzleHttp\Subscriber\Prepare` is now provided by + `GuzzleHttp\PrepareBodyMiddleware` + - `GuzzleHttp\Subscriber\Redirect` is now provided by + `GuzzleHttp\RedirectMiddleware` +- Guzzle now uses `Psr\Http\Message\UriInterface` (implements in + `GuzzleHttp\Psr7\Uri`) for URI support. `GuzzleHttp\Url` is now gone. +- Static functions in `GuzzleHttp\Utils` have been moved to namespaced + functions under the `GuzzleHttp` namespace. This requires either a Composer + based autoloader or you to include functions.php. +- `GuzzleHttp\ClientInterface::getDefaultOption` has been renamed to + `GuzzleHttp\ClientInterface::getConfig`. +- `GuzzleHttp\ClientInterface::setDefaultOption` has been removed. +- The `json` and `xml` methods of response objects has been removed. With the + migration to strictly adhering to PSR-7 as the interface for Guzzle messages, + adding methods to message interfaces would actually require Guzzle messages + to extend from PSR-7 messages rather then work with them directly. + +## Migrating to middleware + +The change to PSR-7 unfortunately required significant refactoring to Guzzle +due to the fact that PSR-7 messages are immutable. Guzzle 5 relied on an event +system from plugins. The event system relied on mutability of HTTP messages and +side effects in order to work. With immutable messages, you have to change your +workflow to become more about either returning a value (e.g., functional +middlewares) or setting a value on an object. Guzzle v6 has chosen the +functional middleware approach. + +Instead of using the event system to listen for things like the `before` event, +you now create a stack based middleware function that intercepts a request on +the way in and the promise of the response on the way out. This is a much +simpler and more predictable approach than the event system and works nicely +with PSR-7 middleware. Due to the use of promises, the middleware system is +also asynchronous. + +v5: + +```php +use GuzzleHttp\Event\BeforeEvent; +$client = new GuzzleHttp\Client(); +// Get the emitter and listen to the before event. +$client->getEmitter()->on('before', function (BeforeEvent $e) { + // Guzzle v5 events relied on mutation + $e->getRequest()->setHeader('X-Foo', 'Bar'); +}); +``` + +v6: + +In v6, you can modify the request before it is sent using the `mapRequest` +middleware. The idiomatic way in v6 to modify the request/response lifecycle is +to setup a handler middleware stack up front and inject the handler into a +client. + +```php +use GuzzleHttp\Middleware; +// Create a handler stack that has all of the default middlewares attached +$handler = GuzzleHttp\HandlerStack::create(); +// Push the handler onto the handler stack +$handler->push(Middleware::mapRequest(function (RequestInterface $request) { + // Notice that we have to return a request object + return $request->withHeader('X-Foo', 'Bar'); +})); +// Inject the handler into the client +$client = new GuzzleHttp\Client(['handler' => $handler]); +``` + +## POST Requests + +This version added the [`form_params`](http://guzzle.readthedocs.org/en/latest/request-options.html#form_params) +and `multipart` request options. `form_params` is an associative array of +strings or array of strings and is used to serialize an +`application/x-www-form-urlencoded` POST request. The +[`multipart`](http://guzzle.readthedocs.org/en/latest/request-options.html#multipart) +option is now used to send a multipart/form-data POST request. + +`GuzzleHttp\Post\PostFile` has been removed. Use the `multipart` option to add +POST files to a multipart/form-data request. + +The `body` option no longer accepts an array to send POST requests. Please use +`multipart` or `form_params` instead. + +The `base_url` option has been renamed to `base_uri`. + +4.x to 5.0 +---------- + +## Rewritten Adapter Layer + +Guzzle now uses [RingPHP](http://ringphp.readthedocs.org/en/latest) to send +HTTP requests. The `adapter` option in a `GuzzleHttp\Client` constructor +is still supported, but it has now been renamed to `handler`. Instead of +passing a `GuzzleHttp\Adapter\AdapterInterface`, you must now pass a PHP +`callable` that follows the RingPHP specification. + +## Removed Fluent Interfaces + +[Fluent interfaces were removed](https://ocramius.github.io/blog/fluent-interfaces-are-evil/) +from the following classes: + +- `GuzzleHttp\Collection` +- `GuzzleHttp\Url` +- `GuzzleHttp\Query` +- `GuzzleHttp\Post\PostBody` +- `GuzzleHttp\Cookie\SetCookie` + +## Removed functions.php + +Removed "functions.php", so that Guzzle is truly PSR-4 compliant. The following +functions can be used as replacements. + +- `GuzzleHttp\json_decode` -> `GuzzleHttp\Utils::jsonDecode` +- `GuzzleHttp\get_path` -> `GuzzleHttp\Utils::getPath` +- `GuzzleHttp\Utils::setPath` -> `GuzzleHttp\set_path` +- `GuzzleHttp\Pool::batch` -> `GuzzleHttp\batch`. This function is, however, + deprecated in favor of using `GuzzleHttp\Pool::batch()`. + +The "procedural" global client has been removed with no replacement (e.g., +`GuzzleHttp\get()`, `GuzzleHttp\post()`, etc.). Use a `GuzzleHttp\Client` +object as a replacement. + +## `throwImmediately` has been removed + +The concept of "throwImmediately" has been removed from exceptions and error +events. This control mechanism was used to stop a transfer of concurrent +requests from completing. This can now be handled by throwing the exception or +by cancelling a pool of requests or each outstanding future request +individually. + +## headers event has been removed + +Removed the "headers" event. This event was only useful for changing the +body a response once the headers of the response were known. You can implement +a similar behavior in a number of ways. One example might be to use a +FnStream that has access to the transaction being sent. For example, when the +first byte is written, you could check if the response headers match your +expectations, and if so, change the actual stream body that is being +written to. + +## Updates to HTTP Messages + +Removed the `asArray` parameter from +`GuzzleHttp\Message\MessageInterface::getHeader`. If you want to get a header +value as an array, then use the newly added `getHeaderAsArray()` method of +`MessageInterface`. This change makes the Guzzle interfaces compatible with +the PSR-7 interfaces. + +3.x to 4.0 +---------- + +## Overarching changes: + +- Now requires PHP 5.4 or greater. +- No longer requires cURL to send requests. +- Guzzle no longer wraps every exception it throws. Only exceptions that are + recoverable are now wrapped by Guzzle. +- Various namespaces have been removed or renamed. +- No longer requiring the Symfony EventDispatcher. A custom event dispatcher + based on the Symfony EventDispatcher is + now utilized in `GuzzleHttp\Event\EmitterInterface` (resulting in significant + speed and functionality improvements). + +Changes per Guzzle 3.x namespace are described below. + +## Batch + +The `Guzzle\Batch` namespace has been removed. This is best left to +third-parties to implement on top of Guzzle's core HTTP library. + +## Cache + +The `Guzzle\Cache` namespace has been removed. (Todo: No suitable replacement +has been implemented yet, but hoping to utilize a PSR cache interface). + +## Common + +- Removed all of the wrapped exceptions. It's better to use the standard PHP + library for unrecoverable exceptions. +- `FromConfigInterface` has been removed. +- `Guzzle\Common\Version` has been removed. The VERSION constant can be found + at `GuzzleHttp\ClientInterface::VERSION`. + +### Collection + +- `getAll` has been removed. Use `toArray` to convert a collection to an array. +- `inject` has been removed. +- `keySearch` has been removed. +- `getPath` no longer supports wildcard expressions. Use something better like + JMESPath for this. +- `setPath` now supports appending to an existing array via the `[]` notation. + +### Events + +Guzzle no longer requires Symfony's EventDispatcher component. Guzzle now uses +`GuzzleHttp\Event\Emitter`. + +- `Symfony\Component\EventDispatcher\EventDispatcherInterface` is replaced by + `GuzzleHttp\Event\EmitterInterface`. +- `Symfony\Component\EventDispatcher\EventDispatcher` is replaced by + `GuzzleHttp\Event\Emitter`. +- `Symfony\Component\EventDispatcher\Event` is replaced by + `GuzzleHttp\Event\Event`, and Guzzle now has an EventInterface in + `GuzzleHttp\Event\EventInterface`. +- `AbstractHasDispatcher` has moved to a trait, `HasEmitterTrait`, and + `HasDispatcherInterface` has moved to `HasEmitterInterface`. Retrieving the + event emitter of a request, client, etc. now uses the `getEmitter` method + rather than the `getDispatcher` method. + +#### Emitter + +- Use the `once()` method to add a listener that automatically removes itself + the first time it is invoked. +- Use the `listeners()` method to retrieve a list of event listeners rather than + the `getListeners()` method. +- Use `emit()` instead of `dispatch()` to emit an event from an emitter. +- Use `attach()` instead of `addSubscriber()` and `detach()` instead of + `removeSubscriber()`. + +```php +$mock = new Mock(); +// 3.x +$request->getEventDispatcher()->addSubscriber($mock); +$request->getEventDispatcher()->removeSubscriber($mock); +// 4.x +$request->getEmitter()->attach($mock); +$request->getEmitter()->detach($mock); +``` + +Use the `on()` method to add a listener rather than the `addListener()` method. + +```php +// 3.x +$request->getEventDispatcher()->addListener('foo', function (Event $event) { /* ... */ } ); +// 4.x +$request->getEmitter()->on('foo', function (Event $event, $name) { /* ... */ } ); +``` + +## Http + +### General changes + +- The cacert.pem certificate has been moved to `src/cacert.pem`. +- Added the concept of adapters that are used to transfer requests over the + wire. +- Simplified the event system. +- Sending requests in parallel is still possible, but batching is no longer a + concept of the HTTP layer. Instead, you must use the `complete` and `error` + events to asynchronously manage parallel request transfers. +- `Guzzle\Http\Url` has moved to `GuzzleHttp\Url`. +- `Guzzle\Http\QueryString` has moved to `GuzzleHttp\Query`. +- QueryAggregators have been rewritten so that they are simply callable + functions. +- `GuzzleHttp\StaticClient` has been removed. Use the functions provided in + `functions.php` for an easy to use static client instance. +- Exceptions in `GuzzleHttp\Exception` have been updated to all extend from + `GuzzleHttp\Exception\TransferException`. + +### Client + +Calling methods like `get()`, `post()`, `head()`, etc. no longer create and +return a request, but rather creates a request, sends the request, and returns +the response. + +```php +// 3.0 +$request = $client->get('/'); +$response = $request->send(); + +// 4.0 +$response = $client->get('/'); + +// or, to mirror the previous behavior +$request = $client->createRequest('GET', '/'); +$response = $client->send($request); +``` + +`GuzzleHttp\ClientInterface` has changed. + +- The `send` method no longer accepts more than one request. Use `sendAll` to + send multiple requests in parallel. +- `setUserAgent()` has been removed. Use a default request option instead. You + could, for example, do something like: + `$client->setConfig('defaults/headers/User-Agent', 'Foo/Bar ' . $client::getDefaultUserAgent())`. +- `setSslVerification()` has been removed. Use default request options instead, + like `$client->setConfig('defaults/verify', true)`. + +`GuzzleHttp\Client` has changed. + +- The constructor now accepts only an associative array. You can include a + `base_url` string or array to use a URI template as the base URL of a client. + You can also specify a `defaults` key that is an associative array of default + request options. You can pass an `adapter` to use a custom adapter, + `batch_adapter` to use a custom adapter for sending requests in parallel, or + a `message_factory` to change the factory used to create HTTP requests and + responses. +- The client no longer emits a `client.create_request` event. +- Creating requests with a client no longer automatically utilize a URI + template. You must pass an array into a creational method (e.g., + `createRequest`, `get`, `put`, etc.) in order to expand a URI template. + +### Messages + +Messages no longer have references to their counterparts (i.e., a request no +longer has a reference to it's response, and a response no loger has a +reference to its request). This association is now managed through a +`GuzzleHttp\Adapter\TransactionInterface` object. You can get references to +these transaction objects using request events that are emitted over the +lifecycle of a request. + +#### Requests with a body + +- `GuzzleHttp\Message\EntityEnclosingRequest` and + `GuzzleHttp\Message\EntityEnclosingRequestInterface` have been removed. The + separation between requests that contain a body and requests that do not + contain a body has been removed, and now `GuzzleHttp\Message\RequestInterface` + handles both use cases. +- Any method that previously accepts a `GuzzleHttp\Response` object now accept a + `GuzzleHttp\Message\ResponseInterface`. +- `GuzzleHttp\Message\RequestFactoryInterface` has been renamed to + `GuzzleHttp\Message\MessageFactoryInterface`. This interface is used to create + both requests and responses and is implemented in + `GuzzleHttp\Message\MessageFactory`. +- POST field and file methods have been removed from the request object. You + must now use the methods made available to `GuzzleHttp\Post\PostBodyInterface` + to control the format of a POST body. Requests that are created using a + standard `GuzzleHttp\Message\MessageFactoryInterface` will automatically use + a `GuzzleHttp\Post\PostBody` body if the body was passed as an array or if + the method is POST and no body is provided. + +```php +$request = $client->createRequest('POST', '/'); +$request->getBody()->setField('foo', 'bar'); +$request->getBody()->addFile(new PostFile('file_key', fopen('/path/to/content', 'r'))); +``` + +#### Headers + +- `GuzzleHttp\Message\Header` has been removed. Header values are now simply + represented by an array of values or as a string. Header values are returned + as a string by default when retrieving a header value from a message. You can + pass an optional argument of `true` to retrieve a header value as an array + of strings instead of a single concatenated string. +- `GuzzleHttp\PostFile` and `GuzzleHttp\PostFileInterface` have been moved to + `GuzzleHttp\Post`. This interface has been simplified and now allows the + addition of arbitrary headers. +- Custom headers like `GuzzleHttp\Message\Header\Link` have been removed. Most + of the custom headers are now handled separately in specific + subscribers/plugins, and `GuzzleHttp\Message\HeaderValues::parseParams()` has + been updated to properly handle headers that contain parameters (like the + `Link` header). + +#### Responses + +- `GuzzleHttp\Message\Response::getInfo()` and + `GuzzleHttp\Message\Response::setInfo()` have been removed. Use the event + system to retrieve this type of information. +- `GuzzleHttp\Message\Response::getRawHeaders()` has been removed. +- `GuzzleHttp\Message\Response::getMessage()` has been removed. +- `GuzzleHttp\Message\Response::calculateAge()` and other cache specific + methods have moved to the CacheSubscriber. +- Header specific helper functions like `getContentMd5()` have been removed. + Just use `getHeader('Content-MD5')` instead. +- `GuzzleHttp\Message\Response::setRequest()` and + `GuzzleHttp\Message\Response::getRequest()` have been removed. Use the event + system to work with request and response objects as a transaction. +- `GuzzleHttp\Message\Response::getRedirectCount()` has been removed. Use the + Redirect subscriber instead. +- `GuzzleHttp\Message\Response::isSuccessful()` and other related methods have + been removed. Use `getStatusCode()` instead. + +#### Streaming responses + +Streaming requests can now be created by a client directly, returning a +`GuzzleHttp\Message\ResponseInterface` object that contains a body stream +referencing an open PHP HTTP stream. + +```php +// 3.0 +use Guzzle\Stream\PhpStreamRequestFactory; +$request = $client->get('/'); +$factory = new PhpStreamRequestFactory(); +$stream = $factory->fromRequest($request); +$data = $stream->read(1024); + +// 4.0 +$response = $client->get('/', ['stream' => true]); +// Read some data off of the stream in the response body +$data = $response->getBody()->read(1024); +``` + +#### Redirects + +The `configureRedirects()` method has been removed in favor of a +`allow_redirects` request option. + +```php +// Standard redirects with a default of a max of 5 redirects +$request = $client->createRequest('GET', '/', ['allow_redirects' => true]); + +// Strict redirects with a custom number of redirects +$request = $client->createRequest('GET', '/', [ + 'allow_redirects' => ['max' => 5, 'strict' => true] +]); +``` + +#### EntityBody + +EntityBody interfaces and classes have been removed or moved to +`GuzzleHttp\Stream`. All classes and interfaces that once required +`GuzzleHttp\EntityBodyInterface` now require +`GuzzleHttp\Stream\StreamInterface`. Creating a new body for a request no +longer uses `GuzzleHttp\EntityBody::factory` but now uses +`GuzzleHttp\Stream\Stream::factory` or even better: +`GuzzleHttp\Stream\create()`. + +- `Guzzle\Http\EntityBodyInterface` is now `GuzzleHttp\Stream\StreamInterface` +- `Guzzle\Http\EntityBody` is now `GuzzleHttp\Stream\Stream` +- `Guzzle\Http\CachingEntityBody` is now `GuzzleHttp\Stream\CachingStream` +- `Guzzle\Http\ReadLimitEntityBody` is now `GuzzleHttp\Stream\LimitStream` +- `Guzzle\Http\IoEmittyinEntityBody` has been removed. + +#### Request lifecycle events + +Requests previously submitted a large number of requests. The number of events +emitted over the lifecycle of a request has been significantly reduced to make +it easier to understand how to extend the behavior of a request. All events +emitted during the lifecycle of a request now emit a custom +`GuzzleHttp\Event\EventInterface` object that contains context providing +methods and a way in which to modify the transaction at that specific point in +time (e.g., intercept the request and set a response on the transaction). + +- `request.before_send` has been renamed to `before` and now emits a + `GuzzleHttp\Event\BeforeEvent` +- `request.complete` has been renamed to `complete` and now emits a + `GuzzleHttp\Event\CompleteEvent`. +- `request.sent` has been removed. Use `complete`. +- `request.success` has been removed. Use `complete`. +- `error` is now an event that emits a `GuzzleHttp\Event\ErrorEvent`. +- `request.exception` has been removed. Use `error`. +- `request.receive.status_line` has been removed. +- `curl.callback.progress` has been removed. Use a custom `StreamInterface` to + maintain a status update. +- `curl.callback.write` has been removed. Use a custom `StreamInterface` to + intercept writes. +- `curl.callback.read` has been removed. Use a custom `StreamInterface` to + intercept reads. + +`headers` is a new event that is emitted after the response headers of a +request have been received before the body of the response is downloaded. This +event emits a `GuzzleHttp\Event\HeadersEvent`. + +You can intercept a request and inject a response using the `intercept()` event +of a `GuzzleHttp\Event\BeforeEvent`, `GuzzleHttp\Event\CompleteEvent`, and +`GuzzleHttp\Event\ErrorEvent` event. + +See: http://docs.guzzlephp.org/en/latest/events.html + +## Inflection + +The `Guzzle\Inflection` namespace has been removed. This is not a core concern +of Guzzle. + +## Iterator + +The `Guzzle\Iterator` namespace has been removed. + +- `Guzzle\Iterator\AppendIterator`, `Guzzle\Iterator\ChunkedIterator`, and + `Guzzle\Iterator\MethodProxyIterator` are nice, but not a core requirement of + Guzzle itself. +- `Guzzle\Iterator\FilterIterator` is no longer needed because an equivalent + class is shipped with PHP 5.4. +- `Guzzle\Iterator\MapIterator` is not really needed when using PHP 5.5 because + it's easier to just wrap an iterator in a generator that maps values. + +For a replacement of these iterators, see https://github.com/nikic/iter + +## Log + +The LogPlugin has moved to https://github.com/guzzle/log-subscriber. The +`Guzzle\Log` namespace has been removed. Guzzle now relies on +`Psr\Log\LoggerInterface` for all logging. The MessageFormatter class has been +moved to `GuzzleHttp\Subscriber\Log\Formatter`. + +## Parser + +The `Guzzle\Parser` namespace has been removed. This was previously used to +make it possible to plug in custom parsers for cookies, messages, URI +templates, and URLs; however, this level of complexity is not needed in Guzzle +so it has been removed. + +- Cookie: Cookie parsing logic has been moved to + `GuzzleHttp\Cookie\SetCookie::fromString`. +- Message: Message parsing logic for both requests and responses has been moved + to `GuzzleHttp\Message\MessageFactory::fromMessage`. Message parsing is only + used in debugging or deserializing messages, so it doesn't make sense for + Guzzle as a library to add this level of complexity to parsing messages. +- UriTemplate: URI template parsing has been moved to + `GuzzleHttp\UriTemplate`. The Guzzle library will automatically use the PECL + URI template library if it is installed. +- Url: URL parsing is now performed in `GuzzleHttp\Url::fromString` (previously + it was `Guzzle\Http\Url::factory()`). If custom URL parsing is necessary, + then developers are free to subclass `GuzzleHttp\Url`. + +## Plugin + +The `Guzzle\Plugin` namespace has been renamed to `GuzzleHttp\Subscriber`. +Several plugins are shipping with the core Guzzle library under this namespace. + +- `GuzzleHttp\Subscriber\Cookie`: Replaces the old CookiePlugin. Cookie jar + code has moved to `GuzzleHttp\Cookie`. +- `GuzzleHttp\Subscriber\History`: Replaces the old HistoryPlugin. +- `GuzzleHttp\Subscriber\HttpError`: Throws errors when a bad HTTP response is + received. +- `GuzzleHttp\Subscriber\Mock`: Replaces the old MockPlugin. +- `GuzzleHttp\Subscriber\Prepare`: Prepares the body of a request just before + sending. This subscriber is attached to all requests by default. +- `GuzzleHttp\Subscriber\Redirect`: Replaces the RedirectPlugin. + +The following plugins have been removed (third-parties are free to re-implement +these if needed): + +- `GuzzleHttp\Plugin\Async` has been removed. +- `GuzzleHttp\Plugin\CurlAuth` has been removed. +- `GuzzleHttp\Plugin\ErrorResponse\ErrorResponsePlugin` has been removed. This + functionality should instead be implemented with event listeners that occur + after normal response parsing occurs in the guzzle/command package. + +The following plugins are not part of the core Guzzle package, but are provided +in separate repositories: + +- `Guzzle\Http\Plugin\BackoffPlugin` has been rewritten to be much simpler + to build custom retry policies using simple functions rather than various + chained classes. See: https://github.com/guzzle/retry-subscriber +- `Guzzle\Http\Plugin\Cache\CachePlugin` has moved to + https://github.com/guzzle/cache-subscriber +- `Guzzle\Http\Plugin\Log\LogPlugin` has moved to + https://github.com/guzzle/log-subscriber +- `Guzzle\Http\Plugin\Md5\Md5Plugin` has moved to + https://github.com/guzzle/message-integrity-subscriber +- `Guzzle\Http\Plugin\Mock\MockPlugin` has moved to + `GuzzleHttp\Subscriber\MockSubscriber`. +- `Guzzle\Http\Plugin\Oauth\OauthPlugin` has moved to + https://github.com/guzzle/oauth-subscriber + +## Service + +The service description layer of Guzzle has moved into two separate packages: + +- http://github.com/guzzle/command Provides a high level abstraction over web + services by representing web service operations using commands. +- http://github.com/guzzle/guzzle-services Provides an implementation of + guzzle/command that provides request serialization and response parsing using + Guzzle service descriptions. + +## Stream + +Stream have moved to a separate package available at +https://github.com/guzzle/streams. + +`Guzzle\Stream\StreamInterface` has been given a large update to cleanly take +on the responsibilities of `Guzzle\Http\EntityBody` and +`Guzzle\Http\EntityBodyInterface` now that they have been removed. The number +of methods implemented by the `StreamInterface` has been drastically reduced to +allow developers to more easily extend and decorate stream behavior. + +## Removed methods from StreamInterface + +- `getStream` and `setStream` have been removed to better encapsulate streams. +- `getMetadata` and `setMetadata` have been removed in favor of + `GuzzleHttp\Stream\MetadataStreamInterface`. +- `getWrapper`, `getWrapperData`, `getStreamType`, and `getUri` have all been + removed. This data is accessible when + using streams that implement `GuzzleHttp\Stream\MetadataStreamInterface`. +- `rewind` has been removed. Use `seek(0)` for a similar behavior. + +## Renamed methods + +- `detachStream` has been renamed to `detach`. +- `feof` has been renamed to `eof`. +- `ftell` has been renamed to `tell`. +- `readLine` has moved from an instance method to a static class method of + `GuzzleHttp\Stream\Stream`. + +## Metadata streams + +`GuzzleHttp\Stream\MetadataStreamInterface` has been added to denote streams +that contain additional metadata accessible via `getMetadata()`. +`GuzzleHttp\Stream\StreamInterface::getMetadata` and +`GuzzleHttp\Stream\StreamInterface::setMetadata` have been removed. + +## StreamRequestFactory + +The entire concept of the StreamRequestFactory has been removed. The way this +was used in Guzzle 3 broke the actual interface of sending streaming requests +(instead of getting back a Response, you got a StreamInterface). Streaming +PHP requests are now implemented through the `GuzzleHttp\Adapter\StreamAdapter`. + +3.6 to 3.7 +---------- + +### Deprecations + +- You can now enable E_USER_DEPRECATED warnings to see if you are using any deprecated methods.: + +```php +\Guzzle\Common\Version::$emitWarnings = true; +``` + +The following APIs and options have been marked as deprecated: + +- Marked `Guzzle\Http\Message\Request::isResponseBodyRepeatable()` as deprecated. Use `$request->getResponseBody()->isRepeatable()` instead. +- Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead. +- Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead. +- Marked `Guzzle\Http\Message\Request::setIsRedirect()` as deprecated. Use the HistoryPlugin instead. +- Marked `Guzzle\Http\Message\Request::isRedirect()` as deprecated. Use the HistoryPlugin instead. +- Marked `Guzzle\Cache\CacheAdapterFactory::factory()` as deprecated +- Marked `Guzzle\Service\Client::enableMagicMethods()` as deprecated. Magic methods can no longer be disabled on a Guzzle\Service\Client. +- Marked `Guzzle\Parser\Url\UrlParser` as deprecated. Just use PHP's `parse_url()` and percent encode your UTF-8. +- Marked `Guzzle\Common\Collection::inject()` as deprecated. +- Marked `Guzzle\Plugin\CurlAuth\CurlAuthPlugin` as deprecated. Use + `$client->getConfig()->setPath('request.options/auth', array('user', 'pass', 'Basic|Digest|NTLM|Any'));` or + `$client->setDefaultOption('auth', array('user', 'pass', 'Basic|Digest|NTLM|Any'));` + +3.7 introduces `request.options` as a parameter for a client configuration and as an optional argument to all creational +request methods. When paired with a client's configuration settings, these options allow you to specify default settings +for various aspects of a request. Because these options make other previous configuration options redundant, several +configuration options and methods of a client and AbstractCommand have been deprecated. + +- Marked `Guzzle\Service\Client::getDefaultHeaders()` as deprecated. Use `$client->getDefaultOption('headers')`. +- Marked `Guzzle\Service\Client::setDefaultHeaders()` as deprecated. Use `$client->setDefaultOption('headers/{header_name}', 'value')`. +- Marked 'request.params' for `Guzzle\Http\Client` as deprecated. Use `$client->setDefaultOption('params/{param_name}', 'value')` +- Marked 'command.headers', 'command.response_body' and 'command.on_complete' as deprecated for AbstractCommand. These will work through Guzzle 4.0 + + $command = $client->getCommand('foo', array( + 'command.headers' => array('Test' => '123'), + 'command.response_body' => '/path/to/file' + )); + + // Should be changed to: + + $command = $client->getCommand('foo', array( + 'command.request_options' => array( + 'headers' => array('Test' => '123'), + 'save_as' => '/path/to/file' + ) + )); + +### Interface changes + +Additions and changes (you will need to update any implementations or subclasses you may have created): + +- Added an `$options` argument to the end of the following methods of `Guzzle\Http\ClientInterface`: + createRequest, head, delete, put, patch, post, options, prepareRequest +- Added an `$options` argument to the end of `Guzzle\Http\Message\Request\RequestFactoryInterface::createRequest()` +- Added an `applyOptions()` method to `Guzzle\Http\Message\Request\RequestFactoryInterface` +- Changed `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $body = null)` to + `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $options = array())`. You can still pass in a + resource, string, or EntityBody into the $options parameter to specify the download location of the response. +- Changed `Guzzle\Common\Collection::__construct($data)` to no longer accepts a null value for `$data` but a + default `array()` +- Added `Guzzle\Stream\StreamInterface::isRepeatable` +- Made `Guzzle\Http\Client::expandTemplate` and `getUriTemplate` protected methods. + +The following methods were removed from interfaces. All of these methods are still available in the concrete classes +that implement them, but you should update your code to use alternative methods: + +- Removed `Guzzle\Http\ClientInterface::setDefaultHeaders(). Use + `$client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. or + `$client->getConfig()->setPath('request.options/headers', array('header_name' => 'value'))` or + `$client->setDefaultOption('headers/{header_name}', 'value')`. or + `$client->setDefaultOption('headers', array('header_name' => 'value'))`. +- Removed `Guzzle\Http\ClientInterface::getDefaultHeaders(). Use `$client->getConfig()->getPath('request.options/headers')`. +- Removed `Guzzle\Http\ClientInterface::expandTemplate()`. This is an implementation detail. +- Removed `Guzzle\Http\ClientInterface::setRequestFactory()`. This is an implementation detail. +- Removed `Guzzle\Http\ClientInterface::getCurlMulti()`. This is a very specific implementation detail. +- Removed `Guzzle\Http\Message\RequestInterface::canCache`. Use the CachePlugin. +- Removed `Guzzle\Http\Message\RequestInterface::setIsRedirect`. Use the HistoryPlugin. +- Removed `Guzzle\Http\Message\RequestInterface::isRedirect`. Use the HistoryPlugin. + +### Cache plugin breaking changes + +- CacheKeyProviderInterface and DefaultCacheKeyProvider are no longer used. All of this logic is handled in a + CacheStorageInterface. These two objects and interface will be removed in a future version. +- Always setting X-cache headers on cached responses +- Default cache TTLs are now handled by the CacheStorageInterface of a CachePlugin +- `CacheStorageInterface::cache($key, Response $response, $ttl = null)` has changed to `cache(RequestInterface + $request, Response $response);` +- `CacheStorageInterface::fetch($key)` has changed to `fetch(RequestInterface $request);` +- `CacheStorageInterface::delete($key)` has changed to `delete(RequestInterface $request);` +- Added `CacheStorageInterface::purge($url)` +- `DefaultRevalidation::__construct(CacheKeyProviderInterface $cacheKey, CacheStorageInterface $cache, CachePlugin + $plugin)` has changed to `DefaultRevalidation::__construct(CacheStorageInterface $cache, + CanCacheStrategyInterface $canCache = null)` +- Added `RevalidationInterface::shouldRevalidate(RequestInterface $request, Response $response)` + +3.5 to 3.6 +---------- + +* Mixed casing of headers are now forced to be a single consistent casing across all values for that header. +* Messages internally use a HeaderCollection object to delegate handling case-insensitive header resolution +* Removed the whole changedHeader() function system of messages because all header changes now go through addHeader(). + For example, setHeader() first removes the header using unset on a HeaderCollection and then calls addHeader(). + Keeping the Host header and URL host in sync is now handled by overriding the addHeader method in Request. +* Specific header implementations can be created for complex headers. When a message creates a header, it uses a + HeaderFactory which can map specific headers to specific header classes. There is now a Link header and + CacheControl header implementation. +* Moved getLinks() from Response to just be used on a Link header object. + +If you previously relied on Guzzle\Http\Message\Header::raw(), then you will need to update your code to use the +HeaderInterface (e.g. toArray(), getAll(), etc.). + +### Interface changes + +* Removed from interface: Guzzle\Http\ClientInterface::setUriTemplate +* Removed from interface: Guzzle\Http\ClientInterface::setCurlMulti() +* Removed Guzzle\Http\Message\Request::receivedRequestHeader() and implemented this functionality in + Guzzle\Http\Curl\RequestMediator +* Removed the optional $asString parameter from MessageInterface::getHeader(). Just cast the header to a string. +* Removed the optional $tryChunkedTransfer option from Guzzle\Http\Message\EntityEnclosingRequestInterface +* Removed the $asObjects argument from Guzzle\Http\Message\MessageInterface::getHeaders() + +### Removed deprecated functions + +* Removed Guzzle\Parser\ParserRegister::get(). Use getParser() +* Removed Guzzle\Parser\ParserRegister::set(). Use registerParser(). + +### Deprecations + +* The ability to case-insensitively search for header values +* Guzzle\Http\Message\Header::hasExactHeader +* Guzzle\Http\Message\Header::raw. Use getAll() +* Deprecated cache control specific methods on Guzzle\Http\Message\AbstractMessage. Use the CacheControl header object + instead. + +### Other changes + +* All response header helper functions return a string rather than mixing Header objects and strings inconsistently +* Removed cURL blacklist support. This is no longer necessary now that Expect, Accept, etc. are managed by Guzzle + directly via interfaces +* Removed the injecting of a request object onto a response object. The methods to get and set a request still exist + but are a no-op until removed. +* Most classes that used to require a `Guzzle\Service\Command\CommandInterface` typehint now request a + `Guzzle\Service\Command\ArrayCommandInterface`. +* Added `Guzzle\Http\Message\RequestInterface::startResponse()` to the RequestInterface to handle injecting a response + on a request while the request is still being transferred +* `Guzzle\Service\Command\CommandInterface` now extends from ToArrayInterface and ArrayAccess + +3.3 to 3.4 +---------- + +Base URLs of a client now follow the rules of https://tools.ietf.org/html/rfc3986#section-5.2.2 when merging URLs. + +3.2 to 3.3 +---------- + +### Response::getEtag() quote stripping removed + +`Guzzle\Http\Message\Response::getEtag()` no longer strips quotes around the ETag response header + +### Removed `Guzzle\Http\Utils` + +The `Guzzle\Http\Utils` class was removed. This class was only used for testing. + +### Stream wrapper and type + +`Guzzle\Stream\Stream::getWrapper()` and `Guzzle\Stream\Stream::getStreamType()` are no longer converted to lowercase. + +### curl.emit_io became emit_io + +Emitting IO events from a RequestMediator is now a parameter that must be set in a request's curl options using the +'emit_io' key. This was previously set under a request's parameters using 'curl.emit_io' + +3.1 to 3.2 +---------- + +### CurlMulti is no longer reused globally + +Before 3.2, the same CurlMulti object was reused globally for each client. This can cause issue where plugins added +to a single client can pollute requests dispatched from other clients. + +If you still wish to reuse the same CurlMulti object with each client, then you can add a listener to the +ServiceBuilder's `service_builder.create_client` event to inject a custom CurlMulti object into each client as it is +created. + +```php +$multi = new Guzzle\Http\Curl\CurlMulti(); +$builder = Guzzle\Service\Builder\ServiceBuilder::factory('/path/to/config.json'); +$builder->addListener('service_builder.create_client', function ($event) use ($multi) { + $event['client']->setCurlMulti($multi); +} +}); +``` + +### No default path + +URLs no longer have a default path value of '/' if no path was specified. + +Before: + +```php +$request = $client->get('http://www.foo.com'); +echo $request->getUrl(); +// >> http://www.foo.com/ +``` + +After: + +```php +$request = $client->get('http://www.foo.com'); +echo $request->getUrl(); +// >> http://www.foo.com +``` + +### Less verbose BadResponseException + +The exception message for `Guzzle\Http\Exception\BadResponseException` no longer contains the full HTTP request and +response information. You can, however, get access to the request and response object by calling `getRequest()` or +`getResponse()` on the exception object. + +### Query parameter aggregation + +Multi-valued query parameters are no longer aggregated using a callback function. `Guzzle\Http\Query` now has a +setAggregator() method that accepts a `Guzzle\Http\QueryAggregator\QueryAggregatorInterface` object. This object is +responsible for handling the aggregation of multi-valued query string variables into a flattened hash. + +2.8 to 3.x +---------- + +### Guzzle\Service\Inspector + +Change `\Guzzle\Service\Inspector::fromConfig` to `\Guzzle\Common\Collection::fromConfig` + +**Before** + +```php +use Guzzle\Service\Inspector; + +class YourClient extends \Guzzle\Service\Client +{ + public static function factory($config = array()) + { + $default = array(); + $required = array('base_url', 'username', 'api_key'); + $config = Inspector::fromConfig($config, $default, $required); + + $client = new self( + $config->get('base_url'), + $config->get('username'), + $config->get('api_key') + ); + $client->setConfig($config); + + $client->setDescription(ServiceDescription::factory(__DIR__ . DIRECTORY_SEPARATOR . 'client.json')); + + return $client; + } +``` + +**After** + +```php +use Guzzle\Common\Collection; + +class YourClient extends \Guzzle\Service\Client +{ + public static function factory($config = array()) + { + $default = array(); + $required = array('base_url', 'username', 'api_key'); + $config = Collection::fromConfig($config, $default, $required); + + $client = new self( + $config->get('base_url'), + $config->get('username'), + $config->get('api_key') + ); + $client->setConfig($config); + + $client->setDescription(ServiceDescription::factory(__DIR__ . DIRECTORY_SEPARATOR . 'client.json')); + + return $client; + } +``` + +### Convert XML Service Descriptions to JSON + +**Before** + +```xml + + + + + + Get a list of groups + + + Uses a search query to get a list of groups + + + + Create a group + + + + + Delete a group by ID + + + + + + + Update a group + + + + + + +``` + +**After** + +```json +{ + "name": "Zendesk REST API v2", + "apiVersion": "2012-12-31", + "description":"Provides access to Zendesk views, groups, tickets, ticket fields, and users", + "operations": { + "list_groups": { + "httpMethod":"GET", + "uri": "groups.json", + "summary": "Get a list of groups" + }, + "search_groups":{ + "httpMethod":"GET", + "uri": "search.json?query=\"{query} type:group\"", + "summary": "Uses a search query to get a list of groups", + "parameters":{ + "query":{ + "location": "uri", + "description":"Zendesk Search Query", + "type": "string", + "required": true + } + } + }, + "create_group": { + "httpMethod":"POST", + "uri": "groups.json", + "summary": "Create a group", + "parameters":{ + "data": { + "type": "array", + "location": "body", + "description":"Group JSON", + "filters": "json_encode", + "required": true + }, + "Content-Type":{ + "type": "string", + "location":"header", + "static": "application/json" + } + } + }, + "delete_group": { + "httpMethod":"DELETE", + "uri": "groups/{id}.json", + "summary": "Delete a group", + "parameters":{ + "id":{ + "location": "uri", + "description":"Group to delete by ID", + "type": "integer", + "required": true + } + } + }, + "get_group": { + "httpMethod":"GET", + "uri": "groups/{id}.json", + "summary": "Get a ticket", + "parameters":{ + "id":{ + "location": "uri", + "description":"Group to get by ID", + "type": "integer", + "required": true + } + } + }, + "update_group": { + "httpMethod":"PUT", + "uri": "groups/{id}.json", + "summary": "Update a group", + "parameters":{ + "id": { + "location": "uri", + "description":"Group to update by ID", + "type": "integer", + "required": true + }, + "data": { + "type": "array", + "location": "body", + "description":"Group JSON", + "filters": "json_encode", + "required": true + }, + "Content-Type":{ + "type": "string", + "location":"header", + "static": "application/json" + } + } + } +} +``` + +### Guzzle\Service\Description\ServiceDescription + +Commands are now called Operations + +**Before** + +```php +use Guzzle\Service\Description\ServiceDescription; + +$sd = new ServiceDescription(); +$sd->getCommands(); // @returns ApiCommandInterface[] +$sd->hasCommand($name); +$sd->getCommand($name); // @returns ApiCommandInterface|null +$sd->addCommand($command); // @param ApiCommandInterface $command +``` + +**After** + +```php +use Guzzle\Service\Description\ServiceDescription; + +$sd = new ServiceDescription(); +$sd->getOperations(); // @returns OperationInterface[] +$sd->hasOperation($name); +$sd->getOperation($name); // @returns OperationInterface|null +$sd->addOperation($operation); // @param OperationInterface $operation +``` + +### Guzzle\Common\Inflection\Inflector + +Namespace is now `Guzzle\Inflection\Inflector` + +### Guzzle\Http\Plugin + +Namespace is now `Guzzle\Plugin`. Many other changes occur within this namespace and are detailed in their own sections below. + +### Guzzle\Http\Plugin\LogPlugin and Guzzle\Common\Log + +Now `Guzzle\Plugin\Log\LogPlugin` and `Guzzle\Log` respectively. + +**Before** + +```php +use Guzzle\Common\Log\ClosureLogAdapter; +use Guzzle\Http\Plugin\LogPlugin; + +/** @var \Guzzle\Http\Client */ +$client; + +// $verbosity is an integer indicating desired message verbosity level +$client->addSubscriber(new LogPlugin(new ClosureLogAdapter(function($m) { echo $m; }, $verbosity = LogPlugin::LOG_VERBOSE); +``` + +**After** + +```php +use Guzzle\Log\ClosureLogAdapter; +use Guzzle\Log\MessageFormatter; +use Guzzle\Plugin\Log\LogPlugin; + +/** @var \Guzzle\Http\Client */ +$client; + +// $format is a string indicating desired message format -- @see MessageFormatter +$client->addSubscriber(new LogPlugin(new ClosureLogAdapter(function($m) { echo $m; }, $format = MessageFormatter::DEBUG_FORMAT); +``` + +### Guzzle\Http\Plugin\CurlAuthPlugin + +Now `Guzzle\Plugin\CurlAuth\CurlAuthPlugin`. + +### Guzzle\Http\Plugin\ExponentialBackoffPlugin + +Now `Guzzle\Plugin\Backoff\BackoffPlugin`, and other changes. + +**Before** + +```php +use Guzzle\Http\Plugin\ExponentialBackoffPlugin; + +$backoffPlugin = new ExponentialBackoffPlugin($maxRetries, array_merge( + ExponentialBackoffPlugin::getDefaultFailureCodes(), array(429) + )); + +$client->addSubscriber($backoffPlugin); +``` + +**After** + +```php +use Guzzle\Plugin\Backoff\BackoffPlugin; +use Guzzle\Plugin\Backoff\HttpBackoffStrategy; + +// Use convenient factory method instead -- see implementation for ideas of what +// you can do with chaining backoff strategies +$backoffPlugin = BackoffPlugin::getExponentialBackoff($maxRetries, array_merge( + HttpBackoffStrategy::getDefaultFailureCodes(), array(429) + )); +$client->addSubscriber($backoffPlugin); +``` + +### Known Issues + +#### [BUG] Accept-Encoding header behavior changed unintentionally. + +(See #217) (Fixed in 09daeb8c666fb44499a0646d655a8ae36456575e) + +In version 2.8 setting the `Accept-Encoding` header would set the CURLOPT_ENCODING option, which permitted cURL to +properly handle gzip/deflate compressed responses from the server. In versions affected by this bug this does not happen. +See issue #217 for a workaround, or use a version containing the fix. diff --git a/vendor/guzzlehttp/guzzle/src/BodySummarizer.php b/vendor/guzzlehttp/guzzle/src/BodySummarizer.php new file mode 100644 index 0000000..6eca94e --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/BodySummarizer.php @@ -0,0 +1,28 @@ +truncateAt = $truncateAt; + } + + /** + * Returns a summarized message body. + */ + public function summarize(MessageInterface $message): ?string + { + return $this->truncateAt === null + ? \GuzzleHttp\Psr7\Message::bodySummary($message) + : \GuzzleHttp\Psr7\Message::bodySummary($message, $this->truncateAt); + } +} diff --git a/vendor/guzzlehttp/guzzle/src/BodySummarizerInterface.php b/vendor/guzzlehttp/guzzle/src/BodySummarizerInterface.php new file mode 100644 index 0000000..3e02e03 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/BodySummarizerInterface.php @@ -0,0 +1,13 @@ + 'http://www.foo.com/1.0/', + * 'timeout' => 0, + * 'allow_redirects' => false, + * 'proxy' => '192.168.16.1:10' + * ]); + * + * Client configuration settings include the following options: + * + * - handler: (callable) Function that transfers HTTP requests over the + * wire. The function is called with a Psr7\Http\Message\RequestInterface + * and array of transfer options, and must return a + * GuzzleHttp\Promise\PromiseInterface that is fulfilled with a + * Psr7\Http\Message\ResponseInterface on success. + * If no handler is provided, a default handler will be created + * that enables all of the request options below by attaching all of the + * default middleware to the handler. + * - base_uri: (string|UriInterface) Base URI of the client that is merged + * into relative URIs. Can be a string or instance of UriInterface. + * - **: any request option + * + * @param array $config Client configuration settings. + * + * @see \GuzzleHttp\RequestOptions for a list of available request options. + */ + public function __construct(array $config = []) + { + if (!isset($config['handler'])) { + $config['handler'] = HandlerStack::create(); + } elseif (!\is_callable($config['handler'])) { + throw new InvalidArgumentException('handler must be a callable'); + } + + // Convert the base_uri to a UriInterface + if (isset($config['base_uri'])) { + $config['base_uri'] = Psr7\Utils::uriFor($config['base_uri']); + } + + $this->configureDefaults($config); + } + + /** + * @param string $method + * @param array $args + * + * @return PromiseInterface|ResponseInterface + * + * @deprecated Client::__call will be removed in guzzlehttp/guzzle:8.0. + */ + public function __call($method, $args) + { + if (\count($args) < 1) { + throw new InvalidArgumentException('Magic request methods require a URI and optional options array'); + } + + $uri = $args[0]; + $opts = $args[1] ?? []; + + return \substr($method, -5) === 'Async' + ? $this->requestAsync(\substr($method, 0, -5), $uri, $opts) + : $this->request($method, $uri, $opts); + } + + /** + * Asynchronously send an HTTP request. + * + * @param array $options Request options to apply to the given + * request and to the transfer. See \GuzzleHttp\RequestOptions. + */ + public function sendAsync(RequestInterface $request, array $options = []): PromiseInterface + { + // Merge the base URI into the request URI if needed. + $options = $this->prepareDefaults($options); + + return $this->transfer( + $request->withUri($this->buildUri($request->getUri(), $options), $request->hasHeader('Host')), + $options + ); + } + + /** + * Send an HTTP request. + * + * @param array $options Request options to apply to the given + * request and to the transfer. See \GuzzleHttp\RequestOptions. + * + * @throws GuzzleException + */ + public function send(RequestInterface $request, array $options = []): ResponseInterface + { + $options[RequestOptions::SYNCHRONOUS] = true; + + return $this->sendAsync($request, $options)->wait(); + } + + /** + * The HttpClient PSR (PSR-18) specify this method. + * + * {@inheritDoc} + */ + public function sendRequest(RequestInterface $request): ResponseInterface + { + $options[RequestOptions::SYNCHRONOUS] = true; + $options[RequestOptions::ALLOW_REDIRECTS] = false; + $options[RequestOptions::HTTP_ERRORS] = false; + + return $this->sendAsync($request, $options)->wait(); + } + + /** + * Create and send an asynchronous HTTP request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. Use an array to provide a URL + * template and additional variables to use in the URL template expansion. + * + * @param string $method HTTP method + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. See \GuzzleHttp\RequestOptions. + */ + public function requestAsync(string $method, $uri = '', array $options = []): PromiseInterface + { + $options = $this->prepareDefaults($options); + // Remove request modifying parameter because it can be done up-front. + $headers = $options['headers'] ?? []; + $body = $options['body'] ?? null; + $version = $options['version'] ?? '1.1'; + // Merge the URI into the base URI. + $uri = $this->buildUri(Psr7\Utils::uriFor($uri), $options); + if (\is_array($body)) { + throw $this->invalidBody(); + } + $request = new Psr7\Request($method, $uri, $headers, $body, $version); + // Remove the option so that they are not doubly-applied. + unset($options['headers'], $options['body'], $options['version']); + + return $this->transfer($request, $options); + } + + /** + * Create and send an HTTP request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. + * + * @param string $method HTTP method. + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. See \GuzzleHttp\RequestOptions. + * + * @throws GuzzleException + */ + public function request(string $method, $uri = '', array $options = []): ResponseInterface + { + $options[RequestOptions::SYNCHRONOUS] = true; + + return $this->requestAsync($method, $uri, $options)->wait(); + } + + /** + * Get a client configuration option. + * + * These options include default request options of the client, a "handler" + * (if utilized by the concrete client), and a "base_uri" if utilized by + * the concrete client. + * + * @param string|null $option The config option to retrieve. + * + * @return mixed + * + * @deprecated Client::getConfig will be removed in guzzlehttp/guzzle:8.0. + */ + public function getConfig(?string $option = null) + { + return $option === null + ? $this->config + : ($this->config[$option] ?? null); + } + + private function buildUri(UriInterface $uri, array $config): UriInterface + { + if (isset($config['base_uri'])) { + $uri = Psr7\UriResolver::resolve(Psr7\Utils::uriFor($config['base_uri']), $uri); + } + + if (isset($config['idn_conversion']) && ($config['idn_conversion'] !== false)) { + $idnOptions = ($config['idn_conversion'] === true) ? \IDNA_DEFAULT : $config['idn_conversion']; + $uri = Utils::idnUriConvert($uri, $idnOptions); + } + + return $uri->getScheme() === '' && $uri->getHost() !== '' ? $uri->withScheme('http') : $uri; + } + + /** + * Configures the default options for a client. + */ + private function configureDefaults(array $config): void + { + $defaults = [ + 'allow_redirects' => RedirectMiddleware::$defaultSettings, + 'http_errors' => true, + 'decode_content' => true, + 'verify' => true, + 'cookies' => false, + 'idn_conversion' => false, + ]; + + // Use the standard Linux HTTP_PROXY and HTTPS_PROXY if set. + + // We can only trust the HTTP_PROXY environment variable in a CLI + // process due to the fact that PHP has no reliable mechanism to + // get environment variables that start with "HTTP_". + if (\PHP_SAPI === 'cli' && ($proxy = Utils::getenv('HTTP_PROXY'))) { + $defaults['proxy']['http'] = $proxy; + } + + if ($proxy = Utils::getenv('HTTPS_PROXY')) { + $defaults['proxy']['https'] = $proxy; + } + + if ($noProxy = Utils::getenv('NO_PROXY')) { + $cleanedNoProxy = \str_replace(' ', '', $noProxy); + $defaults['proxy']['no'] = \explode(',', $cleanedNoProxy); + } + + $this->config = $config + $defaults; + + if (!empty($config['cookies']) && $config['cookies'] === true) { + $this->config['cookies'] = new CookieJar(); + } + + // Add the default user-agent header. + if (!isset($this->config['headers'])) { + $this->config['headers'] = ['User-Agent' => Utils::defaultUserAgent()]; + } else { + // Add the User-Agent header if one was not already set. + foreach (\array_keys($this->config['headers']) as $name) { + if (\strtolower($name) === 'user-agent') { + return; + } + } + $this->config['headers']['User-Agent'] = Utils::defaultUserAgent(); + } + } + + /** + * Merges default options into the array. + * + * @param array $options Options to modify by reference + */ + private function prepareDefaults(array $options): array + { + $defaults = $this->config; + + if (!empty($defaults['headers'])) { + // Default headers are only added if they are not present. + $defaults['_conditional'] = $defaults['headers']; + unset($defaults['headers']); + } + + // Special handling for headers is required as they are added as + // conditional headers and as headers passed to a request ctor. + if (\array_key_exists('headers', $options)) { + // Allows default headers to be unset. + if ($options['headers'] === null) { + $defaults['_conditional'] = []; + unset($options['headers']); + } elseif (!\is_array($options['headers'])) { + throw new InvalidArgumentException('headers must be an array'); + } + } + + // Shallow merge defaults underneath options. + $result = $options + $defaults; + + // Remove null values. + foreach ($result as $k => $v) { + if ($v === null) { + unset($result[$k]); + } + } + + return $result; + } + + /** + * Transfers the given request and applies request options. + * + * The URI of the request is not modified and the request options are used + * as-is without merging in default options. + * + * @param array $options See \GuzzleHttp\RequestOptions. + */ + private function transfer(RequestInterface $request, array $options): PromiseInterface + { + $request = $this->applyOptions($request, $options); + /** @var HandlerStack $handler */ + $handler = $options['handler']; + + try { + return P\Create::promiseFor($handler($request, $options)); + } catch (\Exception $e) { + return P\Create::rejectionFor($e); + } + } + + /** + * Applies the array of request options to a request. + */ + private function applyOptions(RequestInterface $request, array &$options): RequestInterface + { + $modify = [ + 'set_headers' => [], + ]; + + if (isset($options['headers'])) { + if (array_keys($options['headers']) === range(0, count($options['headers']) - 1)) { + throw new InvalidArgumentException('The headers array must have header name as keys.'); + } + $modify['set_headers'] = $options['headers']; + unset($options['headers']); + } + + if (isset($options['form_params'])) { + if (isset($options['multipart'])) { + throw new InvalidArgumentException('You cannot use ' + .'form_params and multipart at the same time. Use the ' + .'form_params option if you want to send application/' + .'x-www-form-urlencoded requests, and the multipart ' + .'option to send multipart/form-data requests.'); + } + $options['body'] = \http_build_query($options['form_params'], '', '&'); + unset($options['form_params']); + // Ensure that we don't have the header in different case and set the new value. + $options['_conditional'] = Psr7\Utils::caselessRemove(['Content-Type'], $options['_conditional']); + $options['_conditional']['Content-Type'] = 'application/x-www-form-urlencoded'; + } + + if (isset($options['multipart'])) { + $options['body'] = new Psr7\MultipartStream($options['multipart']); + unset($options['multipart']); + } + + if (isset($options['json'])) { + $options['body'] = Utils::jsonEncode($options['json']); + unset($options['json']); + // Ensure that we don't have the header in different case and set the new value. + $options['_conditional'] = Psr7\Utils::caselessRemove(['Content-Type'], $options['_conditional']); + $options['_conditional']['Content-Type'] = 'application/json'; + } + + if (!empty($options['decode_content']) + && $options['decode_content'] !== true + ) { + // Ensure that we don't have the header in different case and set the new value. + $options['_conditional'] = Psr7\Utils::caselessRemove(['Accept-Encoding'], $options['_conditional']); + $modify['set_headers']['Accept-Encoding'] = $options['decode_content']; + } + + if (isset($options['body'])) { + if (\is_array($options['body'])) { + throw $this->invalidBody(); + } + $modify['body'] = Psr7\Utils::streamFor($options['body']); + unset($options['body']); + } + + if (!empty($options['auth']) && \is_array($options['auth'])) { + $value = $options['auth']; + $type = isset($value[2]) ? \strtolower($value[2]) : 'basic'; + switch ($type) { + case 'basic': + // Ensure that we don't have the header in different case and set the new value. + $modify['set_headers'] = Psr7\Utils::caselessRemove(['Authorization'], $modify['set_headers']); + $modify['set_headers']['Authorization'] = 'Basic ' + .\base64_encode("$value[0]:$value[1]"); + break; + case 'digest': + // @todo: Do not rely on curl + $options['curl'][\CURLOPT_HTTPAUTH] = \CURLAUTH_DIGEST; + $options['curl'][\CURLOPT_USERPWD] = "$value[0]:$value[1]"; + break; + case 'ntlm': + $options['curl'][\CURLOPT_HTTPAUTH] = \CURLAUTH_NTLM; + $options['curl'][\CURLOPT_USERPWD] = "$value[0]:$value[1]"; + break; + } + } + + if (isset($options['query'])) { + $value = $options['query']; + if (\is_array($value)) { + $value = \http_build_query($value, '', '&', \PHP_QUERY_RFC3986); + } + if (!\is_string($value)) { + throw new InvalidArgumentException('query must be a string or array'); + } + $modify['query'] = $value; + unset($options['query']); + } + + // Ensure that sink is not an invalid value. + if (isset($options['sink'])) { + // TODO: Add more sink validation? + if (\is_bool($options['sink'])) { + throw new InvalidArgumentException('sink must not be a boolean'); + } + } + + if (isset($options['version'])) { + $modify['version'] = $options['version']; + } + + $request = Psr7\Utils::modifyRequest($request, $modify); + if ($request->getBody() instanceof Psr7\MultipartStream) { + // Use a multipart/form-data POST if a Content-Type is not set. + // Ensure that we don't have the header in different case and set the new value. + $options['_conditional'] = Psr7\Utils::caselessRemove(['Content-Type'], $options['_conditional']); + $options['_conditional']['Content-Type'] = 'multipart/form-data; boundary=' + .$request->getBody()->getBoundary(); + } + + // Merge in conditional headers if they are not present. + if (isset($options['_conditional'])) { + // Build up the changes so it's in a single clone of the message. + $modify = []; + foreach ($options['_conditional'] as $k => $v) { + if (!$request->hasHeader($k)) { + $modify['set_headers'][$k] = $v; + } + } + $request = Psr7\Utils::modifyRequest($request, $modify); + // Don't pass this internal value along to middleware/handlers. + unset($options['_conditional']); + } + + return $request; + } + + /** + * Return an InvalidArgumentException with pre-set message. + */ + private function invalidBody(): InvalidArgumentException + { + return new InvalidArgumentException('Passing in the "body" request ' + .'option as an array to send a request is not supported. ' + .'Please use the "form_params" request option to send a ' + .'application/x-www-form-urlencoded request, or the "multipart" ' + .'request option to send a multipart/form-data request.'); + } +} diff --git a/vendor/guzzlehttp/guzzle/src/ClientInterface.php b/vendor/guzzlehttp/guzzle/src/ClientInterface.php new file mode 100644 index 0000000..6aaee61 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/ClientInterface.php @@ -0,0 +1,84 @@ +request('GET', $uri, $options); + } + + /** + * Create and send an HTTP HEAD request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + * + * @throws GuzzleException + */ + public function head($uri, array $options = []): ResponseInterface + { + return $this->request('HEAD', $uri, $options); + } + + /** + * Create and send an HTTP PUT request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + * + * @throws GuzzleException + */ + public function put($uri, array $options = []): ResponseInterface + { + return $this->request('PUT', $uri, $options); + } + + /** + * Create and send an HTTP POST request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + * + * @throws GuzzleException + */ + public function post($uri, array $options = []): ResponseInterface + { + return $this->request('POST', $uri, $options); + } + + /** + * Create and send an HTTP PATCH request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + * + * @throws GuzzleException + */ + public function patch($uri, array $options = []): ResponseInterface + { + return $this->request('PATCH', $uri, $options); + } + + /** + * Create and send an HTTP DELETE request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + * + * @throws GuzzleException + */ + public function delete($uri, array $options = []): ResponseInterface + { + return $this->request('DELETE', $uri, $options); + } + + /** + * Create and send an asynchronous HTTP request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. Use an array to provide a URL + * template and additional variables to use in the URL template expansion. + * + * @param string $method HTTP method + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + */ + abstract public function requestAsync(string $method, $uri, array $options = []): PromiseInterface; + + /** + * Create and send an asynchronous HTTP GET request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. Use an array to provide a URL + * template and additional variables to use in the URL template expansion. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + */ + public function getAsync($uri, array $options = []): PromiseInterface + { + return $this->requestAsync('GET', $uri, $options); + } + + /** + * Create and send an asynchronous HTTP HEAD request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. Use an array to provide a URL + * template and additional variables to use in the URL template expansion. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + */ + public function headAsync($uri, array $options = []): PromiseInterface + { + return $this->requestAsync('HEAD', $uri, $options); + } + + /** + * Create and send an asynchronous HTTP PUT request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. Use an array to provide a URL + * template and additional variables to use in the URL template expansion. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + */ + public function putAsync($uri, array $options = []): PromiseInterface + { + return $this->requestAsync('PUT', $uri, $options); + } + + /** + * Create and send an asynchronous HTTP POST request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. Use an array to provide a URL + * template and additional variables to use in the URL template expansion. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + */ + public function postAsync($uri, array $options = []): PromiseInterface + { + return $this->requestAsync('POST', $uri, $options); + } + + /** + * Create and send an asynchronous HTTP PATCH request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. Use an array to provide a URL + * template and additional variables to use in the URL template expansion. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + */ + public function patchAsync($uri, array $options = []): PromiseInterface + { + return $this->requestAsync('PATCH', $uri, $options); + } + + /** + * Create and send an asynchronous HTTP DELETE request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. Use an array to provide a URL + * template and additional variables to use in the URL template expansion. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + */ + public function deleteAsync($uri, array $options = []): PromiseInterface + { + return $this->requestAsync('DELETE', $uri, $options); + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Cookie/CookieJar.php b/vendor/guzzlehttp/guzzle/src/Cookie/CookieJar.php new file mode 100644 index 0000000..b4ced5a --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Cookie/CookieJar.php @@ -0,0 +1,319 @@ +strictMode = $strictMode; + + foreach ($cookieArray as $cookie) { + if (!($cookie instanceof SetCookie)) { + $cookie = new SetCookie($cookie); + } + $this->setCookie($cookie); + } + } + + /** + * Create a new Cookie jar from an associative array and domain. + * + * @param array $cookies Cookies to create the jar from + * @param string $domain Domain to set the cookies to + */ + public static function fromArray(array $cookies, string $domain): self + { + $cookieJar = new self(); + foreach ($cookies as $name => $value) { + $cookieJar->setCookie(new SetCookie([ + 'Domain' => $domain, + 'Name' => $name, + 'Value' => $value, + 'Discard' => true, + ])); + } + + return $cookieJar; + } + + /** + * Evaluate if this cookie should be persisted to storage + * that survives between requests. + * + * @param SetCookie $cookie Being evaluated. + * @param bool $allowSessionCookies If we should persist session cookies + */ + public static function shouldPersist(SetCookie $cookie, bool $allowSessionCookies = false): bool + { + if ($cookie->getExpires() || $allowSessionCookies) { + if (!$cookie->getDiscard()) { + return true; + } + } + + return false; + } + + /** + * Finds and returns the cookie based on the name + * + * @param string $name cookie name to search for + * + * @return SetCookie|null cookie that was found or null if not found + */ + public function getCookieByName(string $name): ?SetCookie + { + foreach ($this->cookies as $cookie) { + if ($cookie->getName() !== null && \strcasecmp($cookie->getName(), $name) === 0) { + return $cookie; + } + } + + return null; + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return \array_map(static function (SetCookie $cookie): array { + return $cookie->toArray(); + }, $this->getIterator()->getArrayCopy()); + } + + /** + * {@inheritDoc} + */ + public function clear(?string $domain = null, ?string $path = null, ?string $name = null): void + { + if (!$domain) { + $this->cookies = []; + + return; + } elseif (!$path) { + $this->cookies = \array_filter( + $this->cookies, + static function (SetCookie $cookie) use ($domain): bool { + return !$cookie->matchesDomain($domain); + } + ); + } elseif (!$name) { + $this->cookies = \array_filter( + $this->cookies, + static function (SetCookie $cookie) use ($path, $domain): bool { + return !($cookie->matchesPath($path) && + $cookie->matchesDomain($domain)); + } + ); + } else { + $this->cookies = \array_filter( + $this->cookies, + static function (SetCookie $cookie) use ($path, $domain, $name) { + return !($cookie->getName() == $name && + $cookie->matchesPath($path) && + $cookie->matchesDomain($domain)); + } + ); + } + } + + /** + * {@inheritDoc} + */ + public function clearSessionCookies(): void + { + $this->cookies = \array_filter( + $this->cookies, + static function (SetCookie $cookie): bool { + return !$cookie->getDiscard() && $cookie->getExpires(); + } + ); + } + + /** + * {@inheritDoc} + */ + public function setCookie(SetCookie $cookie): bool + { + // If the name string is empty (but not 0), ignore the set-cookie + // string entirely. + $name = $cookie->getName(); + if (!$name && $name !== '0') { + return false; + } + + // Only allow cookies with set and valid domain, name, value + $result = $cookie->validate(); + if ($result !== true) { + if ($this->strictMode) { + throw new \RuntimeException('Invalid cookie: '.$result); + } + $this->removeCookieIfEmpty($cookie); + + return false; + } + + // Resolve conflicts with previously set cookies + foreach ($this->cookies as $i => $c) { + // Two cookies are identical, when their path, and domain are + // identical. + if ($c->getPath() != $cookie->getPath() || + $c->getDomain() != $cookie->getDomain() || + $c->getName() != $cookie->getName() + ) { + continue; + } + + // The previously set cookie is a discard cookie and this one is + // not so allow the new cookie to be set + if (!$cookie->getDiscard() && $c->getDiscard()) { + unset($this->cookies[$i]); + continue; + } + + // If the new cookie's expiration is further into the future, then + // replace the old cookie + if ($cookie->getExpires() > $c->getExpires()) { + unset($this->cookies[$i]); + continue; + } + + // If the value has changed, we better change it + if ($cookie->getValue() !== $c->getValue()) { + unset($this->cookies[$i]); + continue; + } + + // The cookie exists, so no need to continue + return false; + } + + $this->cookies[] = $cookie; + + return true; + } + + public function count(): int + { + return \count($this->cookies); + } + + /** + * @return \ArrayIterator + */ + public function getIterator(): \ArrayIterator + { + return new \ArrayIterator(\array_values($this->cookies)); + } + + public function extractCookies(RequestInterface $request, ResponseInterface $response): void + { + if ($cookieHeader = $response->getHeader('Set-Cookie')) { + foreach ($cookieHeader as $cookie) { + $sc = SetCookie::fromString($cookie); + if (!$sc->getDomain()) { + $sc->setDomain($request->getUri()->getHost()); + } + if (0 !== \strpos($sc->getPath(), '/')) { + $sc->setPath($this->getCookiePathFromRequest($request)); + } + if (!$sc->matchesDomain($request->getUri()->getHost())) { + continue; + } + // Note: At this point `$sc->getDomain()` being a public suffix should + // be rejected, but we don't want to pull in the full PSL dependency. + $this->setCookie($sc); + } + } + } + + /** + * Computes cookie path following RFC 6265 section 5.1.4 + * + * @see https://tools.ietf.org/html/rfc6265#section-5.1.4 + */ + private function getCookiePathFromRequest(RequestInterface $request): string + { + $uriPath = $request->getUri()->getPath(); + if ('' === $uriPath) { + return '/'; + } + if (0 !== \strpos($uriPath, '/')) { + return '/'; + } + if ('/' === $uriPath) { + return '/'; + } + $lastSlashPos = \strrpos($uriPath, '/'); + if (0 === $lastSlashPos || false === $lastSlashPos) { + return '/'; + } + + return \substr($uriPath, 0, $lastSlashPos); + } + + public function withCookieHeader(RequestInterface $request): RequestInterface + { + $values = []; + $uri = $request->getUri(); + $scheme = $uri->getScheme(); + $host = $uri->getHost(); + $path = $uri->getPath() ?: '/'; + + foreach ($this->cookies as $cookie) { + if ($cookie->matchesPath($path) && + $cookie->matchesDomain($host) && + !$cookie->isExpired() && + (!$cookie->getSecure() || $scheme === 'https') + ) { + $values[] = $cookie->getName().'=' + .$cookie->getValue(); + } + } + + return $values + ? $request->withHeader('Cookie', \implode('; ', $values)) + : $request; + } + + /** + * If a cookie already exists and the server asks to set it again with a + * null value, the cookie must be deleted. + */ + private function removeCookieIfEmpty(SetCookie $cookie): void + { + $cookieValue = $cookie->getValue(); + if ($cookieValue === null || $cookieValue === '') { + $this->clear( + $cookie->getDomain(), + $cookie->getPath(), + $cookie->getName() + ); + } + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php b/vendor/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php new file mode 100644 index 0000000..50bc363 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php @@ -0,0 +1,79 @@ + + */ +interface CookieJarInterface extends \Countable, \IteratorAggregate +{ + /** + * Create a request with added cookie headers. + * + * If no matching cookies are found in the cookie jar, then no Cookie + * header is added to the request and the same request is returned. + * + * @param RequestInterface $request Request object to modify. + * + * @return RequestInterface returns the modified request. + */ + public function withCookieHeader(RequestInterface $request): RequestInterface; + + /** + * Extract cookies from an HTTP response and store them in the CookieJar. + * + * @param RequestInterface $request Request that was sent + * @param ResponseInterface $response Response that was received + */ + public function extractCookies(RequestInterface $request, ResponseInterface $response): void; + + /** + * Sets a cookie in the cookie jar. + * + * @param SetCookie $cookie Cookie to set. + * + * @return bool Returns true on success or false on failure + */ + public function setCookie(SetCookie $cookie): bool; + + /** + * Remove cookies currently held in the cookie jar. + * + * Invoking this method without arguments will empty the whole cookie jar. + * If given a $domain argument only cookies belonging to that domain will + * be removed. If given a $domain and $path argument, cookies belonging to + * the specified path within that domain are removed. If given all three + * arguments, then the cookie with the specified name, path and domain is + * removed. + * + * @param string|null $domain Clears cookies matching a domain + * @param string|null $path Clears cookies matching a domain and path + * @param string|null $name Clears cookies matching a domain, path, and name + */ + public function clear(?string $domain = null, ?string $path = null, ?string $name = null): void; + + /** + * Discard all sessions cookies. + * + * Removes cookies that don't have an expire field or a have a discard + * field set to true. To be called when the user agent shuts down according + * to RFC 2965. + */ + public function clearSessionCookies(): void; + + /** + * Converts the cookie jar to an array. + */ + public function toArray(): array; +} diff --git a/vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php b/vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php new file mode 100644 index 0000000..290236d --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php @@ -0,0 +1,101 @@ +filename = $cookieFile; + $this->storeSessionCookies = $storeSessionCookies; + + if (\file_exists($cookieFile)) { + $this->load($cookieFile); + } + } + + /** + * Saves the file when shutting down + */ + public function __destruct() + { + $this->save($this->filename); + } + + /** + * Saves the cookies to a file. + * + * @param string $filename File to save + * + * @throws \RuntimeException if the file cannot be found or created + */ + public function save(string $filename): void + { + $json = []; + /** @var SetCookie $cookie */ + foreach ($this as $cookie) { + if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) { + $json[] = $cookie->toArray(); + } + } + + $jsonStr = Utils::jsonEncode($json); + if (false === \file_put_contents($filename, $jsonStr, \LOCK_EX)) { + throw new \RuntimeException("Unable to save file {$filename}"); + } + } + + /** + * Load cookies from a JSON formatted file. + * + * Old cookies are kept unless overwritten by newly loaded ones. + * + * @param string $filename Cookie file to load. + * + * @throws \RuntimeException if the file cannot be loaded. + */ + public function load(string $filename): void + { + $json = \file_get_contents($filename); + if (false === $json) { + throw new \RuntimeException("Unable to load file {$filename}"); + } + if ($json === '') { + return; + } + + $data = Utils::jsonDecode($json, true); + if (\is_array($data)) { + foreach ($data as $cookie) { + $this->setCookie(new SetCookie($cookie)); + } + } elseif (\is_scalar($data) && !empty($data)) { + throw new \RuntimeException("Invalid cookie file: {$filename}"); + } + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php b/vendor/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php new file mode 100644 index 0000000..cb3e67c --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php @@ -0,0 +1,77 @@ +sessionKey = $sessionKey; + $this->storeSessionCookies = $storeSessionCookies; + $this->load(); + } + + /** + * Saves cookies to session when shutting down + */ + public function __destruct() + { + $this->save(); + } + + /** + * Save cookies to the client session + */ + public function save(): void + { + $json = []; + /** @var SetCookie $cookie */ + foreach ($this as $cookie) { + if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) { + $json[] = $cookie->toArray(); + } + } + + $_SESSION[$this->sessionKey] = \json_encode($json); + } + + /** + * Load the contents of the client session into the data array + */ + protected function load(): void + { + if (!isset($_SESSION[$this->sessionKey])) { + return; + } + $data = \json_decode($_SESSION[$this->sessionKey], true); + if (\is_array($data)) { + foreach ($data as $cookie) { + $this->setCookie(new SetCookie($cookie)); + } + } elseif (\strlen($data)) { + throw new \RuntimeException('Invalid cookie data'); + } + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php b/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php new file mode 100644 index 0000000..d74915b --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php @@ -0,0 +1,488 @@ + null, + 'Value' => null, + 'Domain' => null, + 'Path' => '/', + 'Max-Age' => null, + 'Expires' => null, + 'Secure' => false, + 'Discard' => false, + 'HttpOnly' => false, + ]; + + /** + * @var array Cookie data + */ + private $data; + + /** + * Create a new SetCookie object from a string. + * + * @param string $cookie Set-Cookie header string + */ + public static function fromString(string $cookie): self + { + // Create the default return array + $data = self::$defaults; + // Explode the cookie string using a series of semicolons + $pieces = \array_filter(\array_map('trim', \explode(';', $cookie))); + // The name of the cookie (first kvp) must exist and include an equal sign. + if (!isset($pieces[0]) || \strpos($pieces[0], '=') === false) { + return new self($data); + } + + // Add the cookie pieces into the parsed data array + foreach ($pieces as $part) { + $cookieParts = \explode('=', $part, 2); + $key = \trim($cookieParts[0]); + $value = isset($cookieParts[1]) + ? \trim($cookieParts[1], " \n\r\t\0\x0B") + : true; + + // Only check for non-cookies when cookies have been found + if (!isset($data['Name'])) { + $data['Name'] = $key; + $data['Value'] = $value; + } else { + foreach (\array_keys(self::$defaults) as $search) { + if (!\strcasecmp($search, $key)) { + if ($search === 'Max-Age') { + if (is_numeric($value)) { + $data[$search] = (int) $value; + } + } else { + $data[$search] = $value; + } + continue 2; + } + } + $data[$key] = $value; + } + } + + return new self($data); + } + + /** + * @param array $data Array of cookie data provided by a Cookie parser + */ + public function __construct(array $data = []) + { + $this->data = self::$defaults; + + if (isset($data['Name'])) { + $this->setName($data['Name']); + } + + if (isset($data['Value'])) { + $this->setValue($data['Value']); + } + + if (isset($data['Domain'])) { + $this->setDomain($data['Domain']); + } + + if (isset($data['Path'])) { + $this->setPath($data['Path']); + } + + if (isset($data['Max-Age'])) { + $this->setMaxAge($data['Max-Age']); + } + + if (isset($data['Expires'])) { + $this->setExpires($data['Expires']); + } + + if (isset($data['Secure'])) { + $this->setSecure($data['Secure']); + } + + if (isset($data['Discard'])) { + $this->setDiscard($data['Discard']); + } + + if (isset($data['HttpOnly'])) { + $this->setHttpOnly($data['HttpOnly']); + } + + // Set the remaining values that don't have extra validation logic + foreach (array_diff(array_keys($data), array_keys(self::$defaults)) as $key) { + $this->data[$key] = $data[$key]; + } + + // Extract the Expires value and turn it into a UNIX timestamp if needed + if (!$this->getExpires() && $this->getMaxAge()) { + // Calculate the Expires date + $this->setExpires(\time() + $this->getMaxAge()); + } elseif (null !== ($expires = $this->getExpires()) && !\is_numeric($expires)) { + $this->setExpires($expires); + } + } + + public function __toString() + { + $str = $this->data['Name'].'='.($this->data['Value'] ?? '').'; '; + foreach ($this->data as $k => $v) { + if ($k !== 'Name' && $k !== 'Value' && $v !== null && $v !== false) { + if ($k === 'Expires') { + $str .= 'Expires='.\gmdate('D, d M Y H:i:s \G\M\T', $v).'; '; + } else { + $str .= ($v === true ? $k : "{$k}={$v}").'; '; + } + } + } + + return \rtrim($str, '; '); + } + + public function toArray(): array + { + return $this->data; + } + + /** + * Get the cookie name. + * + * @return string + */ + public function getName() + { + return $this->data['Name']; + } + + /** + * Set the cookie name. + * + * @param string $name Cookie name + */ + public function setName($name): void + { + if (!is_string($name)) { + trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a string to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + + $this->data['Name'] = (string) $name; + } + + /** + * Get the cookie value. + * + * @return string|null + */ + public function getValue() + { + return $this->data['Value']; + } + + /** + * Set the cookie value. + * + * @param string $value Cookie value + */ + public function setValue($value): void + { + if (!is_string($value)) { + trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a string to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + + $this->data['Value'] = (string) $value; + } + + /** + * Get the domain. + * + * @return string|null + */ + public function getDomain() + { + return $this->data['Domain']; + } + + /** + * Set the domain of the cookie. + * + * @param string|null $domain + */ + public function setDomain($domain): void + { + if (!is_string($domain) && null !== $domain) { + trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a string or null to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + + $this->data['Domain'] = null === $domain ? null : (string) $domain; + } + + /** + * Get the path. + * + * @return string + */ + public function getPath() + { + return $this->data['Path']; + } + + /** + * Set the path of the cookie. + * + * @param string $path Path of the cookie + */ + public function setPath($path): void + { + if (!is_string($path)) { + trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a string to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + + $this->data['Path'] = (string) $path; + } + + /** + * Maximum lifetime of the cookie in seconds. + * + * @return int|null + */ + public function getMaxAge() + { + return null === $this->data['Max-Age'] ? null : (int) $this->data['Max-Age']; + } + + /** + * Set the max-age of the cookie. + * + * @param int|null $maxAge Max age of the cookie in seconds + */ + public function setMaxAge($maxAge): void + { + if (!is_int($maxAge) && null !== $maxAge) { + trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing an int or null to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + + $this->data['Max-Age'] = $maxAge === null ? null : (int) $maxAge; + } + + /** + * The UNIX timestamp when the cookie Expires. + * + * @return string|int|null + */ + public function getExpires() + { + return $this->data['Expires']; + } + + /** + * Set the unix timestamp for which the cookie will expire. + * + * @param int|string|null $timestamp Unix timestamp or any English textual datetime description. + */ + public function setExpires($timestamp): void + { + if (!is_int($timestamp) && !is_string($timestamp) && null !== $timestamp) { + trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing an int, string or null to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + + $this->data['Expires'] = null === $timestamp ? null : (\is_numeric($timestamp) ? (int) $timestamp : \strtotime((string) $timestamp)); + } + + /** + * Get whether or not this is a secure cookie. + * + * @return bool + */ + public function getSecure() + { + return $this->data['Secure']; + } + + /** + * Set whether or not the cookie is secure. + * + * @param bool $secure Set to true or false if secure + */ + public function setSecure($secure): void + { + if (!is_bool($secure)) { + trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a bool to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + + $this->data['Secure'] = (bool) $secure; + } + + /** + * Get whether or not this is a session cookie. + * + * @return bool|null + */ + public function getDiscard() + { + return $this->data['Discard']; + } + + /** + * Set whether or not this is a session cookie. + * + * @param bool $discard Set to true or false if this is a session cookie + */ + public function setDiscard($discard): void + { + if (!is_bool($discard)) { + trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a bool to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + + $this->data['Discard'] = (bool) $discard; + } + + /** + * Get whether or not this is an HTTP only cookie. + * + * @return bool + */ + public function getHttpOnly() + { + return $this->data['HttpOnly']; + } + + /** + * Set whether or not this is an HTTP only cookie. + * + * @param bool $httpOnly Set to true or false if this is HTTP only + */ + public function setHttpOnly($httpOnly): void + { + if (!is_bool($httpOnly)) { + trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a bool to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + + $this->data['HttpOnly'] = (bool) $httpOnly; + } + + /** + * Check if the cookie matches a path value. + * + * A request-path path-matches a given cookie-path if at least one of + * the following conditions holds: + * + * - The cookie-path and the request-path are identical. + * - The cookie-path is a prefix of the request-path, and the last + * character of the cookie-path is %x2F ("/"). + * - The cookie-path is a prefix of the request-path, and the first + * character of the request-path that is not included in the cookie- + * path is a %x2F ("/") character. + * + * @param string $requestPath Path to check against + */ + public function matchesPath(string $requestPath): bool + { + $cookiePath = $this->getPath(); + + // Match on exact matches or when path is the default empty "/" + if ($cookiePath === '/' || $cookiePath == $requestPath) { + return true; + } + + // Ensure that the cookie-path is a prefix of the request path. + if (0 !== \strpos($requestPath, $cookiePath)) { + return false; + } + + // Match if the last character of the cookie-path is "/" + if (\substr($cookiePath, -1, 1) === '/') { + return true; + } + + // Match if the first character not included in cookie path is "/" + return \substr($requestPath, \strlen($cookiePath), 1) === '/'; + } + + /** + * Check if the cookie matches a domain value. + * + * @param string $domain Domain to check against + */ + public function matchesDomain(string $domain): bool + { + $cookieDomain = $this->getDomain(); + if (null === $cookieDomain) { + return true; + } + + // Remove the leading '.' as per spec in RFC 6265. + // https://tools.ietf.org/html/rfc6265#section-5.2.3 + $cookieDomain = \ltrim(\strtolower($cookieDomain), '.'); + + $domain = \strtolower($domain); + + // Domain not set or exact match. + if ('' === $cookieDomain || $domain === $cookieDomain) { + return true; + } + + // Matching the subdomain according to RFC 6265. + // https://tools.ietf.org/html/rfc6265#section-5.1.3 + if (\filter_var($domain, \FILTER_VALIDATE_IP)) { + return false; + } + + return (bool) \preg_match('/\.'.\preg_quote($cookieDomain, '/').'$/', $domain); + } + + /** + * Check if the cookie is expired. + */ + public function isExpired(): bool + { + return $this->getExpires() !== null && \time() > $this->getExpires(); + } + + /** + * Check if the cookie is valid according to RFC 6265. + * + * @return bool|string Returns true if valid or an error message if invalid + */ + public function validate() + { + $name = $this->getName(); + if ($name === '') { + return 'The cookie name must not be empty'; + } + + // Check if any of the invalid characters are present in the cookie name + if (\preg_match( + '/[\x00-\x20\x22\x28-\x29\x2c\x2f\x3a-\x40\x5c\x7b\x7d\x7f]/', + $name + )) { + return 'Cookie name must not contain invalid characters: ASCII ' + .'Control characters (0-31;127), space, tab and the ' + .'following characters: ()<>@,;:\"/?={}'; + } + + // Value must not be null. 0 and empty string are valid. Empty strings + // are technically against RFC 6265, but known to happen in the wild. + $value = $this->getValue(); + if ($value === null) { + return 'The cookie value must not be empty'; + } + + // Domains must not be empty, but can be 0. "0" is not a valid internet + // domain, but may be used as server name in a private network. + $domain = $this->getDomain(); + if ($domain === null || $domain === '') { + return 'The cookie domain must not be empty'; + } + + return true; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Exception/BadResponseException.php b/vendor/guzzlehttp/guzzle/src/Exception/BadResponseException.php new file mode 100644 index 0000000..a80956c --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Exception/BadResponseException.php @@ -0,0 +1,39 @@ +request = $request; + $this->handlerContext = $handlerContext; + } + + /** + * Get the request that caused the exception + */ + public function getRequest(): RequestInterface + { + return $this->request; + } + + /** + * Get contextual information about the error from the underlying handler. + * + * The contents of this array will vary depending on which handler you are + * using. It may also be just an empty array. Relying on this data will + * couple you to a specific handler, but can give more debug information + * when needed. + */ + public function getHandlerContext(): array + { + return $this->handlerContext; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Exception/GuzzleException.php b/vendor/guzzlehttp/guzzle/src/Exception/GuzzleException.php new file mode 100644 index 0000000..fa3ed69 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Exception/GuzzleException.php @@ -0,0 +1,9 @@ +getStatusCode() : 0; + parent::__construct($message, $code, $previous); + $this->request = $request; + $this->response = $response; + $this->handlerContext = $handlerContext; + } + + /** + * Wrap non-RequestExceptions with a RequestException + */ + public static function wrapException(RequestInterface $request, \Throwable $e): RequestException + { + return $e instanceof RequestException ? $e : new RequestException($e->getMessage(), $request, null, $e); + } + + /** + * Factory method to create a new exception with a normalized error message + * + * @param RequestInterface $request Request sent + * @param ResponseInterface $response Response received + * @param \Throwable|null $previous Previous exception + * @param array $handlerContext Optional handler context + * @param BodySummarizerInterface|null $bodySummarizer Optional body summarizer + */ + public static function create( + RequestInterface $request, + ResponseInterface $response = null, + \Throwable $previous = null, + array $handlerContext = [], + BodySummarizerInterface $bodySummarizer = null + ): self { + if (!$response) { + return new self( + 'Error completing request', + $request, + null, + $previous, + $handlerContext + ); + } + + $level = (int) \floor($response->getStatusCode() / 100); + if ($level === 4) { + $label = 'Client error'; + $className = ClientException::class; + } elseif ($level === 5) { + $label = 'Server error'; + $className = ServerException::class; + } else { + $label = 'Unsuccessful request'; + $className = __CLASS__; + } + + $uri = $request->getUri(); + $uri = static::obfuscateUri($uri); + + // Client Error: `GET /` resulted in a `404 Not Found` response: + // ... (truncated) + $message = \sprintf( + '%s: `%s %s` resulted in a `%s %s` response', + $label, + $request->getMethod(), + $uri->__toString(), + $response->getStatusCode(), + $response->getReasonPhrase() + ); + + $summary = ($bodySummarizer ?? new BodySummarizer())->summarize($response); + + if ($summary !== null) { + $message .= ":\n{$summary}\n"; + } + + return new $className($message, $request, $response, $previous, $handlerContext); + } + + /** + * Obfuscates URI if there is a username and a password present + */ + private static function obfuscateUri(UriInterface $uri): UriInterface + { + $userInfo = $uri->getUserInfo(); + + if (false !== ($pos = \strpos($userInfo, ':'))) { + return $uri->withUserInfo(\substr($userInfo, 0, $pos), '***'); + } + + return $uri; + } + + /** + * Get the request that caused the exception + */ + public function getRequest(): RequestInterface + { + return $this->request; + } + + /** + * Get the associated response + */ + public function getResponse(): ?ResponseInterface + { + return $this->response; + } + + /** + * Check if a response was received + */ + public function hasResponse(): bool + { + return $this->response !== null; + } + + /** + * Get contextual information about the error from the underlying handler. + * + * The contents of this array will vary depending on which handler you are + * using. It may also be just an empty array. Relying on this data will + * couple you to a specific handler, but can give more debug information + * when needed. + */ + public function getHandlerContext(): array + { + return $this->handlerContext; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Exception/ServerException.php b/vendor/guzzlehttp/guzzle/src/Exception/ServerException.php new file mode 100644 index 0000000..8055e06 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Exception/ServerException.php @@ -0,0 +1,10 @@ +maxHandles = $maxHandles; + } + + public function create(RequestInterface $request, array $options): EasyHandle + { + if (isset($options['curl']['body_as_string'])) { + $options['_body_as_string'] = $options['curl']['body_as_string']; + unset($options['curl']['body_as_string']); + } + + $easy = new EasyHandle(); + $easy->request = $request; + $easy->options = $options; + $conf = $this->getDefaultConf($easy); + $this->applyMethod($easy, $conf); + $this->applyHandlerOptions($easy, $conf); + $this->applyHeaders($easy, $conf); + unset($conf['_headers']); + + // Add handler options from the request configuration options + if (isset($options['curl'])) { + $conf = \array_replace($conf, $options['curl']); + } + + $conf[\CURLOPT_HEADERFUNCTION] = $this->createHeaderFn($easy); + $easy->handle = $this->handles ? \array_pop($this->handles) : \curl_init(); + curl_setopt_array($easy->handle, $conf); + + return $easy; + } + + public function release(EasyHandle $easy): void + { + $resource = $easy->handle; + unset($easy->handle); + + if (\count($this->handles) >= $this->maxHandles) { + \curl_close($resource); + } else { + // Remove all callback functions as they can hold onto references + // and are not cleaned up by curl_reset. Using curl_setopt_array + // does not work for some reason, so removing each one + // individually. + \curl_setopt($resource, \CURLOPT_HEADERFUNCTION, null); + \curl_setopt($resource, \CURLOPT_READFUNCTION, null); + \curl_setopt($resource, \CURLOPT_WRITEFUNCTION, null); + \curl_setopt($resource, \CURLOPT_PROGRESSFUNCTION, null); + \curl_reset($resource); + $this->handles[] = $resource; + } + } + + /** + * Completes a cURL transaction, either returning a response promise or a + * rejected promise. + * + * @param callable(RequestInterface, array): PromiseInterface $handler + * @param CurlFactoryInterface $factory Dictates how the handle is released + */ + public static function finish(callable $handler, EasyHandle $easy, CurlFactoryInterface $factory): PromiseInterface + { + if (isset($easy->options['on_stats'])) { + self::invokeStats($easy); + } + + if (!$easy->response || $easy->errno) { + return self::finishError($handler, $easy, $factory); + } + + // Return the response if it is present and there is no error. + $factory->release($easy); + + // Rewind the body of the response if possible. + $body = $easy->response->getBody(); + if ($body->isSeekable()) { + $body->rewind(); + } + + return new FulfilledPromise($easy->response); + } + + private static function invokeStats(EasyHandle $easy): void + { + $curlStats = \curl_getinfo($easy->handle); + $curlStats['appconnect_time'] = \curl_getinfo($easy->handle, \CURLINFO_APPCONNECT_TIME); + $stats = new TransferStats( + $easy->request, + $easy->response, + $curlStats['total_time'], + $easy->errno, + $curlStats + ); + ($easy->options['on_stats'])($stats); + } + + /** + * @param callable(RequestInterface, array): PromiseInterface $handler + */ + private static function finishError(callable $handler, EasyHandle $easy, CurlFactoryInterface $factory): PromiseInterface + { + // Get error information and release the handle to the factory. + $ctx = [ + 'errno' => $easy->errno, + 'error' => \curl_error($easy->handle), + 'appconnect_time' => \curl_getinfo($easy->handle, \CURLINFO_APPCONNECT_TIME), + ] + \curl_getinfo($easy->handle); + $ctx[self::CURL_VERSION_STR] = \curl_version()['version']; + $factory->release($easy); + + // Retry when nothing is present or when curl failed to rewind. + if (empty($easy->options['_err_message']) && (!$easy->errno || $easy->errno == 65)) { + return self::retryFailedRewind($handler, $easy, $ctx); + } + + return self::createRejection($easy, $ctx); + } + + private static function createRejection(EasyHandle $easy, array $ctx): PromiseInterface + { + static $connectionErrors = [ + \CURLE_OPERATION_TIMEOUTED => true, + \CURLE_COULDNT_RESOLVE_HOST => true, + \CURLE_COULDNT_CONNECT => true, + \CURLE_SSL_CONNECT_ERROR => true, + \CURLE_GOT_NOTHING => true, + ]; + + if ($easy->createResponseException) { + return P\Create::rejectionFor( + new RequestException( + 'An error was encountered while creating the response', + $easy->request, + $easy->response, + $easy->createResponseException, + $ctx + ) + ); + } + + // If an exception was encountered during the onHeaders event, then + // return a rejected promise that wraps that exception. + if ($easy->onHeadersException) { + return P\Create::rejectionFor( + new RequestException( + 'An error was encountered during the on_headers event', + $easy->request, + $easy->response, + $easy->onHeadersException, + $ctx + ) + ); + } + + $message = \sprintf( + 'cURL error %s: %s (%s)', + $ctx['errno'], + $ctx['error'], + 'see https://curl.haxx.se/libcurl/c/libcurl-errors.html' + ); + $uriString = (string) $easy->request->getUri(); + if ($uriString !== '' && false === \strpos($ctx['error'], $uriString)) { + $message .= \sprintf(' for %s', $uriString); + } + + // Create a connection exception if it was a specific error code. + $error = isset($connectionErrors[$easy->errno]) + ? new ConnectException($message, $easy->request, null, $ctx) + : new RequestException($message, $easy->request, $easy->response, null, $ctx); + + return P\Create::rejectionFor($error); + } + + /** + * @return array + */ + private function getDefaultConf(EasyHandle $easy): array + { + $conf = [ + '_headers' => $easy->request->getHeaders(), + \CURLOPT_CUSTOMREQUEST => $easy->request->getMethod(), + \CURLOPT_URL => (string) $easy->request->getUri()->withFragment(''), + \CURLOPT_RETURNTRANSFER => false, + \CURLOPT_HEADER => false, + \CURLOPT_CONNECTTIMEOUT => 300, + ]; + + if (\defined('CURLOPT_PROTOCOLS')) { + $conf[\CURLOPT_PROTOCOLS] = \CURLPROTO_HTTP | \CURLPROTO_HTTPS; + } + + $version = $easy->request->getProtocolVersion(); + if ($version == 1.1) { + $conf[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_1_1; + } elseif ($version == 2.0) { + $conf[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_2_0; + } else { + $conf[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_1_0; + } + + return $conf; + } + + private function applyMethod(EasyHandle $easy, array &$conf): void + { + $body = $easy->request->getBody(); + $size = $body->getSize(); + + if ($size === null || $size > 0) { + $this->applyBody($easy->request, $easy->options, $conf); + + return; + } + + $method = $easy->request->getMethod(); + if ($method === 'PUT' || $method === 'POST') { + // See https://tools.ietf.org/html/rfc7230#section-3.3.2 + if (!$easy->request->hasHeader('Content-Length')) { + $conf[\CURLOPT_HTTPHEADER][] = 'Content-Length: 0'; + } + } elseif ($method === 'HEAD') { + $conf[\CURLOPT_NOBODY] = true; + unset( + $conf[\CURLOPT_WRITEFUNCTION], + $conf[\CURLOPT_READFUNCTION], + $conf[\CURLOPT_FILE], + $conf[\CURLOPT_INFILE] + ); + } + } + + private function applyBody(RequestInterface $request, array $options, array &$conf): void + { + $size = $request->hasHeader('Content-Length') + ? (int) $request->getHeaderLine('Content-Length') + : null; + + // Send the body as a string if the size is less than 1MB OR if the + // [curl][body_as_string] request value is set. + if (($size !== null && $size < 1000000) || !empty($options['_body_as_string'])) { + $conf[\CURLOPT_POSTFIELDS] = (string) $request->getBody(); + // Don't duplicate the Content-Length header + $this->removeHeader('Content-Length', $conf); + $this->removeHeader('Transfer-Encoding', $conf); + } else { + $conf[\CURLOPT_UPLOAD] = true; + if ($size !== null) { + $conf[\CURLOPT_INFILESIZE] = $size; + $this->removeHeader('Content-Length', $conf); + } + $body = $request->getBody(); + if ($body->isSeekable()) { + $body->rewind(); + } + $conf[\CURLOPT_READFUNCTION] = static function ($ch, $fd, $length) use ($body) { + return $body->read($length); + }; + } + + // If the Expect header is not present, prevent curl from adding it + if (!$request->hasHeader('Expect')) { + $conf[\CURLOPT_HTTPHEADER][] = 'Expect:'; + } + + // cURL sometimes adds a content-type by default. Prevent this. + if (!$request->hasHeader('Content-Type')) { + $conf[\CURLOPT_HTTPHEADER][] = 'Content-Type:'; + } + } + + private function applyHeaders(EasyHandle $easy, array &$conf): void + { + foreach ($conf['_headers'] as $name => $values) { + foreach ($values as $value) { + $value = (string) $value; + if ($value === '') { + // cURL requires a special format for empty headers. + // See https://github.com/guzzle/guzzle/issues/1882 for more details. + $conf[\CURLOPT_HTTPHEADER][] = "$name;"; + } else { + $conf[\CURLOPT_HTTPHEADER][] = "$name: $value"; + } + } + } + + // Remove the Accept header if one was not set + if (!$easy->request->hasHeader('Accept')) { + $conf[\CURLOPT_HTTPHEADER][] = 'Accept:'; + } + } + + /** + * Remove a header from the options array. + * + * @param string $name Case-insensitive header to remove + * @param array $options Array of options to modify + */ + private function removeHeader(string $name, array &$options): void + { + foreach (\array_keys($options['_headers']) as $key) { + if (!\strcasecmp($key, $name)) { + unset($options['_headers'][$key]); + + return; + } + } + } + + private function applyHandlerOptions(EasyHandle $easy, array &$conf): void + { + $options = $easy->options; + if (isset($options['verify'])) { + if ($options['verify'] === false) { + unset($conf[\CURLOPT_CAINFO]); + $conf[\CURLOPT_SSL_VERIFYHOST] = 0; + $conf[\CURLOPT_SSL_VERIFYPEER] = false; + } else { + $conf[\CURLOPT_SSL_VERIFYHOST] = 2; + $conf[\CURLOPT_SSL_VERIFYPEER] = true; + if (\is_string($options['verify'])) { + // Throw an error if the file/folder/link path is not valid or doesn't exist. + if (!\file_exists($options['verify'])) { + throw new \InvalidArgumentException("SSL CA bundle not found: {$options['verify']}"); + } + // If it's a directory or a link to a directory use CURLOPT_CAPATH. + // If not, it's probably a file, or a link to a file, so use CURLOPT_CAINFO. + if ( + \is_dir($options['verify']) || + ( + \is_link($options['verify']) === true && + ($verifyLink = \readlink($options['verify'])) !== false && + \is_dir($verifyLink) + ) + ) { + $conf[\CURLOPT_CAPATH] = $options['verify']; + } else { + $conf[\CURLOPT_CAINFO] = $options['verify']; + } + } + } + } + + if (!isset($options['curl'][\CURLOPT_ENCODING]) && !empty($options['decode_content'])) { + $accept = $easy->request->getHeaderLine('Accept-Encoding'); + if ($accept) { + $conf[\CURLOPT_ENCODING] = $accept; + } else { + // The empty string enables all available decoders and implicitly + // sets a matching 'Accept-Encoding' header. + $conf[\CURLOPT_ENCODING] = ''; + // But as the user did not specify any acceptable encodings we need + // to overwrite this implicit header with an empty one. + $conf[\CURLOPT_HTTPHEADER][] = 'Accept-Encoding:'; + } + } + + if (!isset($options['sink'])) { + // Use a default temp stream if no sink was set. + $options['sink'] = \GuzzleHttp\Psr7\Utils::tryFopen('php://temp', 'w+'); + } + $sink = $options['sink']; + if (!\is_string($sink)) { + $sink = \GuzzleHttp\Psr7\Utils::streamFor($sink); + } elseif (!\is_dir(\dirname($sink))) { + // Ensure that the directory exists before failing in curl. + throw new \RuntimeException(\sprintf('Directory %s does not exist for sink value of %s', \dirname($sink), $sink)); + } else { + $sink = new LazyOpenStream($sink, 'w+'); + } + $easy->sink = $sink; + $conf[\CURLOPT_WRITEFUNCTION] = static function ($ch, $write) use ($sink): int { + return $sink->write($write); + }; + + $timeoutRequiresNoSignal = false; + if (isset($options['timeout'])) { + $timeoutRequiresNoSignal |= $options['timeout'] < 1; + $conf[\CURLOPT_TIMEOUT_MS] = $options['timeout'] * 1000; + } + + // CURL default value is CURL_IPRESOLVE_WHATEVER + if (isset($options['force_ip_resolve'])) { + if ('v4' === $options['force_ip_resolve']) { + $conf[\CURLOPT_IPRESOLVE] = \CURL_IPRESOLVE_V4; + } elseif ('v6' === $options['force_ip_resolve']) { + $conf[\CURLOPT_IPRESOLVE] = \CURL_IPRESOLVE_V6; + } + } + + if (isset($options['connect_timeout'])) { + $timeoutRequiresNoSignal |= $options['connect_timeout'] < 1; + $conf[\CURLOPT_CONNECTTIMEOUT_MS] = $options['connect_timeout'] * 1000; + } + + if ($timeoutRequiresNoSignal && \strtoupper(\substr(\PHP_OS, 0, 3)) !== 'WIN') { + $conf[\CURLOPT_NOSIGNAL] = true; + } + + if (isset($options['proxy'])) { + if (!\is_array($options['proxy'])) { + $conf[\CURLOPT_PROXY] = $options['proxy']; + } else { + $scheme = $easy->request->getUri()->getScheme(); + if (isset($options['proxy'][$scheme])) { + $host = $easy->request->getUri()->getHost(); + if (isset($options['proxy']['no']) && Utils::isHostInNoProxy($host, $options['proxy']['no'])) { + unset($conf[\CURLOPT_PROXY]); + } else { + $conf[\CURLOPT_PROXY] = $options['proxy'][$scheme]; + } + } + } + } + + if (isset($options['crypto_method'])) { + if (\STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT === $options['crypto_method']) { + if (!defined('CURL_SSLVERSION_TLSv1_0')) { + throw new \InvalidArgumentException('Invalid crypto_method request option: TLS 1.0 not supported by your version of cURL'); + } + $conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_0; + } elseif (\STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT === $options['crypto_method']) { + if (!defined('CURL_SSLVERSION_TLSv1_1')) { + throw new \InvalidArgumentException('Invalid crypto_method request option: TLS 1.1 not supported by your version of cURL'); + } + $conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_1; + } elseif (\STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT === $options['crypto_method']) { + if (!defined('CURL_SSLVERSION_TLSv1_2')) { + throw new \InvalidArgumentException('Invalid crypto_method request option: TLS 1.2 not supported by your version of cURL'); + } + $conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_2; + } elseif (defined('STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT') && \STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT === $options['crypto_method']) { + if (!defined('CURL_SSLVERSION_TLSv1_3')) { + throw new \InvalidArgumentException('Invalid crypto_method request option: TLS 1.3 not supported by your version of cURL'); + } + $conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_3; + } else { + throw new \InvalidArgumentException('Invalid crypto_method request option: unknown version provided'); + } + } + + if (isset($options['cert'])) { + $cert = $options['cert']; + if (\is_array($cert)) { + $conf[\CURLOPT_SSLCERTPASSWD] = $cert[1]; + $cert = $cert[0]; + } + if (!\file_exists($cert)) { + throw new \InvalidArgumentException("SSL certificate not found: {$cert}"); + } + // OpenSSL (versions 0.9.3 and later) also support "P12" for PKCS#12-encoded files. + // see https://curl.se/libcurl/c/CURLOPT_SSLCERTTYPE.html + $ext = pathinfo($cert, \PATHINFO_EXTENSION); + if (preg_match('#^(der|p12)$#i', $ext)) { + $conf[\CURLOPT_SSLCERTTYPE] = strtoupper($ext); + } + $conf[\CURLOPT_SSLCERT] = $cert; + } + + if (isset($options['ssl_key'])) { + if (\is_array($options['ssl_key'])) { + if (\count($options['ssl_key']) === 2) { + [$sslKey, $conf[\CURLOPT_SSLKEYPASSWD]] = $options['ssl_key']; + } else { + [$sslKey] = $options['ssl_key']; + } + } + + $sslKey = $sslKey ?? $options['ssl_key']; + + if (!\file_exists($sslKey)) { + throw new \InvalidArgumentException("SSL private key not found: {$sslKey}"); + } + $conf[\CURLOPT_SSLKEY] = $sslKey; + } + + if (isset($options['progress'])) { + $progress = $options['progress']; + if (!\is_callable($progress)) { + throw new \InvalidArgumentException('progress client option must be callable'); + } + $conf[\CURLOPT_NOPROGRESS] = false; + $conf[\CURLOPT_PROGRESSFUNCTION] = static function ($resource, int $downloadSize, int $downloaded, int $uploadSize, int $uploaded) use ($progress) { + $progress($downloadSize, $downloaded, $uploadSize, $uploaded); + }; + } + + if (!empty($options['debug'])) { + $conf[\CURLOPT_STDERR] = Utils::debugResource($options['debug']); + $conf[\CURLOPT_VERBOSE] = true; + } + } + + /** + * This function ensures that a response was set on a transaction. If one + * was not set, then the request is retried if possible. This error + * typically means you are sending a payload, curl encountered a + * "Connection died, retrying a fresh connect" error, tried to rewind the + * stream, and then encountered a "necessary data rewind wasn't possible" + * error, causing the request to be sent through curl_multi_info_read() + * without an error status. + * + * @param callable(RequestInterface, array): PromiseInterface $handler + */ + private static function retryFailedRewind(callable $handler, EasyHandle $easy, array $ctx): PromiseInterface + { + try { + // Only rewind if the body has been read from. + $body = $easy->request->getBody(); + if ($body->tell() > 0) { + $body->rewind(); + } + } catch (\RuntimeException $e) { + $ctx['error'] = 'The connection unexpectedly failed without ' + .'providing an error. The request would have been retried, ' + .'but attempting to rewind the request body failed. ' + .'Exception: '.$e; + + return self::createRejection($easy, $ctx); + } + + // Retry no more than 3 times before giving up. + if (!isset($easy->options['_curl_retries'])) { + $easy->options['_curl_retries'] = 1; + } elseif ($easy->options['_curl_retries'] == 2) { + $ctx['error'] = 'The cURL request was retried 3 times ' + .'and did not succeed. The most likely reason for the failure ' + .'is that cURL was unable to rewind the body of the request ' + .'and subsequent retries resulted in the same error. Turn on ' + .'the debug option to see what went wrong. See ' + .'https://bugs.php.net/bug.php?id=47204 for more information.'; + + return self::createRejection($easy, $ctx); + } else { + ++$easy->options['_curl_retries']; + } + + return $handler($easy->request, $easy->options); + } + + private function createHeaderFn(EasyHandle $easy): callable + { + if (isset($easy->options['on_headers'])) { + $onHeaders = $easy->options['on_headers']; + + if (!\is_callable($onHeaders)) { + throw new \InvalidArgumentException('on_headers must be callable'); + } + } else { + $onHeaders = null; + } + + return static function ($ch, $h) use ( + $onHeaders, + $easy, + &$startingResponse + ) { + $value = \trim($h); + if ($value === '') { + $startingResponse = true; + try { + $easy->createResponse(); + } catch (\Exception $e) { + $easy->createResponseException = $e; + + return -1; + } + if ($onHeaders !== null) { + try { + $onHeaders($easy->response); + } catch (\Exception $e) { + // Associate the exception with the handle and trigger + // a curl header write error by returning 0. + $easy->onHeadersException = $e; + + return -1; + } + } + } elseif ($startingResponse) { + $startingResponse = false; + $easy->headers = [$value]; + } else { + $easy->headers[] = $value; + } + + return \strlen($h); + }; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php b/vendor/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php new file mode 100644 index 0000000..fe57ed5 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php @@ -0,0 +1,25 @@ +factory = $options['handle_factory'] + ?? new CurlFactory(3); + } + + public function __invoke(RequestInterface $request, array $options): PromiseInterface + { + if (isset($options['delay'])) { + \usleep($options['delay'] * 1000); + } + + $easy = $this->factory->create($request, $options); + \curl_exec($easy->handle); + $easy->errno = \curl_errno($easy->handle); + + return CurlFactory::finish($this, $easy, $this->factory); + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php b/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php new file mode 100644 index 0000000..f0acde1 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php @@ -0,0 +1,263 @@ + An array of delay times, indexed by handle id in `addRequest`. + * + * @see CurlMultiHandler::addRequest + */ + private $delays = []; + + /** + * @var array An associative array of CURLMOPT_* options and corresponding values for curl_multi_setopt() + */ + private $options = []; + + /** + * This handler accepts the following options: + * + * - handle_factory: An optional factory used to create curl handles + * - select_timeout: Optional timeout (in seconds) to block before timing + * out while selecting curl handles. Defaults to 1 second. + * - options: An associative array of CURLMOPT_* options and + * corresponding values for curl_multi_setopt() + */ + public function __construct(array $options = []) + { + $this->factory = $options['handle_factory'] ?? new CurlFactory(50); + + if (isset($options['select_timeout'])) { + $this->selectTimeout = $options['select_timeout']; + } elseif ($selectTimeout = Utils::getenv('GUZZLE_CURL_SELECT_TIMEOUT')) { + @trigger_error('Since guzzlehttp/guzzle 7.2.0: Using environment variable GUZZLE_CURL_SELECT_TIMEOUT is deprecated. Use option "select_timeout" instead.', \E_USER_DEPRECATED); + $this->selectTimeout = (int) $selectTimeout; + } else { + $this->selectTimeout = 1; + } + + $this->options = $options['options'] ?? []; + } + + /** + * @param string $name + * + * @return resource|\CurlMultiHandle + * + * @throws \BadMethodCallException when another field as `_mh` will be gotten + * @throws \RuntimeException when curl can not initialize a multi handle + */ + public function __get($name) + { + if ($name !== '_mh') { + throw new \BadMethodCallException("Can not get other property as '_mh'."); + } + + $multiHandle = \curl_multi_init(); + + if (false === $multiHandle) { + throw new \RuntimeException('Can not initialize curl multi handle.'); + } + + $this->_mh = $multiHandle; + + foreach ($this->options as $option => $value) { + // A warning is raised in case of a wrong option. + curl_multi_setopt($this->_mh, $option, $value); + } + + return $this->_mh; + } + + public function __destruct() + { + if (isset($this->_mh)) { + \curl_multi_close($this->_mh); + unset($this->_mh); + } + } + + public function __invoke(RequestInterface $request, array $options): PromiseInterface + { + $easy = $this->factory->create($request, $options); + $id = (int) $easy->handle; + + $promise = new Promise( + [$this, 'execute'], + function () use ($id) { + return $this->cancel($id); + } + ); + + $this->addRequest(['easy' => $easy, 'deferred' => $promise]); + + return $promise; + } + + /** + * Ticks the curl event loop. + */ + public function tick(): void + { + // Add any delayed handles if needed. + if ($this->delays) { + $currentTime = Utils::currentTime(); + foreach ($this->delays as $id => $delay) { + if ($currentTime >= $delay) { + unset($this->delays[$id]); + \curl_multi_add_handle( + $this->_mh, + $this->handles[$id]['easy']->handle + ); + } + } + } + + // Step through the task queue which may add additional requests. + P\Utils::queue()->run(); + + if ($this->active && \curl_multi_select($this->_mh, $this->selectTimeout) === -1) { + // Perform a usleep if a select returns -1. + // See: https://bugs.php.net/bug.php?id=61141 + \usleep(250); + } + + while (\curl_multi_exec($this->_mh, $this->active) === \CURLM_CALL_MULTI_PERFORM) { + } + + $this->processMessages(); + } + + /** + * Runs until all outstanding connections have completed. + */ + public function execute(): void + { + $queue = P\Utils::queue(); + + while ($this->handles || !$queue->isEmpty()) { + // If there are no transfers, then sleep for the next delay + if (!$this->active && $this->delays) { + \usleep($this->timeToNext()); + } + $this->tick(); + } + } + + private function addRequest(array $entry): void + { + $easy = $entry['easy']; + $id = (int) $easy->handle; + $this->handles[$id] = $entry; + if (empty($easy->options['delay'])) { + \curl_multi_add_handle($this->_mh, $easy->handle); + } else { + $this->delays[$id] = Utils::currentTime() + ($easy->options['delay'] / 1000); + } + } + + /** + * Cancels a handle from sending and removes references to it. + * + * @param int $id Handle ID to cancel and remove. + * + * @return bool True on success, false on failure. + */ + private function cancel($id): bool + { + if (!is_int($id)) { + trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing an integer to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + + // Cannot cancel if it has been processed. + if (!isset($this->handles[$id])) { + return false; + } + + $handle = $this->handles[$id]['easy']->handle; + unset($this->delays[$id], $this->handles[$id]); + \curl_multi_remove_handle($this->_mh, $handle); + \curl_close($handle); + + return true; + } + + private function processMessages(): void + { + while ($done = \curl_multi_info_read($this->_mh)) { + if ($done['msg'] !== \CURLMSG_DONE) { + // if it's not done, then it would be premature to remove the handle. ref https://github.com/guzzle/guzzle/pull/2892#issuecomment-945150216 + continue; + } + $id = (int) $done['handle']; + \curl_multi_remove_handle($this->_mh, $done['handle']); + + if (!isset($this->handles[$id])) { + // Probably was cancelled. + continue; + } + + $entry = $this->handles[$id]; + unset($this->handles[$id], $this->delays[$id]); + $entry['easy']->errno = $done['result']; + $entry['deferred']->resolve( + CurlFactory::finish($this, $entry['easy'], $this->factory) + ); + } + } + + private function timeToNext(): int + { + $currentTime = Utils::currentTime(); + $nextTime = \PHP_INT_MAX; + foreach ($this->delays as $time) { + if ($time < $nextTime) { + $nextTime = $time; + } + } + + return ((int) \max(0, $nextTime - $currentTime)) * 1000000; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Handler/EasyHandle.php b/vendor/guzzlehttp/guzzle/src/Handler/EasyHandle.php new file mode 100644 index 0000000..1bc39f4 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Handler/EasyHandle.php @@ -0,0 +1,112 @@ +headers); + + $normalizedKeys = Utils::normalizeHeaderKeys($headers); + + if (!empty($this->options['decode_content']) && isset($normalizedKeys['content-encoding'])) { + $headers['x-encoded-content-encoding'] = $headers[$normalizedKeys['content-encoding']]; + unset($headers[$normalizedKeys['content-encoding']]); + if (isset($normalizedKeys['content-length'])) { + $headers['x-encoded-content-length'] = $headers[$normalizedKeys['content-length']]; + + $bodyLength = (int) $this->sink->getSize(); + if ($bodyLength) { + $headers[$normalizedKeys['content-length']] = $bodyLength; + } else { + unset($headers[$normalizedKeys['content-length']]); + } + } + } + + // Attach a response to the easy handle with the parsed headers. + $this->response = new Response( + $status, + $headers, + $this->sink, + $ver, + $reason + ); + } + + /** + * @param string $name + * + * @return void + * + * @throws \BadMethodCallException + */ + public function __get($name) + { + $msg = $name === 'handle' ? 'The EasyHandle has been released' : 'Invalid property: '.$name; + throw new \BadMethodCallException($msg); + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Handler/HeaderProcessor.php b/vendor/guzzlehttp/guzzle/src/Handler/HeaderProcessor.php new file mode 100644 index 0000000..5554b8f --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Handler/HeaderProcessor.php @@ -0,0 +1,42 @@ +|null $queue The parameters to be passed to the append function, as an indexed array. + * @param callable|null $onFulfilled Callback to invoke when the return value is fulfilled. + * @param callable|null $onRejected Callback to invoke when the return value is rejected. + */ + public function __construct(array $queue = null, callable $onFulfilled = null, callable $onRejected = null) + { + $this->onFulfilled = $onFulfilled; + $this->onRejected = $onRejected; + + if ($queue) { + // array_values included for BC + $this->append(...array_values($queue)); + } + } + + public function __invoke(RequestInterface $request, array $options): PromiseInterface + { + if (!$this->queue) { + throw new \OutOfBoundsException('Mock queue is empty'); + } + + if (isset($options['delay']) && \is_numeric($options['delay'])) { + \usleep((int) $options['delay'] * 1000); + } + + $this->lastRequest = $request; + $this->lastOptions = $options; + $response = \array_shift($this->queue); + + if (isset($options['on_headers'])) { + if (!\is_callable($options['on_headers'])) { + throw new \InvalidArgumentException('on_headers must be callable'); + } + try { + $options['on_headers']($response); + } catch (\Exception $e) { + $msg = 'An error was encountered during the on_headers event'; + $response = new RequestException($msg, $request, $response, $e); + } + } + + if (\is_callable($response)) { + $response = $response($request, $options); + } + + $response = $response instanceof \Throwable + ? P\Create::rejectionFor($response) + : P\Create::promiseFor($response); + + return $response->then( + function (?ResponseInterface $value) use ($request, $options) { + $this->invokeStats($request, $options, $value); + if ($this->onFulfilled) { + ($this->onFulfilled)($value); + } + + if ($value !== null && isset($options['sink'])) { + $contents = (string) $value->getBody(); + $sink = $options['sink']; + + if (\is_resource($sink)) { + \fwrite($sink, $contents); + } elseif (\is_string($sink)) { + \file_put_contents($sink, $contents); + } elseif ($sink instanceof StreamInterface) { + $sink->write($contents); + } + } + + return $value; + }, + function ($reason) use ($request, $options) { + $this->invokeStats($request, $options, null, $reason); + if ($this->onRejected) { + ($this->onRejected)($reason); + } + + return P\Create::rejectionFor($reason); + } + ); + } + + /** + * Adds one or more variadic requests, exceptions, callables, or promises + * to the queue. + * + * @param mixed ...$values + */ + public function append(...$values): void + { + foreach ($values as $value) { + if ($value instanceof ResponseInterface + || $value instanceof \Throwable + || $value instanceof PromiseInterface + || \is_callable($value) + ) { + $this->queue[] = $value; + } else { + throw new \TypeError('Expected a Response, Promise, Throwable or callable. Found '.Utils::describeType($value)); + } + } + } + + /** + * Get the last received request. + */ + public function getLastRequest(): ?RequestInterface + { + return $this->lastRequest; + } + + /** + * Get the last received request options. + */ + public function getLastOptions(): array + { + return $this->lastOptions; + } + + /** + * Returns the number of remaining items in the queue. + */ + public function count(): int + { + return \count($this->queue); + } + + public function reset(): void + { + $this->queue = []; + } + + /** + * @param mixed $reason Promise or reason. + */ + private function invokeStats( + RequestInterface $request, + array $options, + ResponseInterface $response = null, + $reason = null + ): void { + if (isset($options['on_stats'])) { + $transferTime = $options['transfer_time'] ?? 0; + $stats = new TransferStats($request, $response, $transferTime, $reason); + ($options['on_stats'])($stats); + } + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Handler/Proxy.php b/vendor/guzzlehttp/guzzle/src/Handler/Proxy.php new file mode 100644 index 0000000..f045b52 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Handler/Proxy.php @@ -0,0 +1,51 @@ +withoutHeader('Expect'); + + // Append a content-length header if body size is zero to match + // cURL's behavior. + if (0 === $request->getBody()->getSize()) { + $request = $request->withHeader('Content-Length', '0'); + } + + return $this->createResponse( + $request, + $options, + $this->createStream($request, $options), + $startTime + ); + } catch (\InvalidArgumentException $e) { + throw $e; + } catch (\Exception $e) { + // Determine if the error was a networking error. + $message = $e->getMessage(); + // This list can probably get more comprehensive. + if (false !== \strpos($message, 'getaddrinfo') // DNS lookup failed + || false !== \strpos($message, 'Connection refused') + || false !== \strpos($message, "couldn't connect to host") // error on HHVM + || false !== \strpos($message, 'connection attempt failed') + ) { + $e = new ConnectException($e->getMessage(), $request, $e); + } else { + $e = RequestException::wrapException($request, $e); + } + $this->invokeStats($options, $request, $startTime, null, $e); + + return P\Create::rejectionFor($e); + } + } + + private function invokeStats( + array $options, + RequestInterface $request, + ?float $startTime, + ResponseInterface $response = null, + \Throwable $error = null + ): void { + if (isset($options['on_stats'])) { + $stats = new TransferStats($request, $response, Utils::currentTime() - $startTime, $error, []); + ($options['on_stats'])($stats); + } + } + + /** + * @param resource $stream + */ + private function createResponse(RequestInterface $request, array $options, $stream, ?float $startTime): PromiseInterface + { + $hdrs = $this->lastHeaders; + $this->lastHeaders = []; + + try { + [$ver, $status, $reason, $headers] = HeaderProcessor::parseHeaders($hdrs); + } catch (\Exception $e) { + return P\Create::rejectionFor( + new RequestException('An error was encountered while creating the response', $request, null, $e) + ); + } + + [$stream, $headers] = $this->checkDecode($options, $headers, $stream); + $stream = Psr7\Utils::streamFor($stream); + $sink = $stream; + + if (\strcasecmp('HEAD', $request->getMethod())) { + $sink = $this->createSink($stream, $options); + } + + try { + $response = new Psr7\Response($status, $headers, $sink, $ver, $reason); + } catch (\Exception $e) { + return P\Create::rejectionFor( + new RequestException('An error was encountered while creating the response', $request, null, $e) + ); + } + + if (isset($options['on_headers'])) { + try { + $options['on_headers']($response); + } catch (\Exception $e) { + return P\Create::rejectionFor( + new RequestException('An error was encountered during the on_headers event', $request, $response, $e) + ); + } + } + + // Do not drain when the request is a HEAD request because they have + // no body. + if ($sink !== $stream) { + $this->drain($stream, $sink, $response->getHeaderLine('Content-Length')); + } + + $this->invokeStats($options, $request, $startTime, $response, null); + + return new FulfilledPromise($response); + } + + private function createSink(StreamInterface $stream, array $options): StreamInterface + { + if (!empty($options['stream'])) { + return $stream; + } + + $sink = $options['sink'] ?? Psr7\Utils::tryFopen('php://temp', 'r+'); + + return \is_string($sink) ? new Psr7\LazyOpenStream($sink, 'w+') : Psr7\Utils::streamFor($sink); + } + + /** + * @param resource $stream + */ + private function checkDecode(array $options, array $headers, $stream): array + { + // Automatically decode responses when instructed. + if (!empty($options['decode_content'])) { + $normalizedKeys = Utils::normalizeHeaderKeys($headers); + if (isset($normalizedKeys['content-encoding'])) { + $encoding = $headers[$normalizedKeys['content-encoding']]; + if ($encoding[0] === 'gzip' || $encoding[0] === 'deflate') { + $stream = new Psr7\InflateStream(Psr7\Utils::streamFor($stream)); + $headers['x-encoded-content-encoding'] = $headers[$normalizedKeys['content-encoding']]; + + // Remove content-encoding header + unset($headers[$normalizedKeys['content-encoding']]); + + // Fix content-length header + if (isset($normalizedKeys['content-length'])) { + $headers['x-encoded-content-length'] = $headers[$normalizedKeys['content-length']]; + $length = (int) $stream->getSize(); + if ($length === 0) { + unset($headers[$normalizedKeys['content-length']]); + } else { + $headers[$normalizedKeys['content-length']] = [$length]; + } + } + } + } + } + + return [$stream, $headers]; + } + + /** + * Drains the source stream into the "sink" client option. + * + * @param string $contentLength Header specifying the amount of + * data to read. + * + * @throws \RuntimeException when the sink option is invalid. + */ + private function drain(StreamInterface $source, StreamInterface $sink, string $contentLength): StreamInterface + { + // If a content-length header is provided, then stop reading once + // that number of bytes has been read. This can prevent infinitely + // reading from a stream when dealing with servers that do not honor + // Connection: Close headers. + Psr7\Utils::copyToStream( + $source, + $sink, + (\strlen($contentLength) > 0 && (int) $contentLength > 0) ? (int) $contentLength : -1 + ); + + $sink->seek(0); + $source->close(); + + return $sink; + } + + /** + * Create a resource and check to ensure it was created successfully + * + * @param callable $callback Callable that returns stream resource + * + * @return resource + * + * @throws \RuntimeException on error + */ + private function createResource(callable $callback) + { + $errors = []; + \set_error_handler(static function ($_, $msg, $file, $line) use (&$errors): bool { + $errors[] = [ + 'message' => $msg, + 'file' => $file, + 'line' => $line, + ]; + + return true; + }); + + try { + $resource = $callback(); + } finally { + \restore_error_handler(); + } + + if (!$resource) { + $message = 'Error creating resource: '; + foreach ($errors as $err) { + foreach ($err as $key => $value) { + $message .= "[$key] $value".\PHP_EOL; + } + } + throw new \RuntimeException(\trim($message)); + } + + return $resource; + } + + /** + * @return resource + */ + private function createStream(RequestInterface $request, array $options) + { + static $methods; + if (!$methods) { + $methods = \array_flip(\get_class_methods(__CLASS__)); + } + + if (!\in_array($request->getUri()->getScheme(), ['http', 'https'])) { + throw new RequestException(\sprintf("The scheme '%s' is not supported.", $request->getUri()->getScheme()), $request); + } + + // HTTP/1.1 streams using the PHP stream wrapper require a + // Connection: close header + if ($request->getProtocolVersion() == '1.1' + && !$request->hasHeader('Connection') + ) { + $request = $request->withHeader('Connection', 'close'); + } + + // Ensure SSL is verified by default + if (!isset($options['verify'])) { + $options['verify'] = true; + } + + $params = []; + $context = $this->getDefaultContext($request); + + if (isset($options['on_headers']) && !\is_callable($options['on_headers'])) { + throw new \InvalidArgumentException('on_headers must be callable'); + } + + if (!empty($options)) { + foreach ($options as $key => $value) { + $method = "add_{$key}"; + if (isset($methods[$method])) { + $this->{$method}($request, $context, $value, $params); + } + } + } + + if (isset($options['stream_context'])) { + if (!\is_array($options['stream_context'])) { + throw new \InvalidArgumentException('stream_context must be an array'); + } + $context = \array_replace_recursive($context, $options['stream_context']); + } + + // Microsoft NTLM authentication only supported with curl handler + if (isset($options['auth'][2]) && 'ntlm' === $options['auth'][2]) { + throw new \InvalidArgumentException('Microsoft NTLM authentication only supported with curl handler'); + } + + $uri = $this->resolveHost($request, $options); + + $contextResource = $this->createResource( + static function () use ($context, $params) { + return \stream_context_create($context, $params); + } + ); + + return $this->createResource( + function () use ($uri, &$http_response_header, $contextResource, $context, $options, $request) { + $resource = @\fopen((string) $uri, 'r', false, $contextResource); + $this->lastHeaders = $http_response_header ?? []; + + if (false === $resource) { + throw new ConnectException(sprintf('Connection refused for URI %s', $uri), $request, null, $context); + } + + if (isset($options['read_timeout'])) { + $readTimeout = $options['read_timeout']; + $sec = (int) $readTimeout; + $usec = ($readTimeout - $sec) * 100000; + \stream_set_timeout($resource, $sec, $usec); + } + + return $resource; + } + ); + } + + private function resolveHost(RequestInterface $request, array $options): UriInterface + { + $uri = $request->getUri(); + + if (isset($options['force_ip_resolve']) && !\filter_var($uri->getHost(), \FILTER_VALIDATE_IP)) { + if ('v4' === $options['force_ip_resolve']) { + $records = \dns_get_record($uri->getHost(), \DNS_A); + if (false === $records || !isset($records[0]['ip'])) { + throw new ConnectException(\sprintf("Could not resolve IPv4 address for host '%s'", $uri->getHost()), $request); + } + + return $uri->withHost($records[0]['ip']); + } + if ('v6' === $options['force_ip_resolve']) { + $records = \dns_get_record($uri->getHost(), \DNS_AAAA); + if (false === $records || !isset($records[0]['ipv6'])) { + throw new ConnectException(\sprintf("Could not resolve IPv6 address for host '%s'", $uri->getHost()), $request); + } + + return $uri->withHost('['.$records[0]['ipv6'].']'); + } + } + + return $uri; + } + + private function getDefaultContext(RequestInterface $request): array + { + $headers = ''; + foreach ($request->getHeaders() as $name => $value) { + foreach ($value as $val) { + $headers .= "$name: $val\r\n"; + } + } + + $context = [ + 'http' => [ + 'method' => $request->getMethod(), + 'header' => $headers, + 'protocol_version' => $request->getProtocolVersion(), + 'ignore_errors' => true, + 'follow_location' => 0, + ], + 'ssl' => [ + 'peer_name' => $request->getUri()->getHost(), + ], + ]; + + $body = (string) $request->getBody(); + + if ('' !== $body) { + $context['http']['content'] = $body; + // Prevent the HTTP handler from adding a Content-Type header. + if (!$request->hasHeader('Content-Type')) { + $context['http']['header'] .= "Content-Type:\r\n"; + } + } + + $context['http']['header'] = \rtrim($context['http']['header']); + + return $context; + } + + /** + * @param mixed $value as passed via Request transfer options. + */ + private function add_proxy(RequestInterface $request, array &$options, $value, array &$params): void + { + $uri = null; + + if (!\is_array($value)) { + $uri = $value; + } else { + $scheme = $request->getUri()->getScheme(); + if (isset($value[$scheme])) { + if (!isset($value['no']) || !Utils::isHostInNoProxy($request->getUri()->getHost(), $value['no'])) { + $uri = $value[$scheme]; + } + } + } + + if (!$uri) { + return; + } + + $parsed = $this->parse_proxy($uri); + $options['http']['proxy'] = $parsed['proxy']; + + if ($parsed['auth']) { + if (!isset($options['http']['header'])) { + $options['http']['header'] = []; + } + $options['http']['header'] .= "\r\nProxy-Authorization: {$parsed['auth']}"; + } + } + + /** + * Parses the given proxy URL to make it compatible with the format PHP's stream context expects. + */ + private function parse_proxy(string $url): array + { + $parsed = \parse_url($url); + + if ($parsed !== false && isset($parsed['scheme']) && $parsed['scheme'] === 'http') { + if (isset($parsed['host']) && isset($parsed['port'])) { + $auth = null; + if (isset($parsed['user']) && isset($parsed['pass'])) { + $auth = \base64_encode("{$parsed['user']}:{$parsed['pass']}"); + } + + return [ + 'proxy' => "tcp://{$parsed['host']}:{$parsed['port']}", + 'auth' => $auth ? "Basic {$auth}" : null, + ]; + } + } + + // Return proxy as-is. + return [ + 'proxy' => $url, + 'auth' => null, + ]; + } + + /** + * @param mixed $value as passed via Request transfer options. + */ + private function add_timeout(RequestInterface $request, array &$options, $value, array &$params): void + { + if ($value > 0) { + $options['http']['timeout'] = $value; + } + } + + /** + * @param mixed $value as passed via Request transfer options. + */ + private function add_crypto_method(RequestInterface $request, array &$options, $value, array &$params): void + { + if ( + $value === \STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT + || $value === \STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT + || $value === \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT + || (defined('STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT') && $value === \STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT) + ) { + $options['http']['crypto_method'] = $value; + + return; + } + + throw new \InvalidArgumentException('Invalid crypto_method request option: unknown version provided'); + } + + /** + * @param mixed $value as passed via Request transfer options. + */ + private function add_verify(RequestInterface $request, array &$options, $value, array &$params): void + { + if ($value === false) { + $options['ssl']['verify_peer'] = false; + $options['ssl']['verify_peer_name'] = false; + + return; + } + + if (\is_string($value)) { + $options['ssl']['cafile'] = $value; + if (!\file_exists($value)) { + throw new \RuntimeException("SSL CA bundle not found: $value"); + } + } elseif ($value !== true) { + throw new \InvalidArgumentException('Invalid verify request option'); + } + + $options['ssl']['verify_peer'] = true; + $options['ssl']['verify_peer_name'] = true; + $options['ssl']['allow_self_signed'] = false; + } + + /** + * @param mixed $value as passed via Request transfer options. + */ + private function add_cert(RequestInterface $request, array &$options, $value, array &$params): void + { + if (\is_array($value)) { + $options['ssl']['passphrase'] = $value[1]; + $value = $value[0]; + } + + if (!\file_exists($value)) { + throw new \RuntimeException("SSL certificate not found: {$value}"); + } + + $options['ssl']['local_cert'] = $value; + } + + /** + * @param mixed $value as passed via Request transfer options. + */ + private function add_progress(RequestInterface $request, array &$options, $value, array &$params): void + { + self::addNotification( + $params, + static function ($code, $a, $b, $c, $transferred, $total) use ($value) { + if ($code == \STREAM_NOTIFY_PROGRESS) { + // The upload progress cannot be determined. Use 0 for cURL compatibility: + // https://curl.se/libcurl/c/CURLOPT_PROGRESSFUNCTION.html + $value($total, $transferred, 0, 0); + } + } + ); + } + + /** + * @param mixed $value as passed via Request transfer options. + */ + private function add_debug(RequestInterface $request, array &$options, $value, array &$params): void + { + if ($value === false) { + return; + } + + static $map = [ + \STREAM_NOTIFY_CONNECT => 'CONNECT', + \STREAM_NOTIFY_AUTH_REQUIRED => 'AUTH_REQUIRED', + \STREAM_NOTIFY_AUTH_RESULT => 'AUTH_RESULT', + \STREAM_NOTIFY_MIME_TYPE_IS => 'MIME_TYPE_IS', + \STREAM_NOTIFY_FILE_SIZE_IS => 'FILE_SIZE_IS', + \STREAM_NOTIFY_REDIRECTED => 'REDIRECTED', + \STREAM_NOTIFY_PROGRESS => 'PROGRESS', + \STREAM_NOTIFY_FAILURE => 'FAILURE', + \STREAM_NOTIFY_COMPLETED => 'COMPLETED', + \STREAM_NOTIFY_RESOLVE => 'RESOLVE', + ]; + static $args = ['severity', 'message', 'message_code', 'bytes_transferred', 'bytes_max']; + + $value = Utils::debugResource($value); + $ident = $request->getMethod().' '.$request->getUri()->withFragment(''); + self::addNotification( + $params, + static function (int $code, ...$passed) use ($ident, $value, $map, $args): void { + \fprintf($value, '<%s> [%s] ', $ident, $map[$code]); + foreach (\array_filter($passed) as $i => $v) { + \fwrite($value, $args[$i].': "'.$v.'" '); + } + \fwrite($value, "\n"); + } + ); + } + + private static function addNotification(array &$params, callable $notify): void + { + // Wrap the existing function if needed. + if (!isset($params['notification'])) { + $params['notification'] = $notify; + } else { + $params['notification'] = self::callArray([ + $params['notification'], + $notify, + ]); + } + } + + private static function callArray(array $functions): callable + { + return static function (...$args) use ($functions) { + foreach ($functions as $fn) { + $fn(...$args); + } + }; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/HandlerStack.php b/vendor/guzzlehttp/guzzle/src/HandlerStack.php new file mode 100644 index 0000000..1ce9c4b --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/HandlerStack.php @@ -0,0 +1,275 @@ +push(Middleware::httpErrors(), 'http_errors'); + $stack->push(Middleware::redirect(), 'allow_redirects'); + $stack->push(Middleware::cookies(), 'cookies'); + $stack->push(Middleware::prepareBody(), 'prepare_body'); + + return $stack; + } + + /** + * @param (callable(RequestInterface, array): PromiseInterface)|null $handler Underlying HTTP handler. + */ + public function __construct(callable $handler = null) + { + $this->handler = $handler; + } + + /** + * Invokes the handler stack as a composed handler + * + * @return ResponseInterface|PromiseInterface + */ + public function __invoke(RequestInterface $request, array $options) + { + $handler = $this->resolve(); + + return $handler($request, $options); + } + + /** + * Dumps a string representation of the stack. + * + * @return string + */ + public function __toString() + { + $depth = 0; + $stack = []; + + if ($this->handler !== null) { + $stack[] = '0) Handler: '.$this->debugCallable($this->handler); + } + + $result = ''; + foreach (\array_reverse($this->stack) as $tuple) { + ++$depth; + $str = "{$depth}) Name: '{$tuple[1]}', "; + $str .= 'Function: '.$this->debugCallable($tuple[0]); + $result = "> {$str}\n{$result}"; + $stack[] = $str; + } + + foreach (\array_keys($stack) as $k) { + $result .= "< {$stack[$k]}\n"; + } + + return $result; + } + + /** + * Set the HTTP handler that actually returns a promise. + * + * @param callable(RequestInterface, array): PromiseInterface $handler Accepts a request and array of options and + * returns a Promise. + */ + public function setHandler(callable $handler): void + { + $this->handler = $handler; + $this->cached = null; + } + + /** + * Returns true if the builder has a handler. + */ + public function hasHandler(): bool + { + return $this->handler !== null; + } + + /** + * Unshift a middleware to the bottom of the stack. + * + * @param callable(callable): callable $middleware Middleware function + * @param string $name Name to register for this middleware. + */ + public function unshift(callable $middleware, ?string $name = null): void + { + \array_unshift($this->stack, [$middleware, $name]); + $this->cached = null; + } + + /** + * Push a middleware to the top of the stack. + * + * @param callable(callable): callable $middleware Middleware function + * @param string $name Name to register for this middleware. + */ + public function push(callable $middleware, string $name = ''): void + { + $this->stack[] = [$middleware, $name]; + $this->cached = null; + } + + /** + * Add a middleware before another middleware by name. + * + * @param string $findName Middleware to find + * @param callable(callable): callable $middleware Middleware function + * @param string $withName Name to register for this middleware. + */ + public function before(string $findName, callable $middleware, string $withName = ''): void + { + $this->splice($findName, $withName, $middleware, true); + } + + /** + * Add a middleware after another middleware by name. + * + * @param string $findName Middleware to find + * @param callable(callable): callable $middleware Middleware function + * @param string $withName Name to register for this middleware. + */ + public function after(string $findName, callable $middleware, string $withName = ''): void + { + $this->splice($findName, $withName, $middleware, false); + } + + /** + * Remove a middleware by instance or name from the stack. + * + * @param callable|string $remove Middleware to remove by instance or name. + */ + public function remove($remove): void + { + if (!is_string($remove) && !is_callable($remove)) { + trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a callable or string to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + + $this->cached = null; + $idx = \is_callable($remove) ? 0 : 1; + $this->stack = \array_values(\array_filter( + $this->stack, + static function ($tuple) use ($idx, $remove) { + return $tuple[$idx] !== $remove; + } + )); + } + + /** + * Compose the middleware and handler into a single callable function. + * + * @return callable(RequestInterface, array): PromiseInterface + */ + public function resolve(): callable + { + if ($this->cached === null) { + if (($prev = $this->handler) === null) { + throw new \LogicException('No handler has been specified'); + } + + foreach (\array_reverse($this->stack) as $fn) { + /** @var callable(RequestInterface, array): PromiseInterface $prev */ + $prev = $fn[0]($prev); + } + + $this->cached = $prev; + } + + return $this->cached; + } + + private function findByName(string $name): int + { + foreach ($this->stack as $k => $v) { + if ($v[1] === $name) { + return $k; + } + } + + throw new \InvalidArgumentException("Middleware not found: $name"); + } + + /** + * Splices a function into the middleware list at a specific position. + */ + private function splice(string $findName, string $withName, callable $middleware, bool $before): void + { + $this->cached = null; + $idx = $this->findByName($findName); + $tuple = [$middleware, $withName]; + + if ($before) { + if ($idx === 0) { + \array_unshift($this->stack, $tuple); + } else { + $replacement = [$tuple, $this->stack[$idx]]; + \array_splice($this->stack, $idx, 1, $replacement); + } + } elseif ($idx === \count($this->stack) - 1) { + $this->stack[] = $tuple; + } else { + $replacement = [$this->stack[$idx], $tuple]; + \array_splice($this->stack, $idx, 1, $replacement); + } + } + + /** + * Provides a debug string for a given callable. + * + * @param callable|string $fn Function to write as a string. + */ + private function debugCallable($fn): string + { + if (\is_string($fn)) { + return "callable({$fn})"; + } + + if (\is_array($fn)) { + return \is_string($fn[0]) + ? "callable({$fn[0]}::{$fn[1]})" + : "callable(['".\get_class($fn[0])."', '{$fn[1]}'])"; + } + + /** @var object $fn */ + return 'callable('.\spl_object_hash($fn).')'; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/MessageFormatter.php b/vendor/guzzlehttp/guzzle/src/MessageFormatter.php new file mode 100644 index 0000000..9b77eee --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/MessageFormatter.php @@ -0,0 +1,199 @@ +>>>>>>>\n{request}\n<<<<<<<<\n{response}\n--------\n{error}"; + public const SHORT = '[{ts}] "{method} {target} HTTP/{version}" {code}'; + + /** + * @var string Template used to format log messages + */ + private $template; + + /** + * @param string $template Log message template + */ + public function __construct(?string $template = self::CLF) + { + $this->template = $template ?: self::CLF; + } + + /** + * Returns a formatted message string. + * + * @param RequestInterface $request Request that was sent + * @param ResponseInterface|null $response Response that was received + * @param \Throwable|null $error Exception that was received + */ + public function format(RequestInterface $request, ?ResponseInterface $response = null, ?\Throwable $error = null): string + { + $cache = []; + + /** @var string */ + return \preg_replace_callback( + '/{\s*([A-Za-z_\-\.0-9]+)\s*}/', + function (array $matches) use ($request, $response, $error, &$cache) { + if (isset($cache[$matches[1]])) { + return $cache[$matches[1]]; + } + + $result = ''; + switch ($matches[1]) { + case 'request': + $result = Psr7\Message::toString($request); + break; + case 'response': + $result = $response ? Psr7\Message::toString($response) : ''; + break; + case 'req_headers': + $result = \trim($request->getMethod() + .' '.$request->getRequestTarget()) + .' HTTP/'.$request->getProtocolVersion()."\r\n" + .$this->headers($request); + break; + case 'res_headers': + $result = $response ? + \sprintf( + 'HTTP/%s %d %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() + )."\r\n".$this->headers($response) + : 'NULL'; + break; + case 'req_body': + $result = $request->getBody()->__toString(); + break; + case 'res_body': + if (!$response instanceof ResponseInterface) { + $result = 'NULL'; + break; + } + + $body = $response->getBody(); + + if (!$body->isSeekable()) { + $result = 'RESPONSE_NOT_LOGGEABLE'; + break; + } + + $result = $response->getBody()->__toString(); + break; + case 'ts': + case 'date_iso_8601': + $result = \gmdate('c'); + break; + case 'date_common_log': + $result = \date('d/M/Y:H:i:s O'); + break; + case 'method': + $result = $request->getMethod(); + break; + case 'version': + $result = $request->getProtocolVersion(); + break; + case 'uri': + case 'url': + $result = $request->getUri()->__toString(); + break; + case 'target': + $result = $request->getRequestTarget(); + break; + case 'req_version': + $result = $request->getProtocolVersion(); + break; + case 'res_version': + $result = $response + ? $response->getProtocolVersion() + : 'NULL'; + break; + case 'host': + $result = $request->getHeaderLine('Host'); + break; + case 'hostname': + $result = \gethostname(); + break; + case 'code': + $result = $response ? $response->getStatusCode() : 'NULL'; + break; + case 'phrase': + $result = $response ? $response->getReasonPhrase() : 'NULL'; + break; + case 'error': + $result = $error ? $error->getMessage() : 'NULL'; + break; + default: + // handle prefixed dynamic headers + if (\strpos($matches[1], 'req_header_') === 0) { + $result = $request->getHeaderLine(\substr($matches[1], 11)); + } elseif (\strpos($matches[1], 'res_header_') === 0) { + $result = $response + ? $response->getHeaderLine(\substr($matches[1], 11)) + : 'NULL'; + } + } + + $cache[$matches[1]] = $result; + + return $result; + }, + $this->template + ); + } + + /** + * Get headers from message as string + */ + private function headers(MessageInterface $message): string + { + $result = ''; + foreach ($message->getHeaders() as $name => $values) { + $result .= $name.': '.\implode(', ', $values)."\r\n"; + } + + return \trim($result); + } +} diff --git a/vendor/guzzlehttp/guzzle/src/MessageFormatterInterface.php b/vendor/guzzlehttp/guzzle/src/MessageFormatterInterface.php new file mode 100644 index 0000000..a39ac24 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/MessageFormatterInterface.php @@ -0,0 +1,18 @@ +withCookieHeader($request); + + return $handler($request, $options) + ->then( + static function (ResponseInterface $response) use ($cookieJar, $request): ResponseInterface { + $cookieJar->extractCookies($request, $response); + + return $response; + } + ); + }; + }; + } + + /** + * Middleware that throws exceptions for 4xx or 5xx responses when the + * "http_errors" request option is set to true. + * + * @param BodySummarizerInterface|null $bodySummarizer The body summarizer to use in exception messages. + * + * @return callable(callable): callable Returns a function that accepts the next handler. + */ + public static function httpErrors(BodySummarizerInterface $bodySummarizer = null): callable + { + return static function (callable $handler) use ($bodySummarizer): callable { + return static function ($request, array $options) use ($handler, $bodySummarizer) { + if (empty($options['http_errors'])) { + return $handler($request, $options); + } + + return $handler($request, $options)->then( + static function (ResponseInterface $response) use ($request, $bodySummarizer) { + $code = $response->getStatusCode(); + if ($code < 400) { + return $response; + } + throw RequestException::create($request, $response, null, [], $bodySummarizer); + } + ); + }; + }; + } + + /** + * Middleware that pushes history data to an ArrayAccess container. + * + * @param array|\ArrayAccess $container Container to hold the history (by reference). + * + * @return callable(callable): callable Returns a function that accepts the next handler. + * + * @throws \InvalidArgumentException if container is not an array or ArrayAccess. + */ + public static function history(&$container): callable + { + if (!\is_array($container) && !$container instanceof \ArrayAccess) { + throw new \InvalidArgumentException('history container must be an array or object implementing ArrayAccess'); + } + + return static function (callable $handler) use (&$container): callable { + return static function (RequestInterface $request, array $options) use ($handler, &$container) { + return $handler($request, $options)->then( + static function ($value) use ($request, &$container, $options) { + $container[] = [ + 'request' => $request, + 'response' => $value, + 'error' => null, + 'options' => $options, + ]; + + return $value; + }, + static function ($reason) use ($request, &$container, $options) { + $container[] = [ + 'request' => $request, + 'response' => null, + 'error' => $reason, + 'options' => $options, + ]; + + return P\Create::rejectionFor($reason); + } + ); + }; + }; + } + + /** + * Middleware that invokes a callback before and after sending a request. + * + * The provided listener cannot modify or alter the response. It simply + * "taps" into the chain to be notified before returning the promise. The + * before listener accepts a request and options array, and the after + * listener accepts a request, options array, and response promise. + * + * @param callable $before Function to invoke before forwarding the request. + * @param callable $after Function invoked after forwarding. + * + * @return callable Returns a function that accepts the next handler. + */ + public static function tap(callable $before = null, callable $after = null): callable + { + return static function (callable $handler) use ($before, $after): callable { + return static function (RequestInterface $request, array $options) use ($handler, $before, $after) { + if ($before) { + $before($request, $options); + } + $response = $handler($request, $options); + if ($after) { + $after($request, $options, $response); + } + + return $response; + }; + }; + } + + /** + * Middleware that handles request redirects. + * + * @return callable Returns a function that accepts the next handler. + */ + public static function redirect(): callable + { + return static function (callable $handler): RedirectMiddleware { + return new RedirectMiddleware($handler); + }; + } + + /** + * Middleware that retries requests based on the boolean result of + * invoking the provided "decider" function. + * + * If no delay function is provided, a simple implementation of exponential + * backoff will be utilized. + * + * @param callable $decider Function that accepts the number of retries, + * a request, [response], and [exception] and + * returns true if the request is to be retried. + * @param callable $delay Function that accepts the number of retries and + * returns the number of milliseconds to delay. + * + * @return callable Returns a function that accepts the next handler. + */ + public static function retry(callable $decider, callable $delay = null): callable + { + return static function (callable $handler) use ($decider, $delay): RetryMiddleware { + return new RetryMiddleware($decider, $handler, $delay); + }; + } + + /** + * Middleware that logs requests, responses, and errors using a message + * formatter. + * + * @phpstan-param \Psr\Log\LogLevel::* $logLevel Level at which to log requests. + * + * @param LoggerInterface $logger Logs messages. + * @param MessageFormatterInterface|MessageFormatter $formatter Formatter used to create message strings. + * @param string $logLevel Level at which to log requests. + * + * @return callable Returns a function that accepts the next handler. + */ + public static function log(LoggerInterface $logger, $formatter, string $logLevel = 'info'): callable + { + // To be compatible with Guzzle 7.1.x we need to allow users to pass a MessageFormatter + if (!$formatter instanceof MessageFormatter && !$formatter instanceof MessageFormatterInterface) { + throw new \LogicException(sprintf('Argument 2 to %s::log() must be of type %s', self::class, MessageFormatterInterface::class)); + } + + return static function (callable $handler) use ($logger, $formatter, $logLevel): callable { + return static function (RequestInterface $request, array $options = []) use ($handler, $logger, $formatter, $logLevel) { + return $handler($request, $options)->then( + static function ($response) use ($logger, $request, $formatter, $logLevel): ResponseInterface { + $message = $formatter->format($request, $response); + $logger->log($logLevel, $message); + + return $response; + }, + static function ($reason) use ($logger, $request, $formatter): PromiseInterface { + $response = $reason instanceof RequestException ? $reason->getResponse() : null; + $message = $formatter->format($request, $response, P\Create::exceptionFor($reason)); + $logger->error($message); + + return P\Create::rejectionFor($reason); + } + ); + }; + }; + } + + /** + * This middleware adds a default content-type if possible, a default + * content-length or transfer-encoding header, and the expect header. + */ + public static function prepareBody(): callable + { + return static function (callable $handler): PrepareBodyMiddleware { + return new PrepareBodyMiddleware($handler); + }; + } + + /** + * Middleware that applies a map function to the request before passing to + * the next handler. + * + * @param callable $fn Function that accepts a RequestInterface and returns + * a RequestInterface. + */ + public static function mapRequest(callable $fn): callable + { + return static function (callable $handler) use ($fn): callable { + return static function (RequestInterface $request, array $options) use ($handler, $fn) { + return $handler($fn($request), $options); + }; + }; + } + + /** + * Middleware that applies a map function to the resolved promise's + * response. + * + * @param callable $fn Function that accepts a ResponseInterface and + * returns a ResponseInterface. + */ + public static function mapResponse(callable $fn): callable + { + return static function (callable $handler) use ($fn): callable { + return static function (RequestInterface $request, array $options) use ($handler, $fn) { + return $handler($request, $options)->then($fn); + }; + }; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Pool.php b/vendor/guzzlehttp/guzzle/src/Pool.php new file mode 100644 index 0000000..6277c61 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Pool.php @@ -0,0 +1,125 @@ + $rfn) { + if ($rfn instanceof RequestInterface) { + yield $key => $client->sendAsync($rfn, $opts); + } elseif (\is_callable($rfn)) { + yield $key => $rfn($opts); + } else { + throw new \InvalidArgumentException('Each value yielded by the iterator must be a Psr7\Http\Message\RequestInterface or a callable that returns a promise that fulfills with a Psr7\Message\Http\ResponseInterface object.'); + } + } + }; + + $this->each = new EachPromise($requests(), $config); + } + + /** + * Get promise + */ + public function promise(): PromiseInterface + { + return $this->each->promise(); + } + + /** + * Sends multiple requests concurrently and returns an array of responses + * and exceptions that uses the same ordering as the provided requests. + * + * IMPORTANT: This method keeps every request and response in memory, and + * as such, is NOT recommended when sending a large number or an + * indeterminate number of requests concurrently. + * + * @param ClientInterface $client Client used to send the requests + * @param array|\Iterator $requests Requests to send concurrently. + * @param array $options Passes through the options available in + * {@see \GuzzleHttp\Pool::__construct} + * + * @return array Returns an array containing the response or an exception + * in the same order that the requests were sent. + * + * @throws \InvalidArgumentException if the event format is incorrect. + */ + public static function batch(ClientInterface $client, $requests, array $options = []): array + { + $res = []; + self::cmpCallback($options, 'fulfilled', $res); + self::cmpCallback($options, 'rejected', $res); + $pool = new static($client, $requests, $options); + $pool->promise()->wait(); + \ksort($res); + + return $res; + } + + /** + * Execute callback(s) + */ + private static function cmpCallback(array &$options, string $name, array &$results): void + { + if (!isset($options[$name])) { + $options[$name] = static function ($v, $k) use (&$results) { + $results[$k] = $v; + }; + } else { + $currentFn = $options[$name]; + $options[$name] = static function ($v, $k) use (&$results, $currentFn) { + $currentFn($v, $k); + $results[$k] = $v; + }; + } + } +} diff --git a/vendor/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php b/vendor/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php new file mode 100644 index 0000000..0a8de81 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php @@ -0,0 +1,105 @@ +nextHandler = $nextHandler; + } + + public function __invoke(RequestInterface $request, array $options): PromiseInterface + { + $fn = $this->nextHandler; + + // Don't do anything if the request has no body. + if ($request->getBody()->getSize() === 0) { + return $fn($request, $options); + } + + $modify = []; + + // Add a default content-type if possible. + if (!$request->hasHeader('Content-Type')) { + if ($uri = $request->getBody()->getMetadata('uri')) { + if (is_string($uri) && $type = Psr7\MimeType::fromFilename($uri)) { + $modify['set_headers']['Content-Type'] = $type; + } + } + } + + // Add a default content-length or transfer-encoding header. + if (!$request->hasHeader('Content-Length') + && !$request->hasHeader('Transfer-Encoding') + ) { + $size = $request->getBody()->getSize(); + if ($size !== null) { + $modify['set_headers']['Content-Length'] = $size; + } else { + $modify['set_headers']['Transfer-Encoding'] = 'chunked'; + } + } + + // Add the expect header if needed. + $this->addExpectHeader($request, $options, $modify); + + return $fn(Psr7\Utils::modifyRequest($request, $modify), $options); + } + + /** + * Add expect header + */ + private function addExpectHeader(RequestInterface $request, array $options, array &$modify): void + { + // Determine if the Expect header should be used + if ($request->hasHeader('Expect')) { + return; + } + + $expect = $options['expect'] ?? null; + + // Return if disabled or if you're not using HTTP/1.1 or HTTP/2.0 + if ($expect === false || $request->getProtocolVersion() < 1.1) { + return; + } + + // The expect header is unconditionally enabled + if ($expect === true) { + $modify['set_headers']['Expect'] = '100-Continue'; + + return; + } + + // By default, send the expect header when the payload is > 1mb + if ($expect === null) { + $expect = 1048576; + } + + // Always add if the body cannot be rewound, the size cannot be + // determined, or the size is greater than the cutoff threshold + $body = $request->getBody(); + $size = $body->getSize(); + + if ($size === null || $size >= (int) $expect || !$body->isSeekable()) { + $modify['set_headers']['Expect'] = '100-Continue'; + } + } +} diff --git a/vendor/guzzlehttp/guzzle/src/RedirectMiddleware.php b/vendor/guzzlehttp/guzzle/src/RedirectMiddleware.php new file mode 100644 index 0000000..f32808a --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/RedirectMiddleware.php @@ -0,0 +1,228 @@ + 5, + 'protocols' => ['http', 'https'], + 'strict' => false, + 'referer' => false, + 'track_redirects' => false, + ]; + + /** + * @var callable(RequestInterface, array): PromiseInterface + */ + private $nextHandler; + + /** + * @param callable(RequestInterface, array): PromiseInterface $nextHandler Next handler to invoke. + */ + public function __construct(callable $nextHandler) + { + $this->nextHandler = $nextHandler; + } + + public function __invoke(RequestInterface $request, array $options): PromiseInterface + { + $fn = $this->nextHandler; + + if (empty($options['allow_redirects'])) { + return $fn($request, $options); + } + + if ($options['allow_redirects'] === true) { + $options['allow_redirects'] = self::$defaultSettings; + } elseif (!\is_array($options['allow_redirects'])) { + throw new \InvalidArgumentException('allow_redirects must be true, false, or array'); + } else { + // Merge the default settings with the provided settings + $options['allow_redirects'] += self::$defaultSettings; + } + + if (empty($options['allow_redirects']['max'])) { + return $fn($request, $options); + } + + return $fn($request, $options) + ->then(function (ResponseInterface $response) use ($request, $options) { + return $this->checkRedirect($request, $options, $response); + }); + } + + /** + * @return ResponseInterface|PromiseInterface + */ + public function checkRedirect(RequestInterface $request, array $options, ResponseInterface $response) + { + if (\strpos((string) $response->getStatusCode(), '3') !== 0 + || !$response->hasHeader('Location') + ) { + return $response; + } + + $this->guardMax($request, $response, $options); + $nextRequest = $this->modifyRequest($request, $options, $response); + + // If authorization is handled by curl, unset it if URI is cross-origin. + if (Psr7\UriComparator::isCrossOrigin($request->getUri(), $nextRequest->getUri()) && defined('\CURLOPT_HTTPAUTH')) { + unset( + $options['curl'][\CURLOPT_HTTPAUTH], + $options['curl'][\CURLOPT_USERPWD] + ); + } + + if (isset($options['allow_redirects']['on_redirect'])) { + ($options['allow_redirects']['on_redirect'])( + $request, + $response, + $nextRequest->getUri() + ); + } + + $promise = $this($nextRequest, $options); + + // Add headers to be able to track history of redirects. + if (!empty($options['allow_redirects']['track_redirects'])) { + return $this->withTracking( + $promise, + (string) $nextRequest->getUri(), + $response->getStatusCode() + ); + } + + return $promise; + } + + /** + * Enable tracking on promise. + */ + private function withTracking(PromiseInterface $promise, string $uri, int $statusCode): PromiseInterface + { + return $promise->then( + static function (ResponseInterface $response) use ($uri, $statusCode) { + // Note that we are pushing to the front of the list as this + // would be an earlier response than what is currently present + // in the history header. + $historyHeader = $response->getHeader(self::HISTORY_HEADER); + $statusHeader = $response->getHeader(self::STATUS_HISTORY_HEADER); + \array_unshift($historyHeader, $uri); + \array_unshift($statusHeader, (string) $statusCode); + + return $response->withHeader(self::HISTORY_HEADER, $historyHeader) + ->withHeader(self::STATUS_HISTORY_HEADER, $statusHeader); + } + ); + } + + /** + * Check for too many redirects. + * + * @throws TooManyRedirectsException Too many redirects. + */ + private function guardMax(RequestInterface $request, ResponseInterface $response, array &$options): void + { + $current = $options['__redirect_count'] + ?? 0; + $options['__redirect_count'] = $current + 1; + $max = $options['allow_redirects']['max']; + + if ($options['__redirect_count'] > $max) { + throw new TooManyRedirectsException("Will not follow more than {$max} redirects", $request, $response); + } + } + + public function modifyRequest(RequestInterface $request, array $options, ResponseInterface $response): RequestInterface + { + // Request modifications to apply. + $modify = []; + $protocols = $options['allow_redirects']['protocols']; + + // Use a GET request if this is an entity enclosing request and we are + // not forcing RFC compliance, but rather emulating what all browsers + // would do. + $statusCode = $response->getStatusCode(); + if ($statusCode == 303 || + ($statusCode <= 302 && !$options['allow_redirects']['strict']) + ) { + $safeMethods = ['GET', 'HEAD', 'OPTIONS']; + $requestMethod = $request->getMethod(); + + $modify['method'] = in_array($requestMethod, $safeMethods) ? $requestMethod : 'GET'; + $modify['body'] = ''; + } + + $uri = self::redirectUri($request, $response, $protocols); + if (isset($options['idn_conversion']) && ($options['idn_conversion'] !== false)) { + $idnOptions = ($options['idn_conversion'] === true) ? \IDNA_DEFAULT : $options['idn_conversion']; + $uri = Utils::idnUriConvert($uri, $idnOptions); + } + + $modify['uri'] = $uri; + Psr7\Message::rewindBody($request); + + // Add the Referer header if it is told to do so and only + // add the header if we are not redirecting from https to http. + if ($options['allow_redirects']['referer'] + && $modify['uri']->getScheme() === $request->getUri()->getScheme() + ) { + $uri = $request->getUri()->withUserInfo(''); + $modify['set_headers']['Referer'] = (string) $uri; + } else { + $modify['remove_headers'][] = 'Referer'; + } + + // Remove Authorization and Cookie headers if URI is cross-origin. + if (Psr7\UriComparator::isCrossOrigin($request->getUri(), $modify['uri'])) { + $modify['remove_headers'][] = 'Authorization'; + $modify['remove_headers'][] = 'Cookie'; + } + + return Psr7\Utils::modifyRequest($request, $modify); + } + + /** + * Set the appropriate URL on the request based on the location header. + */ + private static function redirectUri( + RequestInterface $request, + ResponseInterface $response, + array $protocols + ): UriInterface { + $location = Psr7\UriResolver::resolve( + $request->getUri(), + new Psr7\Uri($response->getHeaderLine('Location')) + ); + + // Ensure that the redirect URI is allowed based on the protocols. + if (!\in_array($location->getScheme(), $protocols)) { + throw new BadResponseException(\sprintf('Redirect URI, %s, does not use one of the allowed redirect protocols: %s', $location, \implode(', ', $protocols)), $request, $response); + } + + return $location; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/RequestOptions.php b/vendor/guzzlehttp/guzzle/src/RequestOptions.php new file mode 100644 index 0000000..bf3b02b --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/RequestOptions.php @@ -0,0 +1,276 @@ +decider = $decider; + $this->nextHandler = $nextHandler; + $this->delay = $delay ?: __CLASS__.'::exponentialDelay'; + } + + /** + * Default exponential backoff delay function. + * + * @return int milliseconds. + */ + public static function exponentialDelay(int $retries): int + { + return (int) 2 ** ($retries - 1) * 1000; + } + + public function __invoke(RequestInterface $request, array $options): PromiseInterface + { + if (!isset($options['retries'])) { + $options['retries'] = 0; + } + + $fn = $this->nextHandler; + + return $fn($request, $options) + ->then( + $this->onFulfilled($request, $options), + $this->onRejected($request, $options) + ); + } + + /** + * Execute fulfilled closure + */ + private function onFulfilled(RequestInterface $request, array $options): callable + { + return function ($value) use ($request, $options) { + if (!($this->decider)( + $options['retries'], + $request, + $value, + null + )) { + return $value; + } + + return $this->doRetry($request, $options, $value); + }; + } + + /** + * Execute rejected closure + */ + private function onRejected(RequestInterface $req, array $options): callable + { + return function ($reason) use ($req, $options) { + if (!($this->decider)( + $options['retries'], + $req, + null, + $reason + )) { + return P\Create::rejectionFor($reason); + } + + return $this->doRetry($req, $options); + }; + } + + private function doRetry(RequestInterface $request, array $options, ResponseInterface $response = null): PromiseInterface + { + $options['delay'] = ($this->delay)(++$options['retries'], $response, $request); + + return $this($request, $options); + } +} diff --git a/vendor/guzzlehttp/guzzle/src/TransferStats.php b/vendor/guzzlehttp/guzzle/src/TransferStats.php new file mode 100644 index 0000000..93fa334 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/TransferStats.php @@ -0,0 +1,133 @@ +request = $request; + $this->response = $response; + $this->transferTime = $transferTime; + $this->handlerErrorData = $handlerErrorData; + $this->handlerStats = $handlerStats; + } + + public function getRequest(): RequestInterface + { + return $this->request; + } + + /** + * Returns the response that was received (if any). + */ + public function getResponse(): ?ResponseInterface + { + return $this->response; + } + + /** + * Returns true if a response was received. + */ + public function hasResponse(): bool + { + return $this->response !== null; + } + + /** + * Gets handler specific error data. + * + * This might be an exception, a integer representing an error code, or + * anything else. Relying on this value assumes that you know what handler + * you are using. + * + * @return mixed + */ + public function getHandlerErrorData() + { + return $this->handlerErrorData; + } + + /** + * Get the effective URI the request was sent to. + */ + public function getEffectiveUri(): UriInterface + { + return $this->request->getUri(); + } + + /** + * Get the estimated time the request was being transferred by the handler. + * + * @return float|null Time in seconds. + */ + public function getTransferTime(): ?float + { + return $this->transferTime; + } + + /** + * Gets an array of all of the handler specific transfer data. + */ + public function getHandlerStats(): array + { + return $this->handlerStats; + } + + /** + * Get a specific handler statistic from the handler by name. + * + * @param string $stat Handler specific transfer stat to retrieve. + * + * @return mixed|null + */ + public function getHandlerStat(string $stat) + { + return $this->handlerStats[$stat] ?? null; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Utils.php b/vendor/guzzlehttp/guzzle/src/Utils.php new file mode 100644 index 0000000..fcf571d --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Utils.php @@ -0,0 +1,385 @@ +getHost()) { + $asciiHost = self::idnToAsci($uri->getHost(), $options, $info); + if ($asciiHost === false) { + $errorBitSet = $info['errors'] ?? 0; + + $errorConstants = array_filter(array_keys(get_defined_constants()), static function (string $name): bool { + return substr($name, 0, 11) === 'IDNA_ERROR_'; + }); + + $errors = []; + foreach ($errorConstants as $errorConstant) { + if ($errorBitSet & constant($errorConstant)) { + $errors[] = $errorConstant; + } + } + + $errorMessage = 'IDN conversion failed'; + if ($errors) { + $errorMessage .= ' (errors: '.implode(', ', $errors).')'; + } + + throw new InvalidArgumentException($errorMessage); + } + if ($uri->getHost() !== $asciiHost) { + // Replace URI only if the ASCII version is different + $uri = $uri->withHost($asciiHost); + } + } + + return $uri; + } + + /** + * @internal + */ + public static function getenv(string $name): ?string + { + if (isset($_SERVER[$name])) { + return (string) $_SERVER[$name]; + } + + if (\PHP_SAPI === 'cli' && ($value = \getenv($name)) !== false && $value !== null) { + return (string) $value; + } + + return null; + } + + /** + * @return string|false + */ + private static function idnToAsci(string $domain, int $options, ?array &$info = []) + { + if (\function_exists('idn_to_ascii') && \defined('INTL_IDNA_VARIANT_UTS46')) { + return \idn_to_ascii($domain, $options, \INTL_IDNA_VARIANT_UTS46, $info); + } + + throw new \Error('ext-idn or symfony/polyfill-intl-idn not loaded or too old'); + } +} diff --git a/vendor/guzzlehttp/guzzle/src/functions.php b/vendor/guzzlehttp/guzzle/src/functions.php new file mode 100644 index 0000000..5edc66a --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/functions.php @@ -0,0 +1,167 @@ + +Copyright (c) 2015 Graham Campbell +Copyright (c) 2017 Tobias Schultze +Copyright (c) 2020 Tobias Nyholm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/guzzlehttp/promises/README.md b/vendor/guzzlehttp/promises/README.md new file mode 100644 index 0000000..4dc7b6a --- /dev/null +++ b/vendor/guzzlehttp/promises/README.md @@ -0,0 +1,559 @@ +# Guzzle Promises + +[Promises/A+](https://promisesaplus.com/) implementation that handles promise +chaining and resolution iteratively, allowing for "infinite" promise chaining +while keeping the stack size constant. Read [this blog post](https://blog.domenic.me/youre-missing-the-point-of-promises/) +for a general introduction to promises. + +- [Features](#features) +- [Quick start](#quick-start) +- [Synchronous wait](#synchronous-wait) +- [Cancellation](#cancellation) +- [API](#api) + - [Promise](#promise) + - [FulfilledPromise](#fulfilledpromise) + - [RejectedPromise](#rejectedpromise) +- [Promise interop](#promise-interop) +- [Implementation notes](#implementation-notes) + + +## Features + +- [Promises/A+](https://promisesaplus.com/) implementation. +- Promise resolution and chaining is handled iteratively, allowing for + "infinite" promise chaining. +- Promises have a synchronous `wait` method. +- Promises can be cancelled. +- Works with any object that has a `then` function. +- C# style async/await coroutine promises using + `GuzzleHttp\Promise\Coroutine::of()`. + + +## Installation + +```shell +composer require guzzlehttp/promises +``` + + +## Version Guidance + +| Version | Status | PHP Version | +|---------|------------------------|--------------| +| 1.x | Bug and security fixes | >=5.5,<8.3 | +| 2.x | Latest | >=7.2.5,<8.3 | + + +## Quick Start + +A *promise* represents the eventual result of an asynchronous operation. The +primary way of interacting with a promise is through its `then` method, which +registers callbacks to receive either a promise's eventual value or the reason +why the promise cannot be fulfilled. + +### Callbacks + +Callbacks are registered with the `then` method by providing an optional +`$onFulfilled` followed by an optional `$onRejected` function. + + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise(); +$promise->then( + // $onFulfilled + function ($value) { + echo 'The promise was fulfilled.'; + }, + // $onRejected + function ($reason) { + echo 'The promise was rejected.'; + } +); +``` + +*Resolving* a promise means that you either fulfill a promise with a *value* or +reject a promise with a *reason*. Resolving a promise triggers callbacks +registered with the promise's `then` method. These callbacks are triggered +only once and in the order in which they were added. + +### Resolving a Promise + +Promises are fulfilled using the `resolve($value)` method. Resolving a promise +with any value other than a `GuzzleHttp\Promise\RejectedPromise` will trigger +all of the onFulfilled callbacks (resolving a promise with a rejected promise +will reject the promise and trigger the `$onRejected` callbacks). + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise(); +$promise + ->then(function ($value) { + // Return a value and don't break the chain + return "Hello, " . $value; + }) + // This then is executed after the first then and receives the value + // returned from the first then. + ->then(function ($value) { + echo $value; + }); + +// Resolving the promise triggers the $onFulfilled callbacks and outputs +// "Hello, reader." +$promise->resolve('reader.'); +``` + +### Promise Forwarding + +Promises can be chained one after the other. Each then in the chain is a new +promise. The return value of a promise is what's forwarded to the next +promise in the chain. Returning a promise in a `then` callback will cause the +subsequent promises in the chain to only be fulfilled when the returned promise +has been fulfilled. The next promise in the chain will be invoked with the +resolved value of the promise. + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise(); +$nextPromise = new Promise(); + +$promise + ->then(function ($value) use ($nextPromise) { + echo $value; + return $nextPromise; + }) + ->then(function ($value) { + echo $value; + }); + +// Triggers the first callback and outputs "A" +$promise->resolve('A'); +// Triggers the second callback and outputs "B" +$nextPromise->resolve('B'); +``` + +### Promise Rejection + +When a promise is rejected, the `$onRejected` callbacks are invoked with the +rejection reason. + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise(); +$promise->then(null, function ($reason) { + echo $reason; +}); + +$promise->reject('Error!'); +// Outputs "Error!" +``` + +### Rejection Forwarding + +If an exception is thrown in an `$onRejected` callback, subsequent +`$onRejected` callbacks are invoked with the thrown exception as the reason. + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise(); +$promise->then(null, function ($reason) { + throw new Exception($reason); +})->then(null, function ($reason) { + assert($reason->getMessage() === 'Error!'); +}); + +$promise->reject('Error!'); +``` + +You can also forward a rejection down the promise chain by returning a +`GuzzleHttp\Promise\RejectedPromise` in either an `$onFulfilled` or +`$onRejected` callback. + +```php +use GuzzleHttp\Promise\Promise; +use GuzzleHttp\Promise\RejectedPromise; + +$promise = new Promise(); +$promise->then(null, function ($reason) { + return new RejectedPromise($reason); +})->then(null, function ($reason) { + assert($reason === 'Error!'); +}); + +$promise->reject('Error!'); +``` + +If an exception is not thrown in a `$onRejected` callback and the callback +does not return a rejected promise, downstream `$onFulfilled` callbacks are +invoked using the value returned from the `$onRejected` callback. + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise(); +$promise + ->then(null, function ($reason) { + return "It's ok"; + }) + ->then(function ($value) { + assert($value === "It's ok"); + }); + +$promise->reject('Error!'); +``` + + +## Synchronous Wait + +You can synchronously force promises to complete using a promise's `wait` +method. When creating a promise, you can provide a wait function that is used +to synchronously force a promise to complete. When a wait function is invoked +it is expected to deliver a value to the promise or reject the promise. If the +wait function does not deliver a value, then an exception is thrown. The wait +function provided to a promise constructor is invoked when the `wait` function +of the promise is called. + +```php +$promise = new Promise(function () use (&$promise) { + $promise->resolve('foo'); +}); + +// Calling wait will return the value of the promise. +echo $promise->wait(); // outputs "foo" +``` + +If an exception is encountered while invoking the wait function of a promise, +the promise is rejected with the exception and the exception is thrown. + +```php +$promise = new Promise(function () use (&$promise) { + throw new Exception('foo'); +}); + +$promise->wait(); // throws the exception. +``` + +Calling `wait` on a promise that has been fulfilled will not trigger the wait +function. It will simply return the previously resolved value. + +```php +$promise = new Promise(function () { die('this is not called!'); }); +$promise->resolve('foo'); +echo $promise->wait(); // outputs "foo" +``` + +Calling `wait` on a promise that has been rejected will throw an exception. If +the rejection reason is an instance of `\Exception` the reason is thrown. +Otherwise, a `GuzzleHttp\Promise\RejectionException` is thrown and the reason +can be obtained by calling the `getReason` method of the exception. + +```php +$promise = new Promise(); +$promise->reject('foo'); +$promise->wait(); +``` + +> PHP Fatal error: Uncaught exception 'GuzzleHttp\Promise\RejectionException' with message 'The promise was rejected with value: foo' + +### Unwrapping a Promise + +When synchronously waiting on a promise, you are joining the state of the +promise into the current state of execution (i.e., return the value of the +promise if it was fulfilled or throw an exception if it was rejected). This is +called "unwrapping" the promise. Waiting on a promise will by default unwrap +the promise state. + +You can force a promise to resolve and *not* unwrap the state of the promise +by passing `false` to the first argument of the `wait` function: + +```php +$promise = new Promise(); +$promise->reject('foo'); +// This will not throw an exception. It simply ensures the promise has +// been resolved. +$promise->wait(false); +``` + +When unwrapping a promise, the resolved value of the promise will be waited +upon until the unwrapped value is not a promise. This means that if you resolve +promise A with a promise B and unwrap promise A, the value returned by the +wait function will be the value delivered to promise B. + +**Note**: when you do not unwrap the promise, no value is returned. + + +## Cancellation + +You can cancel a promise that has not yet been fulfilled using the `cancel()` +method of a promise. When creating a promise you can provide an optional +cancel function that when invoked cancels the action of computing a resolution +of the promise. + + +## API + +### Promise + +When creating a promise object, you can provide an optional `$waitFn` and +`$cancelFn`. `$waitFn` is a function that is invoked with no arguments and is +expected to resolve the promise. `$cancelFn` is a function with no arguments +that is expected to cancel the computation of a promise. It is invoked when the +`cancel()` method of a promise is called. + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise( + function () use (&$promise) { + $promise->resolve('waited'); + }, + function () { + // do something that will cancel the promise computation (e.g., close + // a socket, cancel a database query, etc...) + } +); + +assert('waited' === $promise->wait()); +``` + +A promise has the following methods: + +- `then(callable $onFulfilled, callable $onRejected) : PromiseInterface` + + Appends fulfillment and rejection handlers to the promise, and returns a new promise resolving to the return value of the called handler. + +- `otherwise(callable $onRejected) : PromiseInterface` + + Appends a rejection handler callback to the promise, and returns a new promise resolving to the return value of the callback if it is called, or to its original fulfillment value if the promise is instead fulfilled. + +- `wait($unwrap = true) : mixed` + + Synchronously waits on the promise to complete. + + `$unwrap` controls whether or not the value of the promise is returned for a + fulfilled promise or if an exception is thrown if the promise is rejected. + This is set to `true` by default. + +- `cancel()` + + Attempts to cancel the promise if possible. The promise being cancelled and + the parent most ancestor that has not yet been resolved will also be + cancelled. Any promises waiting on the cancelled promise to resolve will also + be cancelled. + +- `getState() : string` + + Returns the state of the promise. One of `pending`, `fulfilled`, or + `rejected`. + +- `resolve($value)` + + Fulfills the promise with the given `$value`. + +- `reject($reason)` + + Rejects the promise with the given `$reason`. + + +### FulfilledPromise + +A fulfilled promise can be created to represent a promise that has been +fulfilled. + +```php +use GuzzleHttp\Promise\FulfilledPromise; + +$promise = new FulfilledPromise('value'); + +// Fulfilled callbacks are immediately invoked. +$promise->then(function ($value) { + echo $value; +}); +``` + + +### RejectedPromise + +A rejected promise can be created to represent a promise that has been +rejected. + +```php +use GuzzleHttp\Promise\RejectedPromise; + +$promise = new RejectedPromise('Error'); + +// Rejected callbacks are immediately invoked. +$promise->then(null, function ($reason) { + echo $reason; +}); +``` + + +## Promise Interoperability + +This library works with foreign promises that have a `then` method. This means +you can use Guzzle promises with [React promises](https://github.com/reactphp/promise) +for example. When a foreign promise is returned inside of a then method +callback, promise resolution will occur recursively. + +```php +// Create a React promise +$deferred = new React\Promise\Deferred(); +$reactPromise = $deferred->promise(); + +// Create a Guzzle promise that is fulfilled with a React promise. +$guzzlePromise = new GuzzleHttp\Promise\Promise(); +$guzzlePromise->then(function ($value) use ($reactPromise) { + // Do something something with the value... + // Return the React promise + return $reactPromise; +}); +``` + +Please note that wait and cancel chaining is no longer possible when forwarding +a foreign promise. You will need to wrap a third-party promise with a Guzzle +promise in order to utilize wait and cancel functions with foreign promises. + + +### Event Loop Integration + +In order to keep the stack size constant, Guzzle promises are resolved +asynchronously using a task queue. When waiting on promises synchronously, the +task queue will be automatically run to ensure that the blocking promise and +any forwarded promises are resolved. When using promises asynchronously in an +event loop, you will need to run the task queue on each tick of the loop. If +you do not run the task queue, then promises will not be resolved. + +You can run the task queue using the `run()` method of the global task queue +instance. + +```php +// Get the global task queue +$queue = GuzzleHttp\Promise\Utils::queue(); +$queue->run(); +``` + +For example, you could use Guzzle promises with React using a periodic timer: + +```php +$loop = React\EventLoop\Factory::create(); +$loop->addPeriodicTimer(0, [$queue, 'run']); +``` + + +## Implementation Notes + +### Promise Resolution and Chaining is Handled Iteratively + +By shuffling pending handlers from one owner to another, promises are +resolved iteratively, allowing for "infinite" then chaining. + +```php +then(function ($v) { + // The stack size remains constant (a good thing) + echo xdebug_get_stack_depth() . ', '; + return $v + 1; + }); +} + +$parent->resolve(0); +var_dump($p->wait()); // int(1000) + +``` + +When a promise is fulfilled or rejected with a non-promise value, the promise +then takes ownership of the handlers of each child promise and delivers values +down the chain without using recursion. + +When a promise is resolved with another promise, the original promise transfers +all of its pending handlers to the new promise. When the new promise is +eventually resolved, all of the pending handlers are delivered the forwarded +value. + +### A Promise is the Deferred + +Some promise libraries implement promises using a deferred object to represent +a computation and a promise object to represent the delivery of the result of +the computation. This is a nice separation of computation and delivery because +consumers of the promise cannot modify the value that will be eventually +delivered. + +One side effect of being able to implement promise resolution and chaining +iteratively is that you need to be able for one promise to reach into the state +of another promise to shuffle around ownership of handlers. In order to achieve +this without making the handlers of a promise publicly mutable, a promise is +also the deferred value, allowing promises of the same parent class to reach +into and modify the private properties of promises of the same type. While this +does allow consumers of the value to modify the resolution or rejection of the +deferred, it is a small price to pay for keeping the stack size constant. + +```php +$promise = new Promise(); +$promise->then(function ($value) { echo $value; }); +// The promise is the deferred value, so you can deliver a value to it. +$promise->resolve('foo'); +// prints "foo" +``` + + +## Upgrading from Function API + +A static API was first introduced in 1.4.0, in order to mitigate problems with +functions conflicting between global and local copies of the package. The +function API was removed in 2.0.0. A migration table has been provided here for +your convenience: + +| Original Function | Replacement Method | +|----------------|----------------| +| `queue` | `Utils::queue` | +| `task` | `Utils::task` | +| `promise_for` | `Create::promiseFor` | +| `rejection_for` | `Create::rejectionFor` | +| `exception_for` | `Create::exceptionFor` | +| `iter_for` | `Create::iterFor` | +| `inspect` | `Utils::inspect` | +| `inspect_all` | `Utils::inspectAll` | +| `unwrap` | `Utils::unwrap` | +| `all` | `Utils::all` | +| `some` | `Utils::some` | +| `any` | `Utils::any` | +| `settle` | `Utils::settle` | +| `each` | `Each::of` | +| `each_limit` | `Each::ofLimit` | +| `each_limit_all` | `Each::ofLimitAll` | +| `!is_fulfilled` | `Is::pending` | +| `is_fulfilled` | `Is::fulfilled` | +| `is_rejected` | `Is::rejected` | +| `is_settled` | `Is::settled` | +| `coroutine` | `Coroutine::of` | + + +## Security + +If you discover a security vulnerability within this package, please send an email to security@tidelift.com. All security vulnerabilities will be promptly addressed. Please do not disclose security-related issues publicly until a fix has been announced. Please see [Security Policy](https://github.com/guzzle/promises/security/policy) for more information. + + +## License + +Guzzle is made available under the MIT License (MIT). Please see [License File](LICENSE) for more information. + + +## For Enterprise + +Available as part of the Tidelift Subscription + +The maintainers of Guzzle and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/packagist-guzzlehttp-promises?utm_source=packagist-guzzlehttp-promises&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) diff --git a/vendor/guzzlehttp/promises/src/AggregateException.php b/vendor/guzzlehttp/promises/src/AggregateException.php new file mode 100644 index 0000000..40ffdbc --- /dev/null +++ b/vendor/guzzlehttp/promises/src/AggregateException.php @@ -0,0 +1,19 @@ +then(function ($v) { echo $v; }); + * + * @param callable $generatorFn Generator function to wrap into a promise. + * + * @return Promise + * + * @see https://github.com/petkaantonov/bluebird/blob/master/API.md#generators inspiration + */ +final class Coroutine implements PromiseInterface +{ + /** + * @var PromiseInterface|null + */ + private $currentPromise; + + /** + * @var Generator + */ + private $generator; + + /** + * @var Promise + */ + private $result; + + public function __construct(callable $generatorFn) + { + $this->generator = $generatorFn(); + $this->result = new Promise(function (): void { + while (isset($this->currentPromise)) { + $this->currentPromise->wait(); + } + }); + try { + $this->nextCoroutine($this->generator->current()); + } catch (Throwable $throwable) { + $this->result->reject($throwable); + } + } + + /** + * Create a new coroutine. + */ + public static function of(callable $generatorFn): self + { + return new self($generatorFn); + } + + public function then( + callable $onFulfilled = null, + callable $onRejected = null + ): PromiseInterface { + return $this->result->then($onFulfilled, $onRejected); + } + + public function otherwise(callable $onRejected): PromiseInterface + { + return $this->result->otherwise($onRejected); + } + + public function wait(bool $unwrap = true) + { + return $this->result->wait($unwrap); + } + + public function getState(): string + { + return $this->result->getState(); + } + + public function resolve($value): void + { + $this->result->resolve($value); + } + + public function reject($reason): void + { + $this->result->reject($reason); + } + + public function cancel(): void + { + $this->currentPromise->cancel(); + $this->result->cancel(); + } + + private function nextCoroutine($yielded): void + { + $this->currentPromise = Create::promiseFor($yielded) + ->then([$this, '_handleSuccess'], [$this, '_handleFailure']); + } + + /** + * @internal + */ + public function _handleSuccess($value): void + { + unset($this->currentPromise); + try { + $next = $this->generator->send($value); + if ($this->generator->valid()) { + $this->nextCoroutine($next); + } else { + $this->result->resolve($value); + } + } catch (Throwable $throwable) { + $this->result->reject($throwable); + } + } + + /** + * @internal + */ + public function _handleFailure($reason): void + { + unset($this->currentPromise); + try { + $nextYield = $this->generator->throw(Create::exceptionFor($reason)); + // The throw was caught, so keep iterating on the coroutine + $this->nextCoroutine($nextYield); + } catch (Throwable $throwable) { + $this->result->reject($throwable); + } + } +} diff --git a/vendor/guzzlehttp/promises/src/Create.php b/vendor/guzzlehttp/promises/src/Create.php new file mode 100644 index 0000000..9d3fc4a --- /dev/null +++ b/vendor/guzzlehttp/promises/src/Create.php @@ -0,0 +1,79 @@ +then([$promise, 'resolve'], [$promise, 'reject']); + + return $promise; + } + + return new FulfilledPromise($value); + } + + /** + * Creates a rejected promise for a reason if the reason is not a promise. + * If the provided reason is a promise, then it is returned as-is. + * + * @param mixed $reason Promise or reason. + */ + public static function rejectionFor($reason): PromiseInterface + { + if ($reason instanceof PromiseInterface) { + return $reason; + } + + return new RejectedPromise($reason); + } + + /** + * Create an exception for a rejected promise value. + * + * @param mixed $reason + */ + public static function exceptionFor($reason): \Throwable + { + if ($reason instanceof \Throwable) { + return $reason; + } + + return new RejectionException($reason); + } + + /** + * Returns an iterator for the given value. + * + * @param mixed $value + */ + public static function iterFor($value): \Iterator + { + if ($value instanceof \Iterator) { + return $value; + } + + if (is_array($value)) { + return new \ArrayIterator($value); + } + + return new \ArrayIterator([$value]); + } +} diff --git a/vendor/guzzlehttp/promises/src/Each.php b/vendor/guzzlehttp/promises/src/Each.php new file mode 100644 index 0000000..1a7aa0f --- /dev/null +++ b/vendor/guzzlehttp/promises/src/Each.php @@ -0,0 +1,86 @@ + $onFulfilled, + 'rejected' => $onRejected, + ]))->promise(); + } + + /** + * Like of, but only allows a certain number of outstanding promises at any + * given time. + * + * $concurrency may be an integer or a function that accepts the number of + * pending promises and returns a numeric concurrency limit value to allow + * for dynamic a concurrency size. + * + * @param mixed $iterable + * @param int|callable $concurrency + * @param callable $onFulfilled + * @param callable $onRejected + */ + public static function ofLimit( + $iterable, + $concurrency, + callable $onFulfilled = null, + callable $onRejected = null + ): PromiseInterface { + return (new EachPromise($iterable, [ + 'fulfilled' => $onFulfilled, + 'rejected' => $onRejected, + 'concurrency' => $concurrency, + ]))->promise(); + } + + /** + * Like limit, but ensures that no promise in the given $iterable argument + * is rejected. If any promise is rejected, then the aggregate promise is + * rejected with the encountered rejection. + * + * @param mixed $iterable + * @param int|callable $concurrency + * @param callable $onFulfilled + */ + public static function ofLimitAll( + $iterable, + $concurrency, + callable $onFulfilled = null + ): PromiseInterface { + return self::ofLimit( + $iterable, + $concurrency, + $onFulfilled, + function ($reason, $idx, PromiseInterface $aggregate): void { + $aggregate->reject($reason); + } + ); + } +} diff --git a/vendor/guzzlehttp/promises/src/EachPromise.php b/vendor/guzzlehttp/promises/src/EachPromise.php new file mode 100644 index 0000000..28dd979 --- /dev/null +++ b/vendor/guzzlehttp/promises/src/EachPromise.php @@ -0,0 +1,250 @@ +iterable = Create::iterFor($iterable); + + if (isset($config['concurrency'])) { + $this->concurrency = $config['concurrency']; + } + + if (isset($config['fulfilled'])) { + $this->onFulfilled = $config['fulfilled']; + } + + if (isset($config['rejected'])) { + $this->onRejected = $config['rejected']; + } + } + + /** @psalm-suppress InvalidNullableReturnType */ + public function promise(): PromiseInterface + { + if ($this->aggregate) { + return $this->aggregate; + } + + try { + $this->createPromise(); + /** @psalm-assert Promise $this->aggregate */ + $this->iterable->rewind(); + $this->refillPending(); + } catch (\Throwable $e) { + $this->aggregate->reject($e); + } + + /** + * @psalm-suppress NullableReturnStatement + */ + return $this->aggregate; + } + + private function createPromise(): void + { + $this->mutex = false; + $this->aggregate = new Promise(function (): void { + if ($this->checkIfFinished()) { + return; + } + reset($this->pending); + // Consume a potentially fluctuating list of promises while + // ensuring that indexes are maintained (precluding array_shift). + while ($promise = current($this->pending)) { + next($this->pending); + $promise->wait(); + if (Is::settled($this->aggregate)) { + return; + } + } + }); + + // Clear the references when the promise is resolved. + $clearFn = function (): void { + $this->iterable = $this->concurrency = $this->pending = null; + $this->onFulfilled = $this->onRejected = null; + $this->nextPendingIndex = 0; + }; + + $this->aggregate->then($clearFn, $clearFn); + } + + private function refillPending(): void + { + if (!$this->concurrency) { + // Add all pending promises. + while ($this->addPending() && $this->advanceIterator()) { + } + + return; + } + + // Add only up to N pending promises. + $concurrency = is_callable($this->concurrency) + ? call_user_func($this->concurrency, count($this->pending)) + : $this->concurrency; + $concurrency = max($concurrency - count($this->pending), 0); + // Concurrency may be set to 0 to disallow new promises. + if (!$concurrency) { + return; + } + // Add the first pending promise. + $this->addPending(); + // Note this is special handling for concurrency=1 so that we do + // not advance the iterator after adding the first promise. This + // helps work around issues with generators that might not have the + // next value to yield until promise callbacks are called. + while (--$concurrency + && $this->advanceIterator() + && $this->addPending()) { + } + } + + private function addPending(): bool + { + if (!$this->iterable || !$this->iterable->valid()) { + return false; + } + + $promise = Create::promiseFor($this->iterable->current()); + $key = $this->iterable->key(); + + // Iterable keys may not be unique, so we use a counter to + // guarantee uniqueness + $idx = $this->nextPendingIndex++; + + $this->pending[$idx] = $promise->then( + function ($value) use ($idx, $key): void { + if ($this->onFulfilled) { + call_user_func( + $this->onFulfilled, + $value, + $key, + $this->aggregate + ); + } + $this->step($idx); + }, + function ($reason) use ($idx, $key): void { + if ($this->onRejected) { + call_user_func( + $this->onRejected, + $reason, + $key, + $this->aggregate + ); + } + $this->step($idx); + } + ); + + return true; + } + + private function advanceIterator(): bool + { + // Place a lock on the iterator so that we ensure to not recurse, + // preventing fatal generator errors. + if ($this->mutex) { + return false; + } + + $this->mutex = true; + + try { + $this->iterable->next(); + $this->mutex = false; + + return true; + } catch (\Throwable $e) { + $this->aggregate->reject($e); + $this->mutex = false; + + return false; + } + } + + private function step(int $idx): void + { + // If the promise was already resolved, then ignore this step. + if (Is::settled($this->aggregate)) { + return; + } + + unset($this->pending[$idx]); + + // Only refill pending promises if we are not locked, preventing the + // EachPromise to recursively invoke the provided iterator, which + // cause a fatal error: "Cannot resume an already running generator" + if ($this->advanceIterator() && !$this->checkIfFinished()) { + // Add more pending promises if possible. + $this->refillPending(); + } + } + + private function checkIfFinished(): bool + { + if (!$this->pending && !$this->iterable->valid()) { + // Resolve the promise if there's nothing left to do. + $this->aggregate->resolve(null); + + return true; + } + + return false; + } +} diff --git a/vendor/guzzlehttp/promises/src/FulfilledPromise.php b/vendor/guzzlehttp/promises/src/FulfilledPromise.php new file mode 100644 index 0000000..ab71296 --- /dev/null +++ b/vendor/guzzlehttp/promises/src/FulfilledPromise.php @@ -0,0 +1,89 @@ +value = $value; + } + + public function then( + callable $onFulfilled = null, + callable $onRejected = null + ): PromiseInterface { + // Return itself if there is no onFulfilled function. + if (!$onFulfilled) { + return $this; + } + + $queue = Utils::queue(); + $p = new Promise([$queue, 'run']); + $value = $this->value; + $queue->add(static function () use ($p, $value, $onFulfilled): void { + if (Is::pending($p)) { + try { + $p->resolve($onFulfilled($value)); + } catch (\Throwable $e) { + $p->reject($e); + } + } + }); + + return $p; + } + + public function otherwise(callable $onRejected): PromiseInterface + { + return $this->then(null, $onRejected); + } + + public function wait(bool $unwrap = true) + { + return $unwrap ? $this->value : null; + } + + public function getState(): string + { + return self::FULFILLED; + } + + public function resolve($value): void + { + if ($value !== $this->value) { + throw new \LogicException('Cannot resolve a fulfilled promise'); + } + } + + public function reject($reason): void + { + throw new \LogicException('Cannot reject a fulfilled promise'); + } + + public function cancel(): void + { + // pass + } +} diff --git a/vendor/guzzlehttp/promises/src/Is.php b/vendor/guzzlehttp/promises/src/Is.php new file mode 100644 index 0000000..f3f0503 --- /dev/null +++ b/vendor/guzzlehttp/promises/src/Is.php @@ -0,0 +1,40 @@ +getState() === PromiseInterface::PENDING; + } + + /** + * Returns true if a promise is fulfilled or rejected. + */ + public static function settled(PromiseInterface $promise): bool + { + return $promise->getState() !== PromiseInterface::PENDING; + } + + /** + * Returns true if a promise is fulfilled. + */ + public static function fulfilled(PromiseInterface $promise): bool + { + return $promise->getState() === PromiseInterface::FULFILLED; + } + + /** + * Returns true if a promise is rejected. + */ + public static function rejected(PromiseInterface $promise): bool + { + return $promise->getState() === PromiseInterface::REJECTED; + } +} diff --git a/vendor/guzzlehttp/promises/src/Promise.php b/vendor/guzzlehttp/promises/src/Promise.php new file mode 100644 index 0000000..1b07bdc --- /dev/null +++ b/vendor/guzzlehttp/promises/src/Promise.php @@ -0,0 +1,281 @@ +waitFn = $waitFn; + $this->cancelFn = $cancelFn; + } + + public function then( + callable $onFulfilled = null, + callable $onRejected = null + ): PromiseInterface { + if ($this->state === self::PENDING) { + $p = new Promise(null, [$this, 'cancel']); + $this->handlers[] = [$p, $onFulfilled, $onRejected]; + $p->waitList = $this->waitList; + $p->waitList[] = $this; + + return $p; + } + + // Return a fulfilled promise and immediately invoke any callbacks. + if ($this->state === self::FULFILLED) { + $promise = Create::promiseFor($this->result); + + return $onFulfilled ? $promise->then($onFulfilled) : $promise; + } + + // It's either cancelled or rejected, so return a rejected promise + // and immediately invoke any callbacks. + $rejection = Create::rejectionFor($this->result); + + return $onRejected ? $rejection->then(null, $onRejected) : $rejection; + } + + public function otherwise(callable $onRejected): PromiseInterface + { + return $this->then(null, $onRejected); + } + + public function wait(bool $unwrap = true) + { + $this->waitIfPending(); + + if ($this->result instanceof PromiseInterface) { + return $this->result->wait($unwrap); + } + if ($unwrap) { + if ($this->state === self::FULFILLED) { + return $this->result; + } + // It's rejected so "unwrap" and throw an exception. + throw Create::exceptionFor($this->result); + } + } + + public function getState(): string + { + return $this->state; + } + + public function cancel(): void + { + if ($this->state !== self::PENDING) { + return; + } + + $this->waitFn = $this->waitList = null; + + if ($this->cancelFn) { + $fn = $this->cancelFn; + $this->cancelFn = null; + try { + $fn(); + } catch (\Throwable $e) { + $this->reject($e); + } + } + + // Reject the promise only if it wasn't rejected in a then callback. + /** @psalm-suppress RedundantCondition */ + if ($this->state === self::PENDING) { + $this->reject(new CancellationException('Promise has been cancelled')); + } + } + + public function resolve($value): void + { + $this->settle(self::FULFILLED, $value); + } + + public function reject($reason): void + { + $this->settle(self::REJECTED, $reason); + } + + private function settle(string $state, $value): void + { + if ($this->state !== self::PENDING) { + // Ignore calls with the same resolution. + if ($state === $this->state && $value === $this->result) { + return; + } + throw $this->state === $state + ? new \LogicException("The promise is already {$state}.") + : new \LogicException("Cannot change a {$this->state} promise to {$state}"); + } + + if ($value === $this) { + throw new \LogicException('Cannot fulfill or reject a promise with itself'); + } + + // Clear out the state of the promise but stash the handlers. + $this->state = $state; + $this->result = $value; + $handlers = $this->handlers; + $this->handlers = null; + $this->waitList = $this->waitFn = null; + $this->cancelFn = null; + + if (!$handlers) { + return; + } + + // If the value was not a settled promise or a thenable, then resolve + // it in the task queue using the correct ID. + if (!is_object($value) || !method_exists($value, 'then')) { + $id = $state === self::FULFILLED ? 1 : 2; + // It's a success, so resolve the handlers in the queue. + Utils::queue()->add(static function () use ($id, $value, $handlers): void { + foreach ($handlers as $handler) { + self::callHandler($id, $value, $handler); + } + }); + } elseif ($value instanceof Promise && Is::pending($value)) { + // We can just merge our handlers onto the next promise. + $value->handlers = array_merge($value->handlers, $handlers); + } else { + // Resolve the handlers when the forwarded promise is resolved. + $value->then( + static function ($value) use ($handlers): void { + foreach ($handlers as $handler) { + self::callHandler(1, $value, $handler); + } + }, + static function ($reason) use ($handlers): void { + foreach ($handlers as $handler) { + self::callHandler(2, $reason, $handler); + } + } + ); + } + } + + /** + * Call a stack of handlers using a specific callback index and value. + * + * @param int $index 1 (resolve) or 2 (reject). + * @param mixed $value Value to pass to the callback. + * @param array $handler Array of handler data (promise and callbacks). + */ + private static function callHandler(int $index, $value, array $handler): void + { + /** @var PromiseInterface $promise */ + $promise = $handler[0]; + + // The promise may have been cancelled or resolved before placing + // this thunk in the queue. + if (Is::settled($promise)) { + return; + } + + try { + if (isset($handler[$index])) { + /* + * If $f throws an exception, then $handler will be in the exception + * stack trace. Since $handler contains a reference to the callable + * itself we get a circular reference. We clear the $handler + * here to avoid that memory leak. + */ + $f = $handler[$index]; + unset($handler); + $promise->resolve($f($value)); + } elseif ($index === 1) { + // Forward resolution values as-is. + $promise->resolve($value); + } else { + // Forward rejections down the chain. + $promise->reject($value); + } + } catch (\Throwable $reason) { + $promise->reject($reason); + } + } + + private function waitIfPending(): void + { + if ($this->state !== self::PENDING) { + return; + } elseif ($this->waitFn) { + $this->invokeWaitFn(); + } elseif ($this->waitList) { + $this->invokeWaitList(); + } else { + // If there's no wait function, then reject the promise. + $this->reject('Cannot wait on a promise that has ' + .'no internal wait function. You must provide a wait ' + .'function when constructing the promise to be able to ' + .'wait on a promise.'); + } + + Utils::queue()->run(); + + /** @psalm-suppress RedundantCondition */ + if ($this->state === self::PENDING) { + $this->reject('Invoking the wait callback did not resolve the promise'); + } + } + + private function invokeWaitFn(): void + { + try { + $wfn = $this->waitFn; + $this->waitFn = null; + $wfn(true); + } catch (\Throwable $reason) { + if ($this->state === self::PENDING) { + // The promise has not been resolved yet, so reject the promise + // with the exception. + $this->reject($reason); + } else { + // The promise was already resolved, so there's a problem in + // the application. + throw $reason; + } + } + } + + private function invokeWaitList(): void + { + $waitList = $this->waitList; + $this->waitList = null; + + foreach ($waitList as $result) { + do { + $result->waitIfPending(); + $result = $result->result; + } while ($result instanceof Promise); + + if ($result instanceof PromiseInterface) { + $result->wait(false); + } + } + } +} diff --git a/vendor/guzzlehttp/promises/src/PromiseInterface.php b/vendor/guzzlehttp/promises/src/PromiseInterface.php new file mode 100644 index 0000000..2824802 --- /dev/null +++ b/vendor/guzzlehttp/promises/src/PromiseInterface.php @@ -0,0 +1,91 @@ +reason = $reason; + } + + public function then( + callable $onFulfilled = null, + callable $onRejected = null + ): PromiseInterface { + // If there's no onRejected callback then just return self. + if (!$onRejected) { + return $this; + } + + $queue = Utils::queue(); + $reason = $this->reason; + $p = new Promise([$queue, 'run']); + $queue->add(static function () use ($p, $reason, $onRejected): void { + if (Is::pending($p)) { + try { + // Return a resolved promise if onRejected does not throw. + $p->resolve($onRejected($reason)); + } catch (\Throwable $e) { + // onRejected threw, so return a rejected promise. + $p->reject($e); + } + } + }); + + return $p; + } + + public function otherwise(callable $onRejected): PromiseInterface + { + return $this->then(null, $onRejected); + } + + public function wait(bool $unwrap = true) + { + if ($unwrap) { + throw Create::exceptionFor($this->reason); + } + + return null; + } + + public function getState(): string + { + return self::REJECTED; + } + + public function resolve($value): void + { + throw new \LogicException('Cannot resolve a rejected promise'); + } + + public function reject($reason): void + { + if ($reason !== $this->reason) { + throw new \LogicException('Cannot reject a rejected promise'); + } + } + + public function cancel(): void + { + // pass + } +} diff --git a/vendor/guzzlehttp/promises/src/RejectionException.php b/vendor/guzzlehttp/promises/src/RejectionException.php new file mode 100644 index 0000000..0db98ff --- /dev/null +++ b/vendor/guzzlehttp/promises/src/RejectionException.php @@ -0,0 +1,49 @@ +reason = $reason; + + $message = 'The promise was rejected'; + + if ($description) { + $message .= ' with reason: '.$description; + } elseif (is_string($reason) + || (is_object($reason) && method_exists($reason, '__toString')) + ) { + $message .= ' with reason: '.$this->reason; + } elseif ($reason instanceof \JsonSerializable) { + $message .= ' with reason: '.json_encode($this->reason, JSON_PRETTY_PRINT); + } + + parent::__construct($message); + } + + /** + * Returns the rejection reason. + * + * @return mixed + */ + public function getReason() + { + return $this->reason; + } +} diff --git a/vendor/guzzlehttp/promises/src/TaskQueue.php b/vendor/guzzlehttp/promises/src/TaskQueue.php new file mode 100644 index 0000000..503e0b2 --- /dev/null +++ b/vendor/guzzlehttp/promises/src/TaskQueue.php @@ -0,0 +1,71 @@ +run(); + * + * @final + */ +class TaskQueue implements TaskQueueInterface +{ + private $enableShutdown = true; + private $queue = []; + + public function __construct(bool $withShutdown = true) + { + if ($withShutdown) { + register_shutdown_function(function (): void { + if ($this->enableShutdown) { + // Only run the tasks if an E_ERROR didn't occur. + $err = error_get_last(); + if (!$err || ($err['type'] ^ E_ERROR)) { + $this->run(); + } + } + }); + } + } + + public function isEmpty(): bool + { + return !$this->queue; + } + + public function add(callable $task): void + { + $this->queue[] = $task; + } + + public function run(): void + { + while ($task = array_shift($this->queue)) { + /** @var callable $task */ + $task(); + } + } + + /** + * The task queue will be run and exhausted by default when the process + * exits IFF the exit is not the result of a PHP E_ERROR error. + * + * You can disable running the automatic shutdown of the queue by calling + * this function. If you disable the task queue shutdown process, then you + * MUST either run the task queue (as a result of running your event loop + * or manually using the run() method) or wait on each outstanding promise. + * + * Note: This shutdown will occur before any destructors are triggered. + */ + public function disableShutdown(): void + { + $this->enableShutdown = false; + } +} diff --git a/vendor/guzzlehttp/promises/src/TaskQueueInterface.php b/vendor/guzzlehttp/promises/src/TaskQueueInterface.php new file mode 100644 index 0000000..34c561a --- /dev/null +++ b/vendor/guzzlehttp/promises/src/TaskQueueInterface.php @@ -0,0 +1,24 @@ + + * while ($eventLoop->isRunning()) { + * GuzzleHttp\Promise\Utils::queue()->run(); + * } + * + * + * @param TaskQueueInterface|null $assign Optionally specify a new queue instance. + */ + public static function queue(TaskQueueInterface $assign = null): TaskQueueInterface + { + static $queue; + + if ($assign) { + $queue = $assign; + } elseif (!$queue) { + $queue = new TaskQueue(); + } + + return $queue; + } + + /** + * Adds a function to run in the task queue when it is next `run()` and + * returns a promise that is fulfilled or rejected with the result. + * + * @param callable $task Task function to run. + */ + public static function task(callable $task): PromiseInterface + { + $queue = self::queue(); + $promise = new Promise([$queue, 'run']); + $queue->add(function () use ($task, $promise): void { + try { + if (Is::pending($promise)) { + $promise->resolve($task()); + } + } catch (\Throwable $e) { + $promise->reject($e); + } + }); + + return $promise; + } + + /** + * Synchronously waits on a promise to resolve and returns an inspection + * state array. + * + * Returns a state associative array containing a "state" key mapping to a + * valid promise state. If the state of the promise is "fulfilled", the + * array will contain a "value" key mapping to the fulfilled value of the + * promise. If the promise is rejected, the array will contain a "reason" + * key mapping to the rejection reason of the promise. + * + * @param PromiseInterface $promise Promise or value. + */ + public static function inspect(PromiseInterface $promise): array + { + try { + return [ + 'state' => PromiseInterface::FULFILLED, + 'value' => $promise->wait(), + ]; + } catch (RejectionException $e) { + return ['state' => PromiseInterface::REJECTED, 'reason' => $e->getReason()]; + } catch (\Throwable $e) { + return ['state' => PromiseInterface::REJECTED, 'reason' => $e]; + } + } + + /** + * Waits on all of the provided promises, but does not unwrap rejected + * promises as thrown exception. + * + * Returns an array of inspection state arrays. + * + * @see inspect for the inspection state array format. + * + * @param PromiseInterface[] $promises Traversable of promises to wait upon. + */ + public static function inspectAll($promises): array + { + $results = []; + foreach ($promises as $key => $promise) { + $results[$key] = self::inspect($promise); + } + + return $results; + } + + /** + * Waits on all of the provided promises and returns the fulfilled values. + * + * Returns an array that contains the value of each promise (in the same + * order the promises were provided). An exception is thrown if any of the + * promises are rejected. + * + * @param iterable $promises Iterable of PromiseInterface objects to wait on. + * + * @throws \Throwable on error + */ + public static function unwrap($promises): array + { + $results = []; + foreach ($promises as $key => $promise) { + $results[$key] = $promise->wait(); + } + + return $results; + } + + /** + * Given an array of promises, return a promise that is fulfilled when all + * the items in the array are fulfilled. + * + * The promise's fulfillment value is an array with fulfillment values at + * respective positions to the original array. If any promise in the array + * rejects, the returned promise is rejected with the rejection reason. + * + * @param mixed $promises Promises or values. + * @param bool $recursive If true, resolves new promises that might have been added to the stack during its own resolution. + */ + public static function all($promises, bool $recursive = false): PromiseInterface + { + $results = []; + $promise = Each::of( + $promises, + function ($value, $idx) use (&$results): void { + $results[$idx] = $value; + }, + function ($reason, $idx, Promise $aggregate): void { + $aggregate->reject($reason); + } + )->then(function () use (&$results) { + ksort($results); + + return $results; + }); + + if (true === $recursive) { + $promise = $promise->then(function ($results) use ($recursive, &$promises) { + foreach ($promises as $promise) { + if (Is::pending($promise)) { + return self::all($promises, $recursive); + } + } + + return $results; + }); + } + + return $promise; + } + + /** + * Initiate a competitive race between multiple promises or values (values + * will become immediately fulfilled promises). + * + * When count amount of promises have been fulfilled, the returned promise + * is fulfilled with an array that contains the fulfillment values of the + * winners in order of resolution. + * + * This promise is rejected with a {@see AggregateException} if the number + * of fulfilled promises is less than the desired $count. + * + * @param int $count Total number of promises. + * @param mixed $promises Promises or values. + */ + public static function some(int $count, $promises): PromiseInterface + { + $results = []; + $rejections = []; + + return Each::of( + $promises, + function ($value, $idx, PromiseInterface $p) use (&$results, $count): void { + if (Is::settled($p)) { + return; + } + $results[$idx] = $value; + if (count($results) >= $count) { + $p->resolve(null); + } + }, + function ($reason) use (&$rejections): void { + $rejections[] = $reason; + } + )->then( + function () use (&$results, &$rejections, $count) { + if (count($results) !== $count) { + throw new AggregateException( + 'Not enough promises to fulfill count', + $rejections + ); + } + ksort($results); + + return array_values($results); + } + ); + } + + /** + * Like some(), with 1 as count. However, if the promise fulfills, the + * fulfillment value is not an array of 1 but the value directly. + * + * @param mixed $promises Promises or values. + */ + public static function any($promises): PromiseInterface + { + return self::some(1, $promises)->then(function ($values) { + return $values[0]; + }); + } + + /** + * Returns a promise that is fulfilled when all of the provided promises have + * been fulfilled or rejected. + * + * The returned promise is fulfilled with an array of inspection state arrays. + * + * @see inspect for the inspection state array format. + * + * @param mixed $promises Promises or values. + */ + public static function settle($promises): PromiseInterface + { + $results = []; + + return Each::of( + $promises, + function ($value, $idx) use (&$results): void { + $results[$idx] = ['state' => PromiseInterface::FULFILLED, 'value' => $value]; + }, + function ($reason, $idx) use (&$results): void { + $results[$idx] = ['state' => PromiseInterface::REJECTED, 'reason' => $reason]; + } + )->then(function () use (&$results) { + ksort($results); + + return $results; + }); + } +} diff --git a/vendor/guzzlehttp/psr7/CHANGELOG.md b/vendor/guzzlehttp/psr7/CHANGELOG.md new file mode 100644 index 0000000..fa716c0 --- /dev/null +++ b/vendor/guzzlehttp/psr7/CHANGELOG.md @@ -0,0 +1,416 @@ +# Change Log + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Unreleased + +## 2.5.0 - 2023-04-17 + +### Changed + +- Adjusted `psr/http-message` version constraint to `^1.1 || ^2.0` + +## 2.4.5 - 2023-04-17 + +### Fixed + +- Prevent possible warnings on unset variables in `ServerRequest::normalizeNestedFileSpec` +- Fixed `Message::bodySummary` when `preg_match` fails +- Fixed header validation issue + +## 2.4.4 - 2023-03-09 + +### Changed + +- Removed the need for `AllowDynamicProperties` in `LazyOpenStream` + +## 2.4.3 - 2022-10-26 + +### Changed + +- Replaced `sha1(uniqid())` by `bin2hex(random_bytes(20))` + +## 2.4.2 - 2022-10-25 + +### Fixed + +- Fixed erroneous behaviour when combining host and relative path + +## 2.4.1 - 2022-08-28 + +### Fixed + +- Rewind body before reading in `Message::bodySummary` + +## 2.4.0 - 2022-06-20 + +### Added + +- Added provisional PHP 8.2 support +- Added `UriComparator::isCrossOrigin` method + +## 2.3.0 - 2022-06-09 + +### Fixed + +- Added `Header::splitList` method +- Added `Utils::tryGetContents` method +- Improved `Stream::getContents` method +- Updated mimetype mappings + +## 2.2.2 - 2022-06-08 + +### Fixed + +- Fix `Message::parseRequestUri` for numeric headers +- Re-wrap exceptions thrown in `fread` into runtime exceptions +- Throw an exception when multipart options is misformatted + +## 2.2.1 - 2022-03-20 + +### Fixed + +- Correct header value validation + +## 2.2.0 - 2022-03-20 + +### Added + +- A more compressive list of mime types +- Add JsonSerializable to Uri +- Missing return types + +### Fixed + +- Bug MultipartStream no `uri` metadata +- Bug MultipartStream with filename for `data://` streams +- Fixed new line handling in MultipartStream +- Reduced RAM usage when copying streams +- Updated parsing in `Header::normalize()` + +## 2.1.1 - 2022-03-20 + +### Fixed + +- Validate header values properly + +## 2.1.0 - 2021-10-06 + +### Changed + +- Attempting to create a `Uri` object from a malformed URI will no longer throw a generic + `InvalidArgumentException`, but rather a `MalformedUriException`, which inherits from the former + for backwards compatibility. Callers relying on the exception being thrown to detect invalid + URIs should catch the new exception. + +### Fixed + +- Return `null` in caching stream size if remote size is `null` + +## 2.0.0 - 2021-06-30 + +Identical to the RC release. + +## 2.0.0@RC-1 - 2021-04-29 + +### Fixed + +- Handle possibly unset `url` in `stream_get_meta_data` + +## 2.0.0@beta-1 - 2021-03-21 + +### Added + +- PSR-17 factories +- Made classes final +- PHP7 type hints + +### Changed + +- When building a query string, booleans are represented as 1 and 0. + +### Removed + +- PHP < 7.2 support +- All functions in the `GuzzleHttp\Psr7` namespace + +## 1.8.1 - 2021-03-21 + +### Fixed + +- Issue parsing IPv6 URLs +- Issue modifying ServerRequest lost all its attributes + +## 1.8.0 - 2021-03-21 + +### Added + +- Locale independent URL parsing +- Most classes got a `@final` annotation to prepare for 2.0 + +### Fixed + +- Issue when creating stream from `php://input` and curl-ext is not installed +- Broken `Utils::tryFopen()` on PHP 8 + +## 1.7.0 - 2020-09-30 + +### Added + +- Replaced functions by static methods + +### Fixed + +- Converting a non-seekable stream to a string +- Handle multiple Set-Cookie correctly +- Ignore array keys in header values when merging +- Allow multibyte characters to be parsed in `Message:bodySummary()` + +### Changed + +- Restored partial HHVM 3 support + + +## [1.6.1] - 2019-07-02 + +### Fixed + +- Accept null and bool header values again + + +## [1.6.0] - 2019-06-30 + +### Added + +- Allowed version `^3.0` of `ralouphie/getallheaders` dependency (#244) +- Added MIME type for WEBP image format (#246) +- Added more validation of values according to PSR-7 and RFC standards, e.g. status code range (#250, #272) + +### Changed + +- Tests don't pass with HHVM 4.0, so HHVM support got dropped. Other libraries like composer have done the same. (#262) +- Accept port number 0 to be valid (#270) + +### Fixed + +- Fixed subsequent reads from `php://input` in ServerRequest (#247) +- Fixed readable/writable detection for certain stream modes (#248) +- Fixed encoding of special characters in the `userInfo` component of an URI (#253) + + +## [1.5.2] - 2018-12-04 + +### Fixed + +- Check body size when getting the message summary + + +## [1.5.1] - 2018-12-04 + +### Fixed + +- Get the summary of a body only if it is readable + + +## [1.5.0] - 2018-12-03 + +### Added + +- Response first-line to response string exception (fixes #145) +- A test for #129 behavior +- `get_message_body_summary` function in order to get the message summary +- `3gp` and `mkv` mime types + +### Changed + +- Clarify exception message when stream is detached + +### Deprecated + +- Deprecated parsing folded header lines as per RFC 7230 + +### Fixed + +- Fix `AppendStream::detach` to not close streams +- `InflateStream` preserves `isSeekable` attribute of the underlying stream +- `ServerRequest::getUriFromGlobals` to support URLs in query parameters + + +Several other fixes and improvements. + + +## [1.4.2] - 2017-03-20 + +### Fixed + +- Reverted BC break to `Uri::resolve` and `Uri::removeDotSegments` by removing + calls to `trigger_error` when deprecated methods are invoked. + + +## [1.4.1] - 2017-02-27 + +### Added + +- Rriggering of silenced deprecation warnings. + +### Fixed + +- Reverted BC break by reintroducing behavior to automagically fix a URI with a + relative path and an authority by adding a leading slash to the path. It's only + deprecated now. + + +## [1.4.0] - 2017-02-21 + +### Added + +- Added common URI utility methods based on RFC 3986 (see documentation in the readme): + - `Uri::isDefaultPort` + - `Uri::isAbsolute` + - `Uri::isNetworkPathReference` + - `Uri::isAbsolutePathReference` + - `Uri::isRelativePathReference` + - `Uri::isSameDocumentReference` + - `Uri::composeComponents` + - `UriNormalizer::normalize` + - `UriNormalizer::isEquivalent` + - `UriResolver::relativize` + +### Changed + +- Ensure `ServerRequest::getUriFromGlobals` returns a URI in absolute form. +- Allow `parse_response` to parse a response without delimiting space and reason. +- Ensure each URI modification results in a valid URI according to PSR-7 discussions. + Invalid modifications will throw an exception instead of returning a wrong URI or + doing some magic. + - `(new Uri)->withPath('foo')->withHost('example.com')` will throw an exception + because the path of a URI with an authority must start with a slash "/" or be empty + - `(new Uri())->withScheme('http')` will return `'http://localhost'` + +### Deprecated + +- `Uri::resolve` in favor of `UriResolver::resolve` +- `Uri::removeDotSegments` in favor of `UriResolver::removeDotSegments` + +### Fixed + +- `Stream::read` when length parameter <= 0. +- `copy_to_stream` reads bytes in chunks instead of `maxLen` into memory. +- `ServerRequest::getUriFromGlobals` when `Host` header contains port. +- Compatibility of URIs with `file` scheme and empty host. + + +## [1.3.1] - 2016-06-25 + +### Fixed + +- `Uri::__toString` for network path references, e.g. `//example.org`. +- Missing lowercase normalization for host. +- Handling of URI components in case they are `'0'` in a lot of places, + e.g. as a user info password. +- `Uri::withAddedHeader` to correctly merge headers with different case. +- Trimming of header values in `Uri::withAddedHeader`. Header values may + be surrounded by whitespace which should be ignored according to RFC 7230 + Section 3.2.4. This does not apply to header names. +- `Uri::withAddedHeader` with an array of header values. +- `Uri::resolve` when base path has no slash and handling of fragment. +- Handling of encoding in `Uri::with(out)QueryValue` so one can pass the + key/value both in encoded as well as decoded form to those methods. This is + consistent with withPath, withQuery etc. +- `ServerRequest::withoutAttribute` when attribute value is null. + + +## [1.3.0] - 2016-04-13 + +### Added + +- Remaining interfaces needed for full PSR7 compatibility + (ServerRequestInterface, UploadedFileInterface, etc.). +- Support for stream_for from scalars. + +### Changed + +- Can now extend Uri. + +### Fixed +- A bug in validating request methods by making it more permissive. + + +## [1.2.3] - 2016-02-18 + +### Fixed + +- Support in `GuzzleHttp\Psr7\CachingStream` for seeking forward on remote + streams, which can sometimes return fewer bytes than requested with `fread`. +- Handling of gzipped responses with FNAME headers. + + +## [1.2.2] - 2016-01-22 + +### Added + +- Support for URIs without any authority. +- Support for HTTP 451 'Unavailable For Legal Reasons.' +- Support for using '0' as a filename. +- Support for including non-standard ports in Host headers. + + +## [1.2.1] - 2015-11-02 + +### Changes + +- Now supporting negative offsets when seeking to SEEK_END. + + +## [1.2.0] - 2015-08-15 + +### Changed + +- Body as `"0"` is now properly added to a response. +- Now allowing forward seeking in CachingStream. +- Now properly parsing HTTP requests that contain proxy targets in + `parse_request`. +- functions.php is now conditionally required. +- user-info is no longer dropped when resolving URIs. + + +## [1.1.0] - 2015-06-24 + +### Changed + +- URIs can now be relative. +- `multipart/form-data` headers are now overridden case-insensitively. +- URI paths no longer encode the following characters because they are allowed + in URIs: "(", ")", "*", "!", "'" +- A port is no longer added to a URI when the scheme is missing and no port is + present. + + +## 1.0.0 - 2015-05-19 + +Initial release. + +Currently unsupported: + +- `Psr\Http\Message\ServerRequestInterface` +- `Psr\Http\Message\UploadedFileInterface` + + + +[1.6.0]: https://github.com/guzzle/psr7/compare/1.5.2...1.6.0 +[1.5.2]: https://github.com/guzzle/psr7/compare/1.5.1...1.5.2 +[1.5.1]: https://github.com/guzzle/psr7/compare/1.5.0...1.5.1 +[1.5.0]: https://github.com/guzzle/psr7/compare/1.4.2...1.5.0 +[1.4.2]: https://github.com/guzzle/psr7/compare/1.4.1...1.4.2 +[1.4.1]: https://github.com/guzzle/psr7/compare/1.4.0...1.4.1 +[1.4.0]: https://github.com/guzzle/psr7/compare/1.3.1...1.4.0 +[1.3.1]: https://github.com/guzzle/psr7/compare/1.3.0...1.3.1 +[1.3.0]: https://github.com/guzzle/psr7/compare/1.2.3...1.3.0 +[1.2.3]: https://github.com/guzzle/psr7/compare/1.2.2...1.2.3 +[1.2.2]: https://github.com/guzzle/psr7/compare/1.2.1...1.2.2 +[1.2.1]: https://github.com/guzzle/psr7/compare/1.2.0...1.2.1 +[1.2.0]: https://github.com/guzzle/psr7/compare/1.1.0...1.2.0 +[1.1.0]: https://github.com/guzzle/psr7/compare/1.0.0...1.1.0 diff --git a/vendor/guzzlehttp/psr7/LICENSE b/vendor/guzzlehttp/psr7/LICENSE new file mode 100644 index 0000000..51c7ec8 --- /dev/null +++ b/vendor/guzzlehttp/psr7/LICENSE @@ -0,0 +1,26 @@ +The MIT License (MIT) + +Copyright (c) 2015 Michael Dowling +Copyright (c) 2015 Márk Sági-Kazár +Copyright (c) 2015 Graham Campbell +Copyright (c) 2016 Tobias Schultze +Copyright (c) 2016 George Mponos +Copyright (c) 2018 Tobias Nyholm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/guzzlehttp/psr7/README.md b/vendor/guzzlehttp/psr7/README.md new file mode 100644 index 0000000..9566a7d --- /dev/null +++ b/vendor/guzzlehttp/psr7/README.md @@ -0,0 +1,880 @@ +# PSR-7 Message Implementation + +This repository contains a full [PSR-7](https://www.php-fig.org/psr/psr-7/) +message implementation, several stream decorators, and some helpful +functionality like query string parsing. + +![CI](https://github.com/guzzle/psr7/workflows/CI/badge.svg) +![Static analysis](https://github.com/guzzle/psr7/workflows/Static%20analysis/badge.svg) + + +# Installation + +```shell +composer require guzzlehttp/psr7 +``` + +# Stream implementation + +This package comes with a number of stream implementations and stream +decorators. + + +## AppendStream + +`GuzzleHttp\Psr7\AppendStream` + +Reads from multiple streams, one after the other. + +```php +use GuzzleHttp\Psr7; + +$a = Psr7\Utils::streamFor('abc, '); +$b = Psr7\Utils::streamFor('123.'); +$composed = new Psr7\AppendStream([$a, $b]); + +$composed->addStream(Psr7\Utils::streamFor(' Above all listen to me')); + +echo $composed; // abc, 123. Above all listen to me. +``` + + +## BufferStream + +`GuzzleHttp\Psr7\BufferStream` + +Provides a buffer stream that can be written to fill a buffer, and read +from to remove bytes from the buffer. + +This stream returns a "hwm" metadata value that tells upstream consumers +what the configured high water mark of the stream is, or the maximum +preferred size of the buffer. + +```php +use GuzzleHttp\Psr7; + +// When more than 1024 bytes are in the buffer, it will begin returning +// false to writes. This is an indication that writers should slow down. +$buffer = new Psr7\BufferStream(1024); +``` + + +## CachingStream + +The CachingStream is used to allow seeking over previously read bytes on +non-seekable streams. This can be useful when transferring a non-seekable +entity body fails due to needing to rewind the stream (for example, resulting +from a redirect). Data that is read from the remote stream will be buffered in +a PHP temp stream so that previously read bytes are cached first in memory, +then on disk. + +```php +use GuzzleHttp\Psr7; + +$original = Psr7\Utils::streamFor(fopen('http://www.google.com', 'r')); +$stream = new Psr7\CachingStream($original); + +$stream->read(1024); +echo $stream->tell(); +// 1024 + +$stream->seek(0); +echo $stream->tell(); +// 0 +``` + + +## DroppingStream + +`GuzzleHttp\Psr7\DroppingStream` + +Stream decorator that begins dropping data once the size of the underlying +stream becomes too full. + +```php +use GuzzleHttp\Psr7; + +// Create an empty stream +$stream = Psr7\Utils::streamFor(); + +// Start dropping data when the stream has more than 10 bytes +$dropping = new Psr7\DroppingStream($stream, 10); + +$dropping->write('01234567890123456789'); +echo $stream; // 0123456789 +``` + + +## FnStream + +`GuzzleHttp\Psr7\FnStream` + +Compose stream implementations based on a hash of functions. + +Allows for easy testing and extension of a provided stream without needing +to create a concrete class for a simple extension point. + +```php + +use GuzzleHttp\Psr7; + +$stream = Psr7\Utils::streamFor('hi'); +$fnStream = Psr7\FnStream::decorate($stream, [ + 'rewind' => function () use ($stream) { + echo 'About to rewind - '; + $stream->rewind(); + echo 'rewound!'; + } +]); + +$fnStream->rewind(); +// Outputs: About to rewind - rewound! +``` + + +## InflateStream + +`GuzzleHttp\Psr7\InflateStream` + +Uses PHP's zlib.inflate filter to inflate zlib (HTTP deflate, RFC1950) or gzipped (RFC1952) content. + +This stream decorator converts the provided stream to a PHP stream resource, +then appends the zlib.inflate filter. The stream is then converted back +to a Guzzle stream resource to be used as a Guzzle stream. + + +## LazyOpenStream + +`GuzzleHttp\Psr7\LazyOpenStream` + +Lazily reads or writes to a file that is opened only after an IO operation +take place on the stream. + +```php +use GuzzleHttp\Psr7; + +$stream = new Psr7\LazyOpenStream('/path/to/file', 'r'); +// The file has not yet been opened... + +echo $stream->read(10); +// The file is opened and read from only when needed. +``` + + +## LimitStream + +`GuzzleHttp\Psr7\LimitStream` + +LimitStream can be used to read a subset or slice of an existing stream object. +This can be useful for breaking a large file into smaller pieces to be sent in +chunks (e.g. Amazon S3's multipart upload API). + +```php +use GuzzleHttp\Psr7; + +$original = Psr7\Utils::streamFor(fopen('/tmp/test.txt', 'r+')); +echo $original->getSize(); +// >>> 1048576 + +// Limit the size of the body to 1024 bytes and start reading from byte 2048 +$stream = new Psr7\LimitStream($original, 1024, 2048); +echo $stream->getSize(); +// >>> 1024 +echo $stream->tell(); +// >>> 0 +``` + + +## MultipartStream + +`GuzzleHttp\Psr7\MultipartStream` + +Stream that when read returns bytes for a streaming multipart or +multipart/form-data stream. + + +## NoSeekStream + +`GuzzleHttp\Psr7\NoSeekStream` + +NoSeekStream wraps a stream and does not allow seeking. + +```php +use GuzzleHttp\Psr7; + +$original = Psr7\Utils::streamFor('foo'); +$noSeek = new Psr7\NoSeekStream($original); + +echo $noSeek->read(3); +// foo +var_export($noSeek->isSeekable()); +// false +$noSeek->seek(0); +var_export($noSeek->read(3)); +// NULL +``` + + +## PumpStream + +`GuzzleHttp\Psr7\PumpStream` + +Provides a read only stream that pumps data from a PHP callable. + +When invoking the provided callable, the PumpStream will pass the amount of +data requested to read to the callable. The callable can choose to ignore +this value and return fewer or more bytes than requested. Any extra data +returned by the provided callable is buffered internally until drained using +the read() function of the PumpStream. The provided callable MUST return +false when there is no more data to read. + + +## Implementing stream decorators + +Creating a stream decorator is very easy thanks to the +`GuzzleHttp\Psr7\StreamDecoratorTrait`. This trait provides methods that +implement `Psr\Http\Message\StreamInterface` by proxying to an underlying +stream. Just `use` the `StreamDecoratorTrait` and implement your custom +methods. + +For example, let's say we wanted to call a specific function each time the last +byte is read from a stream. This could be implemented by overriding the +`read()` method. + +```php +use Psr\Http\Message\StreamInterface; +use GuzzleHttp\Psr7\StreamDecoratorTrait; + +class EofCallbackStream implements StreamInterface +{ + use StreamDecoratorTrait; + + private $callback; + + private $stream; + + public function __construct(StreamInterface $stream, callable $cb) + { + $this->stream = $stream; + $this->callback = $cb; + } + + public function read($length) + { + $result = $this->stream->read($length); + + // Invoke the callback when EOF is hit. + if ($this->eof()) { + call_user_func($this->callback); + } + + return $result; + } +} +``` + +This decorator could be added to any existing stream and used like so: + +```php +use GuzzleHttp\Psr7; + +$original = Psr7\Utils::streamFor('foo'); + +$eofStream = new EofCallbackStream($original, function () { + echo 'EOF!'; +}); + +$eofStream->read(2); +$eofStream->read(1); +// echoes "EOF!" +$eofStream->seek(0); +$eofStream->read(3); +// echoes "EOF!" +``` + + +## PHP StreamWrapper + +You can use the `GuzzleHttp\Psr7\StreamWrapper` class if you need to use a +PSR-7 stream as a PHP stream resource. + +Use the `GuzzleHttp\Psr7\StreamWrapper::getResource()` method to create a PHP +stream from a PSR-7 stream. + +```php +use GuzzleHttp\Psr7\StreamWrapper; + +$stream = GuzzleHttp\Psr7\Utils::streamFor('hello!'); +$resource = StreamWrapper::getResource($stream); +echo fread($resource, 6); // outputs hello! +``` + + +# Static API + +There are various static methods available under the `GuzzleHttp\Psr7` namespace. + + +## `GuzzleHttp\Psr7\Message::toString` + +`public static function toString(MessageInterface $message): string` + +Returns the string representation of an HTTP message. + +```php +$request = new GuzzleHttp\Psr7\Request('GET', 'http://example.com'); +echo GuzzleHttp\Psr7\Message::toString($request); +``` + + +## `GuzzleHttp\Psr7\Message::bodySummary` + +`public static function bodySummary(MessageInterface $message, int $truncateAt = 120): string|null` + +Get a short summary of the message body. + +Will return `null` if the response is not printable. + + +## `GuzzleHttp\Psr7\Message::rewindBody` + +`public static function rewindBody(MessageInterface $message): void` + +Attempts to rewind a message body and throws an exception on failure. + +The body of the message will only be rewound if a call to `tell()` +returns a value other than `0`. + + +## `GuzzleHttp\Psr7\Message::parseMessage` + +`public static function parseMessage(string $message): array` + +Parses an HTTP message into an associative array. + +The array contains the "start-line" key containing the start line of +the message, "headers" key containing an associative array of header +array values, and a "body" key containing the body of the message. + + +## `GuzzleHttp\Psr7\Message::parseRequestUri` + +`public static function parseRequestUri(string $path, array $headers): string` + +Constructs a URI for an HTTP request message. + + +## `GuzzleHttp\Psr7\Message::parseRequest` + +`public static function parseRequest(string $message): Request` + +Parses a request message string into a request object. + + +## `GuzzleHttp\Psr7\Message::parseResponse` + +`public static function parseResponse(string $message): Response` + +Parses a response message string into a response object. + + +## `GuzzleHttp\Psr7\Header::parse` + +`public static function parse(string|array $header): array` + +Parse an array of header values containing ";" separated data into an +array of associative arrays representing the header key value pair data +of the header. When a parameter does not contain a value, but just +contains a key, this function will inject a key with a '' string value. + + +## `GuzzleHttp\Psr7\Header::splitList` + +`public static function splitList(string|string[] $header): string[]` + +Splits a HTTP header defined to contain a comma-separated list into +each individual value: + +``` +$knownEtags = Header::splitList($request->getHeader('if-none-match')); +``` + +Example headers include `accept`, `cache-control` and `if-none-match`. + + +## `GuzzleHttp\Psr7\Header::normalize` (deprecated) + +`public static function normalize(string|array $header): array` + +`Header::normalize()` is deprecated in favor of [`Header::splitList()`](README.md#guzzlehttppsr7headersplitlist) +which performs the same operation with a cleaned up API and improved +documentation. + +Converts an array of header values that may contain comma separated +headers into an array of headers with no comma separated values. + + +## `GuzzleHttp\Psr7\Query::parse` + +`public static function parse(string $str, int|bool $urlEncoding = true): array` + +Parse a query string into an associative array. + +If multiple values are found for the same key, the value of that key +value pair will become an array. This function does not parse nested +PHP style arrays into an associative array (e.g., `foo[a]=1&foo[b]=2` +will be parsed into `['foo[a]' => '1', 'foo[b]' => '2'])`. + + +## `GuzzleHttp\Psr7\Query::build` + +`public static function build(array $params, int|false $encoding = PHP_QUERY_RFC3986): string` + +Build a query string from an array of key value pairs. + +This function can use the return value of `parse()` to build a query +string. This function does not modify the provided keys when an array is +encountered (like `http_build_query()` would). + + +## `GuzzleHttp\Psr7\Utils::caselessRemove` + +`public static function caselessRemove(iterable $keys, $keys, array $data): array` + +Remove the items given by the keys, case insensitively from the data. + + +## `GuzzleHttp\Psr7\Utils::copyToStream` + +`public static function copyToStream(StreamInterface $source, StreamInterface $dest, int $maxLen = -1): void` + +Copy the contents of a stream into another stream until the given number +of bytes have been read. + + +## `GuzzleHttp\Psr7\Utils::copyToString` + +`public static function copyToString(StreamInterface $stream, int $maxLen = -1): string` + +Copy the contents of a stream into a string until the given number of +bytes have been read. + + +## `GuzzleHttp\Psr7\Utils::hash` + +`public static function hash(StreamInterface $stream, string $algo, bool $rawOutput = false): string` + +Calculate a hash of a stream. + +This method reads the entire stream to calculate a rolling hash, based on +PHP's `hash_init` functions. + + +## `GuzzleHttp\Psr7\Utils::modifyRequest` + +`public static function modifyRequest(RequestInterface $request, array $changes): RequestInterface` + +Clone and modify a request with the given changes. + +This method is useful for reducing the number of clones needed to mutate +a message. + +- method: (string) Changes the HTTP method. +- set_headers: (array) Sets the given headers. +- remove_headers: (array) Remove the given headers. +- body: (mixed) Sets the given body. +- uri: (UriInterface) Set the URI. +- query: (string) Set the query string value of the URI. +- version: (string) Set the protocol version. + + +## `GuzzleHttp\Psr7\Utils::readLine` + +`public static function readLine(StreamInterface $stream, int $maxLength = null): string` + +Read a line from the stream up to the maximum allowed buffer length. + + +## `GuzzleHttp\Psr7\Utils::streamFor` + +`public static function streamFor(resource|string|null|int|float|bool|StreamInterface|callable|\Iterator $resource = '', array $options = []): StreamInterface` + +Create a new stream based on the input type. + +Options is an associative array that can contain the following keys: + +- metadata: Array of custom metadata. +- size: Size of the stream. + +This method accepts the following `$resource` types: + +- `Psr\Http\Message\StreamInterface`: Returns the value as-is. +- `string`: Creates a stream object that uses the given string as the contents. +- `resource`: Creates a stream object that wraps the given PHP stream resource. +- `Iterator`: If the provided value implements `Iterator`, then a read-only + stream object will be created that wraps the given iterable. Each time the + stream is read from, data from the iterator will fill a buffer and will be + continuously called until the buffer is equal to the requested read size. + Subsequent read calls will first read from the buffer and then call `next` + on the underlying iterator until it is exhausted. +- `object` with `__toString()`: If the object has the `__toString()` method, + the object will be cast to a string and then a stream will be returned that + uses the string value. +- `NULL`: When `null` is passed, an empty stream object is returned. +- `callable` When a callable is passed, a read-only stream object will be + created that invokes the given callable. The callable is invoked with the + number of suggested bytes to read. The callable can return any number of + bytes, but MUST return `false` when there is no more data to return. The + stream object that wraps the callable will invoke the callable until the + number of requested bytes are available. Any additional bytes will be + buffered and used in subsequent reads. + +```php +$stream = GuzzleHttp\Psr7\Utils::streamFor('foo'); +$stream = GuzzleHttp\Psr7\Utils::streamFor(fopen('/path/to/file', 'r')); + +$generator = function ($bytes) { + for ($i = 0; $i < $bytes; $i++) { + yield ' '; + } +} + +$stream = GuzzleHttp\Psr7\Utils::streamFor($generator(100)); +``` + + +## `GuzzleHttp\Psr7\Utils::tryFopen` + +`public static function tryFopen(string $filename, string $mode): resource` + +Safely opens a PHP stream resource using a filename. + +When fopen fails, PHP normally raises a warning. This function adds an +error handler that checks for errors and throws an exception instead. + + +## `GuzzleHttp\Psr7\Utils::tryGetContents` + +`public static function tryGetContents(resource $stream): string` + +Safely gets the contents of a given stream. + +When stream_get_contents fails, PHP normally raises a warning. This +function adds an error handler that checks for errors and throws an +exception instead. + + +## `GuzzleHttp\Psr7\Utils::uriFor` + +`public static function uriFor(string|UriInterface $uri): UriInterface` + +Returns a UriInterface for the given value. + +This function accepts a string or UriInterface and returns a +UriInterface for the given value. If the value is already a +UriInterface, it is returned as-is. + + +## `GuzzleHttp\Psr7\MimeType::fromFilename` + +`public static function fromFilename(string $filename): string|null` + +Determines the mimetype of a file by looking at its extension. + + +## `GuzzleHttp\Psr7\MimeType::fromExtension` + +`public static function fromExtension(string $extension): string|null` + +Maps a file extensions to a mimetype. + + +## Upgrading from Function API + +The static API was first introduced in 1.7.0, in order to mitigate problems with functions conflicting between global and local copies of the package. The function API was removed in 2.0.0. A migration table has been provided here for your convenience: + +| Original Function | Replacement Method | +|----------------|----------------| +| `str` | `Message::toString` | +| `uri_for` | `Utils::uriFor` | +| `stream_for` | `Utils::streamFor` | +| `parse_header` | `Header::parse` | +| `normalize_header` | `Header::normalize` | +| `modify_request` | `Utils::modifyRequest` | +| `rewind_body` | `Message::rewindBody` | +| `try_fopen` | `Utils::tryFopen` | +| `copy_to_string` | `Utils::copyToString` | +| `copy_to_stream` | `Utils::copyToStream` | +| `hash` | `Utils::hash` | +| `readline` | `Utils::readLine` | +| `parse_request` | `Message::parseRequest` | +| `parse_response` | `Message::parseResponse` | +| `parse_query` | `Query::parse` | +| `build_query` | `Query::build` | +| `mimetype_from_filename` | `MimeType::fromFilename` | +| `mimetype_from_extension` | `MimeType::fromExtension` | +| `_parse_message` | `Message::parseMessage` | +| `_parse_request_uri` | `Message::parseRequestUri` | +| `get_message_body_summary` | `Message::bodySummary` | +| `_caseless_remove` | `Utils::caselessRemove` | + + +# Additional URI Methods + +Aside from the standard `Psr\Http\Message\UriInterface` implementation in form of the `GuzzleHttp\Psr7\Uri` class, +this library also provides additional functionality when working with URIs as static methods. + +## URI Types + +An instance of `Psr\Http\Message\UriInterface` can either be an absolute URI or a relative reference. +An absolute URI has a scheme. A relative reference is used to express a URI relative to another URI, +the base URI. Relative references can be divided into several forms according to +[RFC 3986 Section 4.2](https://tools.ietf.org/html/rfc3986#section-4.2): + +- network-path references, e.g. `//example.com/path` +- absolute-path references, e.g. `/path` +- relative-path references, e.g. `subpath` + +The following methods can be used to identify the type of the URI. + +### `GuzzleHttp\Psr7\Uri::isAbsolute` + +`public static function isAbsolute(UriInterface $uri): bool` + +Whether the URI is absolute, i.e. it has a scheme. + +### `GuzzleHttp\Psr7\Uri::isNetworkPathReference` + +`public static function isNetworkPathReference(UriInterface $uri): bool` + +Whether the URI is a network-path reference. A relative reference that begins with two slash characters is +termed an network-path reference. + +### `GuzzleHttp\Psr7\Uri::isAbsolutePathReference` + +`public static function isAbsolutePathReference(UriInterface $uri): bool` + +Whether the URI is a absolute-path reference. A relative reference that begins with a single slash character is +termed an absolute-path reference. + +### `GuzzleHttp\Psr7\Uri::isRelativePathReference` + +`public static function isRelativePathReference(UriInterface $uri): bool` + +Whether the URI is a relative-path reference. A relative reference that does not begin with a slash character is +termed a relative-path reference. + +### `GuzzleHttp\Psr7\Uri::isSameDocumentReference` + +`public static function isSameDocumentReference(UriInterface $uri, UriInterface $base = null): bool` + +Whether the URI is a same-document reference. A same-document reference refers to a URI that is, aside from its +fragment component, identical to the base URI. When no base URI is given, only an empty URI reference +(apart from its fragment) is considered a same-document reference. + +## URI Components + +Additional methods to work with URI components. + +### `GuzzleHttp\Psr7\Uri::isDefaultPort` + +`public static function isDefaultPort(UriInterface $uri): bool` + +Whether the URI has the default port of the current scheme. `Psr\Http\Message\UriInterface::getPort` may return null +or the standard port. This method can be used independently of the implementation. + +### `GuzzleHttp\Psr7\Uri::composeComponents` + +`public static function composeComponents($scheme, $authority, $path, $query, $fragment): string` + +Composes a URI reference string from its various components according to +[RFC 3986 Section 5.3](https://tools.ietf.org/html/rfc3986#section-5.3). Usually this method does not need to be called +manually but instead is used indirectly via `Psr\Http\Message\UriInterface::__toString`. + +### `GuzzleHttp\Psr7\Uri::fromParts` + +`public static function fromParts(array $parts): UriInterface` + +Creates a URI from a hash of [`parse_url`](https://www.php.net/manual/en/function.parse-url.php) components. + + +### `GuzzleHttp\Psr7\Uri::withQueryValue` + +`public static function withQueryValue(UriInterface $uri, $key, $value): UriInterface` + +Creates a new URI with a specific query string value. Any existing query string values that exactly match the +provided key are removed and replaced with the given key value pair. A value of null will set the query string +key without a value, e.g. "key" instead of "key=value". + +### `GuzzleHttp\Psr7\Uri::withQueryValues` + +`public static function withQueryValues(UriInterface $uri, array $keyValueArray): UriInterface` + +Creates a new URI with multiple query string values. It has the same behavior as `withQueryValue()` but for an +associative array of key => value. + +### `GuzzleHttp\Psr7\Uri::withoutQueryValue` + +`public static function withoutQueryValue(UriInterface $uri, $key): UriInterface` + +Creates a new URI with a specific query string value removed. Any existing query string values that exactly match the +provided key are removed. + +## Cross-Origin Detection + +`GuzzleHttp\Psr7\UriComparator` provides methods to determine if a modified URL should be considered cross-origin. + +### `GuzzleHttp\Psr7\UriComparator::isCrossOrigin` + +`public static function isCrossOrigin(UriInterface $original, UriInterface $modified): bool` + +Determines if a modified URL should be considered cross-origin with respect to an original URL. + +## Reference Resolution + +`GuzzleHttp\Psr7\UriResolver` provides methods to resolve a URI reference in the context of a base URI according +to [RFC 3986 Section 5](https://tools.ietf.org/html/rfc3986#section-5). This is for example also what web browsers +do when resolving a link in a website based on the current request URI. + +### `GuzzleHttp\Psr7\UriResolver::resolve` + +`public static function resolve(UriInterface $base, UriInterface $rel): UriInterface` + +Converts the relative URI into a new URI that is resolved against the base URI. + +### `GuzzleHttp\Psr7\UriResolver::removeDotSegments` + +`public static function removeDotSegments(string $path): string` + +Removes dot segments from a path and returns the new path according to +[RFC 3986 Section 5.2.4](https://tools.ietf.org/html/rfc3986#section-5.2.4). + +### `GuzzleHttp\Psr7\UriResolver::relativize` + +`public static function relativize(UriInterface $base, UriInterface $target): UriInterface` + +Returns the target URI as a relative reference from the base URI. This method is the counterpart to resolve(): + +```php +(string) $target === (string) UriResolver::resolve($base, UriResolver::relativize($base, $target)) +``` + +One use-case is to use the current request URI as base URI and then generate relative links in your documents +to reduce the document size or offer self-contained downloadable document archives. + +```php +$base = new Uri('http://example.com/a/b/'); +echo UriResolver::relativize($base, new Uri('http://example.com/a/b/c')); // prints 'c'. +echo UriResolver::relativize($base, new Uri('http://example.com/a/x/y')); // prints '../x/y'. +echo UriResolver::relativize($base, new Uri('http://example.com/a/b/?q')); // prints '?q'. +echo UriResolver::relativize($base, new Uri('http://example.org/a/b/')); // prints '//example.org/a/b/'. +``` + +## Normalization and Comparison + +`GuzzleHttp\Psr7\UriNormalizer` provides methods to normalize and compare URIs according to +[RFC 3986 Section 6](https://tools.ietf.org/html/rfc3986#section-6). + +### `GuzzleHttp\Psr7\UriNormalizer::normalize` + +`public static function normalize(UriInterface $uri, $flags = self::PRESERVING_NORMALIZATIONS): UriInterface` + +Returns a normalized URI. The scheme and host component are already normalized to lowercase per PSR-7 UriInterface. +This methods adds additional normalizations that can be configured with the `$flags` parameter which is a bitmask +of normalizations to apply. The following normalizations are available: + +- `UriNormalizer::PRESERVING_NORMALIZATIONS` + + Default normalizations which only include the ones that preserve semantics. + +- `UriNormalizer::CAPITALIZE_PERCENT_ENCODING` + + All letters within a percent-encoding triplet (e.g., "%3A") are case-insensitive, and should be capitalized. + + Example: `http://example.org/a%c2%b1b` → `http://example.org/a%C2%B1b` + +- `UriNormalizer::DECODE_UNRESERVED_CHARACTERS` + + Decodes percent-encoded octets of unreserved characters. For consistency, percent-encoded octets in the ranges of + ALPHA (%41–%5A and %61–%7A), DIGIT (%30–%39), hyphen (%2D), period (%2E), underscore (%5F), or tilde (%7E) should + not be created by URI producers and, when found in a URI, should be decoded to their corresponding unreserved + characters by URI normalizers. + + Example: `http://example.org/%7Eusern%61me/` → `http://example.org/~username/` + +- `UriNormalizer::CONVERT_EMPTY_PATH` + + Converts the empty path to "/" for http and https URIs. + + Example: `http://example.org` → `http://example.org/` + +- `UriNormalizer::REMOVE_DEFAULT_HOST` + + Removes the default host of the given URI scheme from the URI. Only the "file" scheme defines the default host + "localhost". All of `file:/myfile`, `file:///myfile`, and `file://localhost/myfile` are equivalent according to + RFC 3986. + + Example: `file://localhost/myfile` → `file:///myfile` + +- `UriNormalizer::REMOVE_DEFAULT_PORT` + + Removes the default port of the given URI scheme from the URI. + + Example: `http://example.org:80/` → `http://example.org/` + +- `UriNormalizer::REMOVE_DOT_SEGMENTS` + + Removes unnecessary dot-segments. Dot-segments in relative-path references are not removed as it would + change the semantics of the URI reference. + + Example: `http://example.org/../a/b/../c/./d.html` → `http://example.org/a/c/d.html` + +- `UriNormalizer::REMOVE_DUPLICATE_SLASHES` + + Paths which include two or more adjacent slashes are converted to one. Webservers usually ignore duplicate slashes + and treat those URIs equivalent. But in theory those URIs do not need to be equivalent. So this normalization + may change the semantics. Encoded slashes (%2F) are not removed. + + Example: `http://example.org//foo///bar.html` → `http://example.org/foo/bar.html` + +- `UriNormalizer::SORT_QUERY_PARAMETERS` + + Sort query parameters with their values in alphabetical order. However, the order of parameters in a URI may be + significant (this is not defined by the standard). So this normalization is not safe and may change the semantics + of the URI. + + Example: `?lang=en&article=fred` → `?article=fred&lang=en` + +### `GuzzleHttp\Psr7\UriNormalizer::isEquivalent` + +`public static function isEquivalent(UriInterface $uri1, UriInterface $uri2, $normalizations = self::PRESERVING_NORMALIZATIONS): bool` + +Whether two URIs can be considered equivalent. Both URIs are normalized automatically before comparison with the given +`$normalizations` bitmask. The method also accepts relative URI references and returns true when they are equivalent. +This of course assumes they will be resolved against the same base URI. If this is not the case, determination of +equivalence or difference of relative references does not mean anything. + + +## Version Guidance + +| Version | Status | PHP Version | +|---------|----------------|------------------| +| 1.x | Security fixes | >=5.4,<8.1 | +| 2.x | Latest | ^7.2.5 \|\| ^8.0 | + + +## Security + +If you discover a security vulnerability within this package, please send an email to security@tidelift.com. All security vulnerabilities will be promptly addressed. Please do not disclose security-related issues publicly until a fix has been announced. Please see [Security Policy](https://github.com/guzzle/psr7/security/policy) for more information. + + +## License + +Guzzle is made available under the MIT License (MIT). Please see [License File](LICENSE) for more information. + + +## For Enterprise + +Available as part of the Tidelift Subscription + +The maintainers of Guzzle and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/packagist-guzzlehttp-psr7?utm_source=packagist-guzzlehttp-psr7&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) diff --git a/vendor/guzzlehttp/psr7/src/AppendStream.php b/vendor/guzzlehttp/psr7/src/AppendStream.php new file mode 100644 index 0000000..cbcfaee --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/AppendStream.php @@ -0,0 +1,248 @@ +addStream($stream); + } + } + + public function __toString(): string + { + try { + $this->rewind(); + return $this->getContents(); + } catch (\Throwable $e) { + if (\PHP_VERSION_ID >= 70400) { + throw $e; + } + trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR); + return ''; + } + } + + /** + * Add a stream to the AppendStream + * + * @param StreamInterface $stream Stream to append. Must be readable. + * + * @throws \InvalidArgumentException if the stream is not readable + */ + public function addStream(StreamInterface $stream): void + { + if (!$stream->isReadable()) { + throw new \InvalidArgumentException('Each stream must be readable'); + } + + // The stream is only seekable if all streams are seekable + if (!$stream->isSeekable()) { + $this->seekable = false; + } + + $this->streams[] = $stream; + } + + public function getContents(): string + { + return Utils::copyToString($this); + } + + /** + * Closes each attached stream. + */ + public function close(): void + { + $this->pos = $this->current = 0; + $this->seekable = true; + + foreach ($this->streams as $stream) { + $stream->close(); + } + + $this->streams = []; + } + + /** + * Detaches each attached stream. + * + * Returns null as it's not clear which underlying stream resource to return. + */ + public function detach() + { + $this->pos = $this->current = 0; + $this->seekable = true; + + foreach ($this->streams as $stream) { + $stream->detach(); + } + + $this->streams = []; + + return null; + } + + public function tell(): int + { + return $this->pos; + } + + /** + * Tries to calculate the size by adding the size of each stream. + * + * If any of the streams do not return a valid number, then the size of the + * append stream cannot be determined and null is returned. + */ + public function getSize(): ?int + { + $size = 0; + + foreach ($this->streams as $stream) { + $s = $stream->getSize(); + if ($s === null) { + return null; + } + $size += $s; + } + + return $size; + } + + public function eof(): bool + { + return !$this->streams || + ($this->current >= count($this->streams) - 1 && + $this->streams[$this->current]->eof()); + } + + public function rewind(): void + { + $this->seek(0); + } + + /** + * Attempts to seek to the given position. Only supports SEEK_SET. + */ + public function seek($offset, $whence = SEEK_SET): void + { + if (!$this->seekable) { + throw new \RuntimeException('This AppendStream is not seekable'); + } elseif ($whence !== SEEK_SET) { + throw new \RuntimeException('The AppendStream can only seek with SEEK_SET'); + } + + $this->pos = $this->current = 0; + + // Rewind each stream + foreach ($this->streams as $i => $stream) { + try { + $stream->rewind(); + } catch (\Exception $e) { + throw new \RuntimeException('Unable to seek stream ' + . $i . ' of the AppendStream', 0, $e); + } + } + + // Seek to the actual position by reading from each stream + while ($this->pos < $offset && !$this->eof()) { + $result = $this->read(min(8096, $offset - $this->pos)); + if ($result === '') { + break; + } + } + } + + /** + * Reads from all of the appended streams until the length is met or EOF. + */ + public function read($length): string + { + $buffer = ''; + $total = count($this->streams) - 1; + $remaining = $length; + $progressToNext = false; + + while ($remaining > 0) { + // Progress to the next stream if needed. + if ($progressToNext || $this->streams[$this->current]->eof()) { + $progressToNext = false; + if ($this->current === $total) { + break; + } + $this->current++; + } + + $result = $this->streams[$this->current]->read($remaining); + + if ($result === '') { + $progressToNext = true; + continue; + } + + $buffer .= $result; + $remaining = $length - strlen($buffer); + } + + $this->pos += strlen($buffer); + + return $buffer; + } + + public function isReadable(): bool + { + return true; + } + + public function isWritable(): bool + { + return false; + } + + public function isSeekable(): bool + { + return $this->seekable; + } + + public function write($string): int + { + throw new \RuntimeException('Cannot write to an AppendStream'); + } + + /** + * {@inheritdoc} + * + * @return mixed + */ + public function getMetadata($key = null) + { + return $key ? null : []; + } +} diff --git a/vendor/guzzlehttp/psr7/src/BufferStream.php b/vendor/guzzlehttp/psr7/src/BufferStream.php new file mode 100644 index 0000000..21be8c0 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/BufferStream.php @@ -0,0 +1,149 @@ +hwm = $hwm; + } + + public function __toString(): string + { + return $this->getContents(); + } + + public function getContents(): string + { + $buffer = $this->buffer; + $this->buffer = ''; + + return $buffer; + } + + public function close(): void + { + $this->buffer = ''; + } + + public function detach() + { + $this->close(); + + return null; + } + + public function getSize(): ?int + { + return strlen($this->buffer); + } + + public function isReadable(): bool + { + return true; + } + + public function isWritable(): bool + { + return true; + } + + public function isSeekable(): bool + { + return false; + } + + public function rewind(): void + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET): void + { + throw new \RuntimeException('Cannot seek a BufferStream'); + } + + public function eof(): bool + { + return strlen($this->buffer) === 0; + } + + public function tell(): int + { + throw new \RuntimeException('Cannot determine the position of a BufferStream'); + } + + /** + * Reads data from the buffer. + */ + public function read($length): string + { + $currentLength = strlen($this->buffer); + + if ($length >= $currentLength) { + // No need to slice the buffer because we don't have enough data. + $result = $this->buffer; + $this->buffer = ''; + } else { + // Slice up the result to provide a subset of the buffer. + $result = substr($this->buffer, 0, $length); + $this->buffer = substr($this->buffer, $length); + } + + return $result; + } + + /** + * Writes data to the buffer. + */ + public function write($string): int + { + $this->buffer .= $string; + + if (strlen($this->buffer) >= $this->hwm) { + return 0; + } + + return strlen($string); + } + + /** + * {@inheritdoc} + * + * @return mixed + */ + public function getMetadata($key = null) + { + if ($key === 'hwm') { + return $this->hwm; + } + + return $key ? null : []; + } +} diff --git a/vendor/guzzlehttp/psr7/src/CachingStream.php b/vendor/guzzlehttp/psr7/src/CachingStream.php new file mode 100644 index 0000000..f34722c --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/CachingStream.php @@ -0,0 +1,153 @@ +remoteStream = $stream; + $this->stream = $target ?: new Stream(Utils::tryFopen('php://temp', 'r+')); + } + + public function getSize(): ?int + { + $remoteSize = $this->remoteStream->getSize(); + + if (null === $remoteSize) { + return null; + } + + return max($this->stream->getSize(), $remoteSize); + } + + public function rewind(): void + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET): void + { + if ($whence === SEEK_SET) { + $byte = $offset; + } elseif ($whence === SEEK_CUR) { + $byte = $offset + $this->tell(); + } elseif ($whence === SEEK_END) { + $size = $this->remoteStream->getSize(); + if ($size === null) { + $size = $this->cacheEntireStream(); + } + $byte = $size + $offset; + } else { + throw new \InvalidArgumentException('Invalid whence'); + } + + $diff = $byte - $this->stream->getSize(); + + if ($diff > 0) { + // Read the remoteStream until we have read in at least the amount + // of bytes requested, or we reach the end of the file. + while ($diff > 0 && !$this->remoteStream->eof()) { + $this->read($diff); + $diff = $byte - $this->stream->getSize(); + } + } else { + // We can just do a normal seek since we've already seen this byte. + $this->stream->seek($byte); + } + } + + public function read($length): string + { + // Perform a regular read on any previously read data from the buffer + $data = $this->stream->read($length); + $remaining = $length - strlen($data); + + // More data was requested so read from the remote stream + if ($remaining) { + // If data was written to the buffer in a position that would have + // been filled from the remote stream, then we must skip bytes on + // the remote stream to emulate overwriting bytes from that + // position. This mimics the behavior of other PHP stream wrappers. + $remoteData = $this->remoteStream->read( + $remaining + $this->skipReadBytes + ); + + if ($this->skipReadBytes) { + $len = strlen($remoteData); + $remoteData = substr($remoteData, $this->skipReadBytes); + $this->skipReadBytes = max(0, $this->skipReadBytes - $len); + } + + $data .= $remoteData; + $this->stream->write($remoteData); + } + + return $data; + } + + public function write($string): int + { + // When appending to the end of the currently read stream, you'll want + // to skip bytes from being read from the remote stream to emulate + // other stream wrappers. Basically replacing bytes of data of a fixed + // length. + $overflow = (strlen($string) + $this->tell()) - $this->remoteStream->tell(); + if ($overflow > 0) { + $this->skipReadBytes += $overflow; + } + + return $this->stream->write($string); + } + + public function eof(): bool + { + return $this->stream->eof() && $this->remoteStream->eof(); + } + + /** + * Close both the remote stream and buffer stream + */ + public function close(): void + { + $this->remoteStream->close(); + $this->stream->close(); + } + + private function cacheEntireStream(): int + { + $target = new FnStream(['write' => 'strlen']); + Utils::copyToStream($this, $target); + + return $this->tell(); + } +} diff --git a/vendor/guzzlehttp/psr7/src/DroppingStream.php b/vendor/guzzlehttp/psr7/src/DroppingStream.php new file mode 100644 index 0000000..6e3d209 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/DroppingStream.php @@ -0,0 +1,49 @@ +stream = $stream; + $this->maxLength = $maxLength; + } + + public function write($string): int + { + $diff = $this->maxLength - $this->stream->getSize(); + + // Begin returning 0 when the underlying stream is too large. + if ($diff <= 0) { + return 0; + } + + // Write the stream or a subset of the stream if needed. + if (strlen($string) < $diff) { + return $this->stream->write($string); + } + + return $this->stream->write(substr($string, 0, $diff)); + } +} diff --git a/vendor/guzzlehttp/psr7/src/Exception/MalformedUriException.php b/vendor/guzzlehttp/psr7/src/Exception/MalformedUriException.php new file mode 100644 index 0000000..3a08477 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Exception/MalformedUriException.php @@ -0,0 +1,14 @@ + */ + private $methods; + + /** + * @param array $methods Hash of method name to a callable. + */ + public function __construct(array $methods) + { + $this->methods = $methods; + + // Create the functions on the class + foreach ($methods as $name => $fn) { + $this->{'_fn_' . $name} = $fn; + } + } + + /** + * Lazily determine which methods are not implemented. + * + * @throws \BadMethodCallException + */ + public function __get(string $name): void + { + throw new \BadMethodCallException(str_replace('_fn_', '', $name) + . '() is not implemented in the FnStream'); + } + + /** + * The close method is called on the underlying stream only if possible. + */ + public function __destruct() + { + if (isset($this->_fn_close)) { + call_user_func($this->_fn_close); + } + } + + /** + * An unserialize would allow the __destruct to run when the unserialized value goes out of scope. + * + * @throws \LogicException + */ + public function __wakeup(): void + { + throw new \LogicException('FnStream should never be unserialized'); + } + + /** + * Adds custom functionality to an underlying stream by intercepting + * specific method calls. + * + * @param StreamInterface $stream Stream to decorate + * @param array $methods Hash of method name to a closure + * + * @return FnStream + */ + public static function decorate(StreamInterface $stream, array $methods) + { + // If any of the required methods were not provided, then simply + // proxy to the decorated stream. + foreach (array_diff(self::SLOTS, array_keys($methods)) as $diff) { + /** @var callable $callable */ + $callable = [$stream, $diff]; + $methods[$diff] = $callable; + } + + return new self($methods); + } + + public function __toString(): string + { + try { + return call_user_func($this->_fn___toString); + } catch (\Throwable $e) { + if (\PHP_VERSION_ID >= 70400) { + throw $e; + } + trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR); + return ''; + } + } + + public function close(): void + { + call_user_func($this->_fn_close); + } + + public function detach() + { + return call_user_func($this->_fn_detach); + } + + public function getSize(): ?int + { + return call_user_func($this->_fn_getSize); + } + + public function tell(): int + { + return call_user_func($this->_fn_tell); + } + + public function eof(): bool + { + return call_user_func($this->_fn_eof); + } + + public function isSeekable(): bool + { + return call_user_func($this->_fn_isSeekable); + } + + public function rewind(): void + { + call_user_func($this->_fn_rewind); + } + + public function seek($offset, $whence = SEEK_SET): void + { + call_user_func($this->_fn_seek, $offset, $whence); + } + + public function isWritable(): bool + { + return call_user_func($this->_fn_isWritable); + } + + public function write($string): int + { + return call_user_func($this->_fn_write, $string); + } + + public function isReadable(): bool + { + return call_user_func($this->_fn_isReadable); + } + + public function read($length): string + { + return call_user_func($this->_fn_read, $length); + } + + public function getContents(): string + { + return call_user_func($this->_fn_getContents); + } + + /** + * {@inheritdoc} + * + * @return mixed + */ + public function getMetadata($key = null) + { + return call_user_func($this->_fn_getMetadata, $key); + } +} diff --git a/vendor/guzzlehttp/psr7/src/Header.php b/vendor/guzzlehttp/psr7/src/Header.php new file mode 100644 index 0000000..4d7005b --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Header.php @@ -0,0 +1,134 @@ +]+>|[^=]+/', $kvp, $matches)) { + $m = $matches[0]; + if (isset($m[1])) { + $part[trim($m[0], $trimmed)] = trim($m[1], $trimmed); + } else { + $part[] = trim($m[0], $trimmed); + } + } + } + if ($part) { + $params[] = $part; + } + } + } + + return $params; + } + + /** + * Converts an array of header values that may contain comma separated + * headers into an array of headers with no comma separated values. + * + * @param string|array $header Header to normalize. + * + * @deprecated Use self::splitList() instead. + */ + public static function normalize($header): array + { + $result = []; + foreach ((array) $header as $value) { + foreach (self::splitList($value) as $parsed) { + $result[] = $parsed; + } + } + + return $result; + } + + /** + * Splits a HTTP header defined to contain a comma-separated list into + * each individual value. Empty values will be removed. + * + * Example headers include 'accept', 'cache-control' and 'if-none-match'. + * + * This method must not be used to parse headers that are not defined as + * a list, such as 'user-agent' or 'set-cookie'. + * + * @param string|string[] $values Header value as returned by MessageInterface::getHeader() + * + * @return string[] + */ + public static function splitList($values): array + { + if (!\is_array($values)) { + $values = [$values]; + } + + $result = []; + foreach ($values as $value) { + if (!\is_string($value)) { + throw new \TypeError('$header must either be a string or an array containing strings.'); + } + + $v = ''; + $isQuoted = false; + $isEscaped = false; + for ($i = 0, $max = \strlen($value); $i < $max; $i++) { + if ($isEscaped) { + $v .= $value[$i]; + $isEscaped = false; + + continue; + } + + if (!$isQuoted && $value[$i] === ',') { + $v = \trim($v); + if ($v !== '') { + $result[] = $v; + } + + $v = ''; + continue; + } + + if ($isQuoted && $value[$i] === '\\') { + $isEscaped = true; + $v .= $value[$i]; + + continue; + } + if ($value[$i] === '"') { + $isQuoted = !$isQuoted; + $v .= $value[$i]; + + continue; + } + + $v .= $value[$i]; + } + + $v = \trim($v); + if ($v !== '') { + $result[] = $v; + } + } + + return $result; + } +} diff --git a/vendor/guzzlehttp/psr7/src/HttpFactory.php b/vendor/guzzlehttp/psr7/src/HttpFactory.php new file mode 100644 index 0000000..30be222 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/HttpFactory.php @@ -0,0 +1,100 @@ +getSize(); + } + + return new UploadedFile($stream, $size, $error, $clientFilename, $clientMediaType); + } + + public function createStream(string $content = ''): StreamInterface + { + return Utils::streamFor($content); + } + + public function createStreamFromFile(string $file, string $mode = 'r'): StreamInterface + { + try { + $resource = Utils::tryFopen($file, $mode); + } catch (\RuntimeException $e) { + if ('' === $mode || false === \in_array($mode[0], ['r', 'w', 'a', 'x', 'c'], true)) { + throw new \InvalidArgumentException(sprintf('Invalid file opening mode "%s"', $mode), 0, $e); + } + + throw $e; + } + + return Utils::streamFor($resource); + } + + public function createStreamFromResource($resource): StreamInterface + { + return Utils::streamFor($resource); + } + + public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface + { + if (empty($method)) { + if (!empty($serverParams['REQUEST_METHOD'])) { + $method = $serverParams['REQUEST_METHOD']; + } else { + throw new \InvalidArgumentException('Cannot determine HTTP method'); + } + } + + return new ServerRequest($method, $uri, [], null, '1.1', $serverParams); + } + + public function createResponse(int $code = 200, string $reasonPhrase = ''): ResponseInterface + { + return new Response($code, [], null, '1.1', $reasonPhrase); + } + + public function createRequest(string $method, $uri): RequestInterface + { + return new Request($method, $uri); + } + + public function createUri(string $uri = ''): UriInterface + { + return new Uri($uri); + } +} diff --git a/vendor/guzzlehttp/psr7/src/InflateStream.php b/vendor/guzzlehttp/psr7/src/InflateStream.php new file mode 100644 index 0000000..8e00f1c --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/InflateStream.php @@ -0,0 +1,37 @@ + 15 + 32]); + $this->stream = $stream->isSeekable() ? new Stream($resource) : new NoSeekStream(new Stream($resource)); + } +} diff --git a/vendor/guzzlehttp/psr7/src/LazyOpenStream.php b/vendor/guzzlehttp/psr7/src/LazyOpenStream.php new file mode 100644 index 0000000..f6c8490 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/LazyOpenStream.php @@ -0,0 +1,49 @@ +filename = $filename; + $this->mode = $mode; + + // unsetting the property forces the first access to go through + // __get(). + unset($this->stream); + } + + /** + * Creates the underlying stream lazily when required. + */ + protected function createStream(): StreamInterface + { + return Utils::streamFor(Utils::tryFopen($this->filename, $this->mode)); + } +} diff --git a/vendor/guzzlehttp/psr7/src/LimitStream.php b/vendor/guzzlehttp/psr7/src/LimitStream.php new file mode 100644 index 0000000..fb22325 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/LimitStream.php @@ -0,0 +1,157 @@ +stream = $stream; + $this->setLimit($limit); + $this->setOffset($offset); + } + + public function eof(): bool + { + // Always return true if the underlying stream is EOF + if ($this->stream->eof()) { + return true; + } + + // No limit and the underlying stream is not at EOF + if ($this->limit === -1) { + return false; + } + + return $this->stream->tell() >= $this->offset + $this->limit; + } + + /** + * Returns the size of the limited subset of data + */ + public function getSize(): ?int + { + if (null === ($length = $this->stream->getSize())) { + return null; + } elseif ($this->limit === -1) { + return $length - $this->offset; + } else { + return min($this->limit, $length - $this->offset); + } + } + + /** + * Allow for a bounded seek on the read limited stream + */ + public function seek($offset, $whence = SEEK_SET): void + { + if ($whence !== SEEK_SET || $offset < 0) { + throw new \RuntimeException(sprintf( + 'Cannot seek to offset %s with whence %s', + $offset, + $whence + )); + } + + $offset += $this->offset; + + if ($this->limit !== -1) { + if ($offset > $this->offset + $this->limit) { + $offset = $this->offset + $this->limit; + } + } + + $this->stream->seek($offset); + } + + /** + * Give a relative tell() + */ + public function tell(): int + { + return $this->stream->tell() - $this->offset; + } + + /** + * Set the offset to start limiting from + * + * @param int $offset Offset to seek to and begin byte limiting from + * + * @throws \RuntimeException if the stream cannot be seeked. + */ + public function setOffset(int $offset): void + { + $current = $this->stream->tell(); + + if ($current !== $offset) { + // If the stream cannot seek to the offset position, then read to it + if ($this->stream->isSeekable()) { + $this->stream->seek($offset); + } elseif ($current > $offset) { + throw new \RuntimeException("Could not seek to stream offset $offset"); + } else { + $this->stream->read($offset - $current); + } + } + + $this->offset = $offset; + } + + /** + * Set the limit of bytes that the decorator allows to be read from the + * stream. + * + * @param int $limit Number of bytes to allow to be read from the stream. + * Use -1 for no limit. + */ + public function setLimit(int $limit): void + { + $this->limit = $limit; + } + + public function read($length): string + { + if ($this->limit === -1) { + return $this->stream->read($length); + } + + // Check if the current position is less than the total allowed + // bytes + original offset + $remaining = ($this->offset + $this->limit) - $this->stream->tell(); + if ($remaining > 0) { + // Only return the amount of requested data, ensuring that the byte + // limit is not exceeded + return $this->stream->read(min($remaining, $length)); + } + + return ''; + } +} diff --git a/vendor/guzzlehttp/psr7/src/Message.php b/vendor/guzzlehttp/psr7/src/Message.php new file mode 100644 index 0000000..c1e15f8 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Message.php @@ -0,0 +1,246 @@ +getMethod() . ' ' + . $message->getRequestTarget()) + . ' HTTP/' . $message->getProtocolVersion(); + if (!$message->hasHeader('host')) { + $msg .= "\r\nHost: " . $message->getUri()->getHost(); + } + } elseif ($message instanceof ResponseInterface) { + $msg = 'HTTP/' . $message->getProtocolVersion() . ' ' + . $message->getStatusCode() . ' ' + . $message->getReasonPhrase(); + } else { + throw new \InvalidArgumentException('Unknown message type'); + } + + foreach ($message->getHeaders() as $name => $values) { + if (strtolower($name) === 'set-cookie') { + foreach ($values as $value) { + $msg .= "\r\n{$name}: " . $value; + } + } else { + $msg .= "\r\n{$name}: " . implode(', ', $values); + } + } + + return "{$msg}\r\n\r\n" . $message->getBody(); + } + + /** + * Get a short summary of the message body. + * + * Will return `null` if the response is not printable. + * + * @param MessageInterface $message The message to get the body summary + * @param int $truncateAt The maximum allowed size of the summary + */ + public static function bodySummary(MessageInterface $message, int $truncateAt = 120): ?string + { + $body = $message->getBody(); + + if (!$body->isSeekable() || !$body->isReadable()) { + return null; + } + + $size = $body->getSize(); + + if ($size === 0) { + return null; + } + + $body->rewind(); + $summary = $body->read($truncateAt); + $body->rewind(); + + if ($size > $truncateAt) { + $summary .= ' (truncated...)'; + } + + // Matches any printable character, including unicode characters: + // letters, marks, numbers, punctuation, spacing, and separators. + if (preg_match('/[^\pL\pM\pN\pP\pS\pZ\n\r\t]/u', $summary) !== 0) { + return null; + } + + return $summary; + } + + /** + * Attempts to rewind a message body and throws an exception on failure. + * + * The body of the message will only be rewound if a call to `tell()` + * returns a value other than `0`. + * + * @param MessageInterface $message Message to rewind + * + * @throws \RuntimeException + */ + public static function rewindBody(MessageInterface $message): void + { + $body = $message->getBody(); + + if ($body->tell()) { + $body->rewind(); + } + } + + /** + * Parses an HTTP message into an associative array. + * + * The array contains the "start-line" key containing the start line of + * the message, "headers" key containing an associative array of header + * array values, and a "body" key containing the body of the message. + * + * @param string $message HTTP request or response to parse. + */ + public static function parseMessage(string $message): array + { + if (!$message) { + throw new \InvalidArgumentException('Invalid message'); + } + + $message = ltrim($message, "\r\n"); + + $messageParts = preg_split("/\r?\n\r?\n/", $message, 2); + + if ($messageParts === false || count($messageParts) !== 2) { + throw new \InvalidArgumentException('Invalid message: Missing header delimiter'); + } + + [$rawHeaders, $body] = $messageParts; + $rawHeaders .= "\r\n"; // Put back the delimiter we split previously + $headerParts = preg_split("/\r?\n/", $rawHeaders, 2); + + if ($headerParts === false || count($headerParts) !== 2) { + throw new \InvalidArgumentException('Invalid message: Missing status line'); + } + + [$startLine, $rawHeaders] = $headerParts; + + if (preg_match("/(?:^HTTP\/|^[A-Z]+ \S+ HTTP\/)(\d+(?:\.\d+)?)/i", $startLine, $matches) && $matches[1] === '1.0') { + // Header folding is deprecated for HTTP/1.1, but allowed in HTTP/1.0 + $rawHeaders = preg_replace(Rfc7230::HEADER_FOLD_REGEX, ' ', $rawHeaders); + } + + /** @var array[] $headerLines */ + $count = preg_match_all(Rfc7230::HEADER_REGEX, $rawHeaders, $headerLines, PREG_SET_ORDER); + + // If these aren't the same, then one line didn't match and there's an invalid header. + if ($count !== substr_count($rawHeaders, "\n")) { + // Folding is deprecated, see https://tools.ietf.org/html/rfc7230#section-3.2.4 + if (preg_match(Rfc7230::HEADER_FOLD_REGEX, $rawHeaders)) { + throw new \InvalidArgumentException('Invalid header syntax: Obsolete line folding'); + } + + throw new \InvalidArgumentException('Invalid header syntax'); + } + + $headers = []; + + foreach ($headerLines as $headerLine) { + $headers[$headerLine[1]][] = $headerLine[2]; + } + + return [ + 'start-line' => $startLine, + 'headers' => $headers, + 'body' => $body, + ]; + } + + /** + * Constructs a URI for an HTTP request message. + * + * @param string $path Path from the start-line + * @param array $headers Array of headers (each value an array). + */ + public static function parseRequestUri(string $path, array $headers): string + { + $hostKey = array_filter(array_keys($headers), function ($k) { + // Numeric array keys are converted to int by PHP. + $k = (string) $k; + + return strtolower($k) === 'host'; + }); + + // If no host is found, then a full URI cannot be constructed. + if (!$hostKey) { + return $path; + } + + $host = $headers[reset($hostKey)][0]; + $scheme = substr($host, -4) === ':443' ? 'https' : 'http'; + + return $scheme . '://' . $host . '/' . ltrim($path, '/'); + } + + /** + * Parses a request message string into a request object. + * + * @param string $message Request message string. + */ + public static function parseRequest(string $message): RequestInterface + { + $data = self::parseMessage($message); + $matches = []; + if (!preg_match('/^[\S]+\s+([a-zA-Z]+:\/\/|\/).*/', $data['start-line'], $matches)) { + throw new \InvalidArgumentException('Invalid request string'); + } + $parts = explode(' ', $data['start-line'], 3); + $version = isset($parts[2]) ? explode('/', $parts[2])[1] : '1.1'; + + $request = new Request( + $parts[0], + $matches[1] === '/' ? self::parseRequestUri($parts[1], $data['headers']) : $parts[1], + $data['headers'], + $data['body'], + $version + ); + + return $matches[1] === '/' ? $request : $request->withRequestTarget($parts[1]); + } + + /** + * Parses a response message string into a response object. + * + * @param string $message Response message string. + */ + public static function parseResponse(string $message): ResponseInterface + { + $data = self::parseMessage($message); + // According to https://tools.ietf.org/html/rfc7230#section-3.1.2 the space + // between status-code and reason-phrase is required. But browsers accept + // responses without space and reason as well. + if (!preg_match('/^HTTP\/.* [0-9]{3}( .*|$)/', $data['start-line'])) { + throw new \InvalidArgumentException('Invalid response string: ' . $data['start-line']); + } + $parts = explode(' ', $data['start-line'], 3); + + return new Response( + (int) $parts[1], + $data['headers'], + $data['body'], + explode('/', $parts[0])[1], + $parts[2] ?? null + ); + } +} diff --git a/vendor/guzzlehttp/psr7/src/MessageTrait.php b/vendor/guzzlehttp/psr7/src/MessageTrait.php new file mode 100644 index 0000000..464bdfa --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/MessageTrait.php @@ -0,0 +1,263 @@ + Map of all registered headers, as original name => array of values */ + private $headers = []; + + /** @var array Map of lowercase header name => original name at registration */ + private $headerNames = []; + + /** @var string */ + private $protocol = '1.1'; + + /** @var StreamInterface|null */ + private $stream; + + public function getProtocolVersion(): string + { + return $this->protocol; + } + + public function withProtocolVersion($version): MessageInterface + { + if ($this->protocol === $version) { + return $this; + } + + $new = clone $this; + $new->protocol = $version; + return $new; + } + + public function getHeaders(): array + { + return $this->headers; + } + + public function hasHeader($header): bool + { + return isset($this->headerNames[strtolower($header)]); + } + + public function getHeader($header): array + { + $header = strtolower($header); + + if (!isset($this->headerNames[$header])) { + return []; + } + + $header = $this->headerNames[$header]; + + return $this->headers[$header]; + } + + public function getHeaderLine($header): string + { + return implode(', ', $this->getHeader($header)); + } + + public function withHeader($header, $value): MessageInterface + { + $this->assertHeader($header); + $value = $this->normalizeHeaderValue($value); + $normalized = strtolower($header); + + $new = clone $this; + if (isset($new->headerNames[$normalized])) { + unset($new->headers[$new->headerNames[$normalized]]); + } + $new->headerNames[$normalized] = $header; + $new->headers[$header] = $value; + + return $new; + } + + public function withAddedHeader($header, $value): MessageInterface + { + $this->assertHeader($header); + $value = $this->normalizeHeaderValue($value); + $normalized = strtolower($header); + + $new = clone $this; + if (isset($new->headerNames[$normalized])) { + $header = $this->headerNames[$normalized]; + $new->headers[$header] = array_merge($this->headers[$header], $value); + } else { + $new->headerNames[$normalized] = $header; + $new->headers[$header] = $value; + } + + return $new; + } + + public function withoutHeader($header): MessageInterface + { + $normalized = strtolower($header); + + if (!isset($this->headerNames[$normalized])) { + return $this; + } + + $header = $this->headerNames[$normalized]; + + $new = clone $this; + unset($new->headers[$header], $new->headerNames[$normalized]); + + return $new; + } + + public function getBody(): StreamInterface + { + if (!$this->stream) { + $this->stream = Utils::streamFor(''); + } + + return $this->stream; + } + + public function withBody(StreamInterface $body): MessageInterface + { + if ($body === $this->stream) { + return $this; + } + + $new = clone $this; + $new->stream = $body; + return $new; + } + + /** + * @param array $headers + */ + private function setHeaders(array $headers): void + { + $this->headerNames = $this->headers = []; + foreach ($headers as $header => $value) { + // Numeric array keys are converted to int by PHP. + $header = (string) $header; + + $this->assertHeader($header); + $value = $this->normalizeHeaderValue($value); + $normalized = strtolower($header); + if (isset($this->headerNames[$normalized])) { + $header = $this->headerNames[$normalized]; + $this->headers[$header] = array_merge($this->headers[$header], $value); + } else { + $this->headerNames[$normalized] = $header; + $this->headers[$header] = $value; + } + } + } + + /** + * @param mixed $value + * + * @return string[] + */ + private function normalizeHeaderValue($value): array + { + if (!is_array($value)) { + return $this->trimAndValidateHeaderValues([$value]); + } + + if (count($value) === 0) { + throw new \InvalidArgumentException('Header value can not be an empty array.'); + } + + return $this->trimAndValidateHeaderValues($value); + } + + /** + * Trims whitespace from the header values. + * + * Spaces and tabs ought to be excluded by parsers when extracting the field value from a header field. + * + * header-field = field-name ":" OWS field-value OWS + * OWS = *( SP / HTAB ) + * + * @param mixed[] $values Header values + * + * @return string[] Trimmed header values + * + * @see https://tools.ietf.org/html/rfc7230#section-3.2.4 + */ + private function trimAndValidateHeaderValues(array $values): array + { + return array_map(function ($value) { + if (!is_scalar($value) && null !== $value) { + throw new \InvalidArgumentException(sprintf( + 'Header value must be scalar or null but %s provided.', + is_object($value) ? get_class($value) : gettype($value) + )); + } + + $trimmed = trim((string) $value, " \t"); + $this->assertValue($trimmed); + + return $trimmed; + }, array_values($values)); + } + + /** + * @see https://tools.ietf.org/html/rfc7230#section-3.2 + * + * @param mixed $header + */ + private function assertHeader($header): void + { + if (!is_string($header)) { + throw new \InvalidArgumentException(sprintf( + 'Header name must be a string but %s provided.', + is_object($header) ? get_class($header) : gettype($header) + )); + } + + if (! preg_match('/^[a-zA-Z0-9\'`#$%&*+.^_|~!-]+$/D', $header)) { + throw new \InvalidArgumentException( + sprintf('"%s" is not valid header name.', $header) + ); + } + } + + /** + * @see https://tools.ietf.org/html/rfc7230#section-3.2 + * + * field-value = *( field-content / obs-fold ) + * field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] + * field-vchar = VCHAR / obs-text + * VCHAR = %x21-7E + * obs-text = %x80-FF + * obs-fold = CRLF 1*( SP / HTAB ) + */ + private function assertValue(string $value): void + { + // The regular expression intentionally does not support the obs-fold production, because as + // per RFC 7230#3.2.4: + // + // A sender MUST NOT generate a message that includes + // line folding (i.e., that has any field-value that contains a match to + // the obs-fold rule) unless the message is intended for packaging + // within the message/http media type. + // + // Clients must not send a request with line folding and a server sending folded headers is + // likely very rare. Line folding is a fairly obscure feature of HTTP/1.1 and thus not accepting + // folding is not likely to break any legitimate use case. + if (! preg_match('/^[\x20\x09\x21-\x7E\x80-\xFF]*$/D', $value)) { + throw new \InvalidArgumentException( + sprintf('"%s" is not valid header value.', $value) + ); + } + } +} diff --git a/vendor/guzzlehttp/psr7/src/MimeType.php b/vendor/guzzlehttp/psr7/src/MimeType.php new file mode 100644 index 0000000..0debbd1 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/MimeType.php @@ -0,0 +1,1237 @@ + 'application/vnd.1000minds.decision-model+xml', + '3dml' => 'text/vnd.in3d.3dml', + '3ds' => 'image/x-3ds', + '3g2' => 'video/3gpp2', + '3gp' => 'video/3gp', + '3gpp' => 'video/3gpp', + '3mf' => 'model/3mf', + '7z' => 'application/x-7z-compressed', + '7zip' => 'application/x-7z-compressed', + '123' => 'application/vnd.lotus-1-2-3', + 'aab' => 'application/x-authorware-bin', + 'aac' => 'audio/x-acc', + 'aam' => 'application/x-authorware-map', + 'aas' => 'application/x-authorware-seg', + 'abw' => 'application/x-abiword', + 'ac' => 'application/vnd.nokia.n-gage.ac+xml', + 'ac3' => 'audio/ac3', + 'acc' => 'application/vnd.americandynamics.acc', + 'ace' => 'application/x-ace-compressed', + 'acu' => 'application/vnd.acucobol', + 'acutc' => 'application/vnd.acucorp', + 'adp' => 'audio/adpcm', + 'aep' => 'application/vnd.audiograph', + 'afm' => 'application/x-font-type1', + 'afp' => 'application/vnd.ibm.modcap', + 'age' => 'application/vnd.age', + 'ahead' => 'application/vnd.ahead.space', + 'ai' => 'application/pdf', + 'aif' => 'audio/x-aiff', + 'aifc' => 'audio/x-aiff', + 'aiff' => 'audio/x-aiff', + 'air' => 'application/vnd.adobe.air-application-installer-package+zip', + 'ait' => 'application/vnd.dvb.ait', + 'ami' => 'application/vnd.amiga.ami', + 'amr' => 'audio/amr', + 'apk' => 'application/vnd.android.package-archive', + 'apng' => 'image/apng', + 'appcache' => 'text/cache-manifest', + 'application' => 'application/x-ms-application', + 'apr' => 'application/vnd.lotus-approach', + 'arc' => 'application/x-freearc', + 'arj' => 'application/x-arj', + 'asc' => 'application/pgp-signature', + 'asf' => 'video/x-ms-asf', + 'asm' => 'text/x-asm', + 'aso' => 'application/vnd.accpac.simply.aso', + 'asx' => 'video/x-ms-asf', + 'atc' => 'application/vnd.acucorp', + 'atom' => 'application/atom+xml', + 'atomcat' => 'application/atomcat+xml', + 'atomdeleted' => 'application/atomdeleted+xml', + 'atomsvc' => 'application/atomsvc+xml', + 'atx' => 'application/vnd.antix.game-component', + 'au' => 'audio/x-au', + 'avci' => 'image/avci', + 'avcs' => 'image/avcs', + 'avi' => 'video/x-msvideo', + 'avif' => 'image/avif', + 'aw' => 'application/applixware', + 'azf' => 'application/vnd.airzip.filesecure.azf', + 'azs' => 'application/vnd.airzip.filesecure.azs', + 'azv' => 'image/vnd.airzip.accelerator.azv', + 'azw' => 'application/vnd.amazon.ebook', + 'b16' => 'image/vnd.pco.b16', + 'bat' => 'application/x-msdownload', + 'bcpio' => 'application/x-bcpio', + 'bdf' => 'application/x-font-bdf', + 'bdm' => 'application/vnd.syncml.dm+wbxml', + 'bdoc' => 'application/x-bdoc', + 'bed' => 'application/vnd.realvnc.bed', + 'bh2' => 'application/vnd.fujitsu.oasysprs', + 'bin' => 'application/octet-stream', + 'blb' => 'application/x-blorb', + 'blorb' => 'application/x-blorb', + 'bmi' => 'application/vnd.bmi', + 'bmml' => 'application/vnd.balsamiq.bmml+xml', + 'bmp' => 'image/bmp', + 'book' => 'application/vnd.framemaker', + 'box' => 'application/vnd.previewsystems.box', + 'boz' => 'application/x-bzip2', + 'bpk' => 'application/octet-stream', + 'bpmn' => 'application/octet-stream', + 'bsp' => 'model/vnd.valve.source.compiled-map', + 'btif' => 'image/prs.btif', + 'buffer' => 'application/octet-stream', + 'bz' => 'application/x-bzip', + 'bz2' => 'application/x-bzip2', + 'c' => 'text/x-c', + 'c4d' => 'application/vnd.clonk.c4group', + 'c4f' => 'application/vnd.clonk.c4group', + 'c4g' => 'application/vnd.clonk.c4group', + 'c4p' => 'application/vnd.clonk.c4group', + 'c4u' => 'application/vnd.clonk.c4group', + 'c11amc' => 'application/vnd.cluetrust.cartomobile-config', + 'c11amz' => 'application/vnd.cluetrust.cartomobile-config-pkg', + 'cab' => 'application/vnd.ms-cab-compressed', + 'caf' => 'audio/x-caf', + 'cap' => 'application/vnd.tcpdump.pcap', + 'car' => 'application/vnd.curl.car', + 'cat' => 'application/vnd.ms-pki.seccat', + 'cb7' => 'application/x-cbr', + 'cba' => 'application/x-cbr', + 'cbr' => 'application/x-cbr', + 'cbt' => 'application/x-cbr', + 'cbz' => 'application/x-cbr', + 'cc' => 'text/x-c', + 'cco' => 'application/x-cocoa', + 'cct' => 'application/x-director', + 'ccxml' => 'application/ccxml+xml', + 'cdbcmsg' => 'application/vnd.contact.cmsg', + 'cdf' => 'application/x-netcdf', + 'cdfx' => 'application/cdfx+xml', + 'cdkey' => 'application/vnd.mediastation.cdkey', + 'cdmia' => 'application/cdmi-capability', + 'cdmic' => 'application/cdmi-container', + 'cdmid' => 'application/cdmi-domain', + 'cdmio' => 'application/cdmi-object', + 'cdmiq' => 'application/cdmi-queue', + 'cdr' => 'application/cdr', + 'cdx' => 'chemical/x-cdx', + 'cdxml' => 'application/vnd.chemdraw+xml', + 'cdy' => 'application/vnd.cinderella', + 'cer' => 'application/pkix-cert', + 'cfs' => 'application/x-cfs-compressed', + 'cgm' => 'image/cgm', + 'chat' => 'application/x-chat', + 'chm' => 'application/vnd.ms-htmlhelp', + 'chrt' => 'application/vnd.kde.kchart', + 'cif' => 'chemical/x-cif', + 'cii' => 'application/vnd.anser-web-certificate-issue-initiation', + 'cil' => 'application/vnd.ms-artgalry', + 'cjs' => 'application/node', + 'cla' => 'application/vnd.claymore', + 'class' => 'application/octet-stream', + 'clkk' => 'application/vnd.crick.clicker.keyboard', + 'clkp' => 'application/vnd.crick.clicker.palette', + 'clkt' => 'application/vnd.crick.clicker.template', + 'clkw' => 'application/vnd.crick.clicker.wordbank', + 'clkx' => 'application/vnd.crick.clicker', + 'clp' => 'application/x-msclip', + 'cmc' => 'application/vnd.cosmocaller', + 'cmdf' => 'chemical/x-cmdf', + 'cml' => 'chemical/x-cml', + 'cmp' => 'application/vnd.yellowriver-custom-menu', + 'cmx' => 'image/x-cmx', + 'cod' => 'application/vnd.rim.cod', + 'coffee' => 'text/coffeescript', + 'com' => 'application/x-msdownload', + 'conf' => 'text/plain', + 'cpio' => 'application/x-cpio', + 'cpl' => 'application/cpl+xml', + 'cpp' => 'text/x-c', + 'cpt' => 'application/mac-compactpro', + 'crd' => 'application/x-mscardfile', + 'crl' => 'application/pkix-crl', + 'crt' => 'application/x-x509-ca-cert', + 'crx' => 'application/x-chrome-extension', + 'cryptonote' => 'application/vnd.rig.cryptonote', + 'csh' => 'application/x-csh', + 'csl' => 'application/vnd.citationstyles.style+xml', + 'csml' => 'chemical/x-csml', + 'csp' => 'application/vnd.commonspace', + 'csr' => 'application/octet-stream', + 'css' => 'text/css', + 'cst' => 'application/x-director', + 'csv' => 'text/csv', + 'cu' => 'application/cu-seeme', + 'curl' => 'text/vnd.curl', + 'cww' => 'application/prs.cww', + 'cxt' => 'application/x-director', + 'cxx' => 'text/x-c', + 'dae' => 'model/vnd.collada+xml', + 'daf' => 'application/vnd.mobius.daf', + 'dart' => 'application/vnd.dart', + 'dataless' => 'application/vnd.fdsn.seed', + 'davmount' => 'application/davmount+xml', + 'dbf' => 'application/vnd.dbf', + 'dbk' => 'application/docbook+xml', + 'dcr' => 'application/x-director', + 'dcurl' => 'text/vnd.curl.dcurl', + 'dd2' => 'application/vnd.oma.dd2+xml', + 'ddd' => 'application/vnd.fujixerox.ddd', + 'ddf' => 'application/vnd.syncml.dmddf+xml', + 'dds' => 'image/vnd.ms-dds', + 'deb' => 'application/x-debian-package', + 'def' => 'text/plain', + 'deploy' => 'application/octet-stream', + 'der' => 'application/x-x509-ca-cert', + 'dfac' => 'application/vnd.dreamfactory', + 'dgc' => 'application/x-dgc-compressed', + 'dic' => 'text/x-c', + 'dir' => 'application/x-director', + 'dis' => 'application/vnd.mobius.dis', + 'disposition-notification' => 'message/disposition-notification', + 'dist' => 'application/octet-stream', + 'distz' => 'application/octet-stream', + 'djv' => 'image/vnd.djvu', + 'djvu' => 'image/vnd.djvu', + 'dll' => 'application/octet-stream', + 'dmg' => 'application/x-apple-diskimage', + 'dmn' => 'application/octet-stream', + 'dmp' => 'application/vnd.tcpdump.pcap', + 'dms' => 'application/octet-stream', + 'dna' => 'application/vnd.dna', + 'doc' => 'application/msword', + 'docm' => 'application/vnd.ms-word.template.macroEnabled.12', + 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'dot' => 'application/msword', + 'dotm' => 'application/vnd.ms-word.template.macroEnabled.12', + 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', + 'dp' => 'application/vnd.osgi.dp', + 'dpg' => 'application/vnd.dpgraph', + 'dra' => 'audio/vnd.dra', + 'drle' => 'image/dicom-rle', + 'dsc' => 'text/prs.lines.tag', + 'dssc' => 'application/dssc+der', + 'dtb' => 'application/x-dtbook+xml', + 'dtd' => 'application/xml-dtd', + 'dts' => 'audio/vnd.dts', + 'dtshd' => 'audio/vnd.dts.hd', + 'dump' => 'application/octet-stream', + 'dvb' => 'video/vnd.dvb.file', + 'dvi' => 'application/x-dvi', + 'dwd' => 'application/atsc-dwd+xml', + 'dwf' => 'model/vnd.dwf', + 'dwg' => 'image/vnd.dwg', + 'dxf' => 'image/vnd.dxf', + 'dxp' => 'application/vnd.spotfire.dxp', + 'dxr' => 'application/x-director', + 'ear' => 'application/java-archive', + 'ecelp4800' => 'audio/vnd.nuera.ecelp4800', + 'ecelp7470' => 'audio/vnd.nuera.ecelp7470', + 'ecelp9600' => 'audio/vnd.nuera.ecelp9600', + 'ecma' => 'application/ecmascript', + 'edm' => 'application/vnd.novadigm.edm', + 'edx' => 'application/vnd.novadigm.edx', + 'efif' => 'application/vnd.picsel', + 'ei6' => 'application/vnd.pg.osasli', + 'elc' => 'application/octet-stream', + 'emf' => 'image/emf', + 'eml' => 'message/rfc822', + 'emma' => 'application/emma+xml', + 'emotionml' => 'application/emotionml+xml', + 'emz' => 'application/x-msmetafile', + 'eol' => 'audio/vnd.digital-winds', + 'eot' => 'application/vnd.ms-fontobject', + 'eps' => 'application/postscript', + 'epub' => 'application/epub+zip', + 'es' => 'application/ecmascript', + 'es3' => 'application/vnd.eszigno3+xml', + 'esa' => 'application/vnd.osgi.subsystem', + 'esf' => 'application/vnd.epson.esf', + 'et3' => 'application/vnd.eszigno3+xml', + 'etx' => 'text/x-setext', + 'eva' => 'application/x-eva', + 'evy' => 'application/x-envoy', + 'exe' => 'application/octet-stream', + 'exi' => 'application/exi', + 'exp' => 'application/express', + 'exr' => 'image/aces', + 'ext' => 'application/vnd.novadigm.ext', + 'ez' => 'application/andrew-inset', + 'ez2' => 'application/vnd.ezpix-album', + 'ez3' => 'application/vnd.ezpix-package', + 'f' => 'text/x-fortran', + 'f4v' => 'video/mp4', + 'f77' => 'text/x-fortran', + 'f90' => 'text/x-fortran', + 'fbs' => 'image/vnd.fastbidsheet', + 'fcdt' => 'application/vnd.adobe.formscentral.fcdt', + 'fcs' => 'application/vnd.isac.fcs', + 'fdf' => 'application/vnd.fdf', + 'fdt' => 'application/fdt+xml', + 'fe_launch' => 'application/vnd.denovo.fcselayout-link', + 'fg5' => 'application/vnd.fujitsu.oasysgp', + 'fgd' => 'application/x-director', + 'fh' => 'image/x-freehand', + 'fh4' => 'image/x-freehand', + 'fh5' => 'image/x-freehand', + 'fh7' => 'image/x-freehand', + 'fhc' => 'image/x-freehand', + 'fig' => 'application/x-xfig', + 'fits' => 'image/fits', + 'flac' => 'audio/x-flac', + 'fli' => 'video/x-fli', + 'flo' => 'application/vnd.micrografx.flo', + 'flv' => 'video/x-flv', + 'flw' => 'application/vnd.kde.kivio', + 'flx' => 'text/vnd.fmi.flexstor', + 'fly' => 'text/vnd.fly', + 'fm' => 'application/vnd.framemaker', + 'fnc' => 'application/vnd.frogans.fnc', + 'fo' => 'application/vnd.software602.filler.form+xml', + 'for' => 'text/x-fortran', + 'fpx' => 'image/vnd.fpx', + 'frame' => 'application/vnd.framemaker', + 'fsc' => 'application/vnd.fsc.weblaunch', + 'fst' => 'image/vnd.fst', + 'ftc' => 'application/vnd.fluxtime.clip', + 'fti' => 'application/vnd.anser-web-funds-transfer-initiation', + 'fvt' => 'video/vnd.fvt', + 'fxp' => 'application/vnd.adobe.fxp', + 'fxpl' => 'application/vnd.adobe.fxp', + 'fzs' => 'application/vnd.fuzzysheet', + 'g2w' => 'application/vnd.geoplan', + 'g3' => 'image/g3fax', + 'g3w' => 'application/vnd.geospace', + 'gac' => 'application/vnd.groove-account', + 'gam' => 'application/x-tads', + 'gbr' => 'application/rpki-ghostbusters', + 'gca' => 'application/x-gca-compressed', + 'gdl' => 'model/vnd.gdl', + 'gdoc' => 'application/vnd.google-apps.document', + 'ged' => 'text/vnd.familysearch.gedcom', + 'geo' => 'application/vnd.dynageo', + 'geojson' => 'application/geo+json', + 'gex' => 'application/vnd.geometry-explorer', + 'ggb' => 'application/vnd.geogebra.file', + 'ggt' => 'application/vnd.geogebra.tool', + 'ghf' => 'application/vnd.groove-help', + 'gif' => 'image/gif', + 'gim' => 'application/vnd.groove-identity-message', + 'glb' => 'model/gltf-binary', + 'gltf' => 'model/gltf+json', + 'gml' => 'application/gml+xml', + 'gmx' => 'application/vnd.gmx', + 'gnumeric' => 'application/x-gnumeric', + 'gpg' => 'application/gpg-keys', + 'gph' => 'application/vnd.flographit', + 'gpx' => 'application/gpx+xml', + 'gqf' => 'application/vnd.grafeq', + 'gqs' => 'application/vnd.grafeq', + 'gram' => 'application/srgs', + 'gramps' => 'application/x-gramps-xml', + 'gre' => 'application/vnd.geometry-explorer', + 'grv' => 'application/vnd.groove-injector', + 'grxml' => 'application/srgs+xml', + 'gsf' => 'application/x-font-ghostscript', + 'gsheet' => 'application/vnd.google-apps.spreadsheet', + 'gslides' => 'application/vnd.google-apps.presentation', + 'gtar' => 'application/x-gtar', + 'gtm' => 'application/vnd.groove-tool-message', + 'gtw' => 'model/vnd.gtw', + 'gv' => 'text/vnd.graphviz', + 'gxf' => 'application/gxf', + 'gxt' => 'application/vnd.geonext', + 'gz' => 'application/gzip', + 'gzip' => 'application/gzip', + 'h' => 'text/x-c', + 'h261' => 'video/h261', + 'h263' => 'video/h263', + 'h264' => 'video/h264', + 'hal' => 'application/vnd.hal+xml', + 'hbci' => 'application/vnd.hbci', + 'hbs' => 'text/x-handlebars-template', + 'hdd' => 'application/x-virtualbox-hdd', + 'hdf' => 'application/x-hdf', + 'heic' => 'image/heic', + 'heics' => 'image/heic-sequence', + 'heif' => 'image/heif', + 'heifs' => 'image/heif-sequence', + 'hej2' => 'image/hej2k', + 'held' => 'application/atsc-held+xml', + 'hh' => 'text/x-c', + 'hjson' => 'application/hjson', + 'hlp' => 'application/winhlp', + 'hpgl' => 'application/vnd.hp-hpgl', + 'hpid' => 'application/vnd.hp-hpid', + 'hps' => 'application/vnd.hp-hps', + 'hqx' => 'application/mac-binhex40', + 'hsj2' => 'image/hsj2', + 'htc' => 'text/x-component', + 'htke' => 'application/vnd.kenameaapp', + 'htm' => 'text/html', + 'html' => 'text/html', + 'hvd' => 'application/vnd.yamaha.hv-dic', + 'hvp' => 'application/vnd.yamaha.hv-voice', + 'hvs' => 'application/vnd.yamaha.hv-script', + 'i2g' => 'application/vnd.intergeo', + 'icc' => 'application/vnd.iccprofile', + 'ice' => 'x-conference/x-cooltalk', + 'icm' => 'application/vnd.iccprofile', + 'ico' => 'image/x-icon', + 'ics' => 'text/calendar', + 'ief' => 'image/ief', + 'ifb' => 'text/calendar', + 'ifm' => 'application/vnd.shana.informed.formdata', + 'iges' => 'model/iges', + 'igl' => 'application/vnd.igloader', + 'igm' => 'application/vnd.insors.igm', + 'igs' => 'model/iges', + 'igx' => 'application/vnd.micrografx.igx', + 'iif' => 'application/vnd.shana.informed.interchange', + 'img' => 'application/octet-stream', + 'imp' => 'application/vnd.accpac.simply.imp', + 'ims' => 'application/vnd.ms-ims', + 'in' => 'text/plain', + 'ini' => 'text/plain', + 'ink' => 'application/inkml+xml', + 'inkml' => 'application/inkml+xml', + 'install' => 'application/x-install-instructions', + 'iota' => 'application/vnd.astraea-software.iota', + 'ipfix' => 'application/ipfix', + 'ipk' => 'application/vnd.shana.informed.package', + 'irm' => 'application/vnd.ibm.rights-management', + 'irp' => 'application/vnd.irepository.package+xml', + 'iso' => 'application/x-iso9660-image', + 'itp' => 'application/vnd.shana.informed.formtemplate', + 'its' => 'application/its+xml', + 'ivp' => 'application/vnd.immervision-ivp', + 'ivu' => 'application/vnd.immervision-ivu', + 'jad' => 'text/vnd.sun.j2me.app-descriptor', + 'jade' => 'text/jade', + 'jam' => 'application/vnd.jam', + 'jar' => 'application/java-archive', + 'jardiff' => 'application/x-java-archive-diff', + 'java' => 'text/x-java-source', + 'jhc' => 'image/jphc', + 'jisp' => 'application/vnd.jisp', + 'jls' => 'image/jls', + 'jlt' => 'application/vnd.hp-jlyt', + 'jng' => 'image/x-jng', + 'jnlp' => 'application/x-java-jnlp-file', + 'joda' => 'application/vnd.joost.joda-archive', + 'jp2' => 'image/jp2', + 'jpe' => 'image/jpeg', + 'jpeg' => 'image/jpeg', + 'jpf' => 'image/jpx', + 'jpg' => 'image/jpeg', + 'jpg2' => 'image/jp2', + 'jpgm' => 'video/jpm', + 'jpgv' => 'video/jpeg', + 'jph' => 'image/jph', + 'jpm' => 'video/jpm', + 'jpx' => 'image/jpx', + 'js' => 'application/javascript', + 'json' => 'application/json', + 'json5' => 'application/json5', + 'jsonld' => 'application/ld+json', + 'jsonml' => 'application/jsonml+json', + 'jsx' => 'text/jsx', + 'jxr' => 'image/jxr', + 'jxra' => 'image/jxra', + 'jxrs' => 'image/jxrs', + 'jxs' => 'image/jxs', + 'jxsc' => 'image/jxsc', + 'jxsi' => 'image/jxsi', + 'jxss' => 'image/jxss', + 'kar' => 'audio/midi', + 'karbon' => 'application/vnd.kde.karbon', + 'kdb' => 'application/octet-stream', + 'kdbx' => 'application/x-keepass2', + 'key' => 'application/x-iwork-keynote-sffkey', + 'kfo' => 'application/vnd.kde.kformula', + 'kia' => 'application/vnd.kidspiration', + 'kml' => 'application/vnd.google-earth.kml+xml', + 'kmz' => 'application/vnd.google-earth.kmz', + 'kne' => 'application/vnd.kinar', + 'knp' => 'application/vnd.kinar', + 'kon' => 'application/vnd.kde.kontour', + 'kpr' => 'application/vnd.kde.kpresenter', + 'kpt' => 'application/vnd.kde.kpresenter', + 'kpxx' => 'application/vnd.ds-keypoint', + 'ksp' => 'application/vnd.kde.kspread', + 'ktr' => 'application/vnd.kahootz', + 'ktx' => 'image/ktx', + 'ktx2' => 'image/ktx2', + 'ktz' => 'application/vnd.kahootz', + 'kwd' => 'application/vnd.kde.kword', + 'kwt' => 'application/vnd.kde.kword', + 'lasxml' => 'application/vnd.las.las+xml', + 'latex' => 'application/x-latex', + 'lbd' => 'application/vnd.llamagraphics.life-balance.desktop', + 'lbe' => 'application/vnd.llamagraphics.life-balance.exchange+xml', + 'les' => 'application/vnd.hhe.lesson-player', + 'less' => 'text/less', + 'lgr' => 'application/lgr+xml', + 'lha' => 'application/octet-stream', + 'link66' => 'application/vnd.route66.link66+xml', + 'list' => 'text/plain', + 'list3820' => 'application/vnd.ibm.modcap', + 'listafp' => 'application/vnd.ibm.modcap', + 'litcoffee' => 'text/coffeescript', + 'lnk' => 'application/x-ms-shortcut', + 'log' => 'text/plain', + 'lostxml' => 'application/lost+xml', + 'lrf' => 'application/octet-stream', + 'lrm' => 'application/vnd.ms-lrm', + 'ltf' => 'application/vnd.frogans.ltf', + 'lua' => 'text/x-lua', + 'luac' => 'application/x-lua-bytecode', + 'lvp' => 'audio/vnd.lucent.voice', + 'lwp' => 'application/vnd.lotus-wordpro', + 'lzh' => 'application/octet-stream', + 'm1v' => 'video/mpeg', + 'm2a' => 'audio/mpeg', + 'm2v' => 'video/mpeg', + 'm3a' => 'audio/mpeg', + 'm3u' => 'text/plain', + 'm3u8' => 'application/vnd.apple.mpegurl', + 'm4a' => 'audio/x-m4a', + 'm4p' => 'application/mp4', + 'm4s' => 'video/iso.segment', + 'm4u' => 'application/vnd.mpegurl', + 'm4v' => 'video/x-m4v', + 'm13' => 'application/x-msmediaview', + 'm14' => 'application/x-msmediaview', + 'm21' => 'application/mp21', + 'ma' => 'application/mathematica', + 'mads' => 'application/mads+xml', + 'maei' => 'application/mmt-aei+xml', + 'mag' => 'application/vnd.ecowin.chart', + 'maker' => 'application/vnd.framemaker', + 'man' => 'text/troff', + 'manifest' => 'text/cache-manifest', + 'map' => 'application/json', + 'mar' => 'application/octet-stream', + 'markdown' => 'text/markdown', + 'mathml' => 'application/mathml+xml', + 'mb' => 'application/mathematica', + 'mbk' => 'application/vnd.mobius.mbk', + 'mbox' => 'application/mbox', + 'mc1' => 'application/vnd.medcalcdata', + 'mcd' => 'application/vnd.mcd', + 'mcurl' => 'text/vnd.curl.mcurl', + 'md' => 'text/markdown', + 'mdb' => 'application/x-msaccess', + 'mdi' => 'image/vnd.ms-modi', + 'mdx' => 'text/mdx', + 'me' => 'text/troff', + 'mesh' => 'model/mesh', + 'meta4' => 'application/metalink4+xml', + 'metalink' => 'application/metalink+xml', + 'mets' => 'application/mets+xml', + 'mfm' => 'application/vnd.mfmp', + 'mft' => 'application/rpki-manifest', + 'mgp' => 'application/vnd.osgeo.mapguide.package', + 'mgz' => 'application/vnd.proteus.magazine', + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'mie' => 'application/x-mie', + 'mif' => 'application/vnd.mif', + 'mime' => 'message/rfc822', + 'mj2' => 'video/mj2', + 'mjp2' => 'video/mj2', + 'mjs' => 'application/javascript', + 'mk3d' => 'video/x-matroska', + 'mka' => 'audio/x-matroska', + 'mkd' => 'text/x-markdown', + 'mks' => 'video/x-matroska', + 'mkv' => 'video/x-matroska', + 'mlp' => 'application/vnd.dolby.mlp', + 'mmd' => 'application/vnd.chipnuts.karaoke-mmd', + 'mmf' => 'application/vnd.smaf', + 'mml' => 'text/mathml', + 'mmr' => 'image/vnd.fujixerox.edmics-mmr', + 'mng' => 'video/x-mng', + 'mny' => 'application/x-msmoney', + 'mobi' => 'application/x-mobipocket-ebook', + 'mods' => 'application/mods+xml', + 'mov' => 'video/quicktime', + 'movie' => 'video/x-sgi-movie', + 'mp2' => 'audio/mpeg', + 'mp2a' => 'audio/mpeg', + 'mp3' => 'audio/mpeg', + 'mp4' => 'video/mp4', + 'mp4a' => 'audio/mp4', + 'mp4s' => 'application/mp4', + 'mp4v' => 'video/mp4', + 'mp21' => 'application/mp21', + 'mpc' => 'application/vnd.mophun.certificate', + 'mpd' => 'application/dash+xml', + 'mpe' => 'video/mpeg', + 'mpeg' => 'video/mpeg', + 'mpf' => 'application/media-policy-dataset+xml', + 'mpg' => 'video/mpeg', + 'mpg4' => 'video/mp4', + 'mpga' => 'audio/mpeg', + 'mpkg' => 'application/vnd.apple.installer+xml', + 'mpm' => 'application/vnd.blueice.multipass', + 'mpn' => 'application/vnd.mophun.application', + 'mpp' => 'application/vnd.ms-project', + 'mpt' => 'application/vnd.ms-project', + 'mpy' => 'application/vnd.ibm.minipay', + 'mqy' => 'application/vnd.mobius.mqy', + 'mrc' => 'application/marc', + 'mrcx' => 'application/marcxml+xml', + 'ms' => 'text/troff', + 'mscml' => 'application/mediaservercontrol+xml', + 'mseed' => 'application/vnd.fdsn.mseed', + 'mseq' => 'application/vnd.mseq', + 'msf' => 'application/vnd.epson.msf', + 'msg' => 'application/vnd.ms-outlook', + 'msh' => 'model/mesh', + 'msi' => 'application/x-msdownload', + 'msl' => 'application/vnd.mobius.msl', + 'msm' => 'application/octet-stream', + 'msp' => 'application/octet-stream', + 'msty' => 'application/vnd.muvee.style', + 'mtl' => 'model/mtl', + 'mts' => 'model/vnd.mts', + 'mus' => 'application/vnd.musician', + 'musd' => 'application/mmt-usd+xml', + 'musicxml' => 'application/vnd.recordare.musicxml+xml', + 'mvb' => 'application/x-msmediaview', + 'mvt' => 'application/vnd.mapbox-vector-tile', + 'mwf' => 'application/vnd.mfer', + 'mxf' => 'application/mxf', + 'mxl' => 'application/vnd.recordare.musicxml', + 'mxmf' => 'audio/mobile-xmf', + 'mxml' => 'application/xv+xml', + 'mxs' => 'application/vnd.triscape.mxs', + 'mxu' => 'video/vnd.mpegurl', + 'n-gage' => 'application/vnd.nokia.n-gage.symbian.install', + 'n3' => 'text/n3', + 'nb' => 'application/mathematica', + 'nbp' => 'application/vnd.wolfram.player', + 'nc' => 'application/x-netcdf', + 'ncx' => 'application/x-dtbncx+xml', + 'nfo' => 'text/x-nfo', + 'ngdat' => 'application/vnd.nokia.n-gage.data', + 'nitf' => 'application/vnd.nitf', + 'nlu' => 'application/vnd.neurolanguage.nlu', + 'nml' => 'application/vnd.enliven', + 'nnd' => 'application/vnd.noblenet-directory', + 'nns' => 'application/vnd.noblenet-sealer', + 'nnw' => 'application/vnd.noblenet-web', + 'npx' => 'image/vnd.net-fpx', + 'nq' => 'application/n-quads', + 'nsc' => 'application/x-conference', + 'nsf' => 'application/vnd.lotus-notes', + 'nt' => 'application/n-triples', + 'ntf' => 'application/vnd.nitf', + 'numbers' => 'application/x-iwork-numbers-sffnumbers', + 'nzb' => 'application/x-nzb', + 'oa2' => 'application/vnd.fujitsu.oasys2', + 'oa3' => 'application/vnd.fujitsu.oasys3', + 'oas' => 'application/vnd.fujitsu.oasys', + 'obd' => 'application/x-msbinder', + 'obgx' => 'application/vnd.openblox.game+xml', + 'obj' => 'model/obj', + 'oda' => 'application/oda', + 'odb' => 'application/vnd.oasis.opendocument.database', + 'odc' => 'application/vnd.oasis.opendocument.chart', + 'odf' => 'application/vnd.oasis.opendocument.formula', + 'odft' => 'application/vnd.oasis.opendocument.formula-template', + 'odg' => 'application/vnd.oasis.opendocument.graphics', + 'odi' => 'application/vnd.oasis.opendocument.image', + 'odm' => 'application/vnd.oasis.opendocument.text-master', + 'odp' => 'application/vnd.oasis.opendocument.presentation', + 'ods' => 'application/vnd.oasis.opendocument.spreadsheet', + 'odt' => 'application/vnd.oasis.opendocument.text', + 'oga' => 'audio/ogg', + 'ogex' => 'model/vnd.opengex', + 'ogg' => 'audio/ogg', + 'ogv' => 'video/ogg', + 'ogx' => 'application/ogg', + 'omdoc' => 'application/omdoc+xml', + 'onepkg' => 'application/onenote', + 'onetmp' => 'application/onenote', + 'onetoc' => 'application/onenote', + 'onetoc2' => 'application/onenote', + 'opf' => 'application/oebps-package+xml', + 'opml' => 'text/x-opml', + 'oprc' => 'application/vnd.palm', + 'opus' => 'audio/ogg', + 'org' => 'text/x-org', + 'osf' => 'application/vnd.yamaha.openscoreformat', + 'osfpvg' => 'application/vnd.yamaha.openscoreformat.osfpvg+xml', + 'osm' => 'application/vnd.openstreetmap.data+xml', + 'otc' => 'application/vnd.oasis.opendocument.chart-template', + 'otf' => 'font/otf', + 'otg' => 'application/vnd.oasis.opendocument.graphics-template', + 'oth' => 'application/vnd.oasis.opendocument.text-web', + 'oti' => 'application/vnd.oasis.opendocument.image-template', + 'otp' => 'application/vnd.oasis.opendocument.presentation-template', + 'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template', + 'ott' => 'application/vnd.oasis.opendocument.text-template', + 'ova' => 'application/x-virtualbox-ova', + 'ovf' => 'application/x-virtualbox-ovf', + 'owl' => 'application/rdf+xml', + 'oxps' => 'application/oxps', + 'oxt' => 'application/vnd.openofficeorg.extension', + 'p' => 'text/x-pascal', + 'p7a' => 'application/x-pkcs7-signature', + 'p7b' => 'application/x-pkcs7-certificates', + 'p7c' => 'application/pkcs7-mime', + 'p7m' => 'application/pkcs7-mime', + 'p7r' => 'application/x-pkcs7-certreqresp', + 'p7s' => 'application/pkcs7-signature', + 'p8' => 'application/pkcs8', + 'p10' => 'application/x-pkcs10', + 'p12' => 'application/x-pkcs12', + 'pac' => 'application/x-ns-proxy-autoconfig', + 'pages' => 'application/x-iwork-pages-sffpages', + 'pas' => 'text/x-pascal', + 'paw' => 'application/vnd.pawaafile', + 'pbd' => 'application/vnd.powerbuilder6', + 'pbm' => 'image/x-portable-bitmap', + 'pcap' => 'application/vnd.tcpdump.pcap', + 'pcf' => 'application/x-font-pcf', + 'pcl' => 'application/vnd.hp-pcl', + 'pclxl' => 'application/vnd.hp-pclxl', + 'pct' => 'image/x-pict', + 'pcurl' => 'application/vnd.curl.pcurl', + 'pcx' => 'image/x-pcx', + 'pdb' => 'application/x-pilot', + 'pde' => 'text/x-processing', + 'pdf' => 'application/pdf', + 'pem' => 'application/x-x509-user-cert', + 'pfa' => 'application/x-font-type1', + 'pfb' => 'application/x-font-type1', + 'pfm' => 'application/x-font-type1', + 'pfr' => 'application/font-tdpfr', + 'pfx' => 'application/x-pkcs12', + 'pgm' => 'image/x-portable-graymap', + 'pgn' => 'application/x-chess-pgn', + 'pgp' => 'application/pgp', + 'phar' => 'application/octet-stream', + 'php' => 'application/x-httpd-php', + 'php3' => 'application/x-httpd-php', + 'php4' => 'application/x-httpd-php', + 'phps' => 'application/x-httpd-php-source', + 'phtml' => 'application/x-httpd-php', + 'pic' => 'image/x-pict', + 'pkg' => 'application/octet-stream', + 'pki' => 'application/pkixcmp', + 'pkipath' => 'application/pkix-pkipath', + 'pkpass' => 'application/vnd.apple.pkpass', + 'pl' => 'application/x-perl', + 'plb' => 'application/vnd.3gpp.pic-bw-large', + 'plc' => 'application/vnd.mobius.plc', + 'plf' => 'application/vnd.pocketlearn', + 'pls' => 'application/pls+xml', + 'pm' => 'application/x-perl', + 'pml' => 'application/vnd.ctc-posml', + 'png' => 'image/png', + 'pnm' => 'image/x-portable-anymap', + 'portpkg' => 'application/vnd.macports.portpkg', + 'pot' => 'application/vnd.ms-powerpoint', + 'potm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12', + 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template', + 'ppa' => 'application/vnd.ms-powerpoint', + 'ppam' => 'application/vnd.ms-powerpoint.addin.macroEnabled.12', + 'ppd' => 'application/vnd.cups-ppd', + 'ppm' => 'image/x-portable-pixmap', + 'pps' => 'application/vnd.ms-powerpoint', + 'ppsm' => 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12', + 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', + 'ppt' => 'application/powerpoint', + 'pptm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12', + 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'pqa' => 'application/vnd.palm', + 'prc' => 'model/prc', + 'pre' => 'application/vnd.lotus-freelance', + 'prf' => 'application/pics-rules', + 'provx' => 'application/provenance+xml', + 'ps' => 'application/postscript', + 'psb' => 'application/vnd.3gpp.pic-bw-small', + 'psd' => 'application/x-photoshop', + 'psf' => 'application/x-font-linux-psf', + 'pskcxml' => 'application/pskc+xml', + 'pti' => 'image/prs.pti', + 'ptid' => 'application/vnd.pvi.ptid1', + 'pub' => 'application/x-mspublisher', + 'pvb' => 'application/vnd.3gpp.pic-bw-var', + 'pwn' => 'application/vnd.3m.post-it-notes', + 'pya' => 'audio/vnd.ms-playready.media.pya', + 'pyv' => 'video/vnd.ms-playready.media.pyv', + 'qam' => 'application/vnd.epson.quickanime', + 'qbo' => 'application/vnd.intu.qbo', + 'qfx' => 'application/vnd.intu.qfx', + 'qps' => 'application/vnd.publishare-delta-tree', + 'qt' => 'video/quicktime', + 'qwd' => 'application/vnd.quark.quarkxpress', + 'qwt' => 'application/vnd.quark.quarkxpress', + 'qxb' => 'application/vnd.quark.quarkxpress', + 'qxd' => 'application/vnd.quark.quarkxpress', + 'qxl' => 'application/vnd.quark.quarkxpress', + 'qxt' => 'application/vnd.quark.quarkxpress', + 'ra' => 'audio/x-realaudio', + 'ram' => 'audio/x-pn-realaudio', + 'raml' => 'application/raml+yaml', + 'rapd' => 'application/route-apd+xml', + 'rar' => 'application/x-rar', + 'ras' => 'image/x-cmu-raster', + 'rcprofile' => 'application/vnd.ipunplugged.rcprofile', + 'rdf' => 'application/rdf+xml', + 'rdz' => 'application/vnd.data-vision.rdz', + 'relo' => 'application/p2p-overlay+xml', + 'rep' => 'application/vnd.businessobjects', + 'res' => 'application/x-dtbresource+xml', + 'rgb' => 'image/x-rgb', + 'rif' => 'application/reginfo+xml', + 'rip' => 'audio/vnd.rip', + 'ris' => 'application/x-research-info-systems', + 'rl' => 'application/resource-lists+xml', + 'rlc' => 'image/vnd.fujixerox.edmics-rlc', + 'rld' => 'application/resource-lists-diff+xml', + 'rm' => 'audio/x-pn-realaudio', + 'rmi' => 'audio/midi', + 'rmp' => 'audio/x-pn-realaudio-plugin', + 'rms' => 'application/vnd.jcp.javame.midlet-rms', + 'rmvb' => 'application/vnd.rn-realmedia-vbr', + 'rnc' => 'application/relax-ng-compact-syntax', + 'rng' => 'application/xml', + 'roa' => 'application/rpki-roa', + 'roff' => 'text/troff', + 'rp9' => 'application/vnd.cloanto.rp9', + 'rpm' => 'audio/x-pn-realaudio-plugin', + 'rpss' => 'application/vnd.nokia.radio-presets', + 'rpst' => 'application/vnd.nokia.radio-preset', + 'rq' => 'application/sparql-query', + 'rs' => 'application/rls-services+xml', + 'rsa' => 'application/x-pkcs7', + 'rsat' => 'application/atsc-rsat+xml', + 'rsd' => 'application/rsd+xml', + 'rsheet' => 'application/urc-ressheet+xml', + 'rss' => 'application/rss+xml', + 'rtf' => 'text/rtf', + 'rtx' => 'text/richtext', + 'run' => 'application/x-makeself', + 'rusd' => 'application/route-usd+xml', + 'rv' => 'video/vnd.rn-realvideo', + 's' => 'text/x-asm', + 's3m' => 'audio/s3m', + 'saf' => 'application/vnd.yamaha.smaf-audio', + 'sass' => 'text/x-sass', + 'sbml' => 'application/sbml+xml', + 'sc' => 'application/vnd.ibm.secure-container', + 'scd' => 'application/x-msschedule', + 'scm' => 'application/vnd.lotus-screencam', + 'scq' => 'application/scvp-cv-request', + 'scs' => 'application/scvp-cv-response', + 'scss' => 'text/x-scss', + 'scurl' => 'text/vnd.curl.scurl', + 'sda' => 'application/vnd.stardivision.draw', + 'sdc' => 'application/vnd.stardivision.calc', + 'sdd' => 'application/vnd.stardivision.impress', + 'sdkd' => 'application/vnd.solent.sdkm+xml', + 'sdkm' => 'application/vnd.solent.sdkm+xml', + 'sdp' => 'application/sdp', + 'sdw' => 'application/vnd.stardivision.writer', + 'sea' => 'application/octet-stream', + 'see' => 'application/vnd.seemail', + 'seed' => 'application/vnd.fdsn.seed', + 'sema' => 'application/vnd.sema', + 'semd' => 'application/vnd.semd', + 'semf' => 'application/vnd.semf', + 'senmlx' => 'application/senml+xml', + 'sensmlx' => 'application/sensml+xml', + 'ser' => 'application/java-serialized-object', + 'setpay' => 'application/set-payment-initiation', + 'setreg' => 'application/set-registration-initiation', + 'sfd-hdstx' => 'application/vnd.hydrostatix.sof-data', + 'sfs' => 'application/vnd.spotfire.sfs', + 'sfv' => 'text/x-sfv', + 'sgi' => 'image/sgi', + 'sgl' => 'application/vnd.stardivision.writer-global', + 'sgm' => 'text/sgml', + 'sgml' => 'text/sgml', + 'sh' => 'application/x-sh', + 'shar' => 'application/x-shar', + 'shex' => 'text/shex', + 'shf' => 'application/shf+xml', + 'shtml' => 'text/html', + 'sid' => 'image/x-mrsid-image', + 'sieve' => 'application/sieve', + 'sig' => 'application/pgp-signature', + 'sil' => 'audio/silk', + 'silo' => 'model/mesh', + 'sis' => 'application/vnd.symbian.install', + 'sisx' => 'application/vnd.symbian.install', + 'sit' => 'application/x-stuffit', + 'sitx' => 'application/x-stuffitx', + 'siv' => 'application/sieve', + 'skd' => 'application/vnd.koan', + 'skm' => 'application/vnd.koan', + 'skp' => 'application/vnd.koan', + 'skt' => 'application/vnd.koan', + 'sldm' => 'application/vnd.ms-powerpoint.slide.macroenabled.12', + 'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide', + 'slim' => 'text/slim', + 'slm' => 'text/slim', + 'sls' => 'application/route-s-tsid+xml', + 'slt' => 'application/vnd.epson.salt', + 'sm' => 'application/vnd.stepmania.stepchart', + 'smf' => 'application/vnd.stardivision.math', + 'smi' => 'application/smil', + 'smil' => 'application/smil', + 'smv' => 'video/x-smv', + 'smzip' => 'application/vnd.stepmania.package', + 'snd' => 'audio/basic', + 'snf' => 'application/x-font-snf', + 'so' => 'application/octet-stream', + 'spc' => 'application/x-pkcs7-certificates', + 'spdx' => 'text/spdx', + 'spf' => 'application/vnd.yamaha.smaf-phrase', + 'spl' => 'application/x-futuresplash', + 'spot' => 'text/vnd.in3d.spot', + 'spp' => 'application/scvp-vp-response', + 'spq' => 'application/scvp-vp-request', + 'spx' => 'audio/ogg', + 'sql' => 'application/x-sql', + 'src' => 'application/x-wais-source', + 'srt' => 'application/x-subrip', + 'sru' => 'application/sru+xml', + 'srx' => 'application/sparql-results+xml', + 'ssdl' => 'application/ssdl+xml', + 'sse' => 'application/vnd.kodak-descriptor', + 'ssf' => 'application/vnd.epson.ssf', + 'ssml' => 'application/ssml+xml', + 'sst' => 'application/octet-stream', + 'st' => 'application/vnd.sailingtracker.track', + 'stc' => 'application/vnd.sun.xml.calc.template', + 'std' => 'application/vnd.sun.xml.draw.template', + 'stf' => 'application/vnd.wt.stf', + 'sti' => 'application/vnd.sun.xml.impress.template', + 'stk' => 'application/hyperstudio', + 'stl' => 'model/stl', + 'stpx' => 'model/step+xml', + 'stpxz' => 'model/step-xml+zip', + 'stpz' => 'model/step+zip', + 'str' => 'application/vnd.pg.format', + 'stw' => 'application/vnd.sun.xml.writer.template', + 'styl' => 'text/stylus', + 'stylus' => 'text/stylus', + 'sub' => 'text/vnd.dvb.subtitle', + 'sus' => 'application/vnd.sus-calendar', + 'susp' => 'application/vnd.sus-calendar', + 'sv4cpio' => 'application/x-sv4cpio', + 'sv4crc' => 'application/x-sv4crc', + 'svc' => 'application/vnd.dvb.service', + 'svd' => 'application/vnd.svd', + 'svg' => 'image/svg+xml', + 'svgz' => 'image/svg+xml', + 'swa' => 'application/x-director', + 'swf' => 'application/x-shockwave-flash', + 'swi' => 'application/vnd.aristanetworks.swi', + 'swidtag' => 'application/swid+xml', + 'sxc' => 'application/vnd.sun.xml.calc', + 'sxd' => 'application/vnd.sun.xml.draw', + 'sxg' => 'application/vnd.sun.xml.writer.global', + 'sxi' => 'application/vnd.sun.xml.impress', + 'sxm' => 'application/vnd.sun.xml.math', + 'sxw' => 'application/vnd.sun.xml.writer', + 't' => 'text/troff', + 't3' => 'application/x-t3vm-image', + 't38' => 'image/t38', + 'taglet' => 'application/vnd.mynfc', + 'tao' => 'application/vnd.tao.intent-module-archive', + 'tap' => 'image/vnd.tencent.tap', + 'tar' => 'application/x-tar', + 'tcap' => 'application/vnd.3gpp2.tcap', + 'tcl' => 'application/x-tcl', + 'td' => 'application/urc-targetdesc+xml', + 'teacher' => 'application/vnd.smart.teacher', + 'tei' => 'application/tei+xml', + 'teicorpus' => 'application/tei+xml', + 'tex' => 'application/x-tex', + 'texi' => 'application/x-texinfo', + 'texinfo' => 'application/x-texinfo', + 'text' => 'text/plain', + 'tfi' => 'application/thraud+xml', + 'tfm' => 'application/x-tex-tfm', + 'tfx' => 'image/tiff-fx', + 'tga' => 'image/x-tga', + 'tgz' => 'application/x-tar', + 'thmx' => 'application/vnd.ms-officetheme', + 'tif' => 'image/tiff', + 'tiff' => 'image/tiff', + 'tk' => 'application/x-tcl', + 'tmo' => 'application/vnd.tmobile-livetv', + 'toml' => 'application/toml', + 'torrent' => 'application/x-bittorrent', + 'tpl' => 'application/vnd.groove-tool-template', + 'tpt' => 'application/vnd.trid.tpt', + 'tr' => 'text/troff', + 'tra' => 'application/vnd.trueapp', + 'trig' => 'application/trig', + 'trm' => 'application/x-msterminal', + 'ts' => 'video/mp2t', + 'tsd' => 'application/timestamped-data', + 'tsv' => 'text/tab-separated-values', + 'ttc' => 'font/collection', + 'ttf' => 'font/ttf', + 'ttl' => 'text/turtle', + 'ttml' => 'application/ttml+xml', + 'twd' => 'application/vnd.simtech-mindmapper', + 'twds' => 'application/vnd.simtech-mindmapper', + 'txd' => 'application/vnd.genomatix.tuxedo', + 'txf' => 'application/vnd.mobius.txf', + 'txt' => 'text/plain', + 'u3d' => 'model/u3d', + 'u8dsn' => 'message/global-delivery-status', + 'u8hdr' => 'message/global-headers', + 'u8mdn' => 'message/global-disposition-notification', + 'u8msg' => 'message/global', + 'u32' => 'application/x-authorware-bin', + 'ubj' => 'application/ubjson', + 'udeb' => 'application/x-debian-package', + 'ufd' => 'application/vnd.ufdl', + 'ufdl' => 'application/vnd.ufdl', + 'ulx' => 'application/x-glulx', + 'umj' => 'application/vnd.umajin', + 'unityweb' => 'application/vnd.unity', + 'uoml' => 'application/vnd.uoml+xml', + 'uri' => 'text/uri-list', + 'uris' => 'text/uri-list', + 'urls' => 'text/uri-list', + 'usdz' => 'model/vnd.usdz+zip', + 'ustar' => 'application/x-ustar', + 'utz' => 'application/vnd.uiq.theme', + 'uu' => 'text/x-uuencode', + 'uva' => 'audio/vnd.dece.audio', + 'uvd' => 'application/vnd.dece.data', + 'uvf' => 'application/vnd.dece.data', + 'uvg' => 'image/vnd.dece.graphic', + 'uvh' => 'video/vnd.dece.hd', + 'uvi' => 'image/vnd.dece.graphic', + 'uvm' => 'video/vnd.dece.mobile', + 'uvp' => 'video/vnd.dece.pd', + 'uvs' => 'video/vnd.dece.sd', + 'uvt' => 'application/vnd.dece.ttml+xml', + 'uvu' => 'video/vnd.uvvu.mp4', + 'uvv' => 'video/vnd.dece.video', + 'uvva' => 'audio/vnd.dece.audio', + 'uvvd' => 'application/vnd.dece.data', + 'uvvf' => 'application/vnd.dece.data', + 'uvvg' => 'image/vnd.dece.graphic', + 'uvvh' => 'video/vnd.dece.hd', + 'uvvi' => 'image/vnd.dece.graphic', + 'uvvm' => 'video/vnd.dece.mobile', + 'uvvp' => 'video/vnd.dece.pd', + 'uvvs' => 'video/vnd.dece.sd', + 'uvvt' => 'application/vnd.dece.ttml+xml', + 'uvvu' => 'video/vnd.uvvu.mp4', + 'uvvv' => 'video/vnd.dece.video', + 'uvvx' => 'application/vnd.dece.unspecified', + 'uvvz' => 'application/vnd.dece.zip', + 'uvx' => 'application/vnd.dece.unspecified', + 'uvz' => 'application/vnd.dece.zip', + 'vbox' => 'application/x-virtualbox-vbox', + 'vbox-extpack' => 'application/x-virtualbox-vbox-extpack', + 'vcard' => 'text/vcard', + 'vcd' => 'application/x-cdlink', + 'vcf' => 'text/x-vcard', + 'vcg' => 'application/vnd.groove-vcard', + 'vcs' => 'text/x-vcalendar', + 'vcx' => 'application/vnd.vcx', + 'vdi' => 'application/x-virtualbox-vdi', + 'vds' => 'model/vnd.sap.vds', + 'vhd' => 'application/x-virtualbox-vhd', + 'vis' => 'application/vnd.visionary', + 'viv' => 'video/vnd.vivo', + 'vlc' => 'application/videolan', + 'vmdk' => 'application/x-virtualbox-vmdk', + 'vob' => 'video/x-ms-vob', + 'vor' => 'application/vnd.stardivision.writer', + 'vox' => 'application/x-authorware-bin', + 'vrml' => 'model/vrml', + 'vsd' => 'application/vnd.visio', + 'vsf' => 'application/vnd.vsf', + 'vss' => 'application/vnd.visio', + 'vst' => 'application/vnd.visio', + 'vsw' => 'application/vnd.visio', + 'vtf' => 'image/vnd.valve.source.texture', + 'vtt' => 'text/vtt', + 'vtu' => 'model/vnd.vtu', + 'vxml' => 'application/voicexml+xml', + 'w3d' => 'application/x-director', + 'wad' => 'application/x-doom', + 'wadl' => 'application/vnd.sun.wadl+xml', + 'war' => 'application/java-archive', + 'wasm' => 'application/wasm', + 'wav' => 'audio/x-wav', + 'wax' => 'audio/x-ms-wax', + 'wbmp' => 'image/vnd.wap.wbmp', + 'wbs' => 'application/vnd.criticaltools.wbs+xml', + 'wbxml' => 'application/wbxml', + 'wcm' => 'application/vnd.ms-works', + 'wdb' => 'application/vnd.ms-works', + 'wdp' => 'image/vnd.ms-photo', + 'weba' => 'audio/webm', + 'webapp' => 'application/x-web-app-manifest+json', + 'webm' => 'video/webm', + 'webmanifest' => 'application/manifest+json', + 'webp' => 'image/webp', + 'wg' => 'application/vnd.pmi.widget', + 'wgt' => 'application/widget', + 'wif' => 'application/watcherinfo+xml', + 'wks' => 'application/vnd.ms-works', + 'wm' => 'video/x-ms-wm', + 'wma' => 'audio/x-ms-wma', + 'wmd' => 'application/x-ms-wmd', + 'wmf' => 'image/wmf', + 'wml' => 'text/vnd.wap.wml', + 'wmlc' => 'application/wmlc', + 'wmls' => 'text/vnd.wap.wmlscript', + 'wmlsc' => 'application/vnd.wap.wmlscriptc', + 'wmv' => 'video/x-ms-wmv', + 'wmx' => 'video/x-ms-wmx', + 'wmz' => 'application/x-msmetafile', + 'woff' => 'font/woff', + 'woff2' => 'font/woff2', + 'word' => 'application/msword', + 'wpd' => 'application/vnd.wordperfect', + 'wpl' => 'application/vnd.ms-wpl', + 'wps' => 'application/vnd.ms-works', + 'wqd' => 'application/vnd.wqd', + 'wri' => 'application/x-mswrite', + 'wrl' => 'model/vrml', + 'wsc' => 'message/vnd.wfa.wsc', + 'wsdl' => 'application/wsdl+xml', + 'wspolicy' => 'application/wspolicy+xml', + 'wtb' => 'application/vnd.webturbo', + 'wvx' => 'video/x-ms-wvx', + 'x3d' => 'model/x3d+xml', + 'x3db' => 'model/x3d+fastinfoset', + 'x3dbz' => 'model/x3d+binary', + 'x3dv' => 'model/x3d-vrml', + 'x3dvz' => 'model/x3d+vrml', + 'x3dz' => 'model/x3d+xml', + 'x32' => 'application/x-authorware-bin', + 'x_b' => 'model/vnd.parasolid.transmit.binary', + 'x_t' => 'model/vnd.parasolid.transmit.text', + 'xaml' => 'application/xaml+xml', + 'xap' => 'application/x-silverlight-app', + 'xar' => 'application/vnd.xara', + 'xav' => 'application/xcap-att+xml', + 'xbap' => 'application/x-ms-xbap', + 'xbd' => 'application/vnd.fujixerox.docuworks.binder', + 'xbm' => 'image/x-xbitmap', + 'xca' => 'application/xcap-caps+xml', + 'xcs' => 'application/calendar+xml', + 'xdf' => 'application/xcap-diff+xml', + 'xdm' => 'application/vnd.syncml.dm+xml', + 'xdp' => 'application/vnd.adobe.xdp+xml', + 'xdssc' => 'application/dssc+xml', + 'xdw' => 'application/vnd.fujixerox.docuworks', + 'xel' => 'application/xcap-el+xml', + 'xenc' => 'application/xenc+xml', + 'xer' => 'application/patch-ops-error+xml', + 'xfdf' => 'application/vnd.adobe.xfdf', + 'xfdl' => 'application/vnd.xfdl', + 'xht' => 'application/xhtml+xml', + 'xhtml' => 'application/xhtml+xml', + 'xhvml' => 'application/xv+xml', + 'xif' => 'image/vnd.xiff', + 'xl' => 'application/excel', + 'xla' => 'application/vnd.ms-excel', + 'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12', + 'xlc' => 'application/vnd.ms-excel', + 'xlf' => 'application/xliff+xml', + 'xlm' => 'application/vnd.ms-excel', + 'xls' => 'application/vnd.ms-excel', + 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12', + 'xlsm' => 'application/vnd.ms-excel.sheet.macroEnabled.12', + 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'xlt' => 'application/vnd.ms-excel', + 'xltm' => 'application/vnd.ms-excel.template.macroEnabled.12', + 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', + 'xlw' => 'application/vnd.ms-excel', + 'xm' => 'audio/xm', + 'xml' => 'application/xml', + 'xns' => 'application/xcap-ns+xml', + 'xo' => 'application/vnd.olpc-sugar', + 'xop' => 'application/xop+xml', + 'xpi' => 'application/x-xpinstall', + 'xpl' => 'application/xproc+xml', + 'xpm' => 'image/x-xpixmap', + 'xpr' => 'application/vnd.is-xpr', + 'xps' => 'application/vnd.ms-xpsdocument', + 'xpw' => 'application/vnd.intercon.formnet', + 'xpx' => 'application/vnd.intercon.formnet', + 'xsd' => 'application/xml', + 'xsl' => 'application/xml', + 'xslt' => 'application/xslt+xml', + 'xsm' => 'application/vnd.syncml+xml', + 'xspf' => 'application/xspf+xml', + 'xul' => 'application/vnd.mozilla.xul+xml', + 'xvm' => 'application/xv+xml', + 'xvml' => 'application/xv+xml', + 'xwd' => 'image/x-xwindowdump', + 'xyz' => 'chemical/x-xyz', + 'xz' => 'application/x-xz', + 'yaml' => 'text/yaml', + 'yang' => 'application/yang', + 'yin' => 'application/yin+xml', + 'yml' => 'text/yaml', + 'ymp' => 'text/x-suse-ymp', + 'z' => 'application/x-compress', + 'z1' => 'application/x-zmachine', + 'z2' => 'application/x-zmachine', + 'z3' => 'application/x-zmachine', + 'z4' => 'application/x-zmachine', + 'z5' => 'application/x-zmachine', + 'z6' => 'application/x-zmachine', + 'z7' => 'application/x-zmachine', + 'z8' => 'application/x-zmachine', + 'zaz' => 'application/vnd.zzazz.deck+xml', + 'zip' => 'application/zip', + 'zir' => 'application/vnd.zul', + 'zirz' => 'application/vnd.zul', + 'zmm' => 'application/vnd.handheld-entertainment+xml', + 'zsh' => 'text/x-scriptzsh', + ]; + + /** + * Determines the mimetype of a file by looking at its extension. + * + * @link https://raw.githubusercontent.com/jshttp/mime-db/master/db.json + */ + public static function fromFilename(string $filename): ?string + { + return self::fromExtension(pathinfo($filename, PATHINFO_EXTENSION)); + } + + /** + * Maps a file extensions to a mimetype. + * + * @link https://raw.githubusercontent.com/jshttp/mime-db/master/db.json + */ + public static function fromExtension(string $extension): ?string + { + return self::MIME_TYPES[strtolower($extension)] ?? null; + } +} diff --git a/vendor/guzzlehttp/psr7/src/MultipartStream.php b/vendor/guzzlehttp/psr7/src/MultipartStream.php new file mode 100644 index 0000000..3e12b74 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/MultipartStream.php @@ -0,0 +1,159 @@ +boundary = $boundary ?: bin2hex(random_bytes(20)); + $this->stream = $this->createStream($elements); + } + + public function getBoundary(): string + { + return $this->boundary; + } + + public function isWritable(): bool + { + return false; + } + + /** + * Get the headers needed before transferring the content of a POST file + * + * @param array $headers + */ + private function getHeaders(array $headers): string + { + $str = ''; + foreach ($headers as $key => $value) { + $str .= "{$key}: {$value}\r\n"; + } + + return "--{$this->boundary}\r\n" . trim($str) . "\r\n\r\n"; + } + + /** + * Create the aggregate stream that will be used to upload the POST data + */ + protected function createStream(array $elements = []): StreamInterface + { + $stream = new AppendStream(); + + foreach ($elements as $element) { + if (!is_array($element)) { + throw new \UnexpectedValueException("An array is expected"); + } + $this->addElement($stream, $element); + } + + // Add the trailing boundary with CRLF + $stream->addStream(Utils::streamFor("--{$this->boundary}--\r\n")); + + return $stream; + } + + private function addElement(AppendStream $stream, array $element): void + { + foreach (['contents', 'name'] as $key) { + if (!array_key_exists($key, $element)) { + throw new \InvalidArgumentException("A '{$key}' key is required"); + } + } + + $element['contents'] = Utils::streamFor($element['contents']); + + if (empty($element['filename'])) { + $uri = $element['contents']->getMetadata('uri'); + if ($uri && \is_string($uri) && \substr($uri, 0, 6) !== 'php://' && \substr($uri, 0, 7) !== 'data://') { + $element['filename'] = $uri; + } + } + + [$body, $headers] = $this->createElement( + $element['name'], + $element['contents'], + $element['filename'] ?? null, + $element['headers'] ?? [] + ); + + $stream->addStream(Utils::streamFor($this->getHeaders($headers))); + $stream->addStream($body); + $stream->addStream(Utils::streamFor("\r\n")); + } + + private function createElement(string $name, StreamInterface $stream, ?string $filename, array $headers): array + { + // Set a default content-disposition header if one was no provided + $disposition = $this->getHeader($headers, 'content-disposition'); + if (!$disposition) { + $headers['Content-Disposition'] = ($filename === '0' || $filename) + ? sprintf( + 'form-data; name="%s"; filename="%s"', + $name, + basename($filename) + ) + : "form-data; name=\"{$name}\""; + } + + // Set a default content-length header if one was no provided + $length = $this->getHeader($headers, 'content-length'); + if (!$length) { + if ($length = $stream->getSize()) { + $headers['Content-Length'] = (string) $length; + } + } + + // Set a default Content-Type if one was not supplied + $type = $this->getHeader($headers, 'content-type'); + if (!$type && ($filename === '0' || $filename)) { + if ($type = MimeType::fromFilename($filename)) { + $headers['Content-Type'] = $type; + } + } + + return [$stream, $headers]; + } + + private function getHeader(array $headers, string $key) + { + $lowercaseHeader = strtolower($key); + foreach ($headers as $k => $v) { + if (strtolower($k) === $lowercaseHeader) { + return $v; + } + } + + return null; + } +} diff --git a/vendor/guzzlehttp/psr7/src/NoSeekStream.php b/vendor/guzzlehttp/psr7/src/NoSeekStream.php new file mode 100644 index 0000000..161a224 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/NoSeekStream.php @@ -0,0 +1,28 @@ +source = $source; + $this->size = $options['size'] ?? null; + $this->metadata = $options['metadata'] ?? []; + $this->buffer = new BufferStream(); + } + + public function __toString(): string + { + try { + return Utils::copyToString($this); + } catch (\Throwable $e) { + if (\PHP_VERSION_ID >= 70400) { + throw $e; + } + trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR); + return ''; + } + } + + public function close(): void + { + $this->detach(); + } + + public function detach() + { + $this->tellPos = 0; + $this->source = null; + + return null; + } + + public function getSize(): ?int + { + return $this->size; + } + + public function tell(): int + { + return $this->tellPos; + } + + public function eof(): bool + { + return $this->source === null; + } + + public function isSeekable(): bool + { + return false; + } + + public function rewind(): void + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET): void + { + throw new \RuntimeException('Cannot seek a PumpStream'); + } + + public function isWritable(): bool + { + return false; + } + + public function write($string): int + { + throw new \RuntimeException('Cannot write to a PumpStream'); + } + + public function isReadable(): bool + { + return true; + } + + public function read($length): string + { + $data = $this->buffer->read($length); + $readLen = strlen($data); + $this->tellPos += $readLen; + $remaining = $length - $readLen; + + if ($remaining) { + $this->pump($remaining); + $data .= $this->buffer->read($remaining); + $this->tellPos += strlen($data) - $readLen; + } + + return $data; + } + + public function getContents(): string + { + $result = ''; + while (!$this->eof()) { + $result .= $this->read(1000000); + } + + return $result; + } + + /** + * {@inheritdoc} + * + * @return mixed + */ + public function getMetadata($key = null) + { + if (!$key) { + return $this->metadata; + } + + return $this->metadata[$key] ?? null; + } + + private function pump(int $length): void + { + if ($this->source) { + do { + $data = call_user_func($this->source, $length); + if ($data === false || $data === null) { + $this->source = null; + return; + } + $this->buffer->write($data); + $length -= strlen($data); + } while ($length > 0); + } + } +} diff --git a/vendor/guzzlehttp/psr7/src/Query.php b/vendor/guzzlehttp/psr7/src/Query.php new file mode 100644 index 0000000..2faab3a --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Query.php @@ -0,0 +1,113 @@ + '1', 'foo[b]' => '2'])`. + * + * @param string $str Query string to parse + * @param int|bool $urlEncoding How the query string is encoded + */ + public static function parse(string $str, $urlEncoding = true): array + { + $result = []; + + if ($str === '') { + return $result; + } + + if ($urlEncoding === true) { + $decoder = function ($value) { + return rawurldecode(str_replace('+', ' ', (string) $value)); + }; + } elseif ($urlEncoding === PHP_QUERY_RFC3986) { + $decoder = 'rawurldecode'; + } elseif ($urlEncoding === PHP_QUERY_RFC1738) { + $decoder = 'urldecode'; + } else { + $decoder = function ($str) { + return $str; + }; + } + + foreach (explode('&', $str) as $kvp) { + $parts = explode('=', $kvp, 2); + $key = $decoder($parts[0]); + $value = isset($parts[1]) ? $decoder($parts[1]) : null; + if (!array_key_exists($key, $result)) { + $result[$key] = $value; + } else { + if (!is_array($result[$key])) { + $result[$key] = [$result[$key]]; + } + $result[$key][] = $value; + } + } + + return $result; + } + + /** + * Build a query string from an array of key value pairs. + * + * This function can use the return value of `parse()` to build a query + * string. This function does not modify the provided keys when an array is + * encountered (like `http_build_query()` would). + * + * @param array $params Query string parameters. + * @param int|false $encoding Set to false to not encode, PHP_QUERY_RFC3986 + * to encode using RFC3986, or PHP_QUERY_RFC1738 + * to encode using RFC1738. + */ + public static function build(array $params, $encoding = PHP_QUERY_RFC3986): string + { + if (!$params) { + return ''; + } + + if ($encoding === false) { + $encoder = function (string $str): string { + return $str; + }; + } elseif ($encoding === PHP_QUERY_RFC3986) { + $encoder = 'rawurlencode'; + } elseif ($encoding === PHP_QUERY_RFC1738) { + $encoder = 'urlencode'; + } else { + throw new \InvalidArgumentException('Invalid type'); + } + + $qs = ''; + foreach ($params as $k => $v) { + $k = $encoder((string) $k); + if (!is_array($v)) { + $qs .= $k; + $v = is_bool($v) ? (int) $v : $v; + if ($v !== null) { + $qs .= '=' . $encoder((string) $v); + } + $qs .= '&'; + } else { + foreach ($v as $vv) { + $qs .= $k; + $vv = is_bool($vv) ? (int) $vv : $vv; + if ($vv !== null) { + $qs .= '=' . $encoder((string) $vv); + } + $qs .= '&'; + } + } + } + + return $qs ? (string) substr($qs, 0, -1) : ''; + } +} diff --git a/vendor/guzzlehttp/psr7/src/Request.php b/vendor/guzzlehttp/psr7/src/Request.php new file mode 100644 index 0000000..b17af66 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Request.php @@ -0,0 +1,157 @@ + $headers Request headers + * @param string|resource|StreamInterface|null $body Request body + * @param string $version Protocol version + */ + public function __construct( + string $method, + $uri, + array $headers = [], + $body = null, + string $version = '1.1' + ) { + $this->assertMethod($method); + if (!($uri instanceof UriInterface)) { + $uri = new Uri($uri); + } + + $this->method = strtoupper($method); + $this->uri = $uri; + $this->setHeaders($headers); + $this->protocol = $version; + + if (!isset($this->headerNames['host'])) { + $this->updateHostFromUri(); + } + + if ($body !== '' && $body !== null) { + $this->stream = Utils::streamFor($body); + } + } + + public function getRequestTarget(): string + { + if ($this->requestTarget !== null) { + return $this->requestTarget; + } + + $target = $this->uri->getPath(); + if ($target === '') { + $target = '/'; + } + if ($this->uri->getQuery() != '') { + $target .= '?' . $this->uri->getQuery(); + } + + return $target; + } + + public function withRequestTarget($requestTarget): RequestInterface + { + if (preg_match('#\s#', $requestTarget)) { + throw new InvalidArgumentException( + 'Invalid request target provided; cannot contain whitespace' + ); + } + + $new = clone $this; + $new->requestTarget = $requestTarget; + return $new; + } + + public function getMethod(): string + { + return $this->method; + } + + public function withMethod($method): RequestInterface + { + $this->assertMethod($method); + $new = clone $this; + $new->method = strtoupper($method); + return $new; + } + + public function getUri(): UriInterface + { + return $this->uri; + } + + public function withUri(UriInterface $uri, $preserveHost = false): RequestInterface + { + if ($uri === $this->uri) { + return $this; + } + + $new = clone $this; + $new->uri = $uri; + + if (!$preserveHost || !isset($this->headerNames['host'])) { + $new->updateHostFromUri(); + } + + return $new; + } + + private function updateHostFromUri(): void + { + $host = $this->uri->getHost(); + + if ($host == '') { + return; + } + + if (($port = $this->uri->getPort()) !== null) { + $host .= ':' . $port; + } + + if (isset($this->headerNames['host'])) { + $header = $this->headerNames['host']; + } else { + $header = 'Host'; + $this->headerNames['host'] = 'Host'; + } + // Ensure Host is the first header. + // See: http://tools.ietf.org/html/rfc7230#section-5.4 + $this->headers = [$header => [$host]] + $this->headers; + } + + /** + * @param mixed $method + */ + private function assertMethod($method): void + { + if (!is_string($method) || $method === '') { + throw new InvalidArgumentException('Method must be a non-empty string.'); + } + } +} diff --git a/vendor/guzzlehttp/psr7/src/Response.php b/vendor/guzzlehttp/psr7/src/Response.php new file mode 100644 index 0000000..4c6ee6f --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Response.php @@ -0,0 +1,160 @@ + 'Continue', + 101 => 'Switching Protocols', + 102 => 'Processing', + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 207 => 'Multi-status', + 208 => 'Already Reported', + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 306 => 'Switch Proxy', + 307 => 'Temporary Redirect', + 308 => 'Permanent Redirect', + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Time-out', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Large', + 415 => 'Unsupported Media Type', + 416 => 'Requested range not satisfiable', + 417 => 'Expectation Failed', + 418 => 'I\'m a teapot', + 422 => 'Unprocessable Entity', + 423 => 'Locked', + 424 => 'Failed Dependency', + 425 => 'Unordered Collection', + 426 => 'Upgrade Required', + 428 => 'Precondition Required', + 429 => 'Too Many Requests', + 431 => 'Request Header Fields Too Large', + 451 => 'Unavailable For Legal Reasons', + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Time-out', + 505 => 'HTTP Version not supported', + 506 => 'Variant Also Negotiates', + 507 => 'Insufficient Storage', + 508 => 'Loop Detected', + 510 => 'Not Extended', + 511 => 'Network Authentication Required', + ]; + + /** @var string */ + private $reasonPhrase; + + /** @var int */ + private $statusCode; + + /** + * @param int $status Status code + * @param array $headers Response headers + * @param string|resource|StreamInterface|null $body Response body + * @param string $version Protocol version + * @param string|null $reason Reason phrase (when empty a default will be used based on the status code) + */ + public function __construct( + int $status = 200, + array $headers = [], + $body = null, + string $version = '1.1', + string $reason = null + ) { + $this->assertStatusCodeRange($status); + + $this->statusCode = $status; + + if ($body !== '' && $body !== null) { + $this->stream = Utils::streamFor($body); + } + + $this->setHeaders($headers); + if ($reason == '' && isset(self::PHRASES[$this->statusCode])) { + $this->reasonPhrase = self::PHRASES[$this->statusCode]; + } else { + $this->reasonPhrase = (string) $reason; + } + + $this->protocol = $version; + } + + public function getStatusCode(): int + { + return $this->statusCode; + } + + public function getReasonPhrase(): string + { + return $this->reasonPhrase; + } + + public function withStatus($code, $reasonPhrase = ''): ResponseInterface + { + $this->assertStatusCodeIsInteger($code); + $code = (int) $code; + $this->assertStatusCodeRange($code); + + $new = clone $this; + $new->statusCode = $code; + if ($reasonPhrase == '' && isset(self::PHRASES[$new->statusCode])) { + $reasonPhrase = self::PHRASES[$new->statusCode]; + } + $new->reasonPhrase = (string) $reasonPhrase; + return $new; + } + + /** + * @param mixed $statusCode + */ + private function assertStatusCodeIsInteger($statusCode): void + { + if (filter_var($statusCode, FILTER_VALIDATE_INT) === false) { + throw new \InvalidArgumentException('Status code must be an integer value.'); + } + } + + private function assertStatusCodeRange(int $statusCode): void + { + if ($statusCode < 100 || $statusCode >= 600) { + throw new \InvalidArgumentException('Status code must be an integer value between 1xx and 5xx.'); + } + } +} diff --git a/vendor/guzzlehttp/psr7/src/Rfc7230.php b/vendor/guzzlehttp/psr7/src/Rfc7230.php new file mode 100644 index 0000000..3022401 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Rfc7230.php @@ -0,0 +1,23 @@ +@,;:\\\"/[\]?={}\x01-\x20\x7F]++):[ \t]*+((?:[ \t]*+[\x21-\x7E\x80-\xFF]++)*+)[ \t]*+\r?\n)m"; + public const HEADER_FOLD_REGEX = "(\r?\n[ \t]++)"; +} diff --git a/vendor/guzzlehttp/psr7/src/ServerRequest.php b/vendor/guzzlehttp/psr7/src/ServerRequest.php new file mode 100644 index 0000000..b2aa382 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/ServerRequest.php @@ -0,0 +1,344 @@ + $headers Request headers + * @param string|resource|StreamInterface|null $body Request body + * @param string $version Protocol version + * @param array $serverParams Typically the $_SERVER superglobal + */ + public function __construct( + string $method, + $uri, + array $headers = [], + $body = null, + string $version = '1.1', + array $serverParams = [] + ) { + $this->serverParams = $serverParams; + + parent::__construct($method, $uri, $headers, $body, $version); + } + + /** + * Return an UploadedFile instance array. + * + * @param array $files An array which respect $_FILES structure + * + * @throws InvalidArgumentException for unrecognized values + */ + public static function normalizeFiles(array $files): array + { + $normalized = []; + + foreach ($files as $key => $value) { + if ($value instanceof UploadedFileInterface) { + $normalized[$key] = $value; + } elseif (is_array($value) && isset($value['tmp_name'])) { + $normalized[$key] = self::createUploadedFileFromSpec($value); + } elseif (is_array($value)) { + $normalized[$key] = self::normalizeFiles($value); + continue; + } else { + throw new InvalidArgumentException('Invalid value in files specification'); + } + } + + return $normalized; + } + + /** + * Create and return an UploadedFile instance from a $_FILES specification. + * + * If the specification represents an array of values, this method will + * delegate to normalizeNestedFileSpec() and return that return value. + * + * @param array $value $_FILES struct + * + * @return UploadedFileInterface|UploadedFileInterface[] + */ + private static function createUploadedFileFromSpec(array $value) + { + if (is_array($value['tmp_name'])) { + return self::normalizeNestedFileSpec($value); + } + + return new UploadedFile( + $value['tmp_name'], + (int) $value['size'], + (int) $value['error'], + $value['name'], + $value['type'] + ); + } + + /** + * Normalize an array of file specifications. + * + * Loops through all nested files and returns a normalized array of + * UploadedFileInterface instances. + * + * @return UploadedFileInterface[] + */ + private static function normalizeNestedFileSpec(array $files = []): array + { + $normalizedFiles = []; + + foreach (array_keys($files['tmp_name']) as $key) { + $spec = [ + 'tmp_name' => $files['tmp_name'][$key], + 'size' => $files['size'][$key] ?? null, + 'error' => $files['error'][$key] ?? null, + 'name' => $files['name'][$key] ?? null, + 'type' => $files['type'][$key] ?? null, + ]; + $normalizedFiles[$key] = self::createUploadedFileFromSpec($spec); + } + + return $normalizedFiles; + } + + /** + * Return a ServerRequest populated with superglobals: + * $_GET + * $_POST + * $_COOKIE + * $_FILES + * $_SERVER + */ + public static function fromGlobals(): ServerRequestInterface + { + $method = $_SERVER['REQUEST_METHOD'] ?? 'GET'; + $headers = getallheaders(); + $uri = self::getUriFromGlobals(); + $body = new CachingStream(new LazyOpenStream('php://input', 'r+')); + $protocol = isset($_SERVER['SERVER_PROTOCOL']) ? str_replace('HTTP/', '', $_SERVER['SERVER_PROTOCOL']) : '1.1'; + + $serverRequest = new ServerRequest($method, $uri, $headers, $body, $protocol, $_SERVER); + + return $serverRequest + ->withCookieParams($_COOKIE) + ->withQueryParams($_GET) + ->withParsedBody($_POST) + ->withUploadedFiles(self::normalizeFiles($_FILES)); + } + + private static function extractHostAndPortFromAuthority(string $authority): array + { + $uri = 'http://' . $authority; + $parts = parse_url($uri); + if (false === $parts) { + return [null, null]; + } + + $host = $parts['host'] ?? null; + $port = $parts['port'] ?? null; + + return [$host, $port]; + } + + /** + * Get a Uri populated with values from $_SERVER. + */ + public static function getUriFromGlobals(): UriInterface + { + $uri = new Uri(''); + + $uri = $uri->withScheme(!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https' : 'http'); + + $hasPort = false; + if (isset($_SERVER['HTTP_HOST'])) { + [$host, $port] = self::extractHostAndPortFromAuthority($_SERVER['HTTP_HOST']); + if ($host !== null) { + $uri = $uri->withHost($host); + } + + if ($port !== null) { + $hasPort = true; + $uri = $uri->withPort($port); + } + } elseif (isset($_SERVER['SERVER_NAME'])) { + $uri = $uri->withHost($_SERVER['SERVER_NAME']); + } elseif (isset($_SERVER['SERVER_ADDR'])) { + $uri = $uri->withHost($_SERVER['SERVER_ADDR']); + } + + if (!$hasPort && isset($_SERVER['SERVER_PORT'])) { + $uri = $uri->withPort($_SERVER['SERVER_PORT']); + } + + $hasQuery = false; + if (isset($_SERVER['REQUEST_URI'])) { + $requestUriParts = explode('?', $_SERVER['REQUEST_URI'], 2); + $uri = $uri->withPath($requestUriParts[0]); + if (isset($requestUriParts[1])) { + $hasQuery = true; + $uri = $uri->withQuery($requestUriParts[1]); + } + } + + if (!$hasQuery && isset($_SERVER['QUERY_STRING'])) { + $uri = $uri->withQuery($_SERVER['QUERY_STRING']); + } + + return $uri; + } + + public function getServerParams(): array + { + return $this->serverParams; + } + + public function getUploadedFiles(): array + { + return $this->uploadedFiles; + } + + public function withUploadedFiles(array $uploadedFiles): ServerRequestInterface + { + $new = clone $this; + $new->uploadedFiles = $uploadedFiles; + + return $new; + } + + public function getCookieParams(): array + { + return $this->cookieParams; + } + + public function withCookieParams(array $cookies): ServerRequestInterface + { + $new = clone $this; + $new->cookieParams = $cookies; + + return $new; + } + + public function getQueryParams(): array + { + return $this->queryParams; + } + + public function withQueryParams(array $query): ServerRequestInterface + { + $new = clone $this; + $new->queryParams = $query; + + return $new; + } + + /** + * {@inheritdoc} + * + * @return array|object|null + */ + public function getParsedBody() + { + return $this->parsedBody; + } + + public function withParsedBody($data): ServerRequestInterface + { + $new = clone $this; + $new->parsedBody = $data; + + return $new; + } + + public function getAttributes(): array + { + return $this->attributes; + } + + /** + * {@inheritdoc} + * + * @return mixed + */ + public function getAttribute($attribute, $default = null) + { + if (false === array_key_exists($attribute, $this->attributes)) { + return $default; + } + + return $this->attributes[$attribute]; + } + + public function withAttribute($attribute, $value): ServerRequestInterface + { + $new = clone $this; + $new->attributes[$attribute] = $value; + + return $new; + } + + public function withoutAttribute($attribute): ServerRequestInterface + { + if (false === array_key_exists($attribute, $this->attributes)) { + return $this; + } + + $new = clone $this; + unset($new->attributes[$attribute]); + + return $new; + } +} diff --git a/vendor/guzzlehttp/psr7/src/Stream.php b/vendor/guzzlehttp/psr7/src/Stream.php new file mode 100644 index 0000000..ecd3186 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Stream.php @@ -0,0 +1,282 @@ +size = $options['size']; + } + + $this->customMetadata = $options['metadata'] ?? []; + $this->stream = $stream; + $meta = stream_get_meta_data($this->stream); + $this->seekable = $meta['seekable']; + $this->readable = (bool)preg_match(self::READABLE_MODES, $meta['mode']); + $this->writable = (bool)preg_match(self::WRITABLE_MODES, $meta['mode']); + $this->uri = $this->getMetadata('uri'); + } + + /** + * Closes the stream when the destructed + */ + public function __destruct() + { + $this->close(); + } + + public function __toString(): string + { + try { + if ($this->isSeekable()) { + $this->seek(0); + } + return $this->getContents(); + } catch (\Throwable $e) { + if (\PHP_VERSION_ID >= 70400) { + throw $e; + } + trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR); + return ''; + } + } + + public function getContents(): string + { + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + + if (!$this->readable) { + throw new \RuntimeException('Cannot read from non-readable stream'); + } + + return Utils::tryGetContents($this->stream); + } + + public function close(): void + { + if (isset($this->stream)) { + if (is_resource($this->stream)) { + fclose($this->stream); + } + $this->detach(); + } + } + + public function detach() + { + if (!isset($this->stream)) { + return null; + } + + $result = $this->stream; + unset($this->stream); + $this->size = $this->uri = null; + $this->readable = $this->writable = $this->seekable = false; + + return $result; + } + + public function getSize(): ?int + { + if ($this->size !== null) { + return $this->size; + } + + if (!isset($this->stream)) { + return null; + } + + // Clear the stat cache if the stream has a URI + if ($this->uri) { + clearstatcache(true, $this->uri); + } + + $stats = fstat($this->stream); + if (is_array($stats) && isset($stats['size'])) { + $this->size = $stats['size']; + return $this->size; + } + + return null; + } + + public function isReadable(): bool + { + return $this->readable; + } + + public function isWritable(): bool + { + return $this->writable; + } + + public function isSeekable(): bool + { + return $this->seekable; + } + + public function eof(): bool + { + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + + return feof($this->stream); + } + + public function tell(): int + { + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + + $result = ftell($this->stream); + + if ($result === false) { + throw new \RuntimeException('Unable to determine stream position'); + } + + return $result; + } + + public function rewind(): void + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET): void + { + $whence = (int) $whence; + + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + if (!$this->seekable) { + throw new \RuntimeException('Stream is not seekable'); + } + if (fseek($this->stream, $offset, $whence) === -1) { + throw new \RuntimeException('Unable to seek to stream position ' + . $offset . ' with whence ' . var_export($whence, true)); + } + } + + public function read($length): string + { + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + if (!$this->readable) { + throw new \RuntimeException('Cannot read from non-readable stream'); + } + if ($length < 0) { + throw new \RuntimeException('Length parameter cannot be negative'); + } + + if (0 === $length) { + return ''; + } + + try { + $string = fread($this->stream, $length); + } catch (\Exception $e) { + throw new \RuntimeException('Unable to read from stream', 0, $e); + } + + if (false === $string) { + throw new \RuntimeException('Unable to read from stream'); + } + + return $string; + } + + public function write($string): int + { + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + if (!$this->writable) { + throw new \RuntimeException('Cannot write to a non-writable stream'); + } + + // We can't know the size after writing anything + $this->size = null; + $result = fwrite($this->stream, $string); + + if ($result === false) { + throw new \RuntimeException('Unable to write to stream'); + } + + return $result; + } + + /** + * {@inheritdoc} + * + * @return mixed + */ + public function getMetadata($key = null) + { + if (!isset($this->stream)) { + return $key ? null : []; + } elseif (!$key) { + return $this->customMetadata + stream_get_meta_data($this->stream); + } elseif (isset($this->customMetadata[$key])) { + return $this->customMetadata[$key]; + } + + $meta = stream_get_meta_data($this->stream); + + return $meta[$key] ?? null; + } +} diff --git a/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php b/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php new file mode 100644 index 0000000..56d4104 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php @@ -0,0 +1,155 @@ +stream = $stream; + } + + /** + * Magic method used to create a new stream if streams are not added in + * the constructor of a decorator (e.g., LazyOpenStream). + * + * @return StreamInterface + */ + public function __get(string $name) + { + if ($name === 'stream') { + $this->stream = $this->createStream(); + return $this->stream; + } + + throw new \UnexpectedValueException("$name not found on class"); + } + + public function __toString(): string + { + try { + if ($this->isSeekable()) { + $this->seek(0); + } + return $this->getContents(); + } catch (\Throwable $e) { + if (\PHP_VERSION_ID >= 70400) { + throw $e; + } + trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR); + return ''; + } + } + + public function getContents(): string + { + return Utils::copyToString($this); + } + + /** + * Allow decorators to implement custom methods + * + * @return mixed + */ + public function __call(string $method, array $args) + { + /** @var callable $callable */ + $callable = [$this->stream, $method]; + $result = call_user_func_array($callable, $args); + + // Always return the wrapped object if the result is a return $this + return $result === $this->stream ? $this : $result; + } + + public function close(): void + { + $this->stream->close(); + } + + /** + * {@inheritdoc} + * + * @return mixed + */ + public function getMetadata($key = null) + { + return $this->stream->getMetadata($key); + } + + public function detach() + { + return $this->stream->detach(); + } + + public function getSize(): ?int + { + return $this->stream->getSize(); + } + + public function eof(): bool + { + return $this->stream->eof(); + } + + public function tell(): int + { + return $this->stream->tell(); + } + + public function isReadable(): bool + { + return $this->stream->isReadable(); + } + + public function isWritable(): bool + { + return $this->stream->isWritable(); + } + + public function isSeekable(): bool + { + return $this->stream->isSeekable(); + } + + public function rewind(): void + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET): void + { + $this->stream->seek($offset, $whence); + } + + public function read($length): string + { + return $this->stream->read($length); + } + + public function write($string): int + { + return $this->stream->write($string); + } + + /** + * Implement in subclasses to dynamically create streams when requested. + * + * @throws \BadMethodCallException + */ + protected function createStream(): StreamInterface + { + throw new \BadMethodCallException('Not implemented'); + } +} diff --git a/vendor/guzzlehttp/psr7/src/StreamWrapper.php b/vendor/guzzlehttp/psr7/src/StreamWrapper.php new file mode 100644 index 0000000..2a93464 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/StreamWrapper.php @@ -0,0 +1,175 @@ +isReadable()) { + $mode = $stream->isWritable() ? 'r+' : 'r'; + } elseif ($stream->isWritable()) { + $mode = 'w'; + } else { + throw new \InvalidArgumentException('The stream must be readable, ' + . 'writable, or both.'); + } + + return fopen('guzzle://stream', $mode, false, self::createStreamContext($stream)); + } + + /** + * Creates a stream context that can be used to open a stream as a php stream resource. + * + * @return resource + */ + public static function createStreamContext(StreamInterface $stream) + { + return stream_context_create([ + 'guzzle' => ['stream' => $stream] + ]); + } + + /** + * Registers the stream wrapper if needed + */ + public static function register(): void + { + if (!in_array('guzzle', stream_get_wrappers())) { + stream_wrapper_register('guzzle', __CLASS__); + } + } + + public function stream_open(string $path, string $mode, int $options, string &$opened_path = null): bool + { + $options = stream_context_get_options($this->context); + + if (!isset($options['guzzle']['stream'])) { + return false; + } + + $this->mode = $mode; + $this->stream = $options['guzzle']['stream']; + + return true; + } + + public function stream_read(int $count): string + { + return $this->stream->read($count); + } + + public function stream_write(string $data): int + { + return $this->stream->write($data); + } + + public function stream_tell(): int + { + return $this->stream->tell(); + } + + public function stream_eof(): bool + { + return $this->stream->eof(); + } + + public function stream_seek(int $offset, int $whence): bool + { + $this->stream->seek($offset, $whence); + + return true; + } + + /** + * @return resource|false + */ + public function stream_cast(int $cast_as) + { + $stream = clone($this->stream); + $resource = $stream->detach(); + + return $resource ?? false; + } + + /** + * @return array + */ + public function stream_stat(): array + { + static $modeMap = [ + 'r' => 33060, + 'rb' => 33060, + 'r+' => 33206, + 'w' => 33188, + 'wb' => 33188 + ]; + + return [ + 'dev' => 0, + 'ino' => 0, + 'mode' => $modeMap[$this->mode], + 'nlink' => 0, + 'uid' => 0, + 'gid' => 0, + 'rdev' => 0, + 'size' => $this->stream->getSize() ?: 0, + 'atime' => 0, + 'mtime' => 0, + 'ctime' => 0, + 'blksize' => 0, + 'blocks' => 0 + ]; + } + + /** + * @return array + */ + public function url_stat(string $path, int $flags): array + { + return [ + 'dev' => 0, + 'ino' => 0, + 'mode' => 0, + 'nlink' => 0, + 'uid' => 0, + 'gid' => 0, + 'rdev' => 0, + 'size' => 0, + 'atime' => 0, + 'mtime' => 0, + 'ctime' => 0, + 'blksize' => 0, + 'blocks' => 0 + ]; + } +} diff --git a/vendor/guzzlehttp/psr7/src/UploadedFile.php b/vendor/guzzlehttp/psr7/src/UploadedFile.php new file mode 100644 index 0000000..b1521bc --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/UploadedFile.php @@ -0,0 +1,211 @@ +setError($errorStatus); + $this->size = $size; + $this->clientFilename = $clientFilename; + $this->clientMediaType = $clientMediaType; + + if ($this->isOk()) { + $this->setStreamOrFile($streamOrFile); + } + } + + /** + * Depending on the value set file or stream variable + * + * @param StreamInterface|string|resource $streamOrFile + * + * @throws InvalidArgumentException + */ + private function setStreamOrFile($streamOrFile): void + { + if (is_string($streamOrFile)) { + $this->file = $streamOrFile; + } elseif (is_resource($streamOrFile)) { + $this->stream = new Stream($streamOrFile); + } elseif ($streamOrFile instanceof StreamInterface) { + $this->stream = $streamOrFile; + } else { + throw new InvalidArgumentException( + 'Invalid stream or file provided for UploadedFile' + ); + } + } + + /** + * @throws InvalidArgumentException + */ + private function setError(int $error): void + { + if (false === in_array($error, UploadedFile::ERRORS, true)) { + throw new InvalidArgumentException( + 'Invalid error status for UploadedFile' + ); + } + + $this->error = $error; + } + + private function isStringNotEmpty($param): bool + { + return is_string($param) && false === empty($param); + } + + /** + * Return true if there is no upload error + */ + private function isOk(): bool + { + return $this->error === UPLOAD_ERR_OK; + } + + public function isMoved(): bool + { + return $this->moved; + } + + /** + * @throws RuntimeException if is moved or not ok + */ + private function validateActive(): void + { + if (false === $this->isOk()) { + throw new RuntimeException('Cannot retrieve stream due to upload error'); + } + + if ($this->isMoved()) { + throw new RuntimeException('Cannot retrieve stream after it has already been moved'); + } + } + + public function getStream(): StreamInterface + { + $this->validateActive(); + + if ($this->stream instanceof StreamInterface) { + return $this->stream; + } + + /** @var string $file */ + $file = $this->file; + + return new LazyOpenStream($file, 'r+'); + } + + public function moveTo($targetPath): void + { + $this->validateActive(); + + if (false === $this->isStringNotEmpty($targetPath)) { + throw new InvalidArgumentException( + 'Invalid path provided for move operation; must be a non-empty string' + ); + } + + if ($this->file) { + $this->moved = PHP_SAPI === 'cli' + ? rename($this->file, $targetPath) + : move_uploaded_file($this->file, $targetPath); + } else { + Utils::copyToStream( + $this->getStream(), + new LazyOpenStream($targetPath, 'w') + ); + + $this->moved = true; + } + + if (false === $this->moved) { + throw new RuntimeException( + sprintf('Uploaded file could not be moved to %s', $targetPath) + ); + } + } + + public function getSize(): ?int + { + return $this->size; + } + + public function getError(): int + { + return $this->error; + } + + public function getClientFilename(): ?string + { + return $this->clientFilename; + } + + public function getClientMediaType(): ?string + { + return $this->clientMediaType; + } +} diff --git a/vendor/guzzlehttp/psr7/src/Uri.php b/vendor/guzzlehttp/psr7/src/Uri.php new file mode 100644 index 0000000..09e878d --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Uri.php @@ -0,0 +1,740 @@ + 80, + 'https' => 443, + 'ftp' => 21, + 'gopher' => 70, + 'nntp' => 119, + 'news' => 119, + 'telnet' => 23, + 'tn3270' => 23, + 'imap' => 143, + 'pop' => 110, + 'ldap' => 389, + ]; + + /** + * Unreserved characters for use in a regex. + * + * @link https://tools.ietf.org/html/rfc3986#section-2.3 + */ + private const CHAR_UNRESERVED = 'a-zA-Z0-9_\-\.~'; + + /** + * Sub-delims for use in a regex. + * + * @link https://tools.ietf.org/html/rfc3986#section-2.2 + */ + private const CHAR_SUB_DELIMS = '!\$&\'\(\)\*\+,;='; + private const QUERY_SEPARATORS_REPLACEMENT = ['=' => '%3D', '&' => '%26']; + + /** @var string Uri scheme. */ + private $scheme = ''; + + /** @var string Uri user info. */ + private $userInfo = ''; + + /** @var string Uri host. */ + private $host = ''; + + /** @var int|null Uri port. */ + private $port; + + /** @var string Uri path. */ + private $path = ''; + + /** @var string Uri query string. */ + private $query = ''; + + /** @var string Uri fragment. */ + private $fragment = ''; + + /** @var string|null String representation */ + private $composedComponents; + + public function __construct(string $uri = '') + { + if ($uri !== '') { + $parts = self::parse($uri); + if ($parts === false) { + throw new MalformedUriException("Unable to parse URI: $uri"); + } + $this->applyParts($parts); + } + } + /** + * UTF-8 aware \parse_url() replacement. + * + * The internal function produces broken output for non ASCII domain names + * (IDN) when used with locales other than "C". + * + * On the other hand, cURL understands IDN correctly only when UTF-8 locale + * is configured ("C.UTF-8", "en_US.UTF-8", etc.). + * + * @see https://bugs.php.net/bug.php?id=52923 + * @see https://www.php.net/manual/en/function.parse-url.php#114817 + * @see https://curl.haxx.se/libcurl/c/CURLOPT_URL.html#ENCODING + * + * @return array|false + */ + private static function parse(string $url) + { + // If IPv6 + $prefix = ''; + if (preg_match('%^(.*://\[[0-9:a-f]+\])(.*?)$%', $url, $matches)) { + /** @var array{0:string, 1:string, 2:string} $matches */ + $prefix = $matches[1]; + $url = $matches[2]; + } + + /** @var string */ + $encodedUrl = preg_replace_callback( + '%[^:/@?&=#]+%usD', + static function ($matches) { + return urlencode($matches[0]); + }, + $url + ); + + $result = parse_url($prefix . $encodedUrl); + + if ($result === false) { + return false; + } + + return array_map('urldecode', $result); + } + + public function __toString(): string + { + if ($this->composedComponents === null) { + $this->composedComponents = self::composeComponents( + $this->scheme, + $this->getAuthority(), + $this->path, + $this->query, + $this->fragment + ); + } + + return $this->composedComponents; + } + + /** + * Composes a URI reference string from its various components. + * + * Usually this method does not need to be called manually but instead is used indirectly via + * `Psr\Http\Message\UriInterface::__toString`. + * + * PSR-7 UriInterface treats an empty component the same as a missing component as + * getQuery(), getFragment() etc. always return a string. This explains the slight + * difference to RFC 3986 Section 5.3. + * + * Another adjustment is that the authority separator is added even when the authority is missing/empty + * for the "file" scheme. This is because PHP stream functions like `file_get_contents` only work with + * `file:///myfile` but not with `file:/myfile` although they are equivalent according to RFC 3986. But + * `file:///` is the more common syntax for the file scheme anyway (Chrome for example redirects to + * that format). + * + * @link https://tools.ietf.org/html/rfc3986#section-5.3 + */ + public static function composeComponents(?string $scheme, ?string $authority, string $path, ?string $query, ?string $fragment): string + { + $uri = ''; + + // weak type checks to also accept null until we can add scalar type hints + if ($scheme != '') { + $uri .= $scheme . ':'; + } + + if ($authority != '' || $scheme === 'file') { + $uri .= '//' . $authority; + } + + if ($authority != '' && $path != '' && $path[0] != '/') { + $path = '/' . $path; + } + + $uri .= $path; + + if ($query != '') { + $uri .= '?' . $query; + } + + if ($fragment != '') { + $uri .= '#' . $fragment; + } + + return $uri; + } + + /** + * Whether the URI has the default port of the current scheme. + * + * `Psr\Http\Message\UriInterface::getPort` may return null or the standard port. This method can be used + * independently of the implementation. + */ + public static function isDefaultPort(UriInterface $uri): bool + { + return $uri->getPort() === null + || (isset(self::DEFAULT_PORTS[$uri->getScheme()]) && $uri->getPort() === self::DEFAULT_PORTS[$uri->getScheme()]); + } + + /** + * Whether the URI is absolute, i.e. it has a scheme. + * + * An instance of UriInterface can either be an absolute URI or a relative reference. This method returns true + * if it is the former. An absolute URI has a scheme. A relative reference is used to express a URI relative + * to another URI, the base URI. Relative references can be divided into several forms: + * - network-path references, e.g. '//example.com/path' + * - absolute-path references, e.g. '/path' + * - relative-path references, e.g. 'subpath' + * + * @see Uri::isNetworkPathReference + * @see Uri::isAbsolutePathReference + * @see Uri::isRelativePathReference + * @link https://tools.ietf.org/html/rfc3986#section-4 + */ + public static function isAbsolute(UriInterface $uri): bool + { + return $uri->getScheme() !== ''; + } + + /** + * Whether the URI is a network-path reference. + * + * A relative reference that begins with two slash characters is termed an network-path reference. + * + * @link https://tools.ietf.org/html/rfc3986#section-4.2 + */ + public static function isNetworkPathReference(UriInterface $uri): bool + { + return $uri->getScheme() === '' && $uri->getAuthority() !== ''; + } + + /** + * Whether the URI is a absolute-path reference. + * + * A relative reference that begins with a single slash character is termed an absolute-path reference. + * + * @link https://tools.ietf.org/html/rfc3986#section-4.2 + */ + public static function isAbsolutePathReference(UriInterface $uri): bool + { + return $uri->getScheme() === '' + && $uri->getAuthority() === '' + && isset($uri->getPath()[0]) + && $uri->getPath()[0] === '/'; + } + + /** + * Whether the URI is a relative-path reference. + * + * A relative reference that does not begin with a slash character is termed a relative-path reference. + * + * @link https://tools.ietf.org/html/rfc3986#section-4.2 + */ + public static function isRelativePathReference(UriInterface $uri): bool + { + return $uri->getScheme() === '' + && $uri->getAuthority() === '' + && (!isset($uri->getPath()[0]) || $uri->getPath()[0] !== '/'); + } + + /** + * Whether the URI is a same-document reference. + * + * A same-document reference refers to a URI that is, aside from its fragment + * component, identical to the base URI. When no base URI is given, only an empty + * URI reference (apart from its fragment) is considered a same-document reference. + * + * @param UriInterface $uri The URI to check + * @param UriInterface|null $base An optional base URI to compare against + * + * @link https://tools.ietf.org/html/rfc3986#section-4.4 + */ + public static function isSameDocumentReference(UriInterface $uri, UriInterface $base = null): bool + { + if ($base !== null) { + $uri = UriResolver::resolve($base, $uri); + + return ($uri->getScheme() === $base->getScheme()) + && ($uri->getAuthority() === $base->getAuthority()) + && ($uri->getPath() === $base->getPath()) + && ($uri->getQuery() === $base->getQuery()); + } + + return $uri->getScheme() === '' && $uri->getAuthority() === '' && $uri->getPath() === '' && $uri->getQuery() === ''; + } + + /** + * Creates a new URI with a specific query string value removed. + * + * Any existing query string values that exactly match the provided key are + * removed. + * + * @param UriInterface $uri URI to use as a base. + * @param string $key Query string key to remove. + */ + public static function withoutQueryValue(UriInterface $uri, string $key): UriInterface + { + $result = self::getFilteredQueryString($uri, [$key]); + + return $uri->withQuery(implode('&', $result)); + } + + /** + * Creates a new URI with a specific query string value. + * + * Any existing query string values that exactly match the provided key are + * removed and replaced with the given key value pair. + * + * A value of null will set the query string key without a value, e.g. "key" + * instead of "key=value". + * + * @param UriInterface $uri URI to use as a base. + * @param string $key Key to set. + * @param string|null $value Value to set + */ + public static function withQueryValue(UriInterface $uri, string $key, ?string $value): UriInterface + { + $result = self::getFilteredQueryString($uri, [$key]); + + $result[] = self::generateQueryString($key, $value); + + return $uri->withQuery(implode('&', $result)); + } + + /** + * Creates a new URI with multiple specific query string values. + * + * It has the same behavior as withQueryValue() but for an associative array of key => value. + * + * @param UriInterface $uri URI to use as a base. + * @param array $keyValueArray Associative array of key and values + */ + public static function withQueryValues(UriInterface $uri, array $keyValueArray): UriInterface + { + $result = self::getFilteredQueryString($uri, array_keys($keyValueArray)); + + foreach ($keyValueArray as $key => $value) { + $result[] = self::generateQueryString((string) $key, $value !== null ? (string) $value : null); + } + + return $uri->withQuery(implode('&', $result)); + } + + /** + * Creates a URI from a hash of `parse_url` components. + * + * @link http://php.net/manual/en/function.parse-url.php + * + * @throws MalformedUriException If the components do not form a valid URI. + */ + public static function fromParts(array $parts): UriInterface + { + $uri = new self(); + $uri->applyParts($parts); + $uri->validateState(); + + return $uri; + } + + public function getScheme(): string + { + return $this->scheme; + } + + public function getAuthority(): string + { + $authority = $this->host; + if ($this->userInfo !== '') { + $authority = $this->userInfo . '@' . $authority; + } + + if ($this->port !== null) { + $authority .= ':' . $this->port; + } + + return $authority; + } + + public function getUserInfo(): string + { + return $this->userInfo; + } + + public function getHost(): string + { + return $this->host; + } + + public function getPort(): ?int + { + return $this->port; + } + + public function getPath(): string + { + return $this->path; + } + + public function getQuery(): string + { + return $this->query; + } + + public function getFragment(): string + { + return $this->fragment; + } + + public function withScheme($scheme): UriInterface + { + $scheme = $this->filterScheme($scheme); + + if ($this->scheme === $scheme) { + return $this; + } + + $new = clone $this; + $new->scheme = $scheme; + $new->composedComponents = null; + $new->removeDefaultPort(); + $new->validateState(); + + return $new; + } + + public function withUserInfo($user, $password = null): UriInterface + { + $info = $this->filterUserInfoComponent($user); + if ($password !== null) { + $info .= ':' . $this->filterUserInfoComponent($password); + } + + if ($this->userInfo === $info) { + return $this; + } + + $new = clone $this; + $new->userInfo = $info; + $new->composedComponents = null; + $new->validateState(); + + return $new; + } + + public function withHost($host): UriInterface + { + $host = $this->filterHost($host); + + if ($this->host === $host) { + return $this; + } + + $new = clone $this; + $new->host = $host; + $new->composedComponents = null; + $new->validateState(); + + return $new; + } + + public function withPort($port): UriInterface + { + $port = $this->filterPort($port); + + if ($this->port === $port) { + return $this; + } + + $new = clone $this; + $new->port = $port; + $new->composedComponents = null; + $new->removeDefaultPort(); + $new->validateState(); + + return $new; + } + + public function withPath($path): UriInterface + { + $path = $this->filterPath($path); + + if ($this->path === $path) { + return $this; + } + + $new = clone $this; + $new->path = $path; + $new->composedComponents = null; + $new->validateState(); + + return $new; + } + + public function withQuery($query): UriInterface + { + $query = $this->filterQueryAndFragment($query); + + if ($this->query === $query) { + return $this; + } + + $new = clone $this; + $new->query = $query; + $new->composedComponents = null; + + return $new; + } + + public function withFragment($fragment): UriInterface + { + $fragment = $this->filterQueryAndFragment($fragment); + + if ($this->fragment === $fragment) { + return $this; + } + + $new = clone $this; + $new->fragment = $fragment; + $new->composedComponents = null; + + return $new; + } + + public function jsonSerialize(): string + { + return $this->__toString(); + } + + /** + * Apply parse_url parts to a URI. + * + * @param array $parts Array of parse_url parts to apply. + */ + private function applyParts(array $parts): void + { + $this->scheme = isset($parts['scheme']) + ? $this->filterScheme($parts['scheme']) + : ''; + $this->userInfo = isset($parts['user']) + ? $this->filterUserInfoComponent($parts['user']) + : ''; + $this->host = isset($parts['host']) + ? $this->filterHost($parts['host']) + : ''; + $this->port = isset($parts['port']) + ? $this->filterPort($parts['port']) + : null; + $this->path = isset($parts['path']) + ? $this->filterPath($parts['path']) + : ''; + $this->query = isset($parts['query']) + ? $this->filterQueryAndFragment($parts['query']) + : ''; + $this->fragment = isset($parts['fragment']) + ? $this->filterQueryAndFragment($parts['fragment']) + : ''; + if (isset($parts['pass'])) { + $this->userInfo .= ':' . $this->filterUserInfoComponent($parts['pass']); + } + + $this->removeDefaultPort(); + } + + /** + * @param mixed $scheme + * + * @throws \InvalidArgumentException If the scheme is invalid. + */ + private function filterScheme($scheme): string + { + if (!is_string($scheme)) { + throw new \InvalidArgumentException('Scheme must be a string'); + } + + return \strtr($scheme, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); + } + + /** + * @param mixed $component + * + * @throws \InvalidArgumentException If the user info is invalid. + */ + private function filterUserInfoComponent($component): string + { + if (!is_string($component)) { + throw new \InvalidArgumentException('User info must be a string'); + } + + return preg_replace_callback( + '/(?:[^%' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . ']+|%(?![A-Fa-f0-9]{2}))/', + [$this, 'rawurlencodeMatchZero'], + $component + ); + } + + /** + * @param mixed $host + * + * @throws \InvalidArgumentException If the host is invalid. + */ + private function filterHost($host): string + { + if (!is_string($host)) { + throw new \InvalidArgumentException('Host must be a string'); + } + + return \strtr($host, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); + } + + /** + * @param mixed $port + * + * @throws \InvalidArgumentException If the port is invalid. + */ + private function filterPort($port): ?int + { + if ($port === null) { + return null; + } + + $port = (int) $port; + if (0 > $port || 0xffff < $port) { + throw new \InvalidArgumentException( + sprintf('Invalid port: %d. Must be between 0 and 65535', $port) + ); + } + + return $port; + } + + /** + * @param string[] $keys + * + * @return string[] + */ + private static function getFilteredQueryString(UriInterface $uri, array $keys): array + { + $current = $uri->getQuery(); + + if ($current === '') { + return []; + } + + $decodedKeys = array_map('rawurldecode', $keys); + + return array_filter(explode('&', $current), function ($part) use ($decodedKeys) { + return !in_array(rawurldecode(explode('=', $part)[0]), $decodedKeys, true); + }); + } + + private static function generateQueryString(string $key, ?string $value): string + { + // Query string separators ("=", "&") within the key or value need to be encoded + // (while preventing double-encoding) before setting the query string. All other + // chars that need percent-encoding will be encoded by withQuery(). + $queryString = strtr($key, self::QUERY_SEPARATORS_REPLACEMENT); + + if ($value !== null) { + $queryString .= '=' . strtr($value, self::QUERY_SEPARATORS_REPLACEMENT); + } + + return $queryString; + } + + private function removeDefaultPort(): void + { + if ($this->port !== null && self::isDefaultPort($this)) { + $this->port = null; + } + } + + /** + * Filters the path of a URI + * + * @param mixed $path + * + * @throws \InvalidArgumentException If the path is invalid. + */ + private function filterPath($path): string + { + if (!is_string($path)) { + throw new \InvalidArgumentException('Path must be a string'); + } + + return preg_replace_callback( + '/(?:[^' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . '%:@\/]++|%(?![A-Fa-f0-9]{2}))/', + [$this, 'rawurlencodeMatchZero'], + $path + ); + } + + /** + * Filters the query string or fragment of a URI. + * + * @param mixed $str + * + * @throws \InvalidArgumentException If the query or fragment is invalid. + */ + private function filterQueryAndFragment($str): string + { + if (!is_string($str)) { + throw new \InvalidArgumentException('Query and fragment must be a string'); + } + + return preg_replace_callback( + '/(?:[^' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . '%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/', + [$this, 'rawurlencodeMatchZero'], + $str + ); + } + + private function rawurlencodeMatchZero(array $match): string + { + return rawurlencode($match[0]); + } + + private function validateState(): void + { + if ($this->host === '' && ($this->scheme === 'http' || $this->scheme === 'https')) { + $this->host = self::HTTP_DEFAULT_HOST; + } + + if ($this->getAuthority() === '') { + if (0 === strpos($this->path, '//')) { + throw new MalformedUriException('The path of a URI without an authority must not start with two slashes "//"'); + } + if ($this->scheme === '' && false !== strpos(explode('/', $this->path, 2)[0], ':')) { + throw new MalformedUriException('A relative URI must not have a path beginning with a segment containing a colon'); + } + } + } +} diff --git a/vendor/guzzlehttp/psr7/src/UriComparator.php b/vendor/guzzlehttp/psr7/src/UriComparator.php new file mode 100644 index 0000000..70c582a --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/UriComparator.php @@ -0,0 +1,52 @@ +getHost(), $modified->getHost()) !== 0) { + return true; + } + + if ($original->getScheme() !== $modified->getScheme()) { + return true; + } + + if (self::computePort($original) !== self::computePort($modified)) { + return true; + } + + return false; + } + + private static function computePort(UriInterface $uri): int + { + $port = $uri->getPort(); + + if (null !== $port) { + return $port; + } + + return 'https' === $uri->getScheme() ? 443 : 80; + } + + private function __construct() + { + // cannot be instantiated + } +} diff --git a/vendor/guzzlehttp/psr7/src/UriNormalizer.php b/vendor/guzzlehttp/psr7/src/UriNormalizer.php new file mode 100644 index 0000000..e12971e --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/UriNormalizer.php @@ -0,0 +1,220 @@ +getPath() === '' && + ($uri->getScheme() === 'http' || $uri->getScheme() === 'https') + ) { + $uri = $uri->withPath('/'); + } + + if ($flags & self::REMOVE_DEFAULT_HOST && $uri->getScheme() === 'file' && $uri->getHost() === 'localhost') { + $uri = $uri->withHost(''); + } + + if ($flags & self::REMOVE_DEFAULT_PORT && $uri->getPort() !== null && Uri::isDefaultPort($uri)) { + $uri = $uri->withPort(null); + } + + if ($flags & self::REMOVE_DOT_SEGMENTS && !Uri::isRelativePathReference($uri)) { + $uri = $uri->withPath(UriResolver::removeDotSegments($uri->getPath())); + } + + if ($flags & self::REMOVE_DUPLICATE_SLASHES) { + $uri = $uri->withPath(preg_replace('#//++#', '/', $uri->getPath())); + } + + if ($flags & self::SORT_QUERY_PARAMETERS && $uri->getQuery() !== '') { + $queryKeyValues = explode('&', $uri->getQuery()); + sort($queryKeyValues); + $uri = $uri->withQuery(implode('&', $queryKeyValues)); + } + + return $uri; + } + + /** + * Whether two URIs can be considered equivalent. + * + * Both URIs are normalized automatically before comparison with the given $normalizations bitmask. The method also + * accepts relative URI references and returns true when they are equivalent. This of course assumes they will be + * resolved against the same base URI. If this is not the case, determination of equivalence or difference of + * relative references does not mean anything. + * + * @param UriInterface $uri1 An URI to compare + * @param UriInterface $uri2 An URI to compare + * @param int $normalizations A bitmask of normalizations to apply, see constants + * + * @link https://tools.ietf.org/html/rfc3986#section-6.1 + */ + public static function isEquivalent(UriInterface $uri1, UriInterface $uri2, int $normalizations = self::PRESERVING_NORMALIZATIONS): bool + { + return (string) self::normalize($uri1, $normalizations) === (string) self::normalize($uri2, $normalizations); + } + + private static function capitalizePercentEncoding(UriInterface $uri): UriInterface + { + $regex = '/(?:%[A-Fa-f0-9]{2})++/'; + + $callback = function (array $match) { + return strtoupper($match[0]); + }; + + return + $uri->withPath( + preg_replace_callback($regex, $callback, $uri->getPath()) + )->withQuery( + preg_replace_callback($regex, $callback, $uri->getQuery()) + ); + } + + private static function decodeUnreservedCharacters(UriInterface $uri): UriInterface + { + $regex = '/%(?:2D|2E|5F|7E|3[0-9]|[46][1-9A-F]|[57][0-9A])/i'; + + $callback = function (array $match) { + return rawurldecode($match[0]); + }; + + return + $uri->withPath( + preg_replace_callback($regex, $callback, $uri->getPath()) + )->withQuery( + preg_replace_callback($regex, $callback, $uri->getQuery()) + ); + } + + private function __construct() + { + // cannot be instantiated + } +} diff --git a/vendor/guzzlehttp/psr7/src/UriResolver.php b/vendor/guzzlehttp/psr7/src/UriResolver.php new file mode 100644 index 0000000..426e5c9 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/UriResolver.php @@ -0,0 +1,211 @@ +getScheme() != '') { + return $rel->withPath(self::removeDotSegments($rel->getPath())); + } + + if ($rel->getAuthority() != '') { + $targetAuthority = $rel->getAuthority(); + $targetPath = self::removeDotSegments($rel->getPath()); + $targetQuery = $rel->getQuery(); + } else { + $targetAuthority = $base->getAuthority(); + if ($rel->getPath() === '') { + $targetPath = $base->getPath(); + $targetQuery = $rel->getQuery() != '' ? $rel->getQuery() : $base->getQuery(); + } else { + if ($rel->getPath()[0] === '/') { + $targetPath = $rel->getPath(); + } else { + if ($targetAuthority != '' && $base->getPath() === '') { + $targetPath = '/' . $rel->getPath(); + } else { + $lastSlashPos = strrpos($base->getPath(), '/'); + if ($lastSlashPos === false) { + $targetPath = $rel->getPath(); + } else { + $targetPath = substr($base->getPath(), 0, $lastSlashPos + 1) . $rel->getPath(); + } + } + } + $targetPath = self::removeDotSegments($targetPath); + $targetQuery = $rel->getQuery(); + } + } + + return new Uri(Uri::composeComponents( + $base->getScheme(), + $targetAuthority, + $targetPath, + $targetQuery, + $rel->getFragment() + )); + } + + /** + * Returns the target URI as a relative reference from the base URI. + * + * This method is the counterpart to resolve(): + * + * (string) $target === (string) UriResolver::resolve($base, UriResolver::relativize($base, $target)) + * + * One use-case is to use the current request URI as base URI and then generate relative links in your documents + * to reduce the document size or offer self-contained downloadable document archives. + * + * $base = new Uri('http://example.com/a/b/'); + * echo UriResolver::relativize($base, new Uri('http://example.com/a/b/c')); // prints 'c'. + * echo UriResolver::relativize($base, new Uri('http://example.com/a/x/y')); // prints '../x/y'. + * echo UriResolver::relativize($base, new Uri('http://example.com/a/b/?q')); // prints '?q'. + * echo UriResolver::relativize($base, new Uri('http://example.org/a/b/')); // prints '//example.org/a/b/'. + * + * This method also accepts a target that is already relative and will try to relativize it further. Only a + * relative-path reference will be returned as-is. + * + * echo UriResolver::relativize($base, new Uri('/a/b/c')); // prints 'c' as well + */ + public static function relativize(UriInterface $base, UriInterface $target): UriInterface + { + if ($target->getScheme() !== '' && + ($base->getScheme() !== $target->getScheme() || $target->getAuthority() === '' && $base->getAuthority() !== '') + ) { + return $target; + } + + if (Uri::isRelativePathReference($target)) { + // As the target is already highly relative we return it as-is. It would be possible to resolve + // the target with `$target = self::resolve($base, $target);` and then try make it more relative + // by removing a duplicate query. But let's not do that automatically. + return $target; + } + + if ($target->getAuthority() !== '' && $base->getAuthority() !== $target->getAuthority()) { + return $target->withScheme(''); + } + + // We must remove the path before removing the authority because if the path starts with two slashes, the URI + // would turn invalid. And we also cannot set a relative path before removing the authority, as that is also + // invalid. + $emptyPathUri = $target->withScheme('')->withPath('')->withUserInfo('')->withPort(null)->withHost(''); + + if ($base->getPath() !== $target->getPath()) { + return $emptyPathUri->withPath(self::getRelativePath($base, $target)); + } + + if ($base->getQuery() === $target->getQuery()) { + // Only the target fragment is left. And it must be returned even if base and target fragment are the same. + return $emptyPathUri->withQuery(''); + } + + // If the base URI has a query but the target has none, we cannot return an empty path reference as it would + // inherit the base query component when resolving. + if ($target->getQuery() === '') { + $segments = explode('/', $target->getPath()); + /** @var string $lastSegment */ + $lastSegment = end($segments); + + return $emptyPathUri->withPath($lastSegment === '' ? './' : $lastSegment); + } + + return $emptyPathUri; + } + + private static function getRelativePath(UriInterface $base, UriInterface $target): string + { + $sourceSegments = explode('/', $base->getPath()); + $targetSegments = explode('/', $target->getPath()); + array_pop($sourceSegments); + $targetLastSegment = array_pop($targetSegments); + foreach ($sourceSegments as $i => $segment) { + if (isset($targetSegments[$i]) && $segment === $targetSegments[$i]) { + unset($sourceSegments[$i], $targetSegments[$i]); + } else { + break; + } + } + $targetSegments[] = $targetLastSegment; + $relativePath = str_repeat('../', count($sourceSegments)) . implode('/', $targetSegments); + + // A reference to am empty last segment or an empty first sub-segment must be prefixed with "./". + // This also applies to a segment with a colon character (e.g., "file:colon") that cannot be used + // as the first segment of a relative-path reference, as it would be mistaken for a scheme name. + if ('' === $relativePath || false !== strpos(explode('/', $relativePath, 2)[0], ':')) { + $relativePath = "./$relativePath"; + } elseif ('/' === $relativePath[0]) { + if ($base->getAuthority() != '' && $base->getPath() === '') { + // In this case an extra slash is added by resolve() automatically. So we must not add one here. + $relativePath = ".$relativePath"; + } else { + $relativePath = "./$relativePath"; + } + } + + return $relativePath; + } + + private function __construct() + { + // cannot be instantiated + } +} diff --git a/vendor/guzzlehttp/psr7/src/Utils.php b/vendor/guzzlehttp/psr7/src/Utils.php new file mode 100644 index 0000000..3a4cf39 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Utils.php @@ -0,0 +1,459 @@ + $v) { + if (!is_string($k) || !in_array(strtolower($k), $keys)) { + $result[$k] = $v; + } + } + + return $result; + } + + /** + * Copy the contents of a stream into another stream until the given number + * of bytes have been read. + * + * @param StreamInterface $source Stream to read from + * @param StreamInterface $dest Stream to write to + * @param int $maxLen Maximum number of bytes to read. Pass -1 + * to read the entire stream. + * + * @throws \RuntimeException on error. + */ + public static function copyToStream(StreamInterface $source, StreamInterface $dest, int $maxLen = -1): void + { + $bufferSize = 8192; + + if ($maxLen === -1) { + while (!$source->eof()) { + if (!$dest->write($source->read($bufferSize))) { + break; + } + } + } else { + $remaining = $maxLen; + while ($remaining > 0 && !$source->eof()) { + $buf = $source->read(min($bufferSize, $remaining)); + $len = strlen($buf); + if (!$len) { + break; + } + $remaining -= $len; + $dest->write($buf); + } + } + } + + /** + * Copy the contents of a stream into a string until the given number of + * bytes have been read. + * + * @param StreamInterface $stream Stream to read + * @param int $maxLen Maximum number of bytes to read. Pass -1 + * to read the entire stream. + * + * @throws \RuntimeException on error. + */ + public static function copyToString(StreamInterface $stream, int $maxLen = -1): string + { + $buffer = ''; + + if ($maxLen === -1) { + while (!$stream->eof()) { + $buf = $stream->read(1048576); + if ($buf === '') { + break; + } + $buffer .= $buf; + } + return $buffer; + } + + $len = 0; + while (!$stream->eof() && $len < $maxLen) { + $buf = $stream->read($maxLen - $len); + if ($buf === '') { + break; + } + $buffer .= $buf; + $len = strlen($buffer); + } + + return $buffer; + } + + /** + * Calculate a hash of a stream. + * + * This method reads the entire stream to calculate a rolling hash, based + * on PHP's `hash_init` functions. + * + * @param StreamInterface $stream Stream to calculate the hash for + * @param string $algo Hash algorithm (e.g. md5, crc32, etc) + * @param bool $rawOutput Whether or not to use raw output + * + * @throws \RuntimeException on error. + */ + public static function hash(StreamInterface $stream, string $algo, bool $rawOutput = false): string + { + $pos = $stream->tell(); + + if ($pos > 0) { + $stream->rewind(); + } + + $ctx = hash_init($algo); + while (!$stream->eof()) { + hash_update($ctx, $stream->read(1048576)); + } + + $out = hash_final($ctx, $rawOutput); + $stream->seek($pos); + + return $out; + } + + /** + * Clone and modify a request with the given changes. + * + * This method is useful for reducing the number of clones needed to mutate + * a message. + * + * The changes can be one of: + * - method: (string) Changes the HTTP method. + * - set_headers: (array) Sets the given headers. + * - remove_headers: (array) Remove the given headers. + * - body: (mixed) Sets the given body. + * - uri: (UriInterface) Set the URI. + * - query: (string) Set the query string value of the URI. + * - version: (string) Set the protocol version. + * + * @param RequestInterface $request Request to clone and modify. + * @param array $changes Changes to apply. + */ + public static function modifyRequest(RequestInterface $request, array $changes): RequestInterface + { + if (!$changes) { + return $request; + } + + $headers = $request->getHeaders(); + + if (!isset($changes['uri'])) { + $uri = $request->getUri(); + } else { + // Remove the host header if one is on the URI + if ($host = $changes['uri']->getHost()) { + $changes['set_headers']['Host'] = $host; + + if ($port = $changes['uri']->getPort()) { + $standardPorts = ['http' => 80, 'https' => 443]; + $scheme = $changes['uri']->getScheme(); + if (isset($standardPorts[$scheme]) && $port != $standardPorts[$scheme]) { + $changes['set_headers']['Host'] .= ':' . $port; + } + } + } + $uri = $changes['uri']; + } + + if (!empty($changes['remove_headers'])) { + $headers = self::caselessRemove($changes['remove_headers'], $headers); + } + + if (!empty($changes['set_headers'])) { + $headers = self::caselessRemove(array_keys($changes['set_headers']), $headers); + $headers = $changes['set_headers'] + $headers; + } + + if (isset($changes['query'])) { + $uri = $uri->withQuery($changes['query']); + } + + if ($request instanceof ServerRequestInterface) { + $new = (new ServerRequest( + $changes['method'] ?? $request->getMethod(), + $uri, + $headers, + $changes['body'] ?? $request->getBody(), + $changes['version'] ?? $request->getProtocolVersion(), + $request->getServerParams() + )) + ->withParsedBody($request->getParsedBody()) + ->withQueryParams($request->getQueryParams()) + ->withCookieParams($request->getCookieParams()) + ->withUploadedFiles($request->getUploadedFiles()); + + foreach ($request->getAttributes() as $key => $value) { + $new = $new->withAttribute($key, $value); + } + + return $new; + } + + return new Request( + $changes['method'] ?? $request->getMethod(), + $uri, + $headers, + $changes['body'] ?? $request->getBody(), + $changes['version'] ?? $request->getProtocolVersion() + ); + } + + /** + * Read a line from the stream up to the maximum allowed buffer length. + * + * @param StreamInterface $stream Stream to read from + * @param int|null $maxLength Maximum buffer length + */ + public static function readLine(StreamInterface $stream, ?int $maxLength = null): string + { + $buffer = ''; + $size = 0; + + while (!$stream->eof()) { + if ('' === ($byte = $stream->read(1))) { + return $buffer; + } + $buffer .= $byte; + // Break when a new line is found or the max length - 1 is reached + if ($byte === "\n" || ++$size === $maxLength - 1) { + break; + } + } + + return $buffer; + } + + /** + * Create a new stream based on the input type. + * + * Options is an associative array that can contain the following keys: + * - metadata: Array of custom metadata. + * - size: Size of the stream. + * + * This method accepts the following `$resource` types: + * - `Psr\Http\Message\StreamInterface`: Returns the value as-is. + * - `string`: Creates a stream object that uses the given string as the contents. + * - `resource`: Creates a stream object that wraps the given PHP stream resource. + * - `Iterator`: If the provided value implements `Iterator`, then a read-only + * stream object will be created that wraps the given iterable. Each time the + * stream is read from, data from the iterator will fill a buffer and will be + * continuously called until the buffer is equal to the requested read size. + * Subsequent read calls will first read from the buffer and then call `next` + * on the underlying iterator until it is exhausted. + * - `object` with `__toString()`: If the object has the `__toString()` method, + * the object will be cast to a string and then a stream will be returned that + * uses the string value. + * - `NULL`: When `null` is passed, an empty stream object is returned. + * - `callable` When a callable is passed, a read-only stream object will be + * created that invokes the given callable. The callable is invoked with the + * number of suggested bytes to read. The callable can return any number of + * bytes, but MUST return `false` when there is no more data to return. The + * stream object that wraps the callable will invoke the callable until the + * number of requested bytes are available. Any additional bytes will be + * buffered and used in subsequent reads. + * + * @param resource|string|int|float|bool|StreamInterface|callable|\Iterator|null $resource Entity body data + * @param array{size?: int, metadata?: array} $options Additional options + * + * @throws \InvalidArgumentException if the $resource arg is not valid. + */ + public static function streamFor($resource = '', array $options = []): StreamInterface + { + if (is_scalar($resource)) { + $stream = self::tryFopen('php://temp', 'r+'); + if ($resource !== '') { + fwrite($stream, (string) $resource); + fseek($stream, 0); + } + return new Stream($stream, $options); + } + + switch (gettype($resource)) { + case 'resource': + /* + * The 'php://input' is a special stream with quirks and inconsistencies. + * We avoid using that stream by reading it into php://temp + */ + + /** @var resource $resource */ + if ((\stream_get_meta_data($resource)['uri'] ?? '') === 'php://input') { + $stream = self::tryFopen('php://temp', 'w+'); + stream_copy_to_stream($resource, $stream); + fseek($stream, 0); + $resource = $stream; + } + return new Stream($resource, $options); + case 'object': + /** @var object $resource */ + if ($resource instanceof StreamInterface) { + return $resource; + } elseif ($resource instanceof \Iterator) { + return new PumpStream(function () use ($resource) { + if (!$resource->valid()) { + return false; + } + $result = $resource->current(); + $resource->next(); + return $result; + }, $options); + } elseif (method_exists($resource, '__toString')) { + return self::streamFor((string) $resource, $options); + } + break; + case 'NULL': + return new Stream(self::tryFopen('php://temp', 'r+'), $options); + } + + if (is_callable($resource)) { + return new PumpStream($resource, $options); + } + + throw new \InvalidArgumentException('Invalid resource type: ' . gettype($resource)); + } + + /** + * Safely opens a PHP stream resource using a filename. + * + * When fopen fails, PHP normally raises a warning. This function adds an + * error handler that checks for errors and throws an exception instead. + * + * @param string $filename File to open + * @param string $mode Mode used to open the file + * + * @return resource + * + * @throws \RuntimeException if the file cannot be opened + */ + public static function tryFopen(string $filename, string $mode) + { + $ex = null; + set_error_handler(static function (int $errno, string $errstr) use ($filename, $mode, &$ex): bool { + $ex = new \RuntimeException(sprintf( + 'Unable to open "%s" using mode "%s": %s', + $filename, + $mode, + $errstr + )); + + return true; + }); + + try { + /** @var resource $handle */ + $handle = fopen($filename, $mode); + } catch (\Throwable $e) { + $ex = new \RuntimeException(sprintf( + 'Unable to open "%s" using mode "%s": %s', + $filename, + $mode, + $e->getMessage() + ), 0, $e); + } + + restore_error_handler(); + + if ($ex) { + /** @var $ex \RuntimeException */ + throw $ex; + } + + return $handle; + } + + /** + * Safely gets the contents of a given stream. + * + * When stream_get_contents fails, PHP normally raises a warning. This + * function adds an error handler that checks for errors and throws an + * exception instead. + * + * @param resource $stream + * + * @throws \RuntimeException if the stream cannot be read + */ + public static function tryGetContents($stream): string + { + $ex = null; + set_error_handler(static function (int $errno, string $errstr) use (&$ex): bool { + $ex = new \RuntimeException(sprintf( + 'Unable to read stream contents: %s', + $errstr + )); + + return true; + }); + + try { + /** @var string|false $contents */ + $contents = stream_get_contents($stream); + + if ($contents === false) { + $ex = new \RuntimeException('Unable to read stream contents'); + } + } catch (\Throwable $e) { + $ex = new \RuntimeException(sprintf( + 'Unable to read stream contents: %s', + $e->getMessage() + ), 0, $e); + } + + restore_error_handler(); + + if ($ex) { + /** @var $ex \RuntimeException */ + throw $ex; + } + + return $contents; + } + + /** + * Returns a UriInterface for the given value. + * + * This function accepts a string or UriInterface and returns a + * UriInterface for the given value. If the value is already a + * UriInterface, it is returned as-is. + * + * @param string|UriInterface $uri + * + * @throws \InvalidArgumentException + */ + public static function uriFor($uri): UriInterface + { + if ($uri instanceof UriInterface) { + return $uri; + } + + if (is_string($uri)) { + return new Uri($uri); + } + + throw new \InvalidArgumentException('URI must be a string or UriInterface'); + } +} diff --git a/vendor/illuminate/bus/Batch.php b/vendor/illuminate/bus/Batch.php new file mode 100644 index 0000000..d1464e4 --- /dev/null +++ b/vendor/illuminate/bus/Batch.php @@ -0,0 +1,481 @@ +queue = $queue; + $this->repository = $repository; + $this->id = $id; + $this->name = $name; + $this->totalJobs = $totalJobs; + $this->pendingJobs = $pendingJobs; + $this->failedJobs = $failedJobs; + $this->failedJobIds = $failedJobIds; + $this->options = $options; + $this->createdAt = $createdAt; + $this->cancelledAt = $cancelledAt; + $this->finishedAt = $finishedAt; + } + + /** + * Get a fresh instance of the batch represented by this ID. + * + * @return self + */ + public function fresh() + { + return $this->repository->find($this->id); + } + + /** + * Add additional jobs to the batch. + * + * @param \Illuminate\Support\Enumerable|array $jobs + * @return self + */ + public function add($jobs) + { + $count = 0; + + $jobs = Collection::wrap($jobs)->map(function ($job) use (&$count) { + $job = $job instanceof Closure ? CallQueuedClosure::create($job) : $job; + + if (is_array($job)) { + $count += count($job); + + return with($this->prepareBatchedChain($job), function ($chain) { + return $chain->first() + ->allOnQueue($this->options['queue'] ?? null) + ->allOnConnection($this->options['connection'] ?? null) + ->chain($chain->slice(1)->values()->all()); + }); + } else { + $job->withBatchId($this->id); + + $count++; + } + + return $job; + }); + + $this->repository->transaction(function () use ($jobs, $count) { + $this->repository->incrementTotalJobs($this->id, $count); + + $this->queue->connection($this->options['connection'] ?? null)->bulk( + $jobs->all(), + $data = '', + $this->options['queue'] ?? null + ); + }); + + return $this->fresh(); + } + + /** + * Prepare a chain that exists within the jobs being added. + * + * @param array $chain + * @return \Illuminate\Support\Collection + */ + protected function prepareBatchedChain(array $chain) + { + return collect($chain)->map(function ($job) { + $job = $job instanceof Closure ? CallQueuedClosure::create($job) : $job; + + return $job->withBatchId($this->id); + }); + } + + /** + * Get the total number of jobs that have been processed by the batch thus far. + * + * @return int + */ + public function processedJobs() + { + return $this->totalJobs - $this->pendingJobs; + } + + /** + * Get the percentage of jobs that have been processed (between 0-100). + * + * @return int + */ + public function progress() + { + return $this->totalJobs > 0 ? round(($this->processedJobs() / $this->totalJobs) * 100) : 0; + } + + /** + * Record that a job within the batch finished successfully, executing any callbacks if necessary. + * + * @param string $jobId + * @return void + */ + public function recordSuccessfulJob(string $jobId) + { + $counts = $this->decrementPendingJobs($jobId); + + if ($counts->pendingJobs === 0) { + $this->repository->markAsFinished($this->id); + } + + if ($counts->pendingJobs === 0 && $this->hasThenCallbacks()) { + $batch = $this->fresh(); + + collect($this->options['then'])->each(function ($handler) use ($batch) { + $this->invokeHandlerCallback($handler, $batch); + }); + } + + if ($counts->allJobsHaveRanExactlyOnce() && $this->hasFinallyCallbacks()) { + $batch = $this->fresh(); + + collect($this->options['finally'])->each(function ($handler) use ($batch) { + $this->invokeHandlerCallback($handler, $batch); + }); + } + } + + /** + * Decrement the pending jobs for the batch. + * + * @param string $jobId + * @return \Illuminate\Bus\UpdatedBatchJobCounts + */ + public function decrementPendingJobs(string $jobId) + { + return $this->repository->decrementPendingJobs($this->id, $jobId); + } + + /** + * Determine if the batch has finished executing. + * + * @return bool + */ + public function finished() + { + return ! is_null($this->finishedAt); + } + + /** + * Determine if the batch has "success" callbacks. + * + * @return bool + */ + public function hasThenCallbacks() + { + return isset($this->options['then']) && ! empty($this->options['then']); + } + + /** + * Determine if the batch allows jobs to fail without cancelling the batch. + * + * @return bool + */ + public function allowsFailures() + { + return Arr::get($this->options, 'allowFailures', false) === true; + } + + /** + * Determine if the batch has job failures. + * + * @return bool + */ + public function hasFailures() + { + return $this->failedJobs > 0; + } + + /** + * Record that a job within the batch failed to finish successfully, executing any callbacks if necessary. + * + * @param string $jobId + * @param \Throwable $e + * @return void + */ + public function recordFailedJob(string $jobId, $e) + { + $counts = $this->incrementFailedJobs($jobId); + + if ($counts->failedJobs === 1 && ! $this->allowsFailures()) { + $this->cancel(); + } + + if ($counts->failedJobs === 1 && $this->hasCatchCallbacks()) { + $batch = $this->fresh(); + + collect($this->options['catch'])->each(function ($handler) use ($batch, $e) { + $this->invokeHandlerCallback($handler, $batch, $e); + }); + } + + if ($counts->allJobsHaveRanExactlyOnce() && $this->hasFinallyCallbacks()) { + $batch = $this->fresh(); + + collect($this->options['finally'])->each(function ($handler) use ($batch, $e) { + $this->invokeHandlerCallback($handler, $batch, $e); + }); + } + } + + /** + * Increment the failed jobs for the batch. + * + * @param string $jobId + * @return \Illuminate\Bus\UpdatedBatchJobCounts + */ + public function incrementFailedJobs(string $jobId) + { + return $this->repository->incrementFailedJobs($this->id, $jobId); + } + + /** + * Determine if the batch has "catch" callbacks. + * + * @return bool + */ + public function hasCatchCallbacks() + { + return isset($this->options['catch']) && ! empty($this->options['catch']); + } + + /** + * Determine if the batch has "finally" callbacks. + * + * @return bool + */ + public function hasFinallyCallbacks() + { + return isset($this->options['finally']) && ! empty($this->options['finally']); + } + + /** + * Cancel the batch. + * + * @return void + */ + public function cancel() + { + $this->repository->cancel($this->id); + } + + /** + * Determine if the batch has been cancelled. + * + * @return bool + */ + public function canceled() + { + return $this->cancelled(); + } + + /** + * Determine if the batch has been cancelled. + * + * @return bool + */ + public function cancelled() + { + return ! is_null($this->cancelledAt); + } + + /** + * Delete the batch from storage. + * + * @return void + */ + public function delete() + { + $this->repository->delete($this->id); + } + + /** + * Invoke a batch callback handler. + * + * @param callable $handler + * @param \Illuminate\Bus\Batch $batch + * @param \Throwable|null $e + * @return void + */ + protected function invokeHandlerCallback($handler, Batch $batch, Throwable $e = null) + { + try { + return $handler($batch, $e); + } catch (Throwable $e) { + if (function_exists('report')) { + report($e); + } + } + } + + /** + * Convert the batch to an array. + * + * @return array + */ + public function toArray() + { + return [ + 'id' => $this->id, + 'name' => $this->name, + 'totalJobs' => $this->totalJobs, + 'pendingJobs' => $this->pendingJobs, + 'processedJobs' => $this->processedJobs(), + 'progress' => $this->progress(), + 'failedJobs' => $this->failedJobs, + 'options' => $this->options, + 'createdAt' => $this->createdAt, + 'cancelledAt' => $this->cancelledAt, + 'finishedAt' => $this->finishedAt, + ]; + } + + /** + * Get the JSON serializable representation of the object. + * + * @return array + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return $this->toArray(); + } + + /** + * Dynamically access the batch's "options" via properties. + * + * @param string $key + * @return mixed + */ + public function __get($key) + { + return $this->options[$key] ?? null; + } +} diff --git a/vendor/illuminate/bus/BatchFactory.php b/vendor/illuminate/bus/BatchFactory.php new file mode 100644 index 0000000..2c3a4e9 --- /dev/null +++ b/vendor/illuminate/bus/BatchFactory.php @@ -0,0 +1,58 @@ +queue = $queue; + } + + /** + * Create a new batch instance. + * + * @param \Illuminate\Bus\BatchRepository $repository + * @param string $id + * @param string $name + * @param int $totalJobs + * @param int $pendingJobs + * @param int $failedJobs + * @param array $failedJobIds + * @param array $options + * @param \Carbon\CarbonImmutable $createdAt + * @param \Carbon\CarbonImmutable|null $cancelledAt + * @param \Carbon\CarbonImmutable|null $finishedAt + * @return \Illuminate\Bus\Batch + */ + public function make(BatchRepository $repository, + string $id, + string $name, + int $totalJobs, + int $pendingJobs, + int $failedJobs, + array $failedJobIds, + array $options, + CarbonImmutable $createdAt, + ?CarbonImmutable $cancelledAt, + ?CarbonImmutable $finishedAt) + { + return new Batch($this->queue, $repository, $id, $name, $totalJobs, $pendingJobs, $failedJobs, $failedJobIds, $options, $createdAt, $cancelledAt, $finishedAt); + } +} diff --git a/vendor/illuminate/bus/BatchRepository.php b/vendor/illuminate/bus/BatchRepository.php new file mode 100644 index 0000000..098ccef --- /dev/null +++ b/vendor/illuminate/bus/BatchRepository.php @@ -0,0 +1,92 @@ +batchId) { + return Container::getInstance()->make(BatchRepository::class)->find($this->batchId); + } + } + + /** + * Determine if the batch is still active and processing. + * + * @return bool + */ + public function batching() + { + $batch = $this->batch(); + + return $batch && ! $batch->cancelled(); + } + + /** + * Set the batch ID on the job. + * + * @param string $batchId + * @return $this + */ + public function withBatchId(string $batchId) + { + $this->batchId = $batchId; + + return $this; + } +} diff --git a/vendor/illuminate/bus/BusServiceProvider.php b/vendor/illuminate/bus/BusServiceProvider.php new file mode 100644 index 0000000..ff3eef8 --- /dev/null +++ b/vendor/illuminate/bus/BusServiceProvider.php @@ -0,0 +1,69 @@ +app->singleton(Dispatcher::class, function ($app) { + return new Dispatcher($app, function ($connection = null) use ($app) { + return $app[QueueFactoryContract::class]->connection($connection); + }); + }); + + $this->registerBatchServices(); + + $this->app->alias( + Dispatcher::class, DispatcherContract::class + ); + + $this->app->alias( + Dispatcher::class, QueueingDispatcherContract::class + ); + } + + /** + * Register the batch handling services. + * + * @return void + */ + protected function registerBatchServices() + { + $this->app->singleton(BatchRepository::class, DatabaseBatchRepository::class); + + $this->app->singleton(DatabaseBatchRepository::class, function ($app) { + return new DatabaseBatchRepository( + $app->make(BatchFactory::class), + $app->make('db')->connection($app->config->get('queue.batching.database')), + $app->config->get('queue.batching.table', 'job_batches') + ); + }); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return [ + Dispatcher::class, + DispatcherContract::class, + QueueingDispatcherContract::class, + BatchRepository::class, + ]; + } +} diff --git a/vendor/illuminate/bus/DatabaseBatchRepository.php b/vendor/illuminate/bus/DatabaseBatchRepository.php new file mode 100644 index 0000000..ee544b0 --- /dev/null +++ b/vendor/illuminate/bus/DatabaseBatchRepository.php @@ -0,0 +1,347 @@ +factory = $factory; + $this->connection = $connection; + $this->table = $table; + } + + /** + * Retrieve a list of batches. + * + * @param int $limit + * @param mixed $before + * @return \Illuminate\Bus\Batch[] + */ + public function get($limit = 50, $before = null) + { + return $this->connection->table($this->table) + ->orderByDesc('id') + ->take($limit) + ->when($before, function ($q) use ($before) { + return $q->where('id', '<', $before); + }) + ->get() + ->map(function ($batch) { + return $this->toBatch($batch); + }) + ->all(); + } + + /** + * Retrieve information about an existing batch. + * + * @param string $batchId + * @return \Illuminate\Bus\Batch|null + */ + public function find(string $batchId) + { + $batch = $this->connection->table($this->table) + ->where('id', $batchId) + ->first(); + + if ($batch) { + return $this->toBatch($batch); + } + } + + /** + * Store a new pending batch. + * + * @param \Illuminate\Bus\PendingBatch $batch + * @return \Illuminate\Bus\Batch + */ + public function store(PendingBatch $batch) + { + $id = (string) Str::orderedUuid(); + + $this->connection->table($this->table)->insert([ + 'id' => $id, + 'name' => $batch->name, + 'total_jobs' => 0, + 'pending_jobs' => 0, + 'failed_jobs' => 0, + 'failed_job_ids' => '[]', + 'options' => $this->serialize($batch->options), + 'created_at' => time(), + 'cancelled_at' => null, + 'finished_at' => null, + ]); + + return $this->find($id); + } + + /** + * Increment the total number of jobs within the batch. + * + * @param string $batchId + * @param int $amount + * @return void + */ + public function incrementTotalJobs(string $batchId, int $amount) + { + $this->connection->table($this->table)->where('id', $batchId)->update([ + 'total_jobs' => new Expression('total_jobs + '.$amount), + 'pending_jobs' => new Expression('pending_jobs + '.$amount), + 'finished_at' => null, + ]); + } + + /** + * Decrement the total number of pending jobs for the batch. + * + * @param string $batchId + * @param string $jobId + * @return \Illuminate\Bus\UpdatedBatchJobCounts + */ + public function decrementPendingJobs(string $batchId, string $jobId) + { + $values = $this->updateAtomicValues($batchId, function ($batch) use ($jobId) { + return [ + 'pending_jobs' => $batch->pending_jobs - 1, + 'failed_jobs' => $batch->failed_jobs, + 'failed_job_ids' => json_encode(array_values(array_diff(json_decode($batch->failed_job_ids, true), [$jobId]))), + ]; + }); + + return new UpdatedBatchJobCounts( + $values['pending_jobs'], + $values['failed_jobs'] + ); + } + + /** + * Increment the total number of failed jobs for the batch. + * + * @param string $batchId + * @param string $jobId + * @return \Illuminate\Bus\UpdatedBatchJobCounts + */ + public function incrementFailedJobs(string $batchId, string $jobId) + { + $values = $this->updateAtomicValues($batchId, function ($batch) use ($jobId) { + return [ + 'pending_jobs' => $batch->pending_jobs, + 'failed_jobs' => $batch->failed_jobs + 1, + 'failed_job_ids' => json_encode(array_values(array_unique(array_merge(json_decode($batch->failed_job_ids, true), [$jobId])))), + ]; + }); + + return new UpdatedBatchJobCounts( + $values['pending_jobs'], + $values['failed_jobs'] + ); + } + + /** + * Update an atomic value within the batch. + * + * @param string $batchId + * @param \Closure $callback + * @return int|null + */ + protected function updateAtomicValues(string $batchId, Closure $callback) + { + return $this->connection->transaction(function () use ($batchId, $callback) { + $batch = $this->connection->table($this->table)->where('id', $batchId) + ->lockForUpdate() + ->first(); + + return is_null($batch) ? [] : tap($callback($batch), function ($values) use ($batchId) { + $this->connection->table($this->table)->where('id', $batchId)->update($values); + }); + }); + } + + /** + * Mark the batch that has the given ID as finished. + * + * @param string $batchId + * @return void + */ + public function markAsFinished(string $batchId) + { + $this->connection->table($this->table)->where('id', $batchId)->update([ + 'finished_at' => time(), + ]); + } + + /** + * Cancel the batch that has the given ID. + * + * @param string $batchId + * @return void + */ + public function cancel(string $batchId) + { + $this->connection->table($this->table)->where('id', $batchId)->update([ + 'cancelled_at' => time(), + 'finished_at' => time(), + ]); + } + + /** + * Delete the batch that has the given ID. + * + * @param string $batchId + * @return void + */ + public function delete(string $batchId) + { + $this->connection->table($this->table)->where('id', $batchId)->delete(); + } + + /** + * Prune all of the entries older than the given date. + * + * @param \DateTimeInterface $before + * @return int + */ + public function prune(DateTimeInterface $before) + { + $query = $this->connection->table($this->table) + ->whereNotNull('finished_at') + ->where('finished_at', '<', $before->getTimestamp()); + + $totalDeleted = 0; + + do { + $deleted = $query->take(1000)->delete(); + + $totalDeleted += $deleted; + } while ($deleted !== 0); + + return $totalDeleted; + } + + /** + * Prune all of the unfinished entries older than the given date. + * + * @param \DateTimeInterface $before + * @return int + */ + public function pruneUnfinished(DateTimeInterface $before) + { + $query = $this->connection->table($this->table) + ->whereNull('finished_at') + ->where('created_at', '<', $before->getTimestamp()); + + $totalDeleted = 0; + + do { + $deleted = $query->take(1000)->delete(); + + $totalDeleted += $deleted; + } while ($deleted !== 0); + + return $totalDeleted; + } + + /** + * Execute the given Closure within a storage specific transaction. + * + * @param \Closure $callback + * @return mixed + */ + public function transaction(Closure $callback) + { + return $this->connection->transaction(function () use ($callback) { + return $callback(); + }); + } + + /** + * Serialize the given value. + * + * @param mixed $value + * @return string + */ + protected function serialize($value) + { + $serialized = serialize($value); + + return $this->connection instanceof PostgresConnection + ? base64_encode($serialized) + : $serialized; + } + + /** + * Unserialize the given value. + * + * @param string $serialized + * @return mixed + */ + protected function unserialize($serialized) + { + if ($this->connection instanceof PostgresConnection && + ! Str::contains($serialized, [':', ';'])) { + $serialized = base64_decode($serialized); + } + + return unserialize($serialized); + } + + /** + * Convert the given raw batch to a Batch object. + * + * @param object $batch + * @return \Illuminate\Bus\Batch + */ + protected function toBatch($batch) + { + return $this->factory->make( + $this, + $batch->id, + $batch->name, + (int) $batch->total_jobs, + (int) $batch->pending_jobs, + (int) $batch->failed_jobs, + json_decode($batch->failed_job_ids, true), + $this->unserialize($batch->options), + CarbonImmutable::createFromTimestamp($batch->created_at), + $batch->cancelled_at ? CarbonImmutable::createFromTimestamp($batch->cancelled_at) : $batch->cancelled_at, + $batch->finished_at ? CarbonImmutable::createFromTimestamp($batch->finished_at) : $batch->finished_at + ); + } +} diff --git a/vendor/illuminate/bus/Dispatcher.php b/vendor/illuminate/bus/Dispatcher.php new file mode 100644 index 0000000..4dc390e --- /dev/null +++ b/vendor/illuminate/bus/Dispatcher.php @@ -0,0 +1,295 @@ +container = $container; + $this->queueResolver = $queueResolver; + $this->pipeline = new Pipeline($container); + } + + /** + * Dispatch a command to its appropriate handler. + * + * @param mixed $command + * @return mixed + */ + public function dispatch($command) + { + return $this->queueResolver && $this->commandShouldBeQueued($command) + ? $this->dispatchToQueue($command) + : $this->dispatchNow($command); + } + + /** + * Dispatch a command to its appropriate handler in the current process. + * + * Queueable jobs will be dispatched to the "sync" queue. + * + * @param mixed $command + * @param mixed $handler + * @return mixed + */ + public function dispatchSync($command, $handler = null) + { + if ($this->queueResolver && + $this->commandShouldBeQueued($command) && + method_exists($command, 'onConnection')) { + return $this->dispatchToQueue($command->onConnection('sync')); + } + + return $this->dispatchNow($command, $handler); + } + + /** + * Dispatch a command to its appropriate handler in the current process without using the synchronous queue. + * + * @param mixed $command + * @param mixed $handler + * @return mixed + */ + public function dispatchNow($command, $handler = null) + { + $uses = class_uses_recursive($command); + + if (in_array(InteractsWithQueue::class, $uses) && + in_array(Queueable::class, $uses) && + ! $command->job) { + $command->setJob(new SyncJob($this->container, json_encode([]), 'sync', 'sync')); + } + + if ($handler || $handler = $this->getCommandHandler($command)) { + $callback = function ($command) use ($handler) { + $method = method_exists($handler, 'handle') ? 'handle' : '__invoke'; + + return $handler->{$method}($command); + }; + } else { + $callback = function ($command) { + $method = method_exists($command, 'handle') ? 'handle' : '__invoke'; + + return $this->container->call([$command, $method]); + }; + } + + return $this->pipeline->send($command)->through($this->pipes)->then($callback); + } + + /** + * Attempt to find the batch with the given ID. + * + * @param string $batchId + * @return \Illuminate\Bus\Batch|null + */ + public function findBatch(string $batchId) + { + return $this->container->make(BatchRepository::class)->find($batchId); + } + + /** + * Create a new batch of queueable jobs. + * + * @param \Illuminate\Support\Collection|array|mixed $jobs + * @return \Illuminate\Bus\PendingBatch + */ + public function batch($jobs) + { + return new PendingBatch($this->container, Collection::wrap($jobs)); + } + + /** + * Create a new chain of queueable jobs. + * + * @param \Illuminate\Support\Collection|array $jobs + * @return \Illuminate\Foundation\Bus\PendingChain + */ + public function chain($jobs) + { + $jobs = Collection::wrap($jobs); + + return new PendingChain($jobs->shift(), $jobs->toArray()); + } + + /** + * Determine if the given command has a handler. + * + * @param mixed $command + * @return bool + */ + public function hasCommandHandler($command) + { + return array_key_exists(get_class($command), $this->handlers); + } + + /** + * Retrieve the handler for a command. + * + * @param mixed $command + * @return bool|mixed + */ + public function getCommandHandler($command) + { + if ($this->hasCommandHandler($command)) { + return $this->container->make($this->handlers[get_class($command)]); + } + + return false; + } + + /** + * Determine if the given command should be queued. + * + * @param mixed $command + * @return bool + */ + protected function commandShouldBeQueued($command) + { + return $command instanceof ShouldQueue; + } + + /** + * Dispatch a command to its appropriate handler behind a queue. + * + * @param mixed $command + * @return mixed + * + * @throws \RuntimeException + */ + public function dispatchToQueue($command) + { + $connection = $command->connection ?? null; + + $queue = call_user_func($this->queueResolver, $connection); + + if (! $queue instanceof Queue) { + throw new RuntimeException('Queue resolver did not return a Queue implementation.'); + } + + if (method_exists($command, 'queue')) { + return $command->queue($queue, $command); + } + + return $this->pushCommandToQueue($queue, $command); + } + + /** + * Push the command onto the given queue instance. + * + * @param \Illuminate\Contracts\Queue\Queue $queue + * @param mixed $command + * @return mixed + */ + protected function pushCommandToQueue($queue, $command) + { + if (isset($command->queue, $command->delay)) { + return $queue->laterOn($command->queue, $command->delay, $command); + } + + if (isset($command->queue)) { + return $queue->pushOn($command->queue, $command); + } + + if (isset($command->delay)) { + return $queue->later($command->delay, $command); + } + + return $queue->push($command); + } + + /** + * Dispatch a command to its appropriate handler after the current process. + * + * @param mixed $command + * @param mixed $handler + * @return void + */ + public function dispatchAfterResponse($command, $handler = null) + { + $this->container->terminating(function () use ($command, $handler) { + $this->dispatchNow($command, $handler); + }); + } + + /** + * Set the pipes through which commands should be piped before dispatching. + * + * @param array $pipes + * @return $this + */ + public function pipeThrough(array $pipes) + { + $this->pipes = $pipes; + + return $this; + } + + /** + * Map a command to a handler. + * + * @param array $map + * @return $this + */ + public function map(array $map) + { + $this->handlers = array_merge($this->handlers, $map); + + return $this; + } +} diff --git a/vendor/illuminate/bus/Events/BatchDispatched.php b/vendor/illuminate/bus/Events/BatchDispatched.php new file mode 100644 index 0000000..b9a161a --- /dev/null +++ b/vendor/illuminate/bus/Events/BatchDispatched.php @@ -0,0 +1,26 @@ +batch = $batch; + } +} diff --git a/vendor/illuminate/bus/LICENSE.md b/vendor/illuminate/bus/LICENSE.md new file mode 100644 index 0000000..79810c8 --- /dev/null +++ b/vendor/illuminate/bus/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) Taylor Otwell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/illuminate/bus/PendingBatch.php b/vendor/illuminate/bus/PendingBatch.php new file mode 100644 index 0000000..77cb7fa --- /dev/null +++ b/vendor/illuminate/bus/PendingBatch.php @@ -0,0 +1,272 @@ +container = $container; + $this->jobs = $jobs; + } + + /** + * Add jobs to the batch. + * + * @param iterable $jobs + * @return $this + */ + public function add($jobs) + { + foreach ($jobs as $job) { + $this->jobs->push($job); + } + + return $this; + } + + /** + * Add a callback to be executed after all jobs in the batch have executed successfully. + * + * @param callable $callback + * @return $this + */ + public function then($callback) + { + $this->options['then'][] = $callback instanceof Closure + ? SerializableClosureFactory::make($callback) + : $callback; + + return $this; + } + + /** + * Get the "then" callbacks that have been registered with the pending batch. + * + * @return array + */ + public function thenCallbacks() + { + return $this->options['then'] ?? []; + } + + /** + * Add a callback to be executed after the first failing job in the batch. + * + * @param callable $callback + * @return $this + */ + public function catch($callback) + { + $this->options['catch'][] = $callback instanceof Closure + ? SerializableClosureFactory::make($callback) + : $callback; + + return $this; + } + + /** + * Get the "catch" callbacks that have been registered with the pending batch. + * + * @return array + */ + public function catchCallbacks() + { + return $this->options['catch'] ?? []; + } + + /** + * Add a callback to be executed after the batch has finished executing. + * + * @param callable $callback + * @return $this + */ + public function finally($callback) + { + $this->options['finally'][] = $callback instanceof Closure + ? SerializableClosureFactory::make($callback) + : $callback; + + return $this; + } + + /** + * Get the "finally" callbacks that have been registered with the pending batch. + * + * @return array + */ + public function finallyCallbacks() + { + return $this->options['finally'] ?? []; + } + + /** + * Indicate that the batch should not be cancelled when a job within the batch fails. + * + * @param bool $allowFailures + * @return $this + */ + public function allowFailures($allowFailures = true) + { + $this->options['allowFailures'] = $allowFailures; + + return $this; + } + + /** + * Determine if the pending batch allows jobs to fail without cancelling the batch. + * + * @return bool + */ + public function allowsFailures() + { + return Arr::get($this->options, 'allowFailures', false) === true; + } + + /** + * Set the name for the batch. + * + * @param string $name + * @return $this + */ + public function name(string $name) + { + $this->name = $name; + + return $this; + } + + /** + * Specify the queue connection that the batched jobs should run on. + * + * @param string $connection + * @return $this + */ + public function onConnection(string $connection) + { + $this->options['connection'] = $connection; + + return $this; + } + + /** + * Get the connection used by the pending batch. + * + * @return string|null + */ + public function connection() + { + return $this->options['connection'] ?? null; + } + + /** + * Specify the queue that the batched jobs should run on. + * + * @param string $queue + * @return $this + */ + public function onQueue(string $queue) + { + $this->options['queue'] = $queue; + + return $this; + } + + /** + * Get the queue used by the pending batch. + * + * @return string|null + */ + public function queue() + { + return $this->options['queue'] ?? null; + } + + /** + * Add additional data into the batch's options array. + * + * @param string $key + * @param mixed $value + * @return $this + */ + public function withOption(string $key, $value) + { + $this->options[$key] = $value; + + return $this; + } + + /** + * Dispatch the batch. + * + * @return \Illuminate\Bus\Batch + * + * @throws \Throwable + */ + public function dispatch() + { + $repository = $this->container->make(BatchRepository::class); + + try { + $batch = $repository->store($this); + + $batch = $batch->add($this->jobs); + } catch (Throwable $e) { + if (isset($batch)) { + $repository->delete($batch->id); + } + + throw $e; + } + + $this->container->make(EventDispatcher::class)->dispatch( + new BatchDispatched($batch) + ); + + return $batch; + } +} diff --git a/vendor/illuminate/bus/PrunableBatchRepository.php b/vendor/illuminate/bus/PrunableBatchRepository.php new file mode 100644 index 0000000..3f97255 --- /dev/null +++ b/vendor/illuminate/bus/PrunableBatchRepository.php @@ -0,0 +1,16 @@ +connection = $connection; + + return $this; + } + + /** + * Set the desired queue for the job. + * + * @param string|null $queue + * @return $this + */ + public function onQueue($queue) + { + $this->queue = $queue; + + return $this; + } + + /** + * Set the desired connection for the chain. + * + * @param string|null $connection + * @return $this + */ + public function allOnConnection($connection) + { + $this->chainConnection = $connection; + $this->connection = $connection; + + return $this; + } + + /** + * Set the desired queue for the chain. + * + * @param string|null $queue + * @return $this + */ + public function allOnQueue($queue) + { + $this->chainQueue = $queue; + $this->queue = $queue; + + return $this; + } + + /** + * Set the desired delay for the job. + * + * @param \DateTimeInterface|\DateInterval|int|null $delay + * @return $this + */ + public function delay($delay) + { + $this->delay = $delay; + + return $this; + } + + /** + * Indicate that the job should be dispatched after all database transactions have committed. + * + * @return $this + */ + public function afterCommit() + { + $this->afterCommit = true; + + return $this; + } + + /** + * Indicate that the job should not wait until database transactions have been committed before dispatching. + * + * @return $this + */ + public function beforeCommit() + { + $this->afterCommit = false; + + return $this; + } + + /** + * Specify the middleware the job should be dispatched through. + * + * @param array|object $middleware + * @return $this + */ + public function through($middleware) + { + $this->middleware = Arr::wrap($middleware); + + return $this; + } + + /** + * Set the jobs that should run if this job is successful. + * + * @param array $chain + * @return $this + */ + public function chain($chain) + { + $this->chained = collect($chain)->map(function ($job) { + return $this->serializeJob($job); + })->all(); + + return $this; + } + + /** + * Serialize a job for queuing. + * + * @param mixed $job + * @return string + * + * @throws \RuntimeException + */ + protected function serializeJob($job) + { + if ($job instanceof Closure) { + if (! class_exists(CallQueuedClosure::class)) { + throw new RuntimeException( + 'To enable support for closure jobs, please install the illuminate/queue package.' + ); + } + + $job = CallQueuedClosure::create($job); + } + + return serialize($job); + } + + /** + * Dispatch the next job on the chain. + * + * @return void + */ + public function dispatchNextJobInChain() + { + if (! empty($this->chained)) { + dispatch(tap(unserialize(array_shift($this->chained)), function ($next) { + $next->chained = $this->chained; + + $next->onConnection($next->connection ?: $this->chainConnection); + $next->onQueue($next->queue ?: $this->chainQueue); + + $next->chainConnection = $this->chainConnection; + $next->chainQueue = $this->chainQueue; + $next->chainCatchCallbacks = $this->chainCatchCallbacks; + })); + } + } + + /** + * Invoke all of the chain's failed job callbacks. + * + * @param \Throwable $e + * @return void + */ + public function invokeChainCatchCallbacks($e) + { + collect($this->chainCatchCallbacks)->each(function ($callback) use ($e) { + $callback($e); + }); + } +} diff --git a/vendor/illuminate/bus/UniqueLock.php b/vendor/illuminate/bus/UniqueLock.php new file mode 100644 index 0000000..a937b18 --- /dev/null +++ b/vendor/illuminate/bus/UniqueLock.php @@ -0,0 +1,48 @@ +cache = $cache; + } + + /** + * Attempt to acquire a lock for the given job. + * + * @param mixed $job + * @return bool + */ + public function acquire($job) + { + $uniqueId = method_exists($job, 'uniqueId') + ? $job->uniqueId() + : ($job->uniqueId ?? ''); + + $cache = method_exists($job, 'uniqueVia') + ? $job->uniqueVia() + : $this->cache; + + return (bool) $cache->lock( + $key = 'laravel_unique_job:'.get_class($job).$uniqueId, + $job->uniqueFor ?? 0 + )->get(); + } +} diff --git a/vendor/illuminate/bus/UpdatedBatchJobCounts.php b/vendor/illuminate/bus/UpdatedBatchJobCounts.php new file mode 100644 index 0000000..83d33a4 --- /dev/null +++ b/vendor/illuminate/bus/UpdatedBatchJobCounts.php @@ -0,0 +1,43 @@ +pendingJobs = $pendingJobs; + $this->failedJobs = $failedJobs; + } + + /** + * Determine if all jobs have run exactly once. + * + * @return bool + */ + public function allJobsHaveRanExactlyOnce() + { + return ($this->pendingJobs - $this->failedJobs) === 0; + } +} diff --git a/vendor/illuminate/collections/Arr.php b/vendor/illuminate/collections/Arr.php new file mode 100644 index 0000000..fd7dca8 --- /dev/null +++ b/vendor/illuminate/collections/Arr.php @@ -0,0 +1,747 @@ +all(); + } elseif (! is_array($values)) { + continue; + } + + $results[] = $values; + } + + return array_merge([], ...$results); + } + + /** + * Cross join the given arrays, returning all possible permutations. + * + * @param iterable ...$arrays + * @return array + */ + public static function crossJoin(...$arrays) + { + $results = [[]]; + + foreach ($arrays as $index => $array) { + $append = []; + + foreach ($results as $product) { + foreach ($array as $item) { + $product[$index] = $item; + + $append[] = $product; + } + } + + $results = $append; + } + + return $results; + } + + /** + * Divide an array into two arrays. One with keys and the other with values. + * + * @param array $array + * @return array + */ + public static function divide($array) + { + return [array_keys($array), array_values($array)]; + } + + /** + * Flatten a multi-dimensional associative array with dots. + * + * @param iterable $array + * @param string $prepend + * @return array + */ + public static function dot($array, $prepend = '') + { + $results = []; + + foreach ($array as $key => $value) { + if (is_array($value) && ! empty($value)) { + $results = array_merge($results, static::dot($value, $prepend.$key.'.')); + } else { + $results[$prepend.$key] = $value; + } + } + + return $results; + } + + /** + * Convert a flatten "dot" notation array into an expanded array. + * + * @param iterable $array + * @return array + */ + public static function undot($array) + { + $results = []; + + foreach ($array as $key => $value) { + static::set($results, $key, $value); + } + + return $results; + } + + /** + * Get all of the given array except for a specified array of keys. + * + * @param array $array + * @param array|string $keys + * @return array + */ + public static function except($array, $keys) + { + static::forget($array, $keys); + + return $array; + } + + /** + * Determine if the given key exists in the provided array. + * + * @param \ArrayAccess|array $array + * @param string|int $key + * @return bool + */ + public static function exists($array, $key) + { + if ($array instanceof Enumerable) { + return $array->has($key); + } + + if ($array instanceof ArrayAccess) { + return $array->offsetExists($key); + } + + return array_key_exists($key, $array); + } + + /** + * Return the first element in an array passing a given truth test. + * + * @param iterable $array + * @param callable|null $callback + * @param mixed $default + * @return mixed + */ + public static function first($array, callable $callback = null, $default = null) + { + if (is_null($callback)) { + if (empty($array)) { + return value($default); + } + + foreach ($array as $item) { + return $item; + } + } + + foreach ($array as $key => $value) { + if ($callback($value, $key)) { + return $value; + } + } + + return value($default); + } + + /** + * Return the last element in an array passing a given truth test. + * + * @param array $array + * @param callable|null $callback + * @param mixed $default + * @return mixed + */ + public static function last($array, callable $callback = null, $default = null) + { + if (is_null($callback)) { + return empty($array) ? value($default) : end($array); + } + + return static::first(array_reverse($array, true), $callback, $default); + } + + /** + * Flatten a multi-dimensional array into a single level. + * + * @param iterable $array + * @param int $depth + * @return array + */ + public static function flatten($array, $depth = INF) + { + $result = []; + + foreach ($array as $item) { + $item = $item instanceof Collection ? $item->all() : $item; + + if (! is_array($item)) { + $result[] = $item; + } else { + $values = $depth === 1 + ? array_values($item) + : static::flatten($item, $depth - 1); + + foreach ($values as $value) { + $result[] = $value; + } + } + } + + return $result; + } + + /** + * Remove one or many array items from a given array using "dot" notation. + * + * @param array $array + * @param array|string $keys + * @return void + */ + public static function forget(&$array, $keys) + { + $original = &$array; + + $keys = (array) $keys; + + if (count($keys) === 0) { + return; + } + + foreach ($keys as $key) { + // if the exact key exists in the top-level, remove it + if (static::exists($array, $key)) { + unset($array[$key]); + + continue; + } + + $parts = explode('.', $key); + + // clean up before each pass + $array = &$original; + + while (count($parts) > 1) { + $part = array_shift($parts); + + if (isset($array[$part]) && is_array($array[$part])) { + $array = &$array[$part]; + } else { + continue 2; + } + } + + unset($array[array_shift($parts)]); + } + } + + /** + * Get an item from an array using "dot" notation. + * + * @param \ArrayAccess|array $array + * @param string|int|null $key + * @param mixed $default + * @return mixed + */ + public static function get($array, $key, $default = null) + { + if (! static::accessible($array)) { + return value($default); + } + + if (is_null($key)) { + return $array; + } + + if (static::exists($array, $key)) { + return $array[$key]; + } + + if (strpos($key, '.') === false) { + return $array[$key] ?? value($default); + } + + foreach (explode('.', $key) as $segment) { + if (static::accessible($array) && static::exists($array, $segment)) { + $array = $array[$segment]; + } else { + return value($default); + } + } + + return $array; + } + + /** + * Check if an item or items exist in an array using "dot" notation. + * + * @param \ArrayAccess|array $array + * @param string|array $keys + * @return bool + */ + public static function has($array, $keys) + { + $keys = (array) $keys; + + if (! $array || $keys === []) { + return false; + } + + foreach ($keys as $key) { + $subKeyArray = $array; + + if (static::exists($array, $key)) { + continue; + } + + foreach (explode('.', $key) as $segment) { + if (static::accessible($subKeyArray) && static::exists($subKeyArray, $segment)) { + $subKeyArray = $subKeyArray[$segment]; + } else { + return false; + } + } + } + + return true; + } + + /** + * Determine if any of the keys exist in an array using "dot" notation. + * + * @param \ArrayAccess|array $array + * @param string|array $keys + * @return bool + */ + public static function hasAny($array, $keys) + { + if (is_null($keys)) { + return false; + } + + $keys = (array) $keys; + + if (! $array) { + return false; + } + + if ($keys === []) { + return false; + } + + foreach ($keys as $key) { + if (static::has($array, $key)) { + return true; + } + } + + return false; + } + + /** + * Determines if an array is associative. + * + * An array is "associative" if it doesn't have sequential numerical keys beginning with zero. + * + * @param array $array + * @return bool + */ + public static function isAssoc(array $array) + { + $keys = array_keys($array); + + return array_keys($keys) !== $keys; + } + + /** + * Determines if an array is a list. + * + * An array is a "list" if all array keys are sequential integers starting from 0 with no gaps in between. + * + * @param array $array + * @return bool + */ + public static function isList($array) + { + return ! self::isAssoc($array); + } + + /** + * Get a subset of the items from the given array. + * + * @param array $array + * @param array|string $keys + * @return array + */ + public static function only($array, $keys) + { + return array_intersect_key($array, array_flip((array) $keys)); + } + + /** + * Pluck an array of values from an array. + * + * @param iterable $array + * @param string|array|int|null $value + * @param string|array|null $key + * @return array + */ + public static function pluck($array, $value, $key = null) + { + $results = []; + + [$value, $key] = static::explodePluckParameters($value, $key); + + foreach ($array as $item) { + $itemValue = data_get($item, $value); + + // If the key is "null", we will just append the value to the array and keep + // looping. Otherwise we will key the array using the value of the key we + // received from the developer. Then we'll return the final array form. + if (is_null($key)) { + $results[] = $itemValue; + } else { + $itemKey = data_get($item, $key); + + if (is_object($itemKey) && method_exists($itemKey, '__toString')) { + $itemKey = (string) $itemKey; + } + + $results[$itemKey] = $itemValue; + } + } + + return $results; + } + + /** + * Explode the "value" and "key" arguments passed to "pluck". + * + * @param string|array $value + * @param string|array|null $key + * @return array + */ + protected static function explodePluckParameters($value, $key) + { + $value = is_string($value) ? explode('.', $value) : $value; + + $key = is_null($key) || is_array($key) ? $key : explode('.', $key); + + return [$value, $key]; + } + + /** + * Push an item onto the beginning of an array. + * + * @param array $array + * @param mixed $value + * @param mixed $key + * @return array + */ + public static function prepend($array, $value, $key = null) + { + if (func_num_args() == 2) { + array_unshift($array, $value); + } else { + $array = [$key => $value] + $array; + } + + return $array; + } + + /** + * Get a value from the array, and remove it. + * + * @param array $array + * @param string|int $key + * @param mixed $default + * @return mixed + */ + public static function pull(&$array, $key, $default = null) + { + $value = static::get($array, $key, $default); + + static::forget($array, $key); + + return $value; + } + + /** + * Convert the array into a query string. + * + * @param array $array + * @return string + */ + public static function query($array) + { + return http_build_query($array, '', '&', PHP_QUERY_RFC3986); + } + + /** + * Get one or a specified number of random values from an array. + * + * @param array $array + * @param int|null $number + * @param bool|false $preserveKeys + * @return mixed + * + * @throws \InvalidArgumentException + */ + public static function random($array, $number = null, $preserveKeys = false) + { + $requested = is_null($number) ? 1 : $number; + + $count = count($array); + + if ($requested > $count) { + throw new InvalidArgumentException( + "You requested {$requested} items, but there are only {$count} items available." + ); + } + + if (is_null($number)) { + return $array[array_rand($array)]; + } + + if ((int) $number === 0) { + return []; + } + + $keys = array_rand($array, $number); + + $results = []; + + if ($preserveKeys) { + foreach ((array) $keys as $key) { + $results[$key] = $array[$key]; + } + } else { + foreach ((array) $keys as $key) { + $results[] = $array[$key]; + } + } + + return $results; + } + + /** + * Set an array item to a given value using "dot" notation. + * + * If no key is given to the method, the entire array will be replaced. + * + * @param array $array + * @param string|null $key + * @param mixed $value + * @return array + */ + public static function set(&$array, $key, $value) + { + if (is_null($key)) { + return $array = $value; + } + + $keys = explode('.', $key); + + foreach ($keys as $i => $key) { + if (count($keys) === 1) { + break; + } + + unset($keys[$i]); + + // If the key doesn't exist at this depth, we will just create an empty array + // to hold the next value, allowing us to create the arrays to hold final + // values at the correct depth. Then we'll keep digging into the array. + if (! isset($array[$key]) || ! is_array($array[$key])) { + $array[$key] = []; + } + + $array = &$array[$key]; + } + + $array[array_shift($keys)] = $value; + + return $array; + } + + /** + * Shuffle the given array and return the result. + * + * @param array $array + * @param int|null $seed + * @return array + */ + public static function shuffle($array, $seed = null) + { + if (is_null($seed)) { + shuffle($array); + } else { + mt_srand($seed); + shuffle($array); + mt_srand(); + } + + return $array; + } + + /** + * Sort the array using the given callback or "dot" notation. + * + * @param array $array + * @param callable|array|string|null $callback + * @return array + */ + public static function sort($array, $callback = null) + { + return Collection::make($array)->sortBy($callback)->all(); + } + + /** + * Recursively sort an array by keys and values. + * + * @param array $array + * @param int $options + * @param bool $descending + * @return array + */ + public static function sortRecursive($array, $options = SORT_REGULAR, $descending = false) + { + foreach ($array as &$value) { + if (is_array($value)) { + $value = static::sortRecursive($value, $options, $descending); + } + } + + if (static::isAssoc($array)) { + $descending + ? krsort($array, $options) + : ksort($array, $options); + } else { + $descending + ? rsort($array, $options) + : sort($array, $options); + } + + return $array; + } + + /** + * Conditionally compile classes from an array into a CSS class list. + * + * @param array $array + * @return string + */ + public static function toCssClasses($array) + { + $classList = static::wrap($array); + + $classes = []; + + foreach ($classList as $class => $constraint) { + if (is_numeric($class)) { + $classes[] = $constraint; + } elseif ($constraint) { + $classes[] = $class; + } + } + + return implode(' ', $classes); + } + + /** + * Filter the array using the given callback. + * + * @param array $array + * @param callable $callback + * @return array + */ + public static function where($array, callable $callback) + { + return array_filter($array, $callback, ARRAY_FILTER_USE_BOTH); + } + + /** + * Filter items where the value is not null. + * + * @param array $array + * @return array + */ + public static function whereNotNull($array) + { + return static::where($array, function ($value) { + return ! is_null($value); + }); + } + + /** + * If the given value is not an array and not null, wrap it in one. + * + * @param mixed $value + * @return array + */ + public static function wrap($value) + { + if (is_null($value)) { + return []; + } + + return is_array($value) ? $value : [$value]; + } +} diff --git a/vendor/illuminate/collections/Collection.php b/vendor/illuminate/collections/Collection.php new file mode 100644 index 0000000..61a4884 --- /dev/null +++ b/vendor/illuminate/collections/Collection.php @@ -0,0 +1,1672 @@ +items = $this->getArrayableItems($items); + } + + /** + * Create a collection with the given range. + * + * @param int $from + * @param int $to + * @return static + */ + public static function range($from, $to) + { + return new static(range($from, $to)); + } + + /** + * Get all of the items in the collection. + * + * @return array + */ + public function all() + { + return $this->items; + } + + /** + * Get a lazy collection for the items in this collection. + * + * @return \Illuminate\Support\LazyCollection + */ + public function lazy() + { + return new LazyCollection($this->items); + } + + /** + * Get the average value of a given key. + * + * @param callable|string|null $callback + * @return mixed + */ + public function avg($callback = null) + { + $callback = $this->valueRetriever($callback); + + $items = $this->map(function ($value) use ($callback) { + return $callback($value); + })->filter(function ($value) { + return ! is_null($value); + }); + + if ($count = $items->count()) { + return $items->sum() / $count; + } + } + + /** + * Get the median of a given key. + * + * @param string|array|null $key + * @return mixed + */ + public function median($key = null) + { + $values = (isset($key) ? $this->pluck($key) : $this) + ->filter(function ($item) { + return ! is_null($item); + })->sort()->values(); + + $count = $values->count(); + + if ($count === 0) { + return; + } + + $middle = (int) ($count / 2); + + if ($count % 2) { + return $values->get($middle); + } + + return (new static([ + $values->get($middle - 1), $values->get($middle), + ]))->average(); + } + + /** + * Get the mode of a given key. + * + * @param string|array|null $key + * @return array|null + */ + public function mode($key = null) + { + if ($this->count() === 0) { + return; + } + + $collection = isset($key) ? $this->pluck($key) : $this; + + $counts = new static; + + $collection->each(function ($value) use ($counts) { + $counts[$value] = isset($counts[$value]) ? $counts[$value] + 1 : 1; + }); + + $sorted = $counts->sort(); + + $highestValue = $sorted->last(); + + return $sorted->filter(function ($value) use ($highestValue) { + return $value == $highestValue; + })->sort()->keys()->all(); + } + + /** + * Collapse the collection of items into a single array. + * + * @return static + */ + public function collapse() + { + return new static(Arr::collapse($this->items)); + } + + /** + * Determine if an item exists in the collection. + * + * @param mixed $key + * @param mixed $operator + * @param mixed $value + * @return bool + */ + public function contains($key, $operator = null, $value = null) + { + if (func_num_args() === 1) { + if ($this->useAsCallable($key)) { + $placeholder = new stdClass; + + return $this->first($key, $placeholder) !== $placeholder; + } + + return in_array($key, $this->items); + } + + return $this->contains($this->operatorForWhere(...func_get_args())); + } + + /** + * Determine if an item is not contained in the collection. + * + * @param mixed $key + * @param mixed $operator + * @param mixed $value + * @return bool + */ + public function doesntContain($key, $operator = null, $value = null) + { + return ! $this->contains(...func_get_args()); + } + + /** + * Cross join with the given lists, returning all possible permutations. + * + * @param mixed ...$lists + * @return static + */ + public function crossJoin(...$lists) + { + return new static(Arr::crossJoin( + $this->items, ...array_map([$this, 'getArrayableItems'], $lists) + )); + } + + /** + * Get the items in the collection that are not present in the given items. + * + * @param mixed $items + * @return static + */ + public function diff($items) + { + return new static(array_diff($this->items, $this->getArrayableItems($items))); + } + + /** + * Get the items in the collection that are not present in the given items, using the callback. + * + * @param mixed $items + * @param callable $callback + * @return static + */ + public function diffUsing($items, callable $callback) + { + return new static(array_udiff($this->items, $this->getArrayableItems($items), $callback)); + } + + /** + * Get the items in the collection whose keys and values are not present in the given items. + * + * @param mixed $items + * @return static + */ + public function diffAssoc($items) + { + return new static(array_diff_assoc($this->items, $this->getArrayableItems($items))); + } + + /** + * Get the items in the collection whose keys and values are not present in the given items, using the callback. + * + * @param mixed $items + * @param callable $callback + * @return static + */ + public function diffAssocUsing($items, callable $callback) + { + return new static(array_diff_uassoc($this->items, $this->getArrayableItems($items), $callback)); + } + + /** + * Get the items in the collection whose keys are not present in the given items. + * + * @param mixed $items + * @return static + */ + public function diffKeys($items) + { + return new static(array_diff_key($this->items, $this->getArrayableItems($items))); + } + + /** + * Get the items in the collection whose keys are not present in the given items, using the callback. + * + * @param mixed $items + * @param callable $callback + * @return static + */ + public function diffKeysUsing($items, callable $callback) + { + return new static(array_diff_ukey($this->items, $this->getArrayableItems($items), $callback)); + } + + /** + * Retrieve duplicate items from the collection. + * + * @param callable|string|null $callback + * @param bool $strict + * @return static + */ + public function duplicates($callback = null, $strict = false) + { + $items = $this->map($this->valueRetriever($callback)); + + $uniqueItems = $items->unique(null, $strict); + + $compare = $this->duplicateComparator($strict); + + $duplicates = new static; + + foreach ($items as $key => $value) { + if ($uniqueItems->isNotEmpty() && $compare($value, $uniqueItems->first())) { + $uniqueItems->shift(); + } else { + $duplicates[$key] = $value; + } + } + + return $duplicates; + } + + /** + * Retrieve duplicate items from the collection using strict comparison. + * + * @param callable|string|null $callback + * @return static + */ + public function duplicatesStrict($callback = null) + { + return $this->duplicates($callback, true); + } + + /** + * Get the comparison function to detect duplicates. + * + * @param bool $strict + * @return \Closure + */ + protected function duplicateComparator($strict) + { + if ($strict) { + return function ($a, $b) { + return $a === $b; + }; + } + + return function ($a, $b) { + return $a == $b; + }; + } + + /** + * Get all items except for those with the specified keys. + * + * @param \Illuminate\Support\Collection|mixed $keys + * @return static + */ + public function except($keys) + { + if ($keys instanceof Enumerable) { + $keys = $keys->all(); + } elseif (! is_array($keys)) { + $keys = func_get_args(); + } + + return new static(Arr::except($this->items, $keys)); + } + + /** + * Run a filter over each of the items. + * + * @param callable|null $callback + * @return static + */ + public function filter(callable $callback = null) + { + if ($callback) { + return new static(Arr::where($this->items, $callback)); + } + + return new static(array_filter($this->items)); + } + + /** + * Get the first item from the collection passing the given truth test. + * + * @param callable|null $callback + * @param mixed $default + * @return mixed + */ + public function first(callable $callback = null, $default = null) + { + return Arr::first($this->items, $callback, $default); + } + + /** + * Get a flattened array of the items in the collection. + * + * @param int $depth + * @return static + */ + public function flatten($depth = INF) + { + return new static(Arr::flatten($this->items, $depth)); + } + + /** + * Flip the items in the collection. + * + * @return static + */ + public function flip() + { + return new static(array_flip($this->items)); + } + + /** + * Remove an item from the collection by key. + * + * @param string|int|array $keys + * @return $this + */ + public function forget($keys) + { + foreach ((array) $keys as $key) { + $this->offsetUnset($key); + } + + return $this; + } + + /** + * Get an item from the collection by key. + * + * @param mixed $key + * @param mixed $default + * @return mixed + */ + public function get($key, $default = null) + { + if (array_key_exists($key, $this->items)) { + return $this->items[$key]; + } + + return value($default); + } + + /** + * Get an item from the collection by key or add it to collection if it does not exist. + * + * @param mixed $key + * @param mixed $value + * @return mixed + */ + public function getOrPut($key, $value) + { + if (array_key_exists($key, $this->items)) { + return $this->items[$key]; + } + + $this->offsetSet($key, $value = value($value)); + + return $value; + } + + /** + * Group an associative array by a field or using a callback. + * + * @param array|callable|string $groupBy + * @param bool $preserveKeys + * @return static + */ + public function groupBy($groupBy, $preserveKeys = false) + { + if (! $this->useAsCallable($groupBy) && is_array($groupBy)) { + $nextGroups = $groupBy; + + $groupBy = array_shift($nextGroups); + } + + $groupBy = $this->valueRetriever($groupBy); + + $results = []; + + foreach ($this->items as $key => $value) { + $groupKeys = $groupBy($value, $key); + + if (! is_array($groupKeys)) { + $groupKeys = [$groupKeys]; + } + + foreach ($groupKeys as $groupKey) { + $groupKey = is_bool($groupKey) ? (int) $groupKey : $groupKey; + + if (! array_key_exists($groupKey, $results)) { + $results[$groupKey] = new static; + } + + $results[$groupKey]->offsetSet($preserveKeys ? $key : null, $value); + } + } + + $result = new static($results); + + if (! empty($nextGroups)) { + return $result->map->groupBy($nextGroups, $preserveKeys); + } + + return $result; + } + + /** + * Key an associative array by a field or using a callback. + * + * @param callable|string $keyBy + * @return static + */ + public function keyBy($keyBy) + { + $keyBy = $this->valueRetriever($keyBy); + + $results = []; + + foreach ($this->items as $key => $item) { + $resolvedKey = $keyBy($item, $key); + + if (is_object($resolvedKey)) { + $resolvedKey = (string) $resolvedKey; + } + + $results[$resolvedKey] = $item; + } + + return new static($results); + } + + /** + * Determine if an item exists in the collection by key. + * + * @param mixed $key + * @return bool + */ + public function has($key) + { + $keys = is_array($key) ? $key : func_get_args(); + + foreach ($keys as $value) { + if (! array_key_exists($value, $this->items)) { + return false; + } + } + + return true; + } + + /** + * Determine if any of the keys exist in the collection. + * + * @param mixed $key + * @return bool + */ + public function hasAny($key) + { + if ($this->isEmpty()) { + return false; + } + + $keys = is_array($key) ? $key : func_get_args(); + + foreach ($keys as $value) { + if ($this->has($value)) { + return true; + } + } + + return false; + } + + /** + * Concatenate values of a given key as a string. + * + * @param string $value + * @param string|null $glue + * @return string + */ + public function implode($value, $glue = null) + { + $first = $this->first(); + + if (is_array($first) || (is_object($first) && ! $first instanceof Stringable)) { + return implode($glue ?? '', $this->pluck($value)->all()); + } + + return implode($value ?? '', $this->items); + } + + /** + * Intersect the collection with the given items. + * + * @param mixed $items + * @return static + */ + public function intersect($items) + { + return new static(array_intersect($this->items, $this->getArrayableItems($items))); + } + + /** + * Intersect the collection with the given items by key. + * + * @param mixed $items + * @return static + */ + public function intersectByKeys($items) + { + return new static(array_intersect_key( + $this->items, $this->getArrayableItems($items) + )); + } + + /** + * Determine if the collection is empty or not. + * + * @return bool + */ + public function isEmpty() + { + return empty($this->items); + } + + /** + * Determine if the collection contains a single item. + * + * @return bool + */ + public function containsOneItem() + { + return $this->count() === 1; + } + + /** + * Join all items from the collection using a string. The final items can use a separate glue string. + * + * @param string $glue + * @param string $finalGlue + * @return string + */ + public function join($glue, $finalGlue = '') + { + if ($finalGlue === '') { + return $this->implode($glue); + } + + $count = $this->count(); + + if ($count === 0) { + return ''; + } + + if ($count === 1) { + return $this->last(); + } + + $collection = new static($this->items); + + $finalItem = $collection->pop(); + + return $collection->implode($glue).$finalGlue.$finalItem; + } + + /** + * Get the keys of the collection items. + * + * @return static + */ + public function keys() + { + return new static(array_keys($this->items)); + } + + /** + * Get the last item from the collection. + * + * @param callable|null $callback + * @param mixed $default + * @return mixed + */ + public function last(callable $callback = null, $default = null) + { + return Arr::last($this->items, $callback, $default); + } + + /** + * Get the values of a given key. + * + * @param string|array|int|null $value + * @param string|null $key + * @return static + */ + public function pluck($value, $key = null) + { + return new static(Arr::pluck($this->items, $value, $key)); + } + + /** + * Run a map over each of the items. + * + * @param callable $callback + * @return static + */ + public function map(callable $callback) + { + $keys = array_keys($this->items); + + $items = array_map($callback, $this->items, $keys); + + return new static(array_combine($keys, $items)); + } + + /** + * Run a dictionary map over the items. + * + * The callback should return an associative array with a single key/value pair. + * + * @param callable $callback + * @return static + */ + public function mapToDictionary(callable $callback) + { + $dictionary = []; + + foreach ($this->items as $key => $item) { + $pair = $callback($item, $key); + + $key = key($pair); + + $value = reset($pair); + + if (! isset($dictionary[$key])) { + $dictionary[$key] = []; + } + + $dictionary[$key][] = $value; + } + + return new static($dictionary); + } + + /** + * Run an associative map over each of the items. + * + * The callback should return an associative array with a single key/value pair. + * + * @param callable $callback + * @return static + */ + public function mapWithKeys(callable $callback) + { + $result = []; + + foreach ($this->items as $key => $value) { + $assoc = $callback($value, $key); + + foreach ($assoc as $mapKey => $mapValue) { + $result[$mapKey] = $mapValue; + } + } + + return new static($result); + } + + /** + * Merge the collection with the given items. + * + * @param mixed $items + * @return static + */ + public function merge($items) + { + return new static(array_merge($this->items, $this->getArrayableItems($items))); + } + + /** + * Recursively merge the collection with the given items. + * + * @param mixed $items + * @return static + */ + public function mergeRecursive($items) + { + return new static(array_merge_recursive($this->items, $this->getArrayableItems($items))); + } + + /** + * Create a collection by using this collection for keys and another for its values. + * + * @param mixed $values + * @return static + */ + public function combine($values) + { + return new static(array_combine($this->all(), $this->getArrayableItems($values))); + } + + /** + * Union the collection with the given items. + * + * @param mixed $items + * @return static + */ + public function union($items) + { + return new static($this->items + $this->getArrayableItems($items)); + } + + /** + * Create a new collection consisting of every n-th element. + * + * @param int $step + * @param int $offset + * @return static + */ + public function nth($step, $offset = 0) + { + $new = []; + + $position = 0; + + foreach ($this->slice($offset)->items as $item) { + if ($position % $step === 0) { + $new[] = $item; + } + + $position++; + } + + return new static($new); + } + + /** + * Get the items with the specified keys. + * + * @param mixed $keys + * @return static + */ + public function only($keys) + { + if (is_null($keys)) { + return new static($this->items); + } + + if ($keys instanceof Enumerable) { + $keys = $keys->all(); + } + + $keys = is_array($keys) ? $keys : func_get_args(); + + return new static(Arr::only($this->items, $keys)); + } + + /** + * Get and remove the last N items from the collection. + * + * @param int $count + * @return mixed + */ + public function pop($count = 1) + { + if ($count === 1) { + return array_pop($this->items); + } + + if ($this->isEmpty()) { + return new static; + } + + $results = []; + + $collectionCount = $this->count(); + + foreach (range(1, min($count, $collectionCount)) as $item) { + array_push($results, array_pop($this->items)); + } + + return new static($results); + } + + /** + * Push an item onto the beginning of the collection. + * + * @param mixed $value + * @param mixed $key + * @return $this + */ + public function prepend($value, $key = null) + { + $this->items = Arr::prepend($this->items, ...func_get_args()); + + return $this; + } + + /** + * Push one or more items onto the end of the collection. + * + * @param mixed $values + * @return $this + */ + public function push(...$values) + { + foreach ($values as $value) { + $this->items[] = $value; + } + + return $this; + } + + /** + * Push all of the given items onto the collection. + * + * @param iterable $source + * @return static + */ + public function concat($source) + { + $result = new static($this); + + foreach ($source as $item) { + $result->push($item); + } + + return $result; + } + + /** + * Get and remove an item from the collection. + * + * @param mixed $key + * @param mixed $default + * @return mixed + */ + public function pull($key, $default = null) + { + return Arr::pull($this->items, $key, $default); + } + + /** + * Put an item in the collection by key. + * + * @param mixed $key + * @param mixed $value + * @return $this + */ + public function put($key, $value) + { + $this->offsetSet($key, $value); + + return $this; + } + + /** + * Get one or a specified number of items randomly from the collection. + * + * @param int|null $number + * @return static|mixed + * + * @throws \InvalidArgumentException + */ + public function random($number = null) + { + if (is_null($number)) { + return Arr::random($this->items); + } + + return new static(Arr::random($this->items, $number)); + } + + /** + * Replace the collection items with the given items. + * + * @param mixed $items + * @return static + */ + public function replace($items) + { + return new static(array_replace($this->items, $this->getArrayableItems($items))); + } + + /** + * Recursively replace the collection items with the given items. + * + * @param mixed $items + * @return static + */ + public function replaceRecursive($items) + { + return new static(array_replace_recursive($this->items, $this->getArrayableItems($items))); + } + + /** + * Reverse items order. + * + * @return static + */ + public function reverse() + { + return new static(array_reverse($this->items, true)); + } + + /** + * Search the collection for a given value and return the corresponding key if successful. + * + * @param mixed $value + * @param bool $strict + * @return mixed + */ + public function search($value, $strict = false) + { + if (! $this->useAsCallable($value)) { + return array_search($value, $this->items, $strict); + } + + foreach ($this->items as $key => $item) { + if ($value($item, $key)) { + return $key; + } + } + + return false; + } + + /** + * Get and remove the first N items from the collection. + * + * @param int $count + * @return mixed + */ + public function shift($count = 1) + { + if ($count === 1) { + return array_shift($this->items); + } + + if ($this->isEmpty()) { + return new static; + } + + $results = []; + + $collectionCount = $this->count(); + + foreach (range(1, min($count, $collectionCount)) as $item) { + array_push($results, array_shift($this->items)); + } + + return new static($results); + } + + /** + * Shuffle the items in the collection. + * + * @param int|null $seed + * @return static + */ + public function shuffle($seed = null) + { + return new static(Arr::shuffle($this->items, $seed)); + } + + /** + * Create chunks representing a "sliding window" view of the items in the collection. + * + * @param int $size + * @param int $step + * @return static + */ + public function sliding($size = 2, $step = 1) + { + $chunks = floor(($this->count() - $size) / $step) + 1; + + return static::times($chunks, function ($number) use ($size, $step) { + return $this->slice(($number - 1) * $step, $size); + }); + } + + /** + * Skip the first {$count} items. + * + * @param int $count + * @return static + */ + public function skip($count) + { + return $this->slice($count); + } + + /** + * Skip items in the collection until the given condition is met. + * + * @param mixed $value + * @return static + */ + public function skipUntil($value) + { + return new static($this->lazy()->skipUntil($value)->all()); + } + + /** + * Skip items in the collection while the given condition is met. + * + * @param mixed $value + * @return static + */ + public function skipWhile($value) + { + return new static($this->lazy()->skipWhile($value)->all()); + } + + /** + * Slice the underlying collection array. + * + * @param int $offset + * @param int|null $length + * @return static + */ + public function slice($offset, $length = null) + { + return new static(array_slice($this->items, $offset, $length, true)); + } + + /** + * Split a collection into a certain number of groups. + * + * @param int $numberOfGroups + * @return static + */ + public function split($numberOfGroups) + { + if ($this->isEmpty()) { + return new static; + } + + $groups = new static; + + $groupSize = floor($this->count() / $numberOfGroups); + + $remain = $this->count() % $numberOfGroups; + + $start = 0; + + for ($i = 0; $i < $numberOfGroups; $i++) { + $size = $groupSize; + + if ($i < $remain) { + $size++; + } + + if ($size) { + $groups->push(new static(array_slice($this->items, $start, $size))); + + $start += $size; + } + } + + return $groups; + } + + /** + * Split a collection into a certain number of groups, and fill the first groups completely. + * + * @param int $numberOfGroups + * @return static + */ + public function splitIn($numberOfGroups) + { + return $this->chunk(ceil($this->count() / $numberOfGroups)); + } + + /** + * Get the first item in the collection, but only if exactly one item exists. Otherwise, throw an exception. + * + * @param mixed $key + * @param mixed $operator + * @param mixed $value + * @return mixed + * + * @throws \Illuminate\Support\ItemNotFoundException + * @throws \Illuminate\Support\MultipleItemsFoundException + */ + public function sole($key = null, $operator = null, $value = null) + { + $filter = func_num_args() > 1 + ? $this->operatorForWhere(...func_get_args()) + : $key; + + $items = $this->when($filter)->filter($filter); + + if ($items->isEmpty()) { + throw new ItemNotFoundException; + } + + if ($items->count() > 1) { + throw new MultipleItemsFoundException; + } + + return $items->first(); + } + + /** + * Get the first item in the collection but throw an exception if no matching items exist. + * + * @param mixed $key + * @param mixed $operator + * @param mixed $value + * @return mixed + * + * @throws \Illuminate\Support\ItemNotFoundException + */ + public function firstOrFail($key = null, $operator = null, $value = null) + { + $filter = func_num_args() > 1 + ? $this->operatorForWhere(...func_get_args()) + : $key; + + $placeholder = new stdClass(); + + $item = $this->first($filter, $placeholder); + + if ($item === $placeholder) { + throw new ItemNotFoundException; + } + + return $item; + } + + /** + * Chunk the collection into chunks of the given size. + * + * @param int $size + * @return static + */ + public function chunk($size) + { + if ($size <= 0) { + return new static; + } + + $chunks = []; + + foreach (array_chunk($this->items, $size, true) as $chunk) { + $chunks[] = new static($chunk); + } + + return new static($chunks); + } + + /** + * Chunk the collection into chunks with a callback. + * + * @param callable $callback + * @return static + */ + public function chunkWhile(callable $callback) + { + return new static( + $this->lazy()->chunkWhile($callback)->mapInto(static::class) + ); + } + + /** + * Sort through each item with a callback. + * + * @param callable|int|null $callback + * @return static + */ + public function sort($callback = null) + { + $items = $this->items; + + $callback && is_callable($callback) + ? uasort($items, $callback) + : asort($items, $callback ?? SORT_REGULAR); + + return new static($items); + } + + /** + * Sort items in descending order. + * + * @param int $options + * @return static + */ + public function sortDesc($options = SORT_REGULAR) + { + $items = $this->items; + + arsort($items, $options); + + return new static($items); + } + + /** + * Sort the collection using the given callback. + * + * @param callable|array|string $callback + * @param int $options + * @param bool $descending + * @return static + */ + public function sortBy($callback, $options = SORT_REGULAR, $descending = false) + { + if (is_array($callback) && ! is_callable($callback)) { + return $this->sortByMany($callback); + } + + $results = []; + + $callback = $this->valueRetriever($callback); + + // First we will loop through the items and get the comparator from a callback + // function which we were given. Then, we will sort the returned values and + // grab all the corresponding values for the sorted keys from this array. + foreach ($this->items as $key => $value) { + $results[$key] = $callback($value, $key); + } + + $descending ? arsort($results, $options) + : asort($results, $options); + + // Once we have sorted all of the keys in the array, we will loop through them + // and grab the corresponding model so we can set the underlying items list + // to the sorted version. Then we'll just return the collection instance. + foreach (array_keys($results) as $key) { + $results[$key] = $this->items[$key]; + } + + return new static($results); + } + + /** + * Sort the collection using multiple comparisons. + * + * @param array $comparisons + * @return static + */ + protected function sortByMany(array $comparisons = []) + { + $items = $this->items; + + usort($items, function ($a, $b) use ($comparisons) { + foreach ($comparisons as $comparison) { + $comparison = Arr::wrap($comparison); + + $prop = $comparison[0]; + + $ascending = Arr::get($comparison, 1, true) === true || + Arr::get($comparison, 1, true) === 'asc'; + + $result = 0; + + if (! is_string($prop) && is_callable($prop)) { + $result = $prop($a, $b); + } else { + $values = [data_get($a, $prop), data_get($b, $prop)]; + + if (! $ascending) { + $values = array_reverse($values); + } + + $result = $values[0] <=> $values[1]; + } + + if ($result === 0) { + continue; + } + + return $result; + } + }); + + return new static($items); + } + + /** + * Sort the collection in descending order using the given callback. + * + * @param callable|string $callback + * @param int $options + * @return static + */ + public function sortByDesc($callback, $options = SORT_REGULAR) + { + return $this->sortBy($callback, $options, true); + } + + /** + * Sort the collection keys. + * + * @param int $options + * @param bool $descending + * @return static + */ + public function sortKeys($options = SORT_REGULAR, $descending = false) + { + $items = $this->items; + + $descending ? krsort($items, $options) : ksort($items, $options); + + return new static($items); + } + + /** + * Sort the collection keys in descending order. + * + * @param int $options + * @return static + */ + public function sortKeysDesc($options = SORT_REGULAR) + { + return $this->sortKeys($options, true); + } + + /** + * Sort the collection keys using a callback. + * + * @param callable $callback + * @return static + */ + public function sortKeysUsing(callable $callback) + { + $items = $this->items; + + uksort($items, $callback); + + return new static($items); + } + + /** + * Splice a portion of the underlying collection array. + * + * @param int $offset + * @param int|null $length + * @param mixed $replacement + * @return static + */ + public function splice($offset, $length = null, $replacement = []) + { + if (func_num_args() === 1) { + return new static(array_splice($this->items, $offset)); + } + + return new static(array_splice($this->items, $offset, $length, $this->getArrayableItems($replacement))); + } + + /** + * Take the first or last {$limit} items. + * + * @param int $limit + * @return static + */ + public function take($limit) + { + if ($limit < 0) { + return $this->slice($limit, abs($limit)); + } + + return $this->slice(0, $limit); + } + + /** + * Take items in the collection until the given condition is met. + * + * @param mixed $value + * @return static + */ + public function takeUntil($value) + { + return new static($this->lazy()->takeUntil($value)->all()); + } + + /** + * Take items in the collection while the given condition is met. + * + * @param mixed $value + * @return static + */ + public function takeWhile($value) + { + return new static($this->lazy()->takeWhile($value)->all()); + } + + /** + * Transform each item in the collection using a callback. + * + * @param callable $callback + * @return $this + */ + public function transform(callable $callback) + { + $this->items = $this->map($callback)->all(); + + return $this; + } + + /** + * Convert a flatten "dot" notation array into an expanded array. + * + * @return static + */ + public function undot() + { + return new static(Arr::undot($this->all())); + } + + /** + * Return only unique items from the collection array. + * + * @param string|callable|null $key + * @param bool $strict + * @return static + */ + public function unique($key = null, $strict = false) + { + if (is_null($key) && $strict === false) { + return new static(array_unique($this->items, SORT_REGULAR)); + } + + $callback = $this->valueRetriever($key); + + $exists = []; + + return $this->reject(function ($item, $key) use ($callback, $strict, &$exists) { + if (in_array($id = $callback($item, $key), $exists, $strict)) { + return true; + } + + $exists[] = $id; + }); + } + + /** + * Reset the keys on the underlying array. + * + * @return static + */ + public function values() + { + return new static(array_values($this->items)); + } + + /** + * Zip the collection together with one or more arrays. + * + * e.g. new Collection([1, 2, 3])->zip([4, 5, 6]); + * => [[1, 4], [2, 5], [3, 6]] + * + * @param mixed ...$items + * @return static + */ + public function zip($items) + { + $arrayableItems = array_map(function ($items) { + return $this->getArrayableItems($items); + }, func_get_args()); + + $params = array_merge([function () { + return new static(func_get_args()); + }, $this->items], $arrayableItems); + + return new static(array_map(...$params)); + } + + /** + * Pad collection to the specified length with a value. + * + * @param int $size + * @param mixed $value + * @return static + */ + public function pad($size, $value) + { + return new static(array_pad($this->items, $size, $value)); + } + + /** + * Get an iterator for the items. + * + * @return \ArrayIterator + */ + #[\ReturnTypeWillChange] + public function getIterator() + { + return new ArrayIterator($this->items); + } + + /** + * Count the number of items in the collection. + * + * @return int + */ + #[\ReturnTypeWillChange] + public function count() + { + return count($this->items); + } + + /** + * Count the number of items in the collection by a field or using a callback. + * + * @param callable|string $countBy + * @return static + */ + public function countBy($countBy = null) + { + return new static($this->lazy()->countBy($countBy)->all()); + } + + /** + * Add an item to the collection. + * + * @param mixed $item + * @return $this + */ + public function add($item) + { + $this->items[] = $item; + + return $this; + } + + /** + * Get a base Support collection instance from this collection. + * + * @return \Illuminate\Support\Collection + */ + public function toBase() + { + return new self($this); + } + + /** + * Determine if an item exists at an offset. + * + * @param mixed $key + * @return bool + */ + #[\ReturnTypeWillChange] + public function offsetExists($key) + { + return isset($this->items[$key]); + } + + /** + * Get an item at a given offset. + * + * @param mixed $key + * @return mixed + */ + #[\ReturnTypeWillChange] + public function offsetGet($key) + { + return $this->items[$key]; + } + + /** + * Set the item at a given offset. + * + * @param mixed $key + * @param mixed $value + * @return void + */ + #[\ReturnTypeWillChange] + public function offsetSet($key, $value) + { + if (is_null($key)) { + $this->items[] = $value; + } else { + $this->items[$key] = $value; + } + } + + /** + * Unset the item at a given offset. + * + * @param mixed $key + * @return void + */ + #[\ReturnTypeWillChange] + public function offsetUnset($key) + { + unset($this->items[$key]); + } +} diff --git a/vendor/illuminate/collections/Enumerable.php b/vendor/illuminate/collections/Enumerable.php new file mode 100644 index 0000000..261a0c8 --- /dev/null +++ b/vendor/illuminate/collections/Enumerable.php @@ -0,0 +1,1027 @@ +zip([4, 5, 6]); + * => [[1, 4], [2, 5], [3, 6]] + * + * @param mixed ...$items + * @return static + */ + public function zip($items); + + /** + * Collect the values into a collection. + * + * @return \Illuminate\Support\Collection + */ + public function collect(); + + /** + * Convert the collection to its string representation. + * + * @return string + */ + public function __toString(); + + /** + * Add a method to the list of proxied methods. + * + * @param string $method + * @return void + */ + public static function proxy($method); + + /** + * Dynamically access collection proxies. + * + * @param string $key + * @return mixed + * + * @throws \Exception + */ + public function __get($key); +} diff --git a/vendor/illuminate/collections/HigherOrderCollectionProxy.php b/vendor/illuminate/collections/HigherOrderCollectionProxy.php new file mode 100644 index 0000000..106356c --- /dev/null +++ b/vendor/illuminate/collections/HigherOrderCollectionProxy.php @@ -0,0 +1,63 @@ +method = $method; + $this->collection = $collection; + } + + /** + * Proxy accessing an attribute onto the collection items. + * + * @param string $key + * @return mixed + */ + public function __get($key) + { + return $this->collection->{$this->method}(function ($value) use ($key) { + return is_array($value) ? $value[$key] : $value->{$key}; + }); + } + + /** + * Proxy a method call onto the collection items. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + return $this->collection->{$this->method}(function ($value) use ($method, $parameters) { + return $value->{$method}(...$parameters); + }); + } +} diff --git a/vendor/illuminate/collections/HigherOrderWhenProxy.php b/vendor/illuminate/collections/HigherOrderWhenProxy.php new file mode 100644 index 0000000..6653c03 --- /dev/null +++ b/vendor/illuminate/collections/HigherOrderWhenProxy.php @@ -0,0 +1,63 @@ +condition = $condition; + $this->collection = $collection; + } + + /** + * Proxy accessing an attribute onto the collection. + * + * @param string $key + * @return mixed + */ + public function __get($key) + { + return $this->condition + ? $this->collection->{$key} + : $this->collection; + } + + /** + * Proxy a method call onto the collection. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + return $this->condition + ? $this->collection->{$method}(...$parameters) + : $this->collection; + } +} diff --git a/vendor/illuminate/collections/ItemNotFoundException.php b/vendor/illuminate/collections/ItemNotFoundException.php new file mode 100644 index 0000000..05a51d9 --- /dev/null +++ b/vendor/illuminate/collections/ItemNotFoundException.php @@ -0,0 +1,9 @@ +source = $source; + } elseif (is_null($source)) { + $this->source = static::empty(); + } else { + $this->source = $this->getArrayableItems($source); + } + } + + /** + * Create a collection with the given range. + * + * @param int $from + * @param int $to + * @return static + */ + public static function range($from, $to) + { + return new static(function () use ($from, $to) { + if ($from <= $to) { + for (; $from <= $to; $from++) { + yield $from; + } + } else { + for (; $from >= $to; $from--) { + yield $from; + } + } + }); + } + + /** + * Get all items in the enumerable. + * + * @return array + */ + public function all() + { + if (is_array($this->source)) { + return $this->source; + } + + return iterator_to_array($this->getIterator()); + } + + /** + * Eager load all items into a new lazy collection backed by an array. + * + * @return static + */ + public function eager() + { + return new static($this->all()); + } + + /** + * Cache values as they're enumerated. + * + * @return static + */ + public function remember() + { + $iterator = $this->getIterator(); + + $iteratorIndex = 0; + + $cache = []; + + return new static(function () use ($iterator, &$iteratorIndex, &$cache) { + for ($index = 0; true; $index++) { + if (array_key_exists($index, $cache)) { + yield $cache[$index][0] => $cache[$index][1]; + + continue; + } + + if ($iteratorIndex < $index) { + $iterator->next(); + + $iteratorIndex++; + } + + if (! $iterator->valid()) { + break; + } + + $cache[$index] = [$iterator->key(), $iterator->current()]; + + yield $cache[$index][0] => $cache[$index][1]; + } + }); + } + + /** + * Get the average value of a given key. + * + * @param callable|string|null $callback + * @return mixed + */ + public function avg($callback = null) + { + return $this->collect()->avg($callback); + } + + /** + * Get the median of a given key. + * + * @param string|array|null $key + * @return mixed + */ + public function median($key = null) + { + return $this->collect()->median($key); + } + + /** + * Get the mode of a given key. + * + * @param string|array|null $key + * @return array|null + */ + public function mode($key = null) + { + return $this->collect()->mode($key); + } + + /** + * Collapse the collection of items into a single array. + * + * @return static + */ + public function collapse() + { + return new static(function () { + foreach ($this as $values) { + if (is_array($values) || $values instanceof Enumerable) { + foreach ($values as $value) { + yield $value; + } + } + } + }); + } + + /** + * Determine if an item exists in the enumerable. + * + * @param mixed $key + * @param mixed $operator + * @param mixed $value + * @return bool + */ + public function contains($key, $operator = null, $value = null) + { + if (func_num_args() === 1 && $this->useAsCallable($key)) { + $placeholder = new stdClass; + + return $this->first($key, $placeholder) !== $placeholder; + } + + if (func_num_args() === 1) { + $needle = $key; + + foreach ($this as $value) { + if ($value == $needle) { + return true; + } + } + + return false; + } + + return $this->contains($this->operatorForWhere(...func_get_args())); + } + + /** + * Determine if an item is not contained in the enumerable. + * + * @param mixed $key + * @param mixed $operator + * @param mixed $value + * @return bool + */ + public function doesntContain($key, $operator = null, $value = null) + { + return ! $this->contains(...func_get_args()); + } + + /** + * Cross join the given iterables, returning all possible permutations. + * + * @param array ...$arrays + * @return static + */ + public function crossJoin(...$arrays) + { + return $this->passthru('crossJoin', func_get_args()); + } + + /** + * Count the number of items in the collection by a field or using a callback. + * + * @param callable|string $countBy + * @return static + */ + public function countBy($countBy = null) + { + $countBy = is_null($countBy) + ? $this->identity() + : $this->valueRetriever($countBy); + + return new static(function () use ($countBy) { + $counts = []; + + foreach ($this as $key => $value) { + $group = $countBy($value, $key); + + if (empty($counts[$group])) { + $counts[$group] = 0; + } + + $counts[$group]++; + } + + yield from $counts; + }); + } + + /** + * Get the items that are not present in the given items. + * + * @param mixed $items + * @return static + */ + public function diff($items) + { + return $this->passthru('diff', func_get_args()); + } + + /** + * Get the items that are not present in the given items, using the callback. + * + * @param mixed $items + * @param callable $callback + * @return static + */ + public function diffUsing($items, callable $callback) + { + return $this->passthru('diffUsing', func_get_args()); + } + + /** + * Get the items whose keys and values are not present in the given items. + * + * @param mixed $items + * @return static + */ + public function diffAssoc($items) + { + return $this->passthru('diffAssoc', func_get_args()); + } + + /** + * Get the items whose keys and values are not present in the given items, using the callback. + * + * @param mixed $items + * @param callable $callback + * @return static + */ + public function diffAssocUsing($items, callable $callback) + { + return $this->passthru('diffAssocUsing', func_get_args()); + } + + /** + * Get the items whose keys are not present in the given items. + * + * @param mixed $items + * @return static + */ + public function diffKeys($items) + { + return $this->passthru('diffKeys', func_get_args()); + } + + /** + * Get the items whose keys are not present in the given items, using the callback. + * + * @param mixed $items + * @param callable $callback + * @return static + */ + public function diffKeysUsing($items, callable $callback) + { + return $this->passthru('diffKeysUsing', func_get_args()); + } + + /** + * Retrieve duplicate items. + * + * @param callable|string|null $callback + * @param bool $strict + * @return static + */ + public function duplicates($callback = null, $strict = false) + { + return $this->passthru('duplicates', func_get_args()); + } + + /** + * Retrieve duplicate items using strict comparison. + * + * @param callable|string|null $callback + * @return static + */ + public function duplicatesStrict($callback = null) + { + return $this->passthru('duplicatesStrict', func_get_args()); + } + + /** + * Get all items except for those with the specified keys. + * + * @param mixed $keys + * @return static + */ + public function except($keys) + { + return $this->passthru('except', func_get_args()); + } + + /** + * Run a filter over each of the items. + * + * @param callable|null $callback + * @return static + */ + public function filter(callable $callback = null) + { + if (is_null($callback)) { + $callback = function ($value) { + return (bool) $value; + }; + } + + return new static(function () use ($callback) { + foreach ($this as $key => $value) { + if ($callback($value, $key)) { + yield $key => $value; + } + } + }); + } + + /** + * Get the first item from the enumerable passing the given truth test. + * + * @param callable|null $callback + * @param mixed $default + * @return mixed + */ + public function first(callable $callback = null, $default = null) + { + $iterator = $this->getIterator(); + + if (is_null($callback)) { + if (! $iterator->valid()) { + return value($default); + } + + return $iterator->current(); + } + + foreach ($iterator as $key => $value) { + if ($callback($value, $key)) { + return $value; + } + } + + return value($default); + } + + /** + * Get a flattened list of the items in the collection. + * + * @param int $depth + * @return static + */ + public function flatten($depth = INF) + { + $instance = new static(function () use ($depth) { + foreach ($this as $item) { + if (! is_array($item) && ! $item instanceof Enumerable) { + yield $item; + } elseif ($depth === 1) { + yield from $item; + } else { + yield from (new static($item))->flatten($depth - 1); + } + } + }); + + return $instance->values(); + } + + /** + * Flip the items in the collection. + * + * @return static + */ + public function flip() + { + return new static(function () { + foreach ($this as $key => $value) { + yield $value => $key; + } + }); + } + + /** + * Get an item by key. + * + * @param mixed $key + * @param mixed $default + * @return mixed + */ + public function get($key, $default = null) + { + if (is_null($key)) { + return; + } + + foreach ($this as $outerKey => $outerValue) { + if ($outerKey == $key) { + return $outerValue; + } + } + + return value($default); + } + + /** + * Group an associative array by a field or using a callback. + * + * @param array|callable|string $groupBy + * @param bool $preserveKeys + * @return static + */ + public function groupBy($groupBy, $preserveKeys = false) + { + return $this->passthru('groupBy', func_get_args()); + } + + /** + * Key an associative array by a field or using a callback. + * + * @param callable|string $keyBy + * @return static + */ + public function keyBy($keyBy) + { + return new static(function () use ($keyBy) { + $keyBy = $this->valueRetriever($keyBy); + + foreach ($this as $key => $item) { + $resolvedKey = $keyBy($item, $key); + + if (is_object($resolvedKey)) { + $resolvedKey = (string) $resolvedKey; + } + + yield $resolvedKey => $item; + } + }); + } + + /** + * Determine if an item exists in the collection by key. + * + * @param mixed $key + * @return bool + */ + public function has($key) + { + $keys = array_flip(is_array($key) ? $key : func_get_args()); + $count = count($keys); + + foreach ($this as $key => $value) { + if (array_key_exists($key, $keys) && --$count == 0) { + return true; + } + } + + return false; + } + + /** + * Determine if any of the keys exist in the collection. + * + * @param mixed $key + * @return bool + */ + public function hasAny($key) + { + $keys = array_flip(is_array($key) ? $key : func_get_args()); + + foreach ($this as $key => $value) { + if (array_key_exists($key, $keys)) { + return true; + } + } + + return false; + } + + /** + * Concatenate values of a given key as a string. + * + * @param string $value + * @param string|null $glue + * @return string + */ + public function implode($value, $glue = null) + { + return $this->collect()->implode(...func_get_args()); + } + + /** + * Intersect the collection with the given items. + * + * @param mixed $items + * @return static + */ + public function intersect($items) + { + return $this->passthru('intersect', func_get_args()); + } + + /** + * Intersect the collection with the given items by key. + * + * @param mixed $items + * @return static + */ + public function intersectByKeys($items) + { + return $this->passthru('intersectByKeys', func_get_args()); + } + + /** + * Determine if the items are empty or not. + * + * @return bool + */ + public function isEmpty() + { + return ! $this->getIterator()->valid(); + } + + /** + * Determine if the collection contains a single item. + * + * @return bool + */ + public function containsOneItem() + { + return $this->take(2)->count() === 1; + } + + /** + * Join all items from the collection using a string. The final items can use a separate glue string. + * + * @param string $glue + * @param string $finalGlue + * @return string + */ + public function join($glue, $finalGlue = '') + { + return $this->collect()->join(...func_get_args()); + } + + /** + * Get the keys of the collection items. + * + * @return static + */ + public function keys() + { + return new static(function () { + foreach ($this as $key => $value) { + yield $key; + } + }); + } + + /** + * Get the last item from the collection. + * + * @param callable|null $callback + * @param mixed $default + * @return mixed + */ + public function last(callable $callback = null, $default = null) + { + $needle = $placeholder = new stdClass; + + foreach ($this as $key => $value) { + if (is_null($callback) || $callback($value, $key)) { + $needle = $value; + } + } + + return $needle === $placeholder ? value($default) : $needle; + } + + /** + * Get the values of a given key. + * + * @param string|array $value + * @param string|null $key + * @return static + */ + public function pluck($value, $key = null) + { + return new static(function () use ($value, $key) { + [$value, $key] = $this->explodePluckParameters($value, $key); + + foreach ($this as $item) { + $itemValue = data_get($item, $value); + + if (is_null($key)) { + yield $itemValue; + } else { + $itemKey = data_get($item, $key); + + if (is_object($itemKey) && method_exists($itemKey, '__toString')) { + $itemKey = (string) $itemKey; + } + + yield $itemKey => $itemValue; + } + } + }); + } + + /** + * Run a map over each of the items. + * + * @param callable $callback + * @return static + */ + public function map(callable $callback) + { + return new static(function () use ($callback) { + foreach ($this as $key => $value) { + yield $key => $callback($value, $key); + } + }); + } + + /** + * Run a dictionary map over the items. + * + * The callback should return an associative array with a single key/value pair. + * + * @param callable $callback + * @return static + */ + public function mapToDictionary(callable $callback) + { + return $this->passthru('mapToDictionary', func_get_args()); + } + + /** + * Run an associative map over each of the items. + * + * The callback should return an associative array with a single key/value pair. + * + * @param callable $callback + * @return static + */ + public function mapWithKeys(callable $callback) + { + return new static(function () use ($callback) { + foreach ($this as $key => $value) { + yield from $callback($value, $key); + } + }); + } + + /** + * Merge the collection with the given items. + * + * @param mixed $items + * @return static + */ + public function merge($items) + { + return $this->passthru('merge', func_get_args()); + } + + /** + * Recursively merge the collection with the given items. + * + * @param mixed $items + * @return static + */ + public function mergeRecursive($items) + { + return $this->passthru('mergeRecursive', func_get_args()); + } + + /** + * Create a collection by using this collection for keys and another for its values. + * + * @param mixed $values + * @return static + */ + public function combine($values) + { + return new static(function () use ($values) { + $values = $this->makeIterator($values); + + $errorMessage = 'Both parameters should have an equal number of elements'; + + foreach ($this as $key) { + if (! $values->valid()) { + trigger_error($errorMessage, E_USER_WARNING); + + break; + } + + yield $key => $values->current(); + + $values->next(); + } + + if ($values->valid()) { + trigger_error($errorMessage, E_USER_WARNING); + } + }); + } + + /** + * Union the collection with the given items. + * + * @param mixed $items + * @return static + */ + public function union($items) + { + return $this->passthru('union', func_get_args()); + } + + /** + * Create a new collection consisting of every n-th element. + * + * @param int $step + * @param int $offset + * @return static + */ + public function nth($step, $offset = 0) + { + return new static(function () use ($step, $offset) { + $position = 0; + + foreach ($this->slice($offset) as $item) { + if ($position % $step === 0) { + yield $item; + } + + $position++; + } + }); + } + + /** + * Get the items with the specified keys. + * + * @param mixed $keys + * @return static + */ + public function only($keys) + { + if ($keys instanceof Enumerable) { + $keys = $keys->all(); + } elseif (! is_null($keys)) { + $keys = is_array($keys) ? $keys : func_get_args(); + } + + return new static(function () use ($keys) { + if (is_null($keys)) { + yield from $this; + } else { + $keys = array_flip($keys); + + foreach ($this as $key => $value) { + if (array_key_exists($key, $keys)) { + yield $key => $value; + + unset($keys[$key]); + + if (empty($keys)) { + break; + } + } + } + } + }); + } + + /** + * Push all of the given items onto the collection. + * + * @param iterable $source + * @return static + */ + public function concat($source) + { + return (new static(function () use ($source) { + yield from $this; + yield from $source; + }))->values(); + } + + /** + * Get one or a specified number of items randomly from the collection. + * + * @param int|null $number + * @return static|mixed + * + * @throws \InvalidArgumentException + */ + public function random($number = null) + { + $result = $this->collect()->random(...func_get_args()); + + return is_null($number) ? $result : new static($result); + } + + /** + * Replace the collection items with the given items. + * + * @param mixed $items + * @return static + */ + public function replace($items) + { + return new static(function () use ($items) { + $items = $this->getArrayableItems($items); + + foreach ($this as $key => $value) { + if (array_key_exists($key, $items)) { + yield $key => $items[$key]; + + unset($items[$key]); + } else { + yield $key => $value; + } + } + + foreach ($items as $key => $value) { + yield $key => $value; + } + }); + } + + /** + * Recursively replace the collection items with the given items. + * + * @param mixed $items + * @return static + */ + public function replaceRecursive($items) + { + return $this->passthru('replaceRecursive', func_get_args()); + } + + /** + * Reverse items order. + * + * @return static + */ + public function reverse() + { + return $this->passthru('reverse', func_get_args()); + } + + /** + * Search the collection for a given value and return the corresponding key if successful. + * + * @param mixed $value + * @param bool $strict + * @return mixed + */ + public function search($value, $strict = false) + { + $predicate = $this->useAsCallable($value) + ? $value + : function ($item) use ($value, $strict) { + return $strict ? $item === $value : $item == $value; + }; + + foreach ($this as $key => $item) { + if ($predicate($item, $key)) { + return $key; + } + } + + return false; + } + + /** + * Shuffle the items in the collection. + * + * @param int|null $seed + * @return static + */ + public function shuffle($seed = null) + { + return $this->passthru('shuffle', func_get_args()); + } + + /** + * Create chunks representing a "sliding window" view of the items in the collection. + * + * @param int $size + * @param int $step + * @return static + */ + public function sliding($size = 2, $step = 1) + { + return new static(function () use ($size, $step) { + $iterator = $this->getIterator(); + + $chunk = []; + + while ($iterator->valid()) { + $chunk[$iterator->key()] = $iterator->current(); + + if (count($chunk) == $size) { + yield tap(new static($chunk), function () use (&$chunk, $step) { + $chunk = array_slice($chunk, $step, null, true); + }); + + // If the $step between chunks is bigger than each chunk's $size + // we will skip the extra items (which should never be in any + // chunk) before we continue to the next chunk in the loop. + if ($step > $size) { + $skip = $step - $size; + + for ($i = 0; $i < $skip && $iterator->valid(); $i++) { + $iterator->next(); + } + } + } + + $iterator->next(); + } + }); + } + + /** + * Skip the first {$count} items. + * + * @param int $count + * @return static + */ + public function skip($count) + { + return new static(function () use ($count) { + $iterator = $this->getIterator(); + + while ($iterator->valid() && $count--) { + $iterator->next(); + } + + while ($iterator->valid()) { + yield $iterator->key() => $iterator->current(); + + $iterator->next(); + } + }); + } + + /** + * Skip items in the collection until the given condition is met. + * + * @param mixed $value + * @return static + */ + public function skipUntil($value) + { + $callback = $this->useAsCallable($value) ? $value : $this->equality($value); + + return $this->skipWhile($this->negate($callback)); + } + + /** + * Skip items in the collection while the given condition is met. + * + * @param mixed $value + * @return static + */ + public function skipWhile($value) + { + $callback = $this->useAsCallable($value) ? $value : $this->equality($value); + + return new static(function () use ($callback) { + $iterator = $this->getIterator(); + + while ($iterator->valid() && $callback($iterator->current(), $iterator->key())) { + $iterator->next(); + } + + while ($iterator->valid()) { + yield $iterator->key() => $iterator->current(); + + $iterator->next(); + } + }); + } + + /** + * Get a slice of items from the enumerable. + * + * @param int $offset + * @param int|null $length + * @return static + */ + public function slice($offset, $length = null) + { + if ($offset < 0 || $length < 0) { + return $this->passthru('slice', func_get_args()); + } + + $instance = $this->skip($offset); + + return is_null($length) ? $instance : $instance->take($length); + } + + /** + * Split a collection into a certain number of groups. + * + * @param int $numberOfGroups + * @return static + */ + public function split($numberOfGroups) + { + return $this->passthru('split', func_get_args()); + } + + /** + * Get the first item in the collection, but only if exactly one item exists. Otherwise, throw an exception. + * + * @param mixed $key + * @param mixed $operator + * @param mixed $value + * @return mixed + * + * @throws \Illuminate\Support\ItemNotFoundException + * @throws \Illuminate\Support\MultipleItemsFoundException + */ + public function sole($key = null, $operator = null, $value = null) + { + $filter = func_num_args() > 1 + ? $this->operatorForWhere(...func_get_args()) + : $key; + + return $this + ->when($filter) + ->filter($filter) + ->take(2) + ->collect() + ->sole(); + } + + /** + * Get the first item in the collection but throw an exception if no matching items exist. + * + * @param mixed $key + * @param mixed $operator + * @param mixed $value + * @return mixed + * + * @throws \Illuminate\Support\ItemNotFoundException + */ + public function firstOrFail($key = null, $operator = null, $value = null) + { + $filter = func_num_args() > 1 + ? $this->operatorForWhere(...func_get_args()) + : $key; + + return $this + ->when($filter) + ->filter($filter) + ->take(1) + ->collect() + ->firstOrFail(); + } + + /** + * Chunk the collection into chunks of the given size. + * + * @param int $size + * @return static + */ + public function chunk($size) + { + if ($size <= 0) { + return static::empty(); + } + + return new static(function () use ($size) { + $iterator = $this->getIterator(); + + while ($iterator->valid()) { + $chunk = []; + + while (true) { + $chunk[$iterator->key()] = $iterator->current(); + + if (count($chunk) < $size) { + $iterator->next(); + + if (! $iterator->valid()) { + break; + } + } else { + break; + } + } + + yield new static($chunk); + + $iterator->next(); + } + }); + } + + /** + * Split a collection into a certain number of groups, and fill the first groups completely. + * + * @param int $numberOfGroups + * @return static + */ + public function splitIn($numberOfGroups) + { + return $this->chunk(ceil($this->count() / $numberOfGroups)); + } + + /** + * Chunk the collection into chunks with a callback. + * + * @param callable $callback + * @return static + */ + public function chunkWhile(callable $callback) + { + return new static(function () use ($callback) { + $iterator = $this->getIterator(); + + $chunk = new Collection; + + if ($iterator->valid()) { + $chunk[$iterator->key()] = $iterator->current(); + + $iterator->next(); + } + + while ($iterator->valid()) { + if (! $callback($iterator->current(), $iterator->key(), $chunk)) { + yield new static($chunk); + + $chunk = new Collection; + } + + $chunk[$iterator->key()] = $iterator->current(); + + $iterator->next(); + } + + if ($chunk->isNotEmpty()) { + yield new static($chunk); + } + }); + } + + /** + * Sort through each item with a callback. + * + * @param callable|null|int $callback + * @return static + */ + public function sort($callback = null) + { + return $this->passthru('sort', func_get_args()); + } + + /** + * Sort items in descending order. + * + * @param int $options + * @return static + */ + public function sortDesc($options = SORT_REGULAR) + { + return $this->passthru('sortDesc', func_get_args()); + } + + /** + * Sort the collection using the given callback. + * + * @param callable|string $callback + * @param int $options + * @param bool $descending + * @return static + */ + public function sortBy($callback, $options = SORT_REGULAR, $descending = false) + { + return $this->passthru('sortBy', func_get_args()); + } + + /** + * Sort the collection in descending order using the given callback. + * + * @param callable|string $callback + * @param int $options + * @return static + */ + public function sortByDesc($callback, $options = SORT_REGULAR) + { + return $this->passthru('sortByDesc', func_get_args()); + } + + /** + * Sort the collection keys. + * + * @param int $options + * @param bool $descending + * @return static + */ + public function sortKeys($options = SORT_REGULAR, $descending = false) + { + return $this->passthru('sortKeys', func_get_args()); + } + + /** + * Sort the collection keys in descending order. + * + * @param int $options + * @return static + */ + public function sortKeysDesc($options = SORT_REGULAR) + { + return $this->passthru('sortKeysDesc', func_get_args()); + } + + /** + * Sort the collection keys using a callback. + * + * @param callable $callback + * @return static + */ + public function sortKeysUsing(callable $callback) + { + return $this->passthru('sortKeysUsing', func_get_args()); + } + + /** + * Take the first or last {$limit} items. + * + * @param int $limit + * @return static + */ + public function take($limit) + { + if ($limit < 0) { + return $this->passthru('take', func_get_args()); + } + + return new static(function () use ($limit) { + $iterator = $this->getIterator(); + + while ($limit--) { + if (! $iterator->valid()) { + break; + } + + yield $iterator->key() => $iterator->current(); + + if ($limit) { + $iterator->next(); + } + } + }); + } + + /** + * Take items in the collection until the given condition is met. + * + * @param mixed $value + * @return static + */ + public function takeUntil($value) + { + $callback = $this->useAsCallable($value) ? $value : $this->equality($value); + + return new static(function () use ($callback) { + foreach ($this as $key => $item) { + if ($callback($item, $key)) { + break; + } + + yield $key => $item; + } + }); + } + + /** + * Take items in the collection until a given point in time. + * + * @param \DateTimeInterface $timeout + * @return static + */ + public function takeUntilTimeout(DateTimeInterface $timeout) + { + $timeout = $timeout->getTimestamp(); + + return $this->takeWhile(function () use ($timeout) { + return $this->now() < $timeout; + }); + } + + /** + * Take items in the collection while the given condition is met. + * + * @param mixed $value + * @return static + */ + public function takeWhile($value) + { + $callback = $this->useAsCallable($value) ? $value : $this->equality($value); + + return $this->takeUntil(function ($item, $key) use ($callback) { + return ! $callback($item, $key); + }); + } + + /** + * Pass each item in the collection to the given callback, lazily. + * + * @param callable $callback + * @return static + */ + public function tapEach(callable $callback) + { + return new static(function () use ($callback) { + foreach ($this as $key => $value) { + $callback($value, $key); + + yield $key => $value; + } + }); + } + + /** + * Convert a flatten "dot" notation array into an expanded array. + * + * @return static + */ + public function undot() + { + return $this->passthru('undot', []); + } + + /** + * Return only unique items from the collection array. + * + * @param string|callable|null $key + * @param bool $strict + * @return static + */ + public function unique($key = null, $strict = false) + { + $callback = $this->valueRetriever($key); + + return new static(function () use ($callback, $strict) { + $exists = []; + + foreach ($this as $key => $item) { + if (! in_array($id = $callback($item, $key), $exists, $strict)) { + yield $key => $item; + + $exists[] = $id; + } + } + }); + } + + /** + * Reset the keys on the underlying array. + * + * @return static + */ + public function values() + { + return new static(function () { + foreach ($this as $item) { + yield $item; + } + }); + } + + /** + * Zip the collection together with one or more arrays. + * + * e.g. new LazyCollection([1, 2, 3])->zip([4, 5, 6]); + * => [[1, 4], [2, 5], [3, 6]] + * + * @param mixed ...$items + * @return static + */ + public function zip($items) + { + $iterables = func_get_args(); + + return new static(function () use ($iterables) { + $iterators = Collection::make($iterables)->map(function ($iterable) { + return $this->makeIterator($iterable); + })->prepend($this->getIterator()); + + while ($iterators->contains->valid()) { + yield new static($iterators->map->current()); + + $iterators->each->next(); + } + }); + } + + /** + * Pad collection to the specified length with a value. + * + * @param int $size + * @param mixed $value + * @return static + */ + public function pad($size, $value) + { + if ($size < 0) { + return $this->passthru('pad', func_get_args()); + } + + return new static(function () use ($size, $value) { + $yielded = 0; + + foreach ($this as $index => $item) { + yield $index => $item; + + $yielded++; + } + + while ($yielded++ < $size) { + yield $value; + } + }); + } + + /** + * Get the values iterator. + * + * @return \Traversable + */ + #[\ReturnTypeWillChange] + public function getIterator() + { + return $this->makeIterator($this->source); + } + + /** + * Count the number of items in the collection. + * + * @return int + */ + #[\ReturnTypeWillChange] + public function count() + { + if (is_array($this->source)) { + return count($this->source); + } + + return iterator_count($this->getIterator()); + } + + /** + * Make an iterator from the given source. + * + * @param mixed $source + * @return \Traversable + */ + protected function makeIterator($source) + { + if ($source instanceof IteratorAggregate) { + return $source->getIterator(); + } + + if (is_array($source)) { + return new ArrayIterator($source); + } + + return $source(); + } + + /** + * Explode the "value" and "key" arguments passed to "pluck". + * + * @param string|array $value + * @param string|array|null $key + * @return array + */ + protected function explodePluckParameters($value, $key) + { + $value = is_string($value) ? explode('.', $value) : $value; + + $key = is_null($key) || is_array($key) ? $key : explode('.', $key); + + return [$value, $key]; + } + + /** + * Pass this lazy collection through a method on the collection class. + * + * @param string $method + * @param array $params + * @return static + */ + protected function passthru($method, array $params) + { + return new static(function () use ($method, $params) { + yield from $this->collect()->$method(...$params); + }); + } + + /** + * Get the current time. + * + * @return int + */ + protected function now() + { + return time(); + } +} diff --git a/vendor/illuminate/collections/MultipleItemsFoundException.php b/vendor/illuminate/collections/MultipleItemsFoundException.php new file mode 100644 index 0000000..944b2dc --- /dev/null +++ b/vendor/illuminate/collections/MultipleItemsFoundException.php @@ -0,0 +1,9 @@ +all() : $value; + } + + /** + * Create a new instance with no items. + * + * @return static + */ + public static function empty() + { + return new static([]); + } + + /** + * Create a new collection by invoking the callback a given amount of times. + * + * @param int $number + * @param callable|null $callback + * @return static + */ + public static function times($number, callable $callback = null) + { + if ($number < 1) { + return new static; + } + + return static::range(1, $number) + ->when($callback) + ->map($callback); + } + + /** + * Alias for the "avg" method. + * + * @param callable|string|null $callback + * @return mixed + */ + public function average($callback = null) + { + return $this->avg($callback); + } + + /** + * Alias for the "contains" method. + * + * @param mixed $key + * @param mixed $operator + * @param mixed $value + * @return bool + */ + public function some($key, $operator = null, $value = null) + { + return $this->contains(...func_get_args()); + } + + /** + * Determine if an item exists, using strict comparison. + * + * @param mixed $key + * @param mixed $value + * @return bool + */ + public function containsStrict($key, $value = null) + { + if (func_num_args() === 2) { + return $this->contains(function ($item) use ($key, $value) { + return data_get($item, $key) === $value; + }); + } + + if ($this->useAsCallable($key)) { + return ! is_null($this->first($key)); + } + + foreach ($this as $item) { + if ($item === $key) { + return true; + } + } + + return false; + } + + /** + * Dump the items and end the script. + * + * @param mixed ...$args + * @return void + */ + public function dd(...$args) + { + $this->dump(...$args); + + exit(1); + } + + /** + * Dump the items. + * + * @return $this + */ + public function dump() + { + (new Collection(func_get_args())) + ->push($this->all()) + ->each(function ($item) { + VarDumper::dump($item); + }); + + return $this; + } + + /** + * Execute a callback over each item. + * + * @param callable $callback + * @return $this + */ + public function each(callable $callback) + { + foreach ($this as $key => $item) { + if ($callback($item, $key) === false) { + break; + } + } + + return $this; + } + + /** + * Execute a callback over each nested chunk of items. + * + * @param callable $callback + * @return static + */ + public function eachSpread(callable $callback) + { + return $this->each(function ($chunk, $key) use ($callback) { + $chunk[] = $key; + + return $callback(...$chunk); + }); + } + + /** + * Determine if all items pass the given truth test. + * + * @param string|callable $key + * @param mixed $operator + * @param mixed $value + * @return bool + */ + public function every($key, $operator = null, $value = null) + { + if (func_num_args() === 1) { + $callback = $this->valueRetriever($key); + + foreach ($this as $k => $v) { + if (! $callback($v, $k)) { + return false; + } + } + + return true; + } + + return $this->every($this->operatorForWhere(...func_get_args())); + } + + /** + * Get the first item by the given key value pair. + * + * @param string $key + * @param mixed $operator + * @param mixed $value + * @return mixed + */ + public function firstWhere($key, $operator = null, $value = null) + { + return $this->first($this->operatorForWhere(...func_get_args())); + } + + /** + * Determine if the collection is not empty. + * + * @return bool + */ + public function isNotEmpty() + { + return ! $this->isEmpty(); + } + + /** + * Run a map over each nested chunk of items. + * + * @param callable $callback + * @return static + */ + public function mapSpread(callable $callback) + { + return $this->map(function ($chunk, $key) use ($callback) { + $chunk[] = $key; + + return $callback(...$chunk); + }); + } + + /** + * Run a grouping map over the items. + * + * The callback should return an associative array with a single key/value pair. + * + * @param callable $callback + * @return static + */ + public function mapToGroups(callable $callback) + { + $groups = $this->mapToDictionary($callback); + + return $groups->map([$this, 'make']); + } + + /** + * Map a collection and flatten the result by a single level. + * + * @param callable $callback + * @return static + */ + public function flatMap(callable $callback) + { + return $this->map($callback)->collapse(); + } + + /** + * Map the values into a new class. + * + * @param string $class + * @return static + */ + public function mapInto($class) + { + return $this->map(function ($value, $key) use ($class) { + return new $class($value, $key); + }); + } + + /** + * Get the min value of a given key. + * + * @param callable|string|null $callback + * @return mixed + */ + public function min($callback = null) + { + $callback = $this->valueRetriever($callback); + + return $this->map(function ($value) use ($callback) { + return $callback($value); + })->filter(function ($value) { + return ! is_null($value); + })->reduce(function ($result, $value) { + return is_null($result) || $value < $result ? $value : $result; + }); + } + + /** + * Get the max value of a given key. + * + * @param callable|string|null $callback + * @return mixed + */ + public function max($callback = null) + { + $callback = $this->valueRetriever($callback); + + return $this->filter(function ($value) { + return ! is_null($value); + })->reduce(function ($result, $item) use ($callback) { + $value = $callback($item); + + return is_null($result) || $value > $result ? $value : $result; + }); + } + + /** + * "Paginate" the collection by slicing it into a smaller collection. + * + * @param int $page + * @param int $perPage + * @return static + */ + public function forPage($page, $perPage) + { + $offset = max(0, ($page - 1) * $perPage); + + return $this->slice($offset, $perPage); + } + + /** + * Partition the collection into two arrays using the given callback or key. + * + * @param callable|string $key + * @param mixed $operator + * @param mixed $value + * @return static + */ + public function partition($key, $operator = null, $value = null) + { + $passed = []; + $failed = []; + + $callback = func_num_args() === 1 + ? $this->valueRetriever($key) + : $this->operatorForWhere(...func_get_args()); + + foreach ($this as $key => $item) { + if ($callback($item, $key)) { + $passed[$key] = $item; + } else { + $failed[$key] = $item; + } + } + + return new static([new static($passed), new static($failed)]); + } + + /** + * Get the sum of the given values. + * + * @param callable|string|null $callback + * @return mixed + */ + public function sum($callback = null) + { + $callback = is_null($callback) + ? $this->identity() + : $this->valueRetriever($callback); + + return $this->reduce(function ($result, $item) use ($callback) { + return $result + $callback($item); + }, 0); + } + + /** + * Apply the callback if the value is truthy. + * + * @param bool|mixed $value + * @param callable|null $callback + * @param callable|null $default + * @return static|mixed + */ + public function when($value, callable $callback = null, callable $default = null) + { + if (! $callback) { + return new HigherOrderWhenProxy($this, $value); + } + + if ($value) { + return $callback($this, $value); + } elseif ($default) { + return $default($this, $value); + } + + return $this; + } + + /** + * Apply the callback if the collection is empty. + * + * @param callable $callback + * @param callable|null $default + * @return static|mixed + */ + public function whenEmpty(callable $callback, callable $default = null) + { + return $this->when($this->isEmpty(), $callback, $default); + } + + /** + * Apply the callback if the collection is not empty. + * + * @param callable $callback + * @param callable|null $default + * @return static|mixed + */ + public function whenNotEmpty(callable $callback, callable $default = null) + { + return $this->when($this->isNotEmpty(), $callback, $default); + } + + /** + * Apply the callback if the value is falsy. + * + * @param bool $value + * @param callable $callback + * @param callable|null $default + * @return static|mixed + */ + public function unless($value, callable $callback, callable $default = null) + { + return $this->when(! $value, $callback, $default); + } + + /** + * Apply the callback unless the collection is empty. + * + * @param callable $callback + * @param callable|null $default + * @return static|mixed + */ + public function unlessEmpty(callable $callback, callable $default = null) + { + return $this->whenNotEmpty($callback, $default); + } + + /** + * Apply the callback unless the collection is not empty. + * + * @param callable $callback + * @param callable|null $default + * @return static|mixed + */ + public function unlessNotEmpty(callable $callback, callable $default = null) + { + return $this->whenEmpty($callback, $default); + } + + /** + * Filter items by the given key value pair. + * + * @param string $key + * @param mixed $operator + * @param mixed $value + * @return static + */ + public function where($key, $operator = null, $value = null) + { + return $this->filter($this->operatorForWhere(...func_get_args())); + } + + /** + * Filter items where the value for the given key is null. + * + * @param string|null $key + * @return static + */ + public function whereNull($key = null) + { + return $this->whereStrict($key, null); + } + + /** + * Filter items where the value for the given key is not null. + * + * @param string|null $key + * @return static + */ + public function whereNotNull($key = null) + { + return $this->where($key, '!==', null); + } + + /** + * Filter items by the given key value pair using strict comparison. + * + * @param string $key + * @param mixed $value + * @return static + */ + public function whereStrict($key, $value) + { + return $this->where($key, '===', $value); + } + + /** + * Filter items by the given key value pair. + * + * @param string $key + * @param mixed $values + * @param bool $strict + * @return static + */ + public function whereIn($key, $values, $strict = false) + { + $values = $this->getArrayableItems($values); + + return $this->filter(function ($item) use ($key, $values, $strict) { + return in_array(data_get($item, $key), $values, $strict); + }); + } + + /** + * Filter items by the given key value pair using strict comparison. + * + * @param string $key + * @param mixed $values + * @return static + */ + public function whereInStrict($key, $values) + { + return $this->whereIn($key, $values, true); + } + + /** + * Filter items such that the value of the given key is between the given values. + * + * @param string $key + * @param array $values + * @return static + */ + public function whereBetween($key, $values) + { + return $this->where($key, '>=', reset($values))->where($key, '<=', end($values)); + } + + /** + * Filter items such that the value of the given key is not between the given values. + * + * @param string $key + * @param array $values + * @return static + */ + public function whereNotBetween($key, $values) + { + return $this->filter(function ($item) use ($key, $values) { + return data_get($item, $key) < reset($values) || data_get($item, $key) > end($values); + }); + } + + /** + * Filter items by the given key value pair. + * + * @param string $key + * @param mixed $values + * @param bool $strict + * @return static + */ + public function whereNotIn($key, $values, $strict = false) + { + $values = $this->getArrayableItems($values); + + return $this->reject(function ($item) use ($key, $values, $strict) { + return in_array(data_get($item, $key), $values, $strict); + }); + } + + /** + * Filter items by the given key value pair using strict comparison. + * + * @param string $key + * @param mixed $values + * @return static + */ + public function whereNotInStrict($key, $values) + { + return $this->whereNotIn($key, $values, true); + } + + /** + * Filter the items, removing any items that don't match the given type(s). + * + * @param string|string[] $type + * @return static + */ + public function whereInstanceOf($type) + { + return $this->filter(function ($value) use ($type) { + if (is_array($type)) { + foreach ($type as $classType) { + if ($value instanceof $classType) { + return true; + } + } + + return false; + } + + return $value instanceof $type; + }); + } + + /** + * Pass the collection to the given callback and return the result. + * + * @param callable $callback + * @return mixed + */ + public function pipe(callable $callback) + { + return $callback($this); + } + + /** + * Pass the collection into a new class. + * + * @param string $class + * @return mixed + */ + public function pipeInto($class) + { + return new $class($this); + } + + /** + * Pass the collection through a series of callable pipes and return the result. + * + * @param array $pipes + * @return mixed + */ + public function pipeThrough($pipes) + { + return static::make($pipes)->reduce( + function ($carry, $pipe) { + return $pipe($carry); + }, + $this, + ); + } + + /** + * Pass the collection to the given callback and then return it. + * + * @param callable $callback + * @return $this + */ + public function tap(callable $callback) + { + $callback(clone $this); + + return $this; + } + + /** + * Reduce the collection to a single value. + * + * @param callable $callback + * @param mixed $initial + * @return mixed + */ + public function reduce(callable $callback, $initial = null) + { + $result = $initial; + + foreach ($this as $key => $value) { + $result = $callback($result, $value, $key); + } + + return $result; + } + + /** + * Reduce the collection to multiple aggregate values. + * + * @param callable $callback + * @param mixed ...$initial + * @return array + * + * @deprecated Use "reduceSpread" instead + * + * @throws \UnexpectedValueException + */ + public function reduceMany(callable $callback, ...$initial) + { + return $this->reduceSpread($callback, ...$initial); + } + + /** + * Reduce the collection to multiple aggregate values. + * + * @param callable $callback + * @param mixed ...$initial + * @return array + * + * @throws \UnexpectedValueException + */ + public function reduceSpread(callable $callback, ...$initial) + { + $result = $initial; + + foreach ($this as $key => $value) { + $result = call_user_func_array($callback, array_merge($result, [$value, $key])); + + if (! is_array($result)) { + throw new UnexpectedValueException(sprintf( + "%s::reduceMany expects reducer to return an array, but got a '%s' instead.", + class_basename(static::class), gettype($result) + )); + } + } + + return $result; + } + + /** + * Reduce an associative collection to a single value. + * + * @param callable $callback + * @param mixed $initial + * @return mixed + */ + public function reduceWithKeys(callable $callback, $initial = null) + { + return $this->reduce($callback, $initial); + } + + /** + * Create a collection of all elements that do not pass a given truth test. + * + * @param callable|mixed $callback + * @return static + */ + public function reject($callback = true) + { + $useAsCallable = $this->useAsCallable($callback); + + return $this->filter(function ($value, $key) use ($callback, $useAsCallable) { + return $useAsCallable + ? ! $callback($value, $key) + : $value != $callback; + }); + } + + /** + * Return only unique items from the collection array using strict comparison. + * + * @param string|callable|null $key + * @return static + */ + public function uniqueStrict($key = null) + { + return $this->unique($key, true); + } + + /** + * Collect the values into a collection. + * + * @return \Illuminate\Support\Collection + */ + public function collect() + { + return new Collection($this->all()); + } + + /** + * Get the collection of items as a plain array. + * + * @return array + */ + public function toArray() + { + return $this->map(function ($value) { + return $value instanceof Arrayable ? $value->toArray() : $value; + })->all(); + } + + /** + * Convert the object into something JSON serializable. + * + * @return array + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return array_map(function ($value) { + if ($value instanceof JsonSerializable) { + return $value->jsonSerialize(); + } elseif ($value instanceof Jsonable) { + return json_decode($value->toJson(), true); + } elseif ($value instanceof Arrayable) { + return $value->toArray(); + } + + return $value; + }, $this->all()); + } + + /** + * Get the collection of items as JSON. + * + * @param int $options + * @return string + */ + public function toJson($options = 0) + { + return json_encode($this->jsonSerialize(), $options); + } + + /** + * Get a CachingIterator instance. + * + * @param int $flags + * @return \CachingIterator + */ + public function getCachingIterator($flags = CachingIterator::CALL_TOSTRING) + { + return new CachingIterator($this->getIterator(), $flags); + } + + /** + * Convert the collection to its string representation. + * + * @return string + */ + public function __toString() + { + return $this->escapeWhenCastingToString + ? e($this->toJson()) + : $this->toJson(); + } + + /** + * Indicate that the model's string representation should be escaped when __toString is invoked. + * + * @param bool $escape + * @return $this + */ + public function escapeWhenCastingToString($escape = true) + { + $this->escapeWhenCastingToString = $escape; + + return $this; + } + + /** + * Add a method to the list of proxied methods. + * + * @param string $method + * @return void + */ + public static function proxy($method) + { + static::$proxies[] = $method; + } + + /** + * Dynamically access collection proxies. + * + * @param string $key + * @return mixed + * + * @throws \Exception + */ + public function __get($key) + { + if (! in_array($key, static::$proxies)) { + throw new Exception("Property [{$key}] does not exist on this collection instance."); + } + + return new HigherOrderCollectionProxy($this, $key); + } + + /** + * Results array of items from Collection or Arrayable. + * + * @param mixed $items + * @return array + */ + protected function getArrayableItems($items) + { + if (is_array($items)) { + return $items; + } elseif ($items instanceof Enumerable) { + return $items->all(); + } elseif ($items instanceof Arrayable) { + return $items->toArray(); + } elseif ($items instanceof Jsonable) { + return json_decode($items->toJson(), true); + } elseif ($items instanceof JsonSerializable) { + return (array) $items->jsonSerialize(); + } elseif ($items instanceof Traversable) { + return iterator_to_array($items); + } elseif ($items instanceof UnitEnum) { + return [$items]; + } + + return (array) $items; + } + + /** + * Get an operator checker callback. + * + * @param string $key + * @param string|null $operator + * @param mixed $value + * @return \Closure + */ + protected function operatorForWhere($key, $operator = null, $value = null) + { + if (func_num_args() === 1) { + $value = true; + + $operator = '='; + } + + if (func_num_args() === 2) { + $value = $operator; + + $operator = '='; + } + + return function ($item) use ($key, $operator, $value) { + $retrieved = data_get($item, $key); + + $strings = array_filter([$retrieved, $value], function ($value) { + return is_string($value) || (is_object($value) && method_exists($value, '__toString')); + }); + + if (count($strings) < 2 && count(array_filter([$retrieved, $value], 'is_object')) == 1) { + return in_array($operator, ['!=', '<>', '!==']); + } + + switch ($operator) { + default: + case '=': + case '==': return $retrieved == $value; + case '!=': + case '<>': return $retrieved != $value; + case '<': return $retrieved < $value; + case '>': return $retrieved > $value; + case '<=': return $retrieved <= $value; + case '>=': return $retrieved >= $value; + case '===': return $retrieved === $value; + case '!==': return $retrieved !== $value; + } + }; + } + + /** + * Determine if the given value is callable, but not a string. + * + * @param mixed $value + * @return bool + */ + protected function useAsCallable($value) + { + return ! is_string($value) && is_callable($value); + } + + /** + * Get a value retrieving callback. + * + * @param callable|string|null $value + * @return callable + */ + protected function valueRetriever($value) + { + if ($this->useAsCallable($value)) { + return $value; + } + + return function ($item) use ($value) { + return data_get($item, $value); + }; + } + + /** + * Make a function to check an item's equality. + * + * @param mixed $value + * @return \Closure + */ + protected function equality($value) + { + return function ($item) use ($value) { + return $item === $value; + }; + } + + /** + * Make a function using another function, by negating its result. + * + * @param \Closure $callback + * @return \Closure + */ + protected function negate(Closure $callback) + { + return function (...$params) use ($callback) { + return ! $callback(...$params); + }; + } + + /** + * Make a function that returns what's passed to it. + * + * @return \Closure + */ + protected function identity() + { + return function ($value) { + return $value; + }; + } +} diff --git a/vendor/illuminate/collections/helpers.php b/vendor/illuminate/collections/helpers.php new file mode 100644 index 0000000..67669e5 --- /dev/null +++ b/vendor/illuminate/collections/helpers.php @@ -0,0 +1,186 @@ + $segment) { + unset($key[$i]); + + if (is_null($segment)) { + return $target; + } + + if ($segment === '*') { + if ($target instanceof Collection) { + $target = $target->all(); + } elseif (! is_array($target)) { + return value($default); + } + + $result = []; + + foreach ($target as $item) { + $result[] = data_get($item, $key); + } + + return in_array('*', $key) ? Arr::collapse($result) : $result; + } + + if (Arr::accessible($target) && Arr::exists($target, $segment)) { + $target = $target[$segment]; + } elseif (is_object($target) && isset($target->{$segment})) { + $target = $target->{$segment}; + } else { + return value($default); + } + } + + return $target; + } +} + +if (! function_exists('data_set')) { + /** + * Set an item on an array or object using dot notation. + * + * @param mixed $target + * @param string|array $key + * @param mixed $value + * @param bool $overwrite + * @return mixed + */ + function data_set(&$target, $key, $value, $overwrite = true) + { + $segments = is_array($key) ? $key : explode('.', $key); + + if (($segment = array_shift($segments)) === '*') { + if (! Arr::accessible($target)) { + $target = []; + } + + if ($segments) { + foreach ($target as &$inner) { + data_set($inner, $segments, $value, $overwrite); + } + } elseif ($overwrite) { + foreach ($target as &$inner) { + $inner = $value; + } + } + } elseif (Arr::accessible($target)) { + if ($segments) { + if (! Arr::exists($target, $segment)) { + $target[$segment] = []; + } + + data_set($target[$segment], $segments, $value, $overwrite); + } elseif ($overwrite || ! Arr::exists($target, $segment)) { + $target[$segment] = $value; + } + } elseif (is_object($target)) { + if ($segments) { + if (! isset($target->{$segment})) { + $target->{$segment} = []; + } + + data_set($target->{$segment}, $segments, $value, $overwrite); + } elseif ($overwrite || ! isset($target->{$segment})) { + $target->{$segment} = $value; + } + } else { + $target = []; + + if ($segments) { + data_set($target[$segment], $segments, $value, $overwrite); + } elseif ($overwrite) { + $target[$segment] = $value; + } + } + + return $target; + } +} + +if (! function_exists('head')) { + /** + * Get the first element of an array. Useful for method chaining. + * + * @param array $array + * @return mixed + */ + function head($array) + { + return reset($array); + } +} + +if (! function_exists('last')) { + /** + * Get the last element from an array. + * + * @param array $array + * @return mixed + */ + function last($array) + { + return end($array); + } +} + +if (! function_exists('value')) { + /** + * Return the default value of the given value. + * + * @param mixed $value + * @return mixed + */ + function value($value, ...$args) + { + return $value instanceof Closure ? $value(...$args) : $value; + } +} diff --git a/vendor/illuminate/container/BoundMethod.php b/vendor/illuminate/container/BoundMethod.php new file mode 100644 index 0000000..5c96c97 --- /dev/null +++ b/vendor/illuminate/container/BoundMethod.php @@ -0,0 +1,204 @@ +make($segments[0]), $method], $parameters + ); + } + + /** + * Call a method that has been bound to the container. + * + * @param \Illuminate\Container\Container $container + * @param callable $callback + * @param mixed $default + * @return mixed + */ + protected static function callBoundMethod($container, $callback, $default) + { + if (! is_array($callback)) { + return Util::unwrapIfClosure($default); + } + + // Here we need to turn the array callable into a Class@method string we can use to + // examine the container and see if there are any method bindings for this given + // method. If there are, we can call this method binding callback immediately. + $method = static::normalizeMethod($callback); + + if ($container->hasMethodBinding($method)) { + return $container->callMethodBinding($method, $callback[0]); + } + + return Util::unwrapIfClosure($default); + } + + /** + * Normalize the given callback into a Class@method string. + * + * @param callable $callback + * @return string + */ + protected static function normalizeMethod($callback) + { + $class = is_string($callback[0]) ? $callback[0] : get_class($callback[0]); + + return "{$class}@{$callback[1]}"; + } + + /** + * Get all dependencies for a given method. + * + * @param \Illuminate\Container\Container $container + * @param callable|string $callback + * @param array $parameters + * @return array + * + * @throws \ReflectionException + */ + protected static function getMethodDependencies($container, $callback, array $parameters = []) + { + $dependencies = []; + + foreach (static::getCallReflector($callback)->getParameters() as $parameter) { + static::addDependencyForCallParameter($container, $parameter, $parameters, $dependencies); + } + + return array_merge($dependencies, array_values($parameters)); + } + + /** + * Get the proper reflection instance for the given callback. + * + * @param callable|string $callback + * @return \ReflectionFunctionAbstract + * + * @throws \ReflectionException + */ + protected static function getCallReflector($callback) + { + if (is_string($callback) && strpos($callback, '::') !== false) { + $callback = explode('::', $callback); + } elseif (is_object($callback) && ! $callback instanceof Closure) { + $callback = [$callback, '__invoke']; + } + + return is_array($callback) + ? new ReflectionMethod($callback[0], $callback[1]) + : new ReflectionFunction($callback); + } + + /** + * Get the dependency for the given call parameter. + * + * @param \Illuminate\Container\Container $container + * @param \ReflectionParameter $parameter + * @param array $parameters + * @param array $dependencies + * @return void + * + * @throws \Illuminate\Contracts\Container\BindingResolutionException + */ + protected static function addDependencyForCallParameter($container, $parameter, + array &$parameters, &$dependencies) + { + if (array_key_exists($paramName = $parameter->getName(), $parameters)) { + $dependencies[] = $parameters[$paramName]; + + unset($parameters[$paramName]); + } elseif (! is_null($className = Util::getParameterClassName($parameter))) { + if (array_key_exists($className, $parameters)) { + $dependencies[] = $parameters[$className]; + + unset($parameters[$className]); + } else { + if ($parameter->isVariadic()) { + $variadicDependencies = $container->make($className); + + $dependencies = array_merge($dependencies, is_array($variadicDependencies) + ? $variadicDependencies + : [$variadicDependencies]); + } else { + $dependencies[] = $container->make($className); + } + } + } elseif ($parameter->isDefaultValueAvailable()) { + $dependencies[] = $parameter->getDefaultValue(); + } elseif (! $parameter->isOptional() && ! array_key_exists($paramName, $parameters)) { + $message = "Unable to resolve dependency [{$parameter}] in class {$parameter->getDeclaringClass()->getName()}"; + + throw new BindingResolutionException($message); + } + } + + /** + * Determine if the given string is in Class@method syntax. + * + * @param mixed $callback + * @return bool + */ + protected static function isCallableWithAtSign($callback) + { + return is_string($callback) && strpos($callback, '@') !== false; + } +} diff --git a/vendor/illuminate/container/Container.php b/vendor/illuminate/container/Container.php new file mode 100644 index 0000000..e6cd346 --- /dev/null +++ b/vendor/illuminate/container/Container.php @@ -0,0 +1,1471 @@ +getAlias($c); + } + + return new ContextualBindingBuilder($this, $aliases); + } + + /** + * Determine if the given abstract type has been bound. + * + * @param string $abstract + * @return bool + */ + public function bound($abstract) + { + return isset($this->bindings[$abstract]) || + isset($this->instances[$abstract]) || + $this->isAlias($abstract); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function has($id) + { + return $this->bound($id); + } + + /** + * Determine if the given abstract type has been resolved. + * + * @param string $abstract + * @return bool + */ + public function resolved($abstract) + { + if ($this->isAlias($abstract)) { + $abstract = $this->getAlias($abstract); + } + + return isset($this->resolved[$abstract]) || + isset($this->instances[$abstract]); + } + + /** + * Determine if a given type is shared. + * + * @param string $abstract + * @return bool + */ + public function isShared($abstract) + { + return isset($this->instances[$abstract]) || + (isset($this->bindings[$abstract]['shared']) && + $this->bindings[$abstract]['shared'] === true); + } + + /** + * Determine if a given string is an alias. + * + * @param string $name + * @return bool + */ + public function isAlias($name) + { + return isset($this->aliases[$name]); + } + + /** + * Register a binding with the container. + * + * @param string $abstract + * @param \Closure|string|null $concrete + * @param bool $shared + * @return void + * + * @throws \TypeError + */ + public function bind($abstract, $concrete = null, $shared = false) + { + $this->dropStaleInstances($abstract); + + // If no concrete type was given, we will simply set the concrete type to the + // abstract type. After that, the concrete type to be registered as shared + // without being forced to state their classes in both of the parameters. + if (is_null($concrete)) { + $concrete = $abstract; + } + + // If the factory is not a Closure, it means it is just a class name which is + // bound into this container to the abstract type and we will just wrap it + // up inside its own Closure to give us more convenience when extending. + if (! $concrete instanceof Closure) { + if (! is_string($concrete)) { + throw new TypeError(self::class.'::bind(): Argument #2 ($concrete) must be of type Closure|string|null'); + } + + $concrete = $this->getClosure($abstract, $concrete); + } + + $this->bindings[$abstract] = compact('concrete', 'shared'); + + // If the abstract type was already resolved in this container we'll fire the + // rebound listener so that any objects which have already gotten resolved + // can have their copy of the object updated via the listener callbacks. + if ($this->resolved($abstract)) { + $this->rebound($abstract); + } + } + + /** + * Get the Closure to be used when building a type. + * + * @param string $abstract + * @param string $concrete + * @return \Closure + */ + protected function getClosure($abstract, $concrete) + { + return function ($container, $parameters = []) use ($abstract, $concrete) { + if ($abstract == $concrete) { + return $container->build($concrete); + } + + return $container->resolve( + $concrete, $parameters, $raiseEvents = false + ); + }; + } + + /** + * Determine if the container has a method binding. + * + * @param string $method + * @return bool + */ + public function hasMethodBinding($method) + { + return isset($this->methodBindings[$method]); + } + + /** + * Bind a callback to resolve with Container::call. + * + * @param array|string $method + * @param \Closure $callback + * @return void + */ + public function bindMethod($method, $callback) + { + $this->methodBindings[$this->parseBindMethod($method)] = $callback; + } + + /** + * Get the method to be bound in class@method format. + * + * @param array|string $method + * @return string + */ + protected function parseBindMethod($method) + { + if (is_array($method)) { + return $method[0].'@'.$method[1]; + } + + return $method; + } + + /** + * Get the method binding for the given method. + * + * @param string $method + * @param mixed $instance + * @return mixed + */ + public function callMethodBinding($method, $instance) + { + return call_user_func($this->methodBindings[$method], $instance, $this); + } + + /** + * Add a contextual binding to the container. + * + * @param string $concrete + * @param string $abstract + * @param \Closure|string $implementation + * @return void + */ + public function addContextualBinding($concrete, $abstract, $implementation) + { + $this->contextual[$concrete][$this->getAlias($abstract)] = $implementation; + } + + /** + * Register a binding if it hasn't already been registered. + * + * @param string $abstract + * @param \Closure|string|null $concrete + * @param bool $shared + * @return void + */ + public function bindIf($abstract, $concrete = null, $shared = false) + { + if (! $this->bound($abstract)) { + $this->bind($abstract, $concrete, $shared); + } + } + + /** + * Register a shared binding in the container. + * + * @param string $abstract + * @param \Closure|string|null $concrete + * @return void + */ + public function singleton($abstract, $concrete = null) + { + $this->bind($abstract, $concrete, true); + } + + /** + * Register a shared binding if it hasn't already been registered. + * + * @param string $abstract + * @param \Closure|string|null $concrete + * @return void + */ + public function singletonIf($abstract, $concrete = null) + { + if (! $this->bound($abstract)) { + $this->singleton($abstract, $concrete); + } + } + + /** + * Register a scoped binding in the container. + * + * @param string $abstract + * @param \Closure|string|null $concrete + * @return void + */ + public function scoped($abstract, $concrete = null) + { + $this->scopedInstances[] = $abstract; + + $this->singleton($abstract, $concrete); + } + + /** + * Register a scoped binding if it hasn't already been registered. + * + * @param string $abstract + * @param \Closure|string|null $concrete + * @return void + */ + public function scopedIf($abstract, $concrete = null) + { + if (! $this->bound($abstract)) { + $this->scopedInstances[] = $abstract; + + $this->singleton($abstract, $concrete); + } + } + + /** + * "Extend" an abstract type in the container. + * + * @param string $abstract + * @param \Closure $closure + * @return void + * + * @throws \InvalidArgumentException + */ + public function extend($abstract, Closure $closure) + { + $abstract = $this->getAlias($abstract); + + if (isset($this->instances[$abstract])) { + $this->instances[$abstract] = $closure($this->instances[$abstract], $this); + + $this->rebound($abstract); + } else { + $this->extenders[$abstract][] = $closure; + + if ($this->resolved($abstract)) { + $this->rebound($abstract); + } + } + } + + /** + * Register an existing instance as shared in the container. + * + * @param string $abstract + * @param mixed $instance + * @return mixed + */ + public function instance($abstract, $instance) + { + $this->removeAbstractAlias($abstract); + + $isBound = $this->bound($abstract); + + unset($this->aliases[$abstract]); + + // We'll check to determine if this type has been bound before, and if it has + // we will fire the rebound callbacks registered with the container and it + // can be updated with consuming classes that have gotten resolved here. + $this->instances[$abstract] = $instance; + + if ($isBound) { + $this->rebound($abstract); + } + + return $instance; + } + + /** + * Remove an alias from the contextual binding alias cache. + * + * @param string $searched + * @return void + */ + protected function removeAbstractAlias($searched) + { + if (! isset($this->aliases[$searched])) { + return; + } + + foreach ($this->abstractAliases as $abstract => $aliases) { + foreach ($aliases as $index => $alias) { + if ($alias == $searched) { + unset($this->abstractAliases[$abstract][$index]); + } + } + } + } + + /** + * Assign a set of tags to a given binding. + * + * @param array|string $abstracts + * @param array|mixed ...$tags + * @return void + */ + public function tag($abstracts, $tags) + { + $tags = is_array($tags) ? $tags : array_slice(func_get_args(), 1); + + foreach ($tags as $tag) { + if (! isset($this->tags[$tag])) { + $this->tags[$tag] = []; + } + + foreach ((array) $abstracts as $abstract) { + $this->tags[$tag][] = $abstract; + } + } + } + + /** + * Resolve all of the bindings for a given tag. + * + * @param string $tag + * @return iterable + */ + public function tagged($tag) + { + if (! isset($this->tags[$tag])) { + return []; + } + + return new RewindableGenerator(function () use ($tag) { + foreach ($this->tags[$tag] as $abstract) { + yield $this->make($abstract); + } + }, count($this->tags[$tag])); + } + + /** + * Alias a type to a different name. + * + * @param string $abstract + * @param string $alias + * @return void + * + * @throws \LogicException + */ + public function alias($abstract, $alias) + { + if ($alias === $abstract) { + throw new LogicException("[{$abstract}] is aliased to itself."); + } + + $this->aliases[$alias] = $abstract; + + $this->abstractAliases[$abstract][] = $alias; + } + + /** + * Bind a new callback to an abstract's rebind event. + * + * @param string $abstract + * @param \Closure $callback + * @return mixed + */ + public function rebinding($abstract, Closure $callback) + { + $this->reboundCallbacks[$abstract = $this->getAlias($abstract)][] = $callback; + + if ($this->bound($abstract)) { + return $this->make($abstract); + } + } + + /** + * Refresh an instance on the given target and method. + * + * @param string $abstract + * @param mixed $target + * @param string $method + * @return mixed + */ + public function refresh($abstract, $target, $method) + { + return $this->rebinding($abstract, function ($app, $instance) use ($target, $method) { + $target->{$method}($instance); + }); + } + + /** + * Fire the "rebound" callbacks for the given abstract type. + * + * @param string $abstract + * @return void + */ + protected function rebound($abstract) + { + $instance = $this->make($abstract); + + foreach ($this->getReboundCallbacks($abstract) as $callback) { + call_user_func($callback, $this, $instance); + } + } + + /** + * Get the rebound callbacks for a given type. + * + * @param string $abstract + * @return array + */ + protected function getReboundCallbacks($abstract) + { + return $this->reboundCallbacks[$abstract] ?? []; + } + + /** + * Wrap the given closure such that its dependencies will be injected when executed. + * + * @param \Closure $callback + * @param array $parameters + * @return \Closure + */ + public function wrap(Closure $callback, array $parameters = []) + { + return function () use ($callback, $parameters) { + return $this->call($callback, $parameters); + }; + } + + /** + * Call the given Closure / class@method and inject its dependencies. + * + * @param callable|string $callback + * @param array $parameters + * @param string|null $defaultMethod + * @return mixed + * + * @throws \InvalidArgumentException + */ + public function call($callback, array $parameters = [], $defaultMethod = null) + { + return BoundMethod::call($this, $callback, $parameters, $defaultMethod); + } + + /** + * Get a closure to resolve the given type from the container. + * + * @param string $abstract + * @return \Closure + */ + public function factory($abstract) + { + return function () use ($abstract) { + return $this->make($abstract); + }; + } + + /** + * An alias function name for make(). + * + * @param string|callable $abstract + * @param array $parameters + * @return mixed + * + * @throws \Illuminate\Contracts\Container\BindingResolutionException + */ + public function makeWith($abstract, array $parameters = []) + { + return $this->make($abstract, $parameters); + } + + /** + * Resolve the given type from the container. + * + * @param string|callable $abstract + * @param array $parameters + * @return mixed + * + * @throws \Illuminate\Contracts\Container\BindingResolutionException + */ + public function make($abstract, array $parameters = []) + { + return $this->resolve($abstract, $parameters); + } + + /** + * {@inheritdoc} + * + * @return mixed + */ + public function get($id) + { + try { + return $this->resolve($id); + } catch (Exception $e) { + if ($this->has($id) || $e instanceof CircularDependencyException) { + throw $e; + } + + throw new EntryNotFoundException($id, $e->getCode(), $e); + } + } + + /** + * Resolve the given type from the container. + * + * @param string|callable $abstract + * @param array $parameters + * @param bool $raiseEvents + * @return mixed + * + * @throws \Illuminate\Contracts\Container\BindingResolutionException + * @throws \Illuminate\Contracts\Container\CircularDependencyException + */ + protected function resolve($abstract, $parameters = [], $raiseEvents = true) + { + $abstract = $this->getAlias($abstract); + + // First we'll fire any event handlers which handle the "before" resolving of + // specific types. This gives some hooks the chance to add various extends + // calls to change the resolution of objects that they're interested in. + if ($raiseEvents) { + $this->fireBeforeResolvingCallbacks($abstract, $parameters); + } + + $concrete = $this->getContextualConcrete($abstract); + + $needsContextualBuild = ! empty($parameters) || ! is_null($concrete); + + // If an instance of the type is currently being managed as a singleton we'll + // just return an existing instance instead of instantiating new instances + // so the developer can keep using the same objects instance every time. + if (isset($this->instances[$abstract]) && ! $needsContextualBuild) { + return $this->instances[$abstract]; + } + + $this->with[] = $parameters; + + if (is_null($concrete)) { + $concrete = $this->getConcrete($abstract); + } + + // We're ready to instantiate an instance of the concrete type registered for + // the binding. This will instantiate the types, as well as resolve any of + // its "nested" dependencies recursively until all have gotten resolved. + if ($this->isBuildable($concrete, $abstract)) { + $object = $this->build($concrete); + } else { + $object = $this->make($concrete); + } + + // If we defined any extenders for this type, we'll need to spin through them + // and apply them to the object being built. This allows for the extension + // of services, such as changing configuration or decorating the object. + foreach ($this->getExtenders($abstract) as $extender) { + $object = $extender($object, $this); + } + + // If the requested type is registered as a singleton we'll want to cache off + // the instances in "memory" so we can return it later without creating an + // entirely new instance of an object on each subsequent request for it. + if ($this->isShared($abstract) && ! $needsContextualBuild) { + $this->instances[$abstract] = $object; + } + + if ($raiseEvents) { + $this->fireResolvingCallbacks($abstract, $object); + } + + // Before returning, we will also set the resolved flag to "true" and pop off + // the parameter overrides for this build. After those two things are done + // we will be ready to return back the fully constructed class instance. + $this->resolved[$abstract] = true; + + array_pop($this->with); + + return $object; + } + + /** + * Get the concrete type for a given abstract. + * + * @param string|callable $abstract + * @return mixed + */ + protected function getConcrete($abstract) + { + // If we don't have a registered resolver or concrete for the type, we'll just + // assume each type is a concrete name and will attempt to resolve it as is + // since the container should be able to resolve concretes automatically. + if (isset($this->bindings[$abstract])) { + return $this->bindings[$abstract]['concrete']; + } + + return $abstract; + } + + /** + * Get the contextual concrete binding for the given abstract. + * + * @param string|callable $abstract + * @return \Closure|string|array|null + */ + protected function getContextualConcrete($abstract) + { + if (! is_null($binding = $this->findInContextualBindings($abstract))) { + return $binding; + } + + // Next we need to see if a contextual binding might be bound under an alias of the + // given abstract type. So, we will need to check if any aliases exist with this + // type and then spin through them and check for contextual bindings on these. + if (empty($this->abstractAliases[$abstract])) { + return; + } + + foreach ($this->abstractAliases[$abstract] as $alias) { + if (! is_null($binding = $this->findInContextualBindings($alias))) { + return $binding; + } + } + } + + /** + * Find the concrete binding for the given abstract in the contextual binding array. + * + * @param string|callable $abstract + * @return \Closure|string|null + */ + protected function findInContextualBindings($abstract) + { + return $this->contextual[end($this->buildStack)][$abstract] ?? null; + } + + /** + * Determine if the given concrete is buildable. + * + * @param mixed $concrete + * @param string $abstract + * @return bool + */ + protected function isBuildable($concrete, $abstract) + { + return $concrete === $abstract || $concrete instanceof Closure; + } + + /** + * Instantiate a concrete instance of the given type. + * + * @param \Closure|string $concrete + * @return mixed + * + * @throws \Illuminate\Contracts\Container\BindingResolutionException + * @throws \Illuminate\Contracts\Container\CircularDependencyException + */ + public function build($concrete) + { + // If the concrete type is actually a Closure, we will just execute it and + // hand back the results of the functions, which allows functions to be + // used as resolvers for more fine-tuned resolution of these objects. + if ($concrete instanceof Closure) { + return $concrete($this, $this->getLastParameterOverride()); + } + + try { + $reflector = new ReflectionClass($concrete); + } catch (ReflectionException $e) { + throw new BindingResolutionException("Target class [$concrete] does not exist.", 0, $e); + } + + // If the type is not instantiable, the developer is attempting to resolve + // an abstract type such as an Interface or Abstract Class and there is + // no binding registered for the abstractions so we need to bail out. + if (! $reflector->isInstantiable()) { + return $this->notInstantiable($concrete); + } + + $this->buildStack[] = $concrete; + + $constructor = $reflector->getConstructor(); + + // If there are no constructors, that means there are no dependencies then + // we can just resolve the instances of the objects right away, without + // resolving any other types or dependencies out of these containers. + if (is_null($constructor)) { + array_pop($this->buildStack); + + return new $concrete; + } + + $dependencies = $constructor->getParameters(); + + // Once we have all the constructor's parameters we can create each of the + // dependency instances and then use the reflection instances to make a + // new instance of this class, injecting the created dependencies in. + try { + $instances = $this->resolveDependencies($dependencies); + } catch (BindingResolutionException $e) { + array_pop($this->buildStack); + + throw $e; + } + + array_pop($this->buildStack); + + return $reflector->newInstanceArgs($instances); + } + + /** + * Resolve all of the dependencies from the ReflectionParameters. + * + * @param \ReflectionParameter[] $dependencies + * @return array + * + * @throws \Illuminate\Contracts\Container\BindingResolutionException + */ + protected function resolveDependencies(array $dependencies) + { + $results = []; + + foreach ($dependencies as $dependency) { + // If the dependency has an override for this particular build we will use + // that instead as the value. Otherwise, we will continue with this run + // of resolutions and let reflection attempt to determine the result. + if ($this->hasParameterOverride($dependency)) { + $results[] = $this->getParameterOverride($dependency); + + continue; + } + + // If the class is null, it means the dependency is a string or some other + // primitive type which we can not resolve since it is not a class and + // we will just bomb out with an error since we have no-where to go. + $result = is_null(Util::getParameterClassName($dependency)) + ? $this->resolvePrimitive($dependency) + : $this->resolveClass($dependency); + + if ($dependency->isVariadic()) { + $results = array_merge($results, $result); + } else { + $results[] = $result; + } + } + + return $results; + } + + /** + * Determine if the given dependency has a parameter override. + * + * @param \ReflectionParameter $dependency + * @return bool + */ + protected function hasParameterOverride($dependency) + { + return array_key_exists( + $dependency->name, $this->getLastParameterOverride() + ); + } + + /** + * Get a parameter override for a dependency. + * + * @param \ReflectionParameter $dependency + * @return mixed + */ + protected function getParameterOverride($dependency) + { + return $this->getLastParameterOverride()[$dependency->name]; + } + + /** + * Get the last parameter override. + * + * @return array + */ + protected function getLastParameterOverride() + { + return count($this->with) ? end($this->with) : []; + } + + /** + * Resolve a non-class hinted primitive dependency. + * + * @param \ReflectionParameter $parameter + * @return mixed + * + * @throws \Illuminate\Contracts\Container\BindingResolutionException + */ + protected function resolvePrimitive(ReflectionParameter $parameter) + { + if (! is_null($concrete = $this->getContextualConcrete('$'.$parameter->getName()))) { + return $concrete instanceof Closure ? $concrete($this) : $concrete; + } + + if ($parameter->isDefaultValueAvailable()) { + return $parameter->getDefaultValue(); + } + + $this->unresolvablePrimitive($parameter); + } + + /** + * Resolve a class based dependency from the container. + * + * @param \ReflectionParameter $parameter + * @return mixed + * + * @throws \Illuminate\Contracts\Container\BindingResolutionException + */ + protected function resolveClass(ReflectionParameter $parameter) + { + try { + return $parameter->isVariadic() + ? $this->resolveVariadicClass($parameter) + : $this->make(Util::getParameterClassName($parameter)); + } + + // If we can not resolve the class instance, we will check to see if the value + // is optional, and if it is we will return the optional parameter value as + // the value of the dependency, similarly to how we do this with scalars. + catch (BindingResolutionException $e) { + if ($parameter->isDefaultValueAvailable()) { + array_pop($this->with); + + return $parameter->getDefaultValue(); + } + + if ($parameter->isVariadic()) { + array_pop($this->with); + + return []; + } + + throw $e; + } + } + + /** + * Resolve a class based variadic dependency from the container. + * + * @param \ReflectionParameter $parameter + * @return mixed + */ + protected function resolveVariadicClass(ReflectionParameter $parameter) + { + $className = Util::getParameterClassName($parameter); + + $abstract = $this->getAlias($className); + + if (! is_array($concrete = $this->getContextualConcrete($abstract))) { + return $this->make($className); + } + + return array_map(function ($abstract) { + return $this->resolve($abstract); + }, $concrete); + } + + /** + * Throw an exception that the concrete is not instantiable. + * + * @param string $concrete + * @return void + * + * @throws \Illuminate\Contracts\Container\BindingResolutionException + */ + protected function notInstantiable($concrete) + { + if (! empty($this->buildStack)) { + $previous = implode(', ', $this->buildStack); + + $message = "Target [$concrete] is not instantiable while building [$previous]."; + } else { + $message = "Target [$concrete] is not instantiable."; + } + + throw new BindingResolutionException($message); + } + + /** + * Throw an exception for an unresolvable primitive. + * + * @param \ReflectionParameter $parameter + * @return void + * + * @throws \Illuminate\Contracts\Container\BindingResolutionException + */ + protected function unresolvablePrimitive(ReflectionParameter $parameter) + { + $message = "Unresolvable dependency resolving [$parameter] in class {$parameter->getDeclaringClass()->getName()}"; + + throw new BindingResolutionException($message); + } + + /** + * Register a new before resolving callback for all types. + * + * @param \Closure|string $abstract + * @param \Closure|null $callback + * @return void + */ + public function beforeResolving($abstract, Closure $callback = null) + { + if (is_string($abstract)) { + $abstract = $this->getAlias($abstract); + } + + if ($abstract instanceof Closure && is_null($callback)) { + $this->globalBeforeResolvingCallbacks[] = $abstract; + } else { + $this->beforeResolvingCallbacks[$abstract][] = $callback; + } + } + + /** + * Register a new resolving callback. + * + * @param \Closure|string $abstract + * @param \Closure|null $callback + * @return void + */ + public function resolving($abstract, Closure $callback = null) + { + if (is_string($abstract)) { + $abstract = $this->getAlias($abstract); + } + + if (is_null($callback) && $abstract instanceof Closure) { + $this->globalResolvingCallbacks[] = $abstract; + } else { + $this->resolvingCallbacks[$abstract][] = $callback; + } + } + + /** + * Register a new after resolving callback for all types. + * + * @param \Closure|string $abstract + * @param \Closure|null $callback + * @return void + */ + public function afterResolving($abstract, Closure $callback = null) + { + if (is_string($abstract)) { + $abstract = $this->getAlias($abstract); + } + + if ($abstract instanceof Closure && is_null($callback)) { + $this->globalAfterResolvingCallbacks[] = $abstract; + } else { + $this->afterResolvingCallbacks[$abstract][] = $callback; + } + } + + /** + * Fire all of the before resolving callbacks. + * + * @param string $abstract + * @param array $parameters + * @return void + */ + protected function fireBeforeResolvingCallbacks($abstract, $parameters = []) + { + $this->fireBeforeCallbackArray($abstract, $parameters, $this->globalBeforeResolvingCallbacks); + + foreach ($this->beforeResolvingCallbacks as $type => $callbacks) { + if ($type === $abstract || is_subclass_of($abstract, $type)) { + $this->fireBeforeCallbackArray($abstract, $parameters, $callbacks); + } + } + } + + /** + * Fire an array of callbacks with an object. + * + * @param string $abstract + * @param array $parameters + * @param array $callbacks + * @return void + */ + protected function fireBeforeCallbackArray($abstract, $parameters, array $callbacks) + { + foreach ($callbacks as $callback) { + $callback($abstract, $parameters, $this); + } + } + + /** + * Fire all of the resolving callbacks. + * + * @param string $abstract + * @param mixed $object + * @return void + */ + protected function fireResolvingCallbacks($abstract, $object) + { + $this->fireCallbackArray($object, $this->globalResolvingCallbacks); + + $this->fireCallbackArray( + $object, $this->getCallbacksForType($abstract, $object, $this->resolvingCallbacks) + ); + + $this->fireAfterResolvingCallbacks($abstract, $object); + } + + /** + * Fire all of the after resolving callbacks. + * + * @param string $abstract + * @param mixed $object + * @return void + */ + protected function fireAfterResolvingCallbacks($abstract, $object) + { + $this->fireCallbackArray($object, $this->globalAfterResolvingCallbacks); + + $this->fireCallbackArray( + $object, $this->getCallbacksForType($abstract, $object, $this->afterResolvingCallbacks) + ); + } + + /** + * Get all callbacks for a given type. + * + * @param string $abstract + * @param object $object + * @param array $callbacksPerType + * @return array + */ + protected function getCallbacksForType($abstract, $object, array $callbacksPerType) + { + $results = []; + + foreach ($callbacksPerType as $type => $callbacks) { + if ($type === $abstract || $object instanceof $type) { + $results = array_merge($results, $callbacks); + } + } + + return $results; + } + + /** + * Fire an array of callbacks with an object. + * + * @param mixed $object + * @param array $callbacks + * @return void + */ + protected function fireCallbackArray($object, array $callbacks) + { + foreach ($callbacks as $callback) { + $callback($object, $this); + } + } + + /** + * Get the container's bindings. + * + * @return array + */ + public function getBindings() + { + return $this->bindings; + } + + /** + * Get the alias for an abstract if available. + * + * @param string $abstract + * @return string + */ + public function getAlias($abstract) + { + return isset($this->aliases[$abstract]) + ? $this->getAlias($this->aliases[$abstract]) + : $abstract; + } + + /** + * Get the extender callbacks for a given type. + * + * @param string $abstract + * @return array + */ + protected function getExtenders($abstract) + { + return $this->extenders[$this->getAlias($abstract)] ?? []; + } + + /** + * Remove all of the extender callbacks for a given type. + * + * @param string $abstract + * @return void + */ + public function forgetExtenders($abstract) + { + unset($this->extenders[$this->getAlias($abstract)]); + } + + /** + * Drop all of the stale instances and aliases. + * + * @param string $abstract + * @return void + */ + protected function dropStaleInstances($abstract) + { + unset($this->instances[$abstract], $this->aliases[$abstract]); + } + + /** + * Remove a resolved instance from the instance cache. + * + * @param string $abstract + * @return void + */ + public function forgetInstance($abstract) + { + unset($this->instances[$abstract]); + } + + /** + * Clear all of the instances from the container. + * + * @return void + */ + public function forgetInstances() + { + $this->instances = []; + } + + /** + * Clear all of the scoped instances from the container. + * + * @return void + */ + public function forgetScopedInstances() + { + foreach ($this->scopedInstances as $scoped) { + unset($this->instances[$scoped]); + } + } + + /** + * Flush the container of all bindings and resolved instances. + * + * @return void + */ + public function flush() + { + $this->aliases = []; + $this->resolved = []; + $this->bindings = []; + $this->instances = []; + $this->abstractAliases = []; + $this->scopedInstances = []; + } + + /** + * Get the globally available instance of the container. + * + * @return static + */ + public static function getInstance() + { + if (is_null(static::$instance)) { + static::$instance = new static; + } + + return static::$instance; + } + + /** + * Set the shared instance of the container. + * + * @param \Illuminate\Contracts\Container\Container|null $container + * @return \Illuminate\Contracts\Container\Container|static + */ + public static function setInstance(ContainerContract $container = null) + { + return static::$instance = $container; + } + + /** + * Determine if a given offset exists. + * + * @param string $key + * @return bool + */ + #[\ReturnTypeWillChange] + public function offsetExists($key) + { + return $this->bound($key); + } + + /** + * Get the value at a given offset. + * + * @param string $key + * @return mixed + */ + #[\ReturnTypeWillChange] + public function offsetGet($key) + { + return $this->make($key); + } + + /** + * Set the value at a given offset. + * + * @param string $key + * @param mixed $value + * @return void + */ + #[\ReturnTypeWillChange] + public function offsetSet($key, $value) + { + $this->bind($key, $value instanceof Closure ? $value : function () use ($value) { + return $value; + }); + } + + /** + * Unset the value at a given offset. + * + * @param string $key + * @return void + */ + #[\ReturnTypeWillChange] + public function offsetUnset($key) + { + unset($this->bindings[$key], $this->instances[$key], $this->resolved[$key]); + } + + /** + * Dynamically access container services. + * + * @param string $key + * @return mixed + */ + public function __get($key) + { + return $this[$key]; + } + + /** + * Dynamically set container services. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function __set($key, $value) + { + $this[$key] = $value; + } +} diff --git a/vendor/illuminate/container/ContextualBindingBuilder.php b/vendor/illuminate/container/ContextualBindingBuilder.php new file mode 100644 index 0000000..1d15dcd --- /dev/null +++ b/vendor/illuminate/container/ContextualBindingBuilder.php @@ -0,0 +1,98 @@ +concrete = $concrete; + $this->container = $container; + } + + /** + * Define the abstract target that depends on the context. + * + * @param string $abstract + * @return $this + */ + public function needs($abstract) + { + $this->needs = $abstract; + + return $this; + } + + /** + * Define the implementation for the contextual binding. + * + * @param \Closure|string|array $implementation + * @return void + */ + public function give($implementation) + { + foreach (Util::arrayWrap($this->concrete) as $concrete) { + $this->container->addContextualBinding($concrete, $this->needs, $implementation); + } + } + + /** + * Define tagged services to be used as the implementation for the contextual binding. + * + * @param string $tag + * @return void + */ + public function giveTagged($tag) + { + $this->give(function ($container) use ($tag) { + $taggedServices = $container->tagged($tag); + + return is_array($taggedServices) ? $taggedServices : iterator_to_array($taggedServices); + }); + } + + /** + * Specify the configuration item to bind as a primitive. + * + * @param string $key + * @param ?string $default + * @return void + */ + public function giveConfig($key, $default = null) + { + $this->give(function ($container) use ($key, $default) { + return $container->get('config')->get($key, $default); + }); + } +} diff --git a/vendor/illuminate/container/EntryNotFoundException.php b/vendor/illuminate/container/EntryNotFoundException.php new file mode 100644 index 0000000..4266921 --- /dev/null +++ b/vendor/illuminate/container/EntryNotFoundException.php @@ -0,0 +1,11 @@ +count = $count; + $this->generator = $generator; + } + + /** + * Get an iterator from the generator. + * + * @return mixed + */ + #[\ReturnTypeWillChange] + public function getIterator() + { + return ($this->generator)(); + } + + /** + * Get the total number of tagged services. + * + * @return int + */ + #[\ReturnTypeWillChange] + public function count() + { + if (is_callable($count = $this->count)) { + $this->count = $count(); + } + + return $this->count; + } +} diff --git a/vendor/illuminate/container/Util.php b/vendor/illuminate/container/Util.php new file mode 100644 index 0000000..8f7e917 --- /dev/null +++ b/vendor/illuminate/container/Util.php @@ -0,0 +1,73 @@ +getType(); + + if (! $type instanceof ReflectionNamedType || $type->isBuiltin()) { + return null; + } + + $name = $type->getName(); + + if (! is_null($class = $parameter->getDeclaringClass())) { + if ($name === 'self') { + return $class->getName(); + } + + if ($name === 'parent' && $parent = $class->getParentClass()) { + return $parent->getName(); + } + } + + return $name; + } +} diff --git a/vendor/illuminate/contracts/Auth/Access/Authorizable.php b/vendor/illuminate/contracts/Auth/Access/Authorizable.php new file mode 100644 index 0000000..cedeb6e --- /dev/null +++ b/vendor/illuminate/contracts/Auth/Access/Authorizable.php @@ -0,0 +1,15 @@ +id = $id; + $this->class = $class; + $this->relations = $relations; + $this->connection = $connection; + } +} diff --git a/vendor/illuminate/contracts/Debug/ExceptionHandler.php b/vendor/illuminate/contracts/Debug/ExceptionHandler.php new file mode 100644 index 0000000..54381a1 --- /dev/null +++ b/vendor/illuminate/contracts/Debug/ExceptionHandler.php @@ -0,0 +1,46 @@ +setupContainer($container ?: new Container); + + // Once we have the container setup, we will setup the default configuration + // options in the container "config" binding. This will make the database + // manager work correctly out of the box without extreme configuration. + $this->setupDefaultConfiguration(); + + $this->setupManager(); + } + + /** + * Setup the default database configuration options. + * + * @return void + */ + protected function setupDefaultConfiguration() + { + $this->container['config']['database.fetch'] = PDO::FETCH_OBJ; + + $this->container['config']['database.default'] = 'default'; + } + + /** + * Build the database manager instance. + * + * @return void + */ + protected function setupManager() + { + $factory = new ConnectionFactory($this->container); + + $this->manager = new DatabaseManager($this->container, $factory); + } + + /** + * Get a connection instance from the global manager. + * + * @param string|null $connection + * @return \Illuminate\Database\Connection + */ + public static function connection($connection = null) + { + return static::$instance->getConnection($connection); + } + + /** + * Get a fluent query builder instance. + * + * @param \Closure|\Illuminate\Database\Query\Builder|string $table + * @param string|null $as + * @param string|null $connection + * @return \Illuminate\Database\Query\Builder + */ + public static function table($table, $as = null, $connection = null) + { + return static::$instance->connection($connection)->table($table, $as); + } + + /** + * Get a schema builder instance. + * + * @param string|null $connection + * @return \Illuminate\Database\Schema\Builder + */ + public static function schema($connection = null) + { + return static::$instance->connection($connection)->getSchemaBuilder(); + } + + /** + * Get a registered connection instance. + * + * @param string|null $name + * @return \Illuminate\Database\Connection + */ + public function getConnection($name = null) + { + return $this->manager->connection($name); + } + + /** + * Register a connection with the manager. + * + * @param array $config + * @param string $name + * @return void + */ + public function addConnection(array $config, $name = 'default') + { + $connections = $this->container['config']['database.connections']; + + $connections[$name] = $config; + + $this->container['config']['database.connections'] = $connections; + } + + /** + * Bootstrap Eloquent so it is ready for usage. + * + * @return void + */ + public function bootEloquent() + { + Eloquent::setConnectionResolver($this->manager); + + // If we have an event dispatcher instance, we will go ahead and register it + // with the Eloquent ORM, allowing for model callbacks while creating and + // updating "model" instances; however, it is not necessary to operate. + if ($dispatcher = $this->getEventDispatcher()) { + Eloquent::setEventDispatcher($dispatcher); + } + } + + /** + * Set the fetch mode for the database connections. + * + * @param int $fetchMode + * @return $this + */ + public function setFetchMode($fetchMode) + { + $this->container['config']['database.fetch'] = $fetchMode; + + return $this; + } + + /** + * Get the database manager instance. + * + * @return \Illuminate\Database\DatabaseManager + */ + public function getDatabaseManager() + { + return $this->manager; + } + + /** + * Get the current event dispatcher instance. + * + * @return \Illuminate\Contracts\Events\Dispatcher|null + */ + public function getEventDispatcher() + { + if ($this->container->bound('events')) { + return $this->container['events']; + } + } + + /** + * Set the event dispatcher instance to be used by connections. + * + * @param \Illuminate\Contracts\Events\Dispatcher $dispatcher + * @return void + */ + public function setEventDispatcher(Dispatcher $dispatcher) + { + $this->container->instance('events', $dispatcher); + } + + /** + * Dynamically pass methods to the default connection. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public static function __callStatic($method, $parameters) + { + return static::connection()->$method(...$parameters); + } +} diff --git a/vendor/illuminate/database/ClassMorphViolationException.php b/vendor/illuminate/database/ClassMorphViolationException.php new file mode 100644 index 0000000..6594d2d --- /dev/null +++ b/vendor/illuminate/database/ClassMorphViolationException.php @@ -0,0 +1,29 @@ +model = $class; + } +} diff --git a/vendor/illuminate/database/Concerns/BuildsQueries.php b/vendor/illuminate/database/Concerns/BuildsQueries.php new file mode 100644 index 0000000..7a49d77 --- /dev/null +++ b/vendor/illuminate/database/Concerns/BuildsQueries.php @@ -0,0 +1,496 @@ +enforceOrderBy(); + + $page = 1; + + do { + // We'll execute the query for the given page and get the results. If there are + // no results we can just break and return from here. When there are results + // we will call the callback with the current chunk of these results here. + $results = $this->forPage($page, $count)->get(); + + $countResults = $results->count(); + + if ($countResults == 0) { + break; + } + + // On each chunk result set, we will pass them to the callback and then let the + // developer take care of everything within the callback, which allows us to + // keep the memory low for spinning through large result sets for working. + if ($callback($results, $page) === false) { + return false; + } + + unset($results); + + $page++; + } while ($countResults == $count); + + return true; + } + + /** + * Run a map over each item while chunking. + * + * @param callable $callback + * @param int $count + * @return \Illuminate\Support\Collection + */ + public function chunkMap(callable $callback, $count = 1000) + { + $collection = Collection::make(); + + $this->chunk($count, function ($items) use ($collection, $callback) { + $items->each(function ($item) use ($collection, $callback) { + $collection->push($callback($item)); + }); + }); + + return $collection; + } + + /** + * Execute a callback over each item while chunking. + * + * @param callable $callback + * @param int $count + * @return bool + * + * @throws \RuntimeException + */ + public function each(callable $callback, $count = 1000) + { + return $this->chunk($count, function ($results) use ($callback) { + foreach ($results as $key => $value) { + if ($callback($value, $key) === false) { + return false; + } + } + }); + } + + /** + * Chunk the results of a query by comparing IDs. + * + * @param int $count + * @param callable $callback + * @param string|null $column + * @param string|null $alias + * @return bool + */ + public function chunkById($count, callable $callback, $column = null, $alias = null) + { + $column = $column ?? $this->defaultKeyName(); + + $alias = $alias ?? $column; + + $lastId = null; + + $page = 1; + + do { + $clone = clone $this; + + // We'll execute the query for the given page and get the results. If there are + // no results we can just break and return from here. When there are results + // we will call the callback with the current chunk of these results here. + $results = $clone->forPageAfterId($count, $lastId, $column)->get(); + + $countResults = $results->count(); + + if ($countResults == 0) { + break; + } + + // On each chunk result set, we will pass them to the callback and then let the + // developer take care of everything within the callback, which allows us to + // keep the memory low for spinning through large result sets for working. + if ($callback($results, $page) === false) { + return false; + } + + $lastId = $results->last()->{$alias}; + + if ($lastId === null) { + throw new RuntimeException("The chunkById operation was aborted because the [{$alias}] column is not present in the query result."); + } + + unset($results); + + $page++; + } while ($countResults == $count); + + return true; + } + + /** + * Execute a callback over each item while chunking by ID. + * + * @param callable $callback + * @param int $count + * @param string|null $column + * @param string|null $alias + * @return bool + */ + public function eachById(callable $callback, $count = 1000, $column = null, $alias = null) + { + return $this->chunkById($count, function ($results, $page) use ($callback, $count) { + foreach ($results as $key => $value) { + if ($callback($value, (($page - 1) * $count) + $key) === false) { + return false; + } + } + }, $column, $alias); + } + + /** + * Query lazily, by chunks of the given size. + * + * @param int $chunkSize + * @return \Illuminate\Support\LazyCollection + * + * @throws \InvalidArgumentException + */ + public function lazy($chunkSize = 1000) + { + if ($chunkSize < 1) { + throw new InvalidArgumentException('The chunk size should be at least 1'); + } + + $this->enforceOrderBy(); + + return LazyCollection::make(function () use ($chunkSize) { + $page = 1; + + while (true) { + $results = $this->forPage($page++, $chunkSize)->get(); + + foreach ($results as $result) { + yield $result; + } + + if ($results->count() < $chunkSize) { + return; + } + } + }); + } + + /** + * Query lazily, by chunking the results of a query by comparing IDs. + * + * @param int $chunkSize + * @param string|null $column + * @param string|null $alias + * @return \Illuminate\Support\LazyCollection + * + * @throws \InvalidArgumentException + */ + public function lazyById($chunkSize = 1000, $column = null, $alias = null) + { + return $this->orderedLazyById($chunkSize, $column, $alias); + } + + /** + * Query lazily, by chunking the results of a query by comparing IDs in descending order. + * + * @param int $chunkSize + * @param string|null $column + * @param string|null $alias + * @return \Illuminate\Support\LazyCollection + * + * @throws \InvalidArgumentException + */ + public function lazyByIdDesc($chunkSize = 1000, $column = null, $alias = null) + { + return $this->orderedLazyById($chunkSize, $column, $alias, true); + } + + /** + * Query lazily, by chunking the results of a query by comparing IDs in a given order. + * + * @param int $chunkSize + * @param string|null $column + * @param string|null $alias + * @param bool $descending + * @return \Illuminate\Support\LazyCollection + * + * @throws \InvalidArgumentException + */ + protected function orderedLazyById($chunkSize = 1000, $column = null, $alias = null, $descending = false) + { + if ($chunkSize < 1) { + throw new InvalidArgumentException('The chunk size should be at least 1'); + } + + $column = $column ?? $this->defaultKeyName(); + + $alias = $alias ?? $column; + + return LazyCollection::make(function () use ($chunkSize, $column, $alias, $descending) { + $lastId = null; + + while (true) { + $clone = clone $this; + + if ($descending) { + $results = $clone->forPageBeforeId($chunkSize, $lastId, $column)->get(); + } else { + $results = $clone->forPageAfterId($chunkSize, $lastId, $column)->get(); + } + + foreach ($results as $result) { + yield $result; + } + + if ($results->count() < $chunkSize) { + return; + } + + $lastId = $results->last()->{$alias}; + } + }); + } + + /** + * Execute the query and get the first result. + * + * @param array|string $columns + * @return \Illuminate\Database\Eloquent\Model|object|static|null + */ + public function first($columns = ['*']) + { + return $this->take(1)->get($columns)->first(); + } + + /** + * Execute the query and get the first result if it's the sole matching record. + * + * @param array|string $columns + * @return \Illuminate\Database\Eloquent\Model|object|static|null + * + * @throws \Illuminate\Database\RecordsNotFoundException + * @throws \Illuminate\Database\MultipleRecordsFoundException + */ + public function sole($columns = ['*']) + { + $result = $this->take(2)->get($columns); + + if ($result->isEmpty()) { + throw new RecordsNotFoundException; + } + + if ($result->count() > 1) { + throw new MultipleRecordsFoundException; + } + + return $result->first(); + } + + /** + * Paginate the given query using a cursor paginator. + * + * @param int $perPage + * @param array $columns + * @param string $cursorName + * @param \Illuminate\Pagination\Cursor|string|null $cursor + * @return \Illuminate\Contracts\Pagination\CursorPaginator + */ + protected function paginateUsingCursor($perPage, $columns = ['*'], $cursorName = 'cursor', $cursor = null) + { + if (! $cursor instanceof Cursor) { + $cursor = is_string($cursor) + ? Cursor::fromEncoded($cursor) + : CursorPaginator::resolveCurrentCursor($cursorName, $cursor); + } + + $orders = $this->ensureOrderForCursorPagination(! is_null($cursor) && $cursor->pointsToPreviousItems()); + + if (! is_null($cursor)) { + $addCursorConditions = function (self $builder, $previousColumn, $i) use (&$addCursorConditions, $cursor, $orders) { + $unionBuilders = isset($builder->unions) ? collect($builder->unions)->pluck('query') : collect(); + + if (! is_null($previousColumn)) { + $builder->where( + $this->getOriginalColumnNameForCursorPagination($this, $previousColumn), + '=', + $cursor->parameter($previousColumn) + ); + + $unionBuilders->each(function ($unionBuilder) use ($previousColumn, $cursor) { + $unionBuilder->where( + $this->getOriginalColumnNameForCursorPagination($this, $previousColumn), + '=', + $cursor->parameter($previousColumn) + ); + + $this->addBinding($unionBuilder->getRawBindings()['where'], 'union'); + }); + } + + $builder->where(function (self $builder) use ($addCursorConditions, $cursor, $orders, $i, $unionBuilders) { + ['column' => $column, 'direction' => $direction] = $orders[$i]; + + $builder->where( + $this->getOriginalColumnNameForCursorPagination($this, $column), + $direction === 'asc' ? '>' : '<', + $cursor->parameter($column) + ); + + if ($i < $orders->count() - 1) { + $builder->orWhere(function (self $builder) use ($addCursorConditions, $column, $i) { + $addCursorConditions($builder, $column, $i + 1); + }); + } + + $unionBuilders->each(function ($unionBuilder) use ($column, $direction, $cursor, $i, $orders, $addCursorConditions) { + $unionBuilder->where(function ($unionBuilder) use ($column, $direction, $cursor, $i, $orders, $addCursorConditions) { + $unionBuilder->where( + $this->getOriginalColumnNameForCursorPagination($this, $column), + $direction === 'asc' ? '>' : '<', + $cursor->parameter($column) + ); + + if ($i < $orders->count() - 1) { + $unionBuilder->orWhere(function (self $builder) use ($addCursorConditions, $column, $i) { + $addCursorConditions($builder, $column, $i + 1); + }); + } + + $this->addBinding($unionBuilder->getRawBindings()['where'], 'union'); + }); + }); + }); + }; + + $addCursorConditions($this, null, 0); + } + + $this->limit($perPage + 1); + + return $this->cursorPaginator($this->get($columns), $perPage, $cursor, [ + 'path' => Paginator::resolveCurrentPath(), + 'cursorName' => $cursorName, + 'parameters' => $orders->pluck('column')->toArray(), + ]); + } + + /** + * Get the original column name of the given column, without any aliasing. + * + * @param \Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder $builder + * @param string $parameter + * @return string + */ + protected function getOriginalColumnNameForCursorPagination($builder, string $parameter) + { + $columns = $builder instanceof Builder ? $builder->getQuery()->columns : $builder->columns; + + if (! is_null($columns)) { + foreach ($columns as $column) { + if (($position = stripos($column, ' as ')) !== false) { + $as = substr($column, $position, 4); + + [$original, $alias] = explode($as, $column); + + if ($parameter === $alias) { + return $original; + } + } + } + } + + return $parameter; + } + + /** + * Create a new length-aware paginator instance. + * + * @param \Illuminate\Support\Collection $items + * @param int $total + * @param int $perPage + * @param int $currentPage + * @param array $options + * @return \Illuminate\Pagination\LengthAwarePaginator + */ + protected function paginator($items, $total, $perPage, $currentPage, $options) + { + return Container::getInstance()->makeWith(LengthAwarePaginator::class, compact( + 'items', 'total', 'perPage', 'currentPage', 'options' + )); + } + + /** + * Create a new simple paginator instance. + * + * @param \Illuminate\Support\Collection $items + * @param int $perPage + * @param int $currentPage + * @param array $options + * @return \Illuminate\Pagination\Paginator + */ + protected function simplePaginator($items, $perPage, $currentPage, $options) + { + return Container::getInstance()->makeWith(Paginator::class, compact( + 'items', 'perPage', 'currentPage', 'options' + )); + } + + /** + * Create a new cursor paginator instance. + * + * @param \Illuminate\Support\Collection $items + * @param int $perPage + * @param \Illuminate\Pagination\Cursor $cursor + * @param array $options + * @return \Illuminate\Pagination\CursorPaginator + */ + protected function cursorPaginator($items, $perPage, $cursor, $options) + { + return Container::getInstance()->makeWith(CursorPaginator::class, compact( + 'items', 'perPage', 'cursor', 'options' + )); + } + + /** + * Pass the query to a given callback. + * + * @param callable $callback + * @return $this|mixed + */ + public function tap($callback) + { + return $this->when(true, $callback); + } +} diff --git a/vendor/illuminate/database/Concerns/ExplainsQueries.php b/vendor/illuminate/database/Concerns/ExplainsQueries.php new file mode 100644 index 0000000..7168de1 --- /dev/null +++ b/vendor/illuminate/database/Concerns/ExplainsQueries.php @@ -0,0 +1,24 @@ +toSql(); + + $bindings = $this->getBindings(); + + $explanation = $this->getConnection()->select('EXPLAIN '.$sql, $bindings); + + return new Collection($explanation); + } +} diff --git a/vendor/illuminate/database/Concerns/ManagesTransactions.php b/vendor/illuminate/database/Concerns/ManagesTransactions.php new file mode 100644 index 0000000..fac7029 --- /dev/null +++ b/vendor/illuminate/database/Concerns/ManagesTransactions.php @@ -0,0 +1,334 @@ +beginTransaction(); + + // We'll simply execute the given callback within a try / catch block and if we + // catch any exception we can rollback this transaction so that none of this + // gets actually persisted to a database or stored in a permanent fashion. + try { + $callbackResult = $callback($this); + } + + // If we catch an exception we'll rollback this transaction and try again if we + // are not out of attempts. If we are out of attempts we will just throw the + // exception back out and let the developer handle an uncaught exceptions. + catch (Throwable $e) { + $this->handleTransactionException( + $e, $currentAttempt, $attempts + ); + + continue; + } + + try { + if ($this->transactions == 1) { + $this->getPdo()->commit(); + } + + $this->transactions = max(0, $this->transactions - 1); + + if ($this->transactions == 0) { + optional($this->transactionsManager)->commit($this->getName()); + } + } catch (Throwable $e) { + $this->handleCommitTransactionException( + $e, $currentAttempt, $attempts + ); + + continue; + } + + $this->fireConnectionEvent('committed'); + + return $callbackResult; + } + } + + /** + * Handle an exception encountered when running a transacted statement. + * + * @param \Throwable $e + * @param int $currentAttempt + * @param int $maxAttempts + * @return void + * + * @throws \Throwable + */ + protected function handleTransactionException(Throwable $e, $currentAttempt, $maxAttempts) + { + // On a deadlock, MySQL rolls back the entire transaction so we can't just + // retry the query. We have to throw this exception all the way out and + // let the developer handle it in another way. We will decrement too. + if ($this->causedByConcurrencyError($e) && + $this->transactions > 1) { + $this->transactions--; + + optional($this->transactionsManager)->rollback( + $this->getName(), $this->transactions + ); + + throw $e; + } + + // If there was an exception we will rollback this transaction and then we + // can check if we have exceeded the maximum attempt count for this and + // if we haven't we will return and try this query again in our loop. + $this->rollBack(); + + if ($this->causedByConcurrencyError($e) && + $currentAttempt < $maxAttempts) { + return; + } + + throw $e; + } + + /** + * Start a new database transaction. + * + * @return void + * + * @throws \Throwable + */ + public function beginTransaction() + { + $this->createTransaction(); + + $this->transactions++; + + optional($this->transactionsManager)->begin( + $this->getName(), $this->transactions + ); + + $this->fireConnectionEvent('beganTransaction'); + } + + /** + * Create a transaction within the database. + * + * @return void + * + * @throws \Throwable + */ + protected function createTransaction() + { + if ($this->transactions == 0) { + $this->reconnectIfMissingConnection(); + + try { + $this->getPdo()->beginTransaction(); + } catch (Throwable $e) { + $this->handleBeginTransactionException($e); + } + } elseif ($this->transactions >= 1 && $this->queryGrammar->supportsSavepoints()) { + $this->createSavepoint(); + } + } + + /** + * Create a save point within the database. + * + * @return void + * + * @throws \Throwable + */ + protected function createSavepoint() + { + $this->getPdo()->exec( + $this->queryGrammar->compileSavepoint('trans'.($this->transactions + 1)) + ); + } + + /** + * Handle an exception from a transaction beginning. + * + * @param \Throwable $e + * @return void + * + * @throws \Throwable + */ + protected function handleBeginTransactionException(Throwable $e) + { + if ($this->causedByLostConnection($e)) { + $this->reconnect(); + + $this->getPdo()->beginTransaction(); + } else { + throw $e; + } + } + + /** + * Commit the active database transaction. + * + * @return void + * + * @throws \Throwable + */ + public function commit() + { + if ($this->transactions == 1) { + $this->getPdo()->commit(); + } + + $this->transactions = max(0, $this->transactions - 1); + + if ($this->transactions == 0) { + optional($this->transactionsManager)->commit($this->getName()); + } + + $this->fireConnectionEvent('committed'); + } + + /** + * Handle an exception encountered when committing a transaction. + * + * @param \Throwable $e + * @param int $currentAttempt + * @param int $maxAttempts + * @return void + * + * @throws \Throwable + */ + protected function handleCommitTransactionException(Throwable $e, $currentAttempt, $maxAttempts) + { + $this->transactions = max(0, $this->transactions - 1); + + if ($this->causedByConcurrencyError($e) && + $currentAttempt < $maxAttempts) { + return; + } + + if ($this->causedByLostConnection($e)) { + $this->transactions = 0; + } + + throw $e; + } + + /** + * Rollback the active database transaction. + * + * @param int|null $toLevel + * @return void + * + * @throws \Throwable + */ + public function rollBack($toLevel = null) + { + // We allow developers to rollback to a certain transaction level. We will verify + // that this given transaction level is valid before attempting to rollback to + // that level. If it's not we will just return out and not attempt anything. + $toLevel = is_null($toLevel) + ? $this->transactions - 1 + : $toLevel; + + if ($toLevel < 0 || $toLevel >= $this->transactions) { + return; + } + + // Next, we will actually perform this rollback within this database and fire the + // rollback event. We will also set the current transaction level to the given + // level that was passed into this method so it will be right from here out. + try { + $this->performRollBack($toLevel); + } catch (Throwable $e) { + $this->handleRollBackException($e); + } + + $this->transactions = $toLevel; + + optional($this->transactionsManager)->rollback( + $this->getName(), $this->transactions + ); + + $this->fireConnectionEvent('rollingBack'); + } + + /** + * Perform a rollback within the database. + * + * @param int $toLevel + * @return void + * + * @throws \Throwable + */ + protected function performRollBack($toLevel) + { + if ($toLevel == 0) { + $this->getPdo()->rollBack(); + } elseif ($this->queryGrammar->supportsSavepoints()) { + $this->getPdo()->exec( + $this->queryGrammar->compileSavepointRollBack('trans'.($toLevel + 1)) + ); + } + } + + /** + * Handle an exception from a rollback. + * + * @param \Throwable $e + * @return void + * + * @throws \Throwable + */ + protected function handleRollBackException(Throwable $e) + { + if ($this->causedByLostConnection($e)) { + $this->transactions = 0; + + optional($this->transactionsManager)->rollback( + $this->getName(), $this->transactions + ); + } + + throw $e; + } + + /** + * Get the number of active transactions. + * + * @return int + */ + public function transactionLevel() + { + return $this->transactions; + } + + /** + * Execute the callback after a transaction commits. + * + * @param callable $callback + * @return void + * + * @throws \RuntimeException + */ + public function afterCommit($callback) + { + if ($this->transactionsManager) { + return $this->transactionsManager->addCallback($callback); + } + + throw new RuntimeException('Transactions Manager has not been set.'); + } +} diff --git a/vendor/illuminate/database/ConfigurationUrlParser.php b/vendor/illuminate/database/ConfigurationUrlParser.php new file mode 100644 index 0000000..bc7c624 --- /dev/null +++ b/vendor/illuminate/database/ConfigurationUrlParser.php @@ -0,0 +1,10 @@ +pdo = $pdo; + + // First we will setup the default properties. We keep track of the DB + // name we are connected to since it is needed when some reflective + // type commands are run such as checking whether a table exists. + $this->database = $database; + + $this->tablePrefix = $tablePrefix; + + $this->config = $config; + + // We need to initialize a query grammar and the query post processors + // which are both very important parts of the database abstractions + // so we initialize these to their default values while starting. + $this->useDefaultQueryGrammar(); + + $this->useDefaultPostProcessor(); + } + + /** + * Set the query grammar to the default implementation. + * + * @return void + */ + public function useDefaultQueryGrammar() + { + $this->queryGrammar = $this->getDefaultQueryGrammar(); + } + + /** + * Get the default query grammar instance. + * + * @return \Illuminate\Database\Query\Grammars\Grammar + */ + protected function getDefaultQueryGrammar() + { + return new QueryGrammar; + } + + /** + * Set the schema grammar to the default implementation. + * + * @return void + */ + public function useDefaultSchemaGrammar() + { + $this->schemaGrammar = $this->getDefaultSchemaGrammar(); + } + + /** + * Get the default schema grammar instance. + * + * @return \Illuminate\Database\Schema\Grammars\Grammar + */ + protected function getDefaultSchemaGrammar() + { + // + } + + /** + * Set the query post processor to the default implementation. + * + * @return void + */ + public function useDefaultPostProcessor() + { + $this->postProcessor = $this->getDefaultPostProcessor(); + } + + /** + * Get the default post processor instance. + * + * @return \Illuminate\Database\Query\Processors\Processor + */ + protected function getDefaultPostProcessor() + { + return new Processor; + } + + /** + * Get a schema builder instance for the connection. + * + * @return \Illuminate\Database\Schema\Builder + */ + public function getSchemaBuilder() + { + if (is_null($this->schemaGrammar)) { + $this->useDefaultSchemaGrammar(); + } + + return new SchemaBuilder($this); + } + + /** + * Begin a fluent query against a database table. + * + * @param \Closure|\Illuminate\Database\Query\Builder|string $table + * @param string|null $as + * @return \Illuminate\Database\Query\Builder + */ + public function table($table, $as = null) + { + return $this->query()->from($table, $as); + } + + /** + * Get a new query builder instance. + * + * @return \Illuminate\Database\Query\Builder + */ + public function query() + { + return new QueryBuilder( + $this, $this->getQueryGrammar(), $this->getPostProcessor() + ); + } + + /** + * Run a select statement and return a single result. + * + * @param string $query + * @param array $bindings + * @param bool $useReadPdo + * @return mixed + */ + public function selectOne($query, $bindings = [], $useReadPdo = true) + { + $records = $this->select($query, $bindings, $useReadPdo); + + return array_shift($records); + } + + /** + * Run a select statement against the database. + * + * @param string $query + * @param array $bindings + * @return array + */ + public function selectFromWriteConnection($query, $bindings = []) + { + return $this->select($query, $bindings, false); + } + + /** + * Run a select statement against the database. + * + * @param string $query + * @param array $bindings + * @param bool $useReadPdo + * @return array + */ + public function select($query, $bindings = [], $useReadPdo = true) + { + return $this->run($query, $bindings, function ($query, $bindings) use ($useReadPdo) { + if ($this->pretending()) { + return []; + } + + // For select statements, we'll simply execute the query and return an array + // of the database result set. Each element in the array will be a single + // row from the database table, and will either be an array or objects. + $statement = $this->prepared( + $this->getPdoForSelect($useReadPdo)->prepare($query) + ); + + $this->bindValues($statement, $this->prepareBindings($bindings)); + + $statement->execute(); + + return $statement->fetchAll(); + }); + } + + /** + * Run a select statement against the database and returns a generator. + * + * @param string $query + * @param array $bindings + * @param bool $useReadPdo + * @return \Generator + */ + public function cursor($query, $bindings = [], $useReadPdo = true) + { + $statement = $this->run($query, $bindings, function ($query, $bindings) use ($useReadPdo) { + if ($this->pretending()) { + return []; + } + + // First we will create a statement for the query. Then, we will set the fetch + // mode and prepare the bindings for the query. Once that's done we will be + // ready to execute the query against the database and return the cursor. + $statement = $this->prepared($this->getPdoForSelect($useReadPdo) + ->prepare($query)); + + $this->bindValues( + $statement, $this->prepareBindings($bindings) + ); + + // Next, we'll execute the query against the database and return the statement + // so we can return the cursor. The cursor will use a PHP generator to give + // back one row at a time without using a bunch of memory to render them. + $statement->execute(); + + return $statement; + }); + + while ($record = $statement->fetch()) { + yield $record; + } + } + + /** + * Configure the PDO prepared statement. + * + * @param \PDOStatement $statement + * @return \PDOStatement + */ + protected function prepared(PDOStatement $statement) + { + $statement->setFetchMode($this->fetchMode); + + $this->event(new StatementPrepared( + $this, $statement + )); + + return $statement; + } + + /** + * Get the PDO connection to use for a select query. + * + * @param bool $useReadPdo + * @return \PDO + */ + protected function getPdoForSelect($useReadPdo = true) + { + return $useReadPdo ? $this->getReadPdo() : $this->getPdo(); + } + + /** + * Run an insert statement against the database. + * + * @param string $query + * @param array $bindings + * @return bool + */ + public function insert($query, $bindings = []) + { + return $this->statement($query, $bindings); + } + + /** + * Run an update statement against the database. + * + * @param string $query + * @param array $bindings + * @return int + */ + public function update($query, $bindings = []) + { + return $this->affectingStatement($query, $bindings); + } + + /** + * Run a delete statement against the database. + * + * @param string $query + * @param array $bindings + * @return int + */ + public function delete($query, $bindings = []) + { + return $this->affectingStatement($query, $bindings); + } + + /** + * Execute an SQL statement and return the boolean result. + * + * @param string $query + * @param array $bindings + * @return bool + */ + public function statement($query, $bindings = []) + { + return $this->run($query, $bindings, function ($query, $bindings) { + if ($this->pretending()) { + return true; + } + + $statement = $this->getPdo()->prepare($query); + + $this->bindValues($statement, $this->prepareBindings($bindings)); + + $this->recordsHaveBeenModified(); + + return $statement->execute(); + }); + } + + /** + * Run an SQL statement and get the number of rows affected. + * + * @param string $query + * @param array $bindings + * @return int + */ + public function affectingStatement($query, $bindings = []) + { + return $this->run($query, $bindings, function ($query, $bindings) { + if ($this->pretending()) { + return 0; + } + + // For update or delete statements, we want to get the number of rows affected + // by the statement and return that back to the developer. We'll first need + // to execute the statement and then we'll use PDO to fetch the affected. + $statement = $this->getPdo()->prepare($query); + + $this->bindValues($statement, $this->prepareBindings($bindings)); + + $statement->execute(); + + $this->recordsHaveBeenModified( + ($count = $statement->rowCount()) > 0 + ); + + return $count; + }); + } + + /** + * Run a raw, unprepared query against the PDO connection. + * + * @param string $query + * @return bool + */ + public function unprepared($query) + { + return $this->run($query, [], function ($query) { + if ($this->pretending()) { + return true; + } + + $this->recordsHaveBeenModified( + $change = $this->getPdo()->exec($query) !== false + ); + + return $change; + }); + } + + /** + * Execute the given callback in "dry run" mode. + * + * @param \Closure $callback + * @return array + */ + public function pretend(Closure $callback) + { + return $this->withFreshQueryLog(function () use ($callback) { + $this->pretending = true; + + // Basically to make the database connection "pretend", we will just return + // the default values for all the query methods, then we will return an + // array of queries that were "executed" within the Closure callback. + $callback($this); + + $this->pretending = false; + + return $this->queryLog; + }); + } + + /** + * Execute the given callback in "dry run" mode. + * + * @param \Closure $callback + * @return array + */ + protected function withFreshQueryLog($callback) + { + $loggingQueries = $this->loggingQueries; + + // First we will back up the value of the logging queries property and then + // we'll be ready to run callbacks. This query log will also get cleared + // so we will have a new log of all the queries that are executed now. + $this->enableQueryLog(); + + $this->queryLog = []; + + // Now we'll execute this callback and capture the result. Once it has been + // executed we will restore the value of query logging and give back the + // value of the callback so the original callers can have the results. + $result = $callback(); + + $this->loggingQueries = $loggingQueries; + + return $result; + } + + /** + * Bind values to their parameters in the given statement. + * + * @param \PDOStatement $statement + * @param array $bindings + * @return void + */ + public function bindValues($statement, $bindings) + { + foreach ($bindings as $key => $value) { + $statement->bindValue( + is_string($key) ? $key : $key + 1, + $value, + is_int($value) ? PDO::PARAM_INT : PDO::PARAM_STR + ); + } + } + + /** + * Prepare the query bindings for execution. + * + * @param array $bindings + * @return array + */ + public function prepareBindings(array $bindings) + { + $grammar = $this->getQueryGrammar(); + + foreach ($bindings as $key => $value) { + // We need to transform all instances of DateTimeInterface into the actual + // date string. Each query grammar maintains its own date string format + // so we'll just ask the grammar for the format to get from the date. + if ($value instanceof DateTimeInterface) { + $bindings[$key] = $value->format($grammar->getDateFormat()); + } elseif (is_bool($value)) { + $bindings[$key] = (int) $value; + } + } + + return $bindings; + } + + /** + * Run a SQL statement and log its execution context. + * + * @param string $query + * @param array $bindings + * @param \Closure $callback + * @return mixed + * + * @throws \Illuminate\Database\QueryException + */ + protected function run($query, $bindings, Closure $callback) + { + foreach ($this->beforeExecutingCallbacks as $beforeExecutingCallback) { + $beforeExecutingCallback($query, $bindings, $this); + } + + $this->reconnectIfMissingConnection(); + + $start = microtime(true); + + // Here we will run this query. If an exception occurs we'll determine if it was + // caused by a connection that has been lost. If that is the cause, we'll try + // to re-establish connection and re-run the query with a fresh connection. + try { + $result = $this->runQueryCallback($query, $bindings, $callback); + } catch (QueryException $e) { + $result = $this->handleQueryException( + $e, $query, $bindings, $callback + ); + } + + // Once we have run the query we will calculate the time that it took to run and + // then log the query, bindings, and execution time so we will report them on + // the event that the developer needs them. We'll log time in milliseconds. + $this->logQuery( + $query, $bindings, $this->getElapsedTime($start) + ); + + return $result; + } + + /** + * Run a SQL statement. + * + * @param string $query + * @param array $bindings + * @param \Closure $callback + * @return mixed + * + * @throws \Illuminate\Database\QueryException + */ + protected function runQueryCallback($query, $bindings, Closure $callback) + { + // To execute the statement, we'll simply call the callback, which will actually + // run the SQL against the PDO connection. Then we can calculate the time it + // took to execute and log the query SQL, bindings and time in our memory. + try { + return $callback($query, $bindings); + } + + // If an exception occurs when attempting to run a query, we'll format the error + // message to include the bindings with SQL, which will make this exception a + // lot more helpful to the developer instead of just the database's errors. + catch (Exception $e) { + throw new QueryException( + $query, $this->prepareBindings($bindings), $e + ); + } + } + + /** + * Log a query in the connection's query log. + * + * @param string $query + * @param array $bindings + * @param float|null $time + * @return void + */ + public function logQuery($query, $bindings, $time = null) + { + $this->event(new QueryExecuted($query, $bindings, $time, $this)); + + if ($this->loggingQueries) { + $this->queryLog[] = compact('query', 'bindings', 'time'); + } + } + + /** + * Get the elapsed time since a given starting point. + * + * @param int $start + * @return float + */ + protected function getElapsedTime($start) + { + return round((microtime(true) - $start) * 1000, 2); + } + + /** + * Handle a query exception. + * + * @param \Illuminate\Database\QueryException $e + * @param string $query + * @param array $bindings + * @param \Closure $callback + * @return mixed + * + * @throws \Illuminate\Database\QueryException + */ + protected function handleQueryException(QueryException $e, $query, $bindings, Closure $callback) + { + if ($this->transactions >= 1) { + throw $e; + } + + return $this->tryAgainIfCausedByLostConnection( + $e, $query, $bindings, $callback + ); + } + + /** + * Handle a query exception that occurred during query execution. + * + * @param \Illuminate\Database\QueryException $e + * @param string $query + * @param array $bindings + * @param \Closure $callback + * @return mixed + * + * @throws \Illuminate\Database\QueryException + */ + protected function tryAgainIfCausedByLostConnection(QueryException $e, $query, $bindings, Closure $callback) + { + if ($this->causedByLostConnection($e->getPrevious())) { + $this->reconnect(); + + return $this->runQueryCallback($query, $bindings, $callback); + } + + throw $e; + } + + /** + * Reconnect to the database. + * + * @return void + * + * @throws \LogicException + */ + public function reconnect() + { + if (is_callable($this->reconnector)) { + $this->doctrineConnection = null; + + return call_user_func($this->reconnector, $this); + } + + throw new LogicException('Lost connection and no reconnector available.'); + } + + /** + * Reconnect to the database if a PDO connection is missing. + * + * @return void + */ + protected function reconnectIfMissingConnection() + { + if (is_null($this->pdo)) { + $this->reconnect(); + } + } + + /** + * Disconnect from the underlying PDO connection. + * + * @return void + */ + public function disconnect() + { + $this->setPdo(null)->setReadPdo(null); + + $this->doctrineConnection = null; + } + + /** + * Register a hook to be run just before a database query is executed. + * + * @param \Closure $callback + * @return $this + */ + public function beforeExecuting(Closure $callback) + { + $this->beforeExecutingCallbacks[] = $callback; + + return $this; + } + + /** + * Register a database query listener with the connection. + * + * @param \Closure $callback + * @return void + */ + public function listen(Closure $callback) + { + if (isset($this->events)) { + $this->events->listen(Events\QueryExecuted::class, $callback); + } + } + + /** + * Fire an event for this connection. + * + * @param string $event + * @return array|null + */ + protected function fireConnectionEvent($event) + { + if (! isset($this->events)) { + return; + } + + switch ($event) { + case 'beganTransaction': + return $this->events->dispatch(new TransactionBeginning($this)); + case 'committed': + return $this->events->dispatch(new TransactionCommitted($this)); + case 'rollingBack': + return $this->events->dispatch(new TransactionRolledBack($this)); + } + } + + /** + * Fire the given event if possible. + * + * @param mixed $event + * @return void + */ + protected function event($event) + { + if (isset($this->events)) { + $this->events->dispatch($event); + } + } + + /** + * Get a new raw query expression. + * + * @param mixed $value + * @return \Illuminate\Database\Query\Expression + */ + public function raw($value) + { + return new Expression($value); + } + + /** + * Determine if the database connection has modified any database records. + * + * @return bool + */ + public function hasModifiedRecords() + { + return $this->recordsModified; + } + + /** + * Indicate if any records have been modified. + * + * @param bool $value + * @return void + */ + public function recordsHaveBeenModified($value = true) + { + if (! $this->recordsModified) { + $this->recordsModified = $value; + } + } + + /** + * Set the record modification state. + * + * @param bool $value + * @return $this + */ + public function setRecordModificationState(bool $value) + { + $this->recordsModified = $value; + + return $this; + } + + /** + * Reset the record modification state. + * + * @return void + */ + public function forgetRecordModificationState() + { + $this->recordsModified = false; + } + + /** + * Indicate that the connection should use the write PDO connection for reads. + * + * @param bool $value + * @return $this + */ + public function useWriteConnectionWhenReading($value = true) + { + $this->readOnWriteConnection = $value; + + return $this; + } + + /** + * Is Doctrine available? + * + * @return bool + */ + public function isDoctrineAvailable() + { + return class_exists('Doctrine\DBAL\Connection'); + } + + /** + * Get a Doctrine Schema Column instance. + * + * @param string $table + * @param string $column + * @return \Doctrine\DBAL\Schema\Column + */ + public function getDoctrineColumn($table, $column) + { + $schema = $this->getDoctrineSchemaManager(); + + return $schema->listTableDetails($table)->getColumn($column); + } + + /** + * Get the Doctrine DBAL schema manager for the connection. + * + * @return \Doctrine\DBAL\Schema\AbstractSchemaManager + */ + public function getDoctrineSchemaManager() + { + $connection = $this->getDoctrineConnection(); + + // Doctrine v2 expects one parameter while v3 expects two. 2nd will be ignored on v2... + return $this->getDoctrineDriver()->getSchemaManager( + $connection, + $connection->getDatabasePlatform() + ); + } + + /** + * Get the Doctrine DBAL database connection instance. + * + * @return \Doctrine\DBAL\Connection + */ + public function getDoctrineConnection() + { + if (is_null($this->doctrineConnection)) { + $driver = $this->getDoctrineDriver(); + + $this->doctrineConnection = new DoctrineConnection(array_filter([ + 'pdo' => $this->getPdo(), + 'dbname' => $this->getDatabaseName(), + 'driver' => method_exists($driver, 'getName') ? $driver->getName() : null, + 'serverVersion' => $this->getConfig('server_version'), + ]), $driver); + + foreach ($this->doctrineTypeMappings as $name => $type) { + $this->doctrineConnection + ->getDatabasePlatform() + ->registerDoctrineTypeMapping($type, $name); + } + } + + return $this->doctrineConnection; + } + + /** + * Register a custom Doctrine mapping type. + * + * @param string $class + * @param string $name + * @param string $type + * @return void + * + * @throws \Doctrine\DBAL\DBALException + * @throws \RuntimeException + */ + public function registerDoctrineType(string $class, string $name, string $type): void + { + if (! $this->isDoctrineAvailable()) { + throw new RuntimeException( + 'Registering a custom Doctrine type requires Doctrine DBAL (doctrine/dbal).' + ); + } + + if (! Type::hasType($name)) { + Type::addType($name, $class); + } + + $this->doctrineTypeMappings[$name] = $type; + } + + /** + * Get the current PDO connection. + * + * @return \PDO + */ + public function getPdo() + { + if ($this->pdo instanceof Closure) { + return $this->pdo = call_user_func($this->pdo); + } + + return $this->pdo; + } + + /** + * Get the current PDO connection parameter without executing any reconnect logic. + * + * @return \PDO|\Closure|null + */ + public function getRawPdo() + { + return $this->pdo; + } + + /** + * Get the current PDO connection used for reading. + * + * @return \PDO + */ + public function getReadPdo() + { + if ($this->transactions > 0) { + return $this->getPdo(); + } + + if ($this->readOnWriteConnection || + ($this->recordsModified && $this->getConfig('sticky'))) { + return $this->getPdo(); + } + + if ($this->readPdo instanceof Closure) { + return $this->readPdo = call_user_func($this->readPdo); + } + + return $this->readPdo ?: $this->getPdo(); + } + + /** + * Get the current read PDO connection parameter without executing any reconnect logic. + * + * @return \PDO|\Closure|null + */ + public function getRawReadPdo() + { + return $this->readPdo; + } + + /** + * Set the PDO connection. + * + * @param \PDO|\Closure|null $pdo + * @return $this + */ + public function setPdo($pdo) + { + $this->transactions = 0; + + $this->pdo = $pdo; + + return $this; + } + + /** + * Set the PDO connection used for reading. + * + * @param \PDO|\Closure|null $pdo + * @return $this + */ + public function setReadPdo($pdo) + { + $this->readPdo = $pdo; + + return $this; + } + + /** + * Set the reconnect instance on the connection. + * + * @param callable $reconnector + * @return $this + */ + public function setReconnector(callable $reconnector) + { + $this->reconnector = $reconnector; + + return $this; + } + + /** + * Get the database connection name. + * + * @return string|null + */ + public function getName() + { + return $this->getConfig('name'); + } + + /** + * Get the database connection full name. + * + * @return string|null + */ + public function getNameWithReadWriteType() + { + return $this->getName().($this->readWriteType ? '::'.$this->readWriteType : ''); + } + + /** + * Get an option from the configuration options. + * + * @param string|null $option + * @return mixed + */ + public function getConfig($option = null) + { + return Arr::get($this->config, $option); + } + + /** + * Get the PDO driver name. + * + * @return string + */ + public function getDriverName() + { + return $this->getConfig('driver'); + } + + /** + * Get the query grammar used by the connection. + * + * @return \Illuminate\Database\Query\Grammars\Grammar + */ + public function getQueryGrammar() + { + return $this->queryGrammar; + } + + /** + * Set the query grammar used by the connection. + * + * @param \Illuminate\Database\Query\Grammars\Grammar $grammar + * @return $this + */ + public function setQueryGrammar(Query\Grammars\Grammar $grammar) + { + $this->queryGrammar = $grammar; + + return $this; + } + + /** + * Get the schema grammar used by the connection. + * + * @return \Illuminate\Database\Schema\Grammars\Grammar + */ + public function getSchemaGrammar() + { + return $this->schemaGrammar; + } + + /** + * Set the schema grammar used by the connection. + * + * @param \Illuminate\Database\Schema\Grammars\Grammar $grammar + * @return $this + */ + public function setSchemaGrammar(Schema\Grammars\Grammar $grammar) + { + $this->schemaGrammar = $grammar; + + return $this; + } + + /** + * Get the query post processor used by the connection. + * + * @return \Illuminate\Database\Query\Processors\Processor + */ + public function getPostProcessor() + { + return $this->postProcessor; + } + + /** + * Set the query post processor used by the connection. + * + * @param \Illuminate\Database\Query\Processors\Processor $processor + * @return $this + */ + public function setPostProcessor(Processor $processor) + { + $this->postProcessor = $processor; + + return $this; + } + + /** + * Get the event dispatcher used by the connection. + * + * @return \Illuminate\Contracts\Events\Dispatcher + */ + public function getEventDispatcher() + { + return $this->events; + } + + /** + * Set the event dispatcher instance on the connection. + * + * @param \Illuminate\Contracts\Events\Dispatcher $events + * @return $this + */ + public function setEventDispatcher(Dispatcher $events) + { + $this->events = $events; + + return $this; + } + + /** + * Unset the event dispatcher for this connection. + * + * @return void + */ + public function unsetEventDispatcher() + { + $this->events = null; + } + + /** + * Set the transaction manager instance on the connection. + * + * @param \Illuminate\Database\DatabaseTransactionsManager $manager + * @return $this + */ + public function setTransactionManager($manager) + { + $this->transactionsManager = $manager; + + return $this; + } + + /** + * Unset the transaction manager for this connection. + * + * @return void + */ + public function unsetTransactionManager() + { + $this->transactionsManager = null; + } + + /** + * Determine if the connection is in a "dry run". + * + * @return bool + */ + public function pretending() + { + return $this->pretending === true; + } + + /** + * Get the connection query log. + * + * @return array + */ + public function getQueryLog() + { + return $this->queryLog; + } + + /** + * Clear the query log. + * + * @return void + */ + public function flushQueryLog() + { + $this->queryLog = []; + } + + /** + * Enable the query log on the connection. + * + * @return void + */ + public function enableQueryLog() + { + $this->loggingQueries = true; + } + + /** + * Disable the query log on the connection. + * + * @return void + */ + public function disableQueryLog() + { + $this->loggingQueries = false; + } + + /** + * Determine whether we're logging queries. + * + * @return bool + */ + public function logging() + { + return $this->loggingQueries; + } + + /** + * Get the name of the connected database. + * + * @return string + */ + public function getDatabaseName() + { + return $this->database; + } + + /** + * Set the name of the connected database. + * + * @param string $database + * @return $this + */ + public function setDatabaseName($database) + { + $this->database = $database; + + return $this; + } + + /** + * Set the read / write type of the connection. + * + * @param string|null $readWriteType + * @return $this + */ + public function setReadWriteType($readWriteType) + { + $this->readWriteType = $readWriteType; + + return $this; + } + + /** + * Get the table prefix for the connection. + * + * @return string + */ + public function getTablePrefix() + { + return $this->tablePrefix; + } + + /** + * Set the table prefix in use by the connection. + * + * @param string $prefix + * @return $this + */ + public function setTablePrefix($prefix) + { + $this->tablePrefix = $prefix; + + $this->getQueryGrammar()->setTablePrefix($prefix); + + return $this; + } + + /** + * Set the table prefix and return the grammar. + * + * @param \Illuminate\Database\Grammar $grammar + * @return \Illuminate\Database\Grammar + */ + public function withTablePrefix(Grammar $grammar) + { + $grammar->setTablePrefix($this->tablePrefix); + + return $grammar; + } + + /** + * Register a connection resolver. + * + * @param string $driver + * @param \Closure $callback + * @return void + */ + public static function resolverFor($driver, Closure $callback) + { + static::$resolvers[$driver] = $callback; + } + + /** + * Get the connection resolver for the given driver. + * + * @param string $driver + * @return mixed + */ + public static function getResolver($driver) + { + return static::$resolvers[$driver] ?? null; + } +} diff --git a/vendor/illuminate/database/ConnectionInterface.php b/vendor/illuminate/database/ConnectionInterface.php new file mode 100644 index 0000000..00b2395 --- /dev/null +++ b/vendor/illuminate/database/ConnectionInterface.php @@ -0,0 +1,170 @@ + $connection) { + $this->addConnection($name, $connection); + } + } + + /** + * Get a database connection instance. + * + * @param string|null $name + * @return \Illuminate\Database\ConnectionInterface + */ + public function connection($name = null) + { + if (is_null($name)) { + $name = $this->getDefaultConnection(); + } + + return $this->connections[$name]; + } + + /** + * Add a connection to the resolver. + * + * @param string $name + * @param \Illuminate\Database\ConnectionInterface $connection + * @return void + */ + public function addConnection($name, ConnectionInterface $connection) + { + $this->connections[$name] = $connection; + } + + /** + * Check if a connection has been registered. + * + * @param string $name + * @return bool + */ + public function hasConnection($name) + { + return isset($this->connections[$name]); + } + + /** + * Get the default connection name. + * + * @return string + */ + public function getDefaultConnection() + { + return $this->default; + } + + /** + * Set the default connection name. + * + * @param string $name + * @return void + */ + public function setDefaultConnection($name) + { + $this->default = $name; + } +} diff --git a/vendor/illuminate/database/ConnectionResolverInterface.php b/vendor/illuminate/database/ConnectionResolverInterface.php new file mode 100644 index 0000000..b31e5a7 --- /dev/null +++ b/vendor/illuminate/database/ConnectionResolverInterface.php @@ -0,0 +1,29 @@ +container = $container; + } + + /** + * Establish a PDO connection based on the configuration. + * + * @param array $config + * @param string|null $name + * @return \Illuminate\Database\Connection + */ + public function make(array $config, $name = null) + { + $config = $this->parseConfig($config, $name); + + if (isset($config['read'])) { + return $this->createReadWriteConnection($config); + } + + return $this->createSingleConnection($config); + } + + /** + * Parse and prepare the database configuration. + * + * @param array $config + * @param string $name + * @return array + */ + protected function parseConfig(array $config, $name) + { + return Arr::add(Arr::add($config, 'prefix', ''), 'name', $name); + } + + /** + * Create a single database connection instance. + * + * @param array $config + * @return \Illuminate\Database\Connection + */ + protected function createSingleConnection(array $config) + { + $pdo = $this->createPdoResolver($config); + + return $this->createConnection( + $config['driver'], $pdo, $config['database'], $config['prefix'], $config + ); + } + + /** + * Create a read / write database connection instance. + * + * @param array $config + * @return \Illuminate\Database\Connection + */ + protected function createReadWriteConnection(array $config) + { + $connection = $this->createSingleConnection($this->getWriteConfig($config)); + + return $connection->setReadPdo($this->createReadPdo($config)); + } + + /** + * Create a new PDO instance for reading. + * + * @param array $config + * @return \Closure + */ + protected function createReadPdo(array $config) + { + return $this->createPdoResolver($this->getReadConfig($config)); + } + + /** + * Get the read configuration for a read / write connection. + * + * @param array $config + * @return array + */ + protected function getReadConfig(array $config) + { + return $this->mergeReadWriteConfig( + $config, $this->getReadWriteConfig($config, 'read') + ); + } + + /** + * Get the write configuration for a read / write connection. + * + * @param array $config + * @return array + */ + protected function getWriteConfig(array $config) + { + return $this->mergeReadWriteConfig( + $config, $this->getReadWriteConfig($config, 'write') + ); + } + + /** + * Get a read / write level configuration. + * + * @param array $config + * @param string $type + * @return array + */ + protected function getReadWriteConfig(array $config, $type) + { + return isset($config[$type][0]) + ? Arr::random($config[$type]) + : $config[$type]; + } + + /** + * Merge a configuration for a read / write connection. + * + * @param array $config + * @param array $merge + * @return array + */ + protected function mergeReadWriteConfig(array $config, array $merge) + { + return Arr::except(array_merge($config, $merge), ['read', 'write']); + } + + /** + * Create a new Closure that resolves to a PDO instance. + * + * @param array $config + * @return \Closure + */ + protected function createPdoResolver(array $config) + { + return array_key_exists('host', $config) + ? $this->createPdoResolverWithHosts($config) + : $this->createPdoResolverWithoutHosts($config); + } + + /** + * Create a new Closure that resolves to a PDO instance with a specific host or an array of hosts. + * + * @param array $config + * @return \Closure + * + * @throws \PDOException + */ + protected function createPdoResolverWithHosts(array $config) + { + return function () use ($config) { + foreach (Arr::shuffle($hosts = $this->parseHosts($config)) as $key => $host) { + $config['host'] = $host; + + try { + return $this->createConnector($config)->connect($config); + } catch (PDOException $e) { + continue; + } + } + + throw $e; + }; + } + + /** + * Parse the hosts configuration item into an array. + * + * @param array $config + * @return array + * + * @throws \InvalidArgumentException + */ + protected function parseHosts(array $config) + { + $hosts = Arr::wrap($config['host']); + + if (empty($hosts)) { + throw new InvalidArgumentException('Database hosts array is empty.'); + } + + return $hosts; + } + + /** + * Create a new Closure that resolves to a PDO instance where there is no configured host. + * + * @param array $config + * @return \Closure + */ + protected function createPdoResolverWithoutHosts(array $config) + { + return function () use ($config) { + return $this->createConnector($config)->connect($config); + }; + } + + /** + * Create a connector instance based on the configuration. + * + * @param array $config + * @return \Illuminate\Database\Connectors\ConnectorInterface + * + * @throws \InvalidArgumentException + */ + public function createConnector(array $config) + { + if (! isset($config['driver'])) { + throw new InvalidArgumentException('A driver must be specified.'); + } + + if ($this->container->bound($key = "db.connector.{$config['driver']}")) { + return $this->container->make($key); + } + + switch ($config['driver']) { + case 'mysql': + return new MySqlConnector; + case 'pgsql': + return new PostgresConnector; + case 'sqlite': + return new SQLiteConnector; + case 'sqlsrv': + return new SqlServerConnector; + } + + throw new InvalidArgumentException("Unsupported driver [{$config['driver']}]."); + } + + /** + * Create a new connection instance. + * + * @param string $driver + * @param \PDO|\Closure $connection + * @param string $database + * @param string $prefix + * @param array $config + * @return \Illuminate\Database\Connection + * + * @throws \InvalidArgumentException + */ + protected function createConnection($driver, $connection, $database, $prefix = '', array $config = []) + { + if ($resolver = Connection::getResolver($driver)) { + return $resolver($connection, $database, $prefix, $config); + } + + switch ($driver) { + case 'mysql': + return new MySqlConnection($connection, $database, $prefix, $config); + case 'pgsql': + return new PostgresConnection($connection, $database, $prefix, $config); + case 'sqlite': + return new SQLiteConnection($connection, $database, $prefix, $config); + case 'sqlsrv': + return new SqlServerConnection($connection, $database, $prefix, $config); + } + + throw new InvalidArgumentException("Unsupported driver [{$driver}]."); + } +} diff --git a/vendor/illuminate/database/Connectors/Connector.php b/vendor/illuminate/database/Connectors/Connector.php new file mode 100644 index 0000000..0fecfb5 --- /dev/null +++ b/vendor/illuminate/database/Connectors/Connector.php @@ -0,0 +1,139 @@ + PDO::CASE_NATURAL, + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL, + PDO::ATTR_STRINGIFY_FETCHES => false, + PDO::ATTR_EMULATE_PREPARES => false, + ]; + + /** + * Create a new PDO connection. + * + * @param string $dsn + * @param array $config + * @param array $options + * @return \PDO + * + * @throws \Exception + */ + public function createConnection($dsn, array $config, array $options) + { + [$username, $password] = [ + $config['username'] ?? null, $config['password'] ?? null, + ]; + + try { + return $this->createPdoConnection( + $dsn, $username, $password, $options + ); + } catch (Exception $e) { + return $this->tryAgainIfCausedByLostConnection( + $e, $dsn, $username, $password, $options + ); + } + } + + /** + * Create a new PDO connection instance. + * + * @param string $dsn + * @param string $username + * @param string $password + * @param array $options + * @return \PDO + */ + protected function createPdoConnection($dsn, $username, $password, $options) + { + if (class_exists(PDOConnection::class) && ! $this->isPersistentConnection($options)) { + return new PDOConnection($dsn, $username, $password, $options); + } + + return new PDO($dsn, $username, $password, $options); + } + + /** + * Determine if the connection is persistent. + * + * @param array $options + * @return bool + */ + protected function isPersistentConnection($options) + { + return isset($options[PDO::ATTR_PERSISTENT]) && + $options[PDO::ATTR_PERSISTENT]; + } + + /** + * Handle an exception that occurred during connect execution. + * + * @param \Throwable $e + * @param string $dsn + * @param string $username + * @param string $password + * @param array $options + * @return \PDO + * + * @throws \Exception + */ + protected function tryAgainIfCausedByLostConnection(Throwable $e, $dsn, $username, $password, $options) + { + if ($this->causedByLostConnection($e)) { + return $this->createPdoConnection($dsn, $username, $password, $options); + } + + throw $e; + } + + /** + * Get the PDO options based on the configuration. + * + * @param array $config + * @return array + */ + public function getOptions(array $config) + { + $options = $config['options'] ?? []; + + return array_diff_key($this->options, $options) + $options; + } + + /** + * Get the default PDO connection options. + * + * @return array + */ + public function getDefaultOptions() + { + return $this->options; + } + + /** + * Set the default PDO connection options. + * + * @param array $options + * @return void + */ + public function setDefaultOptions(array $options) + { + $this->options = $options; + } +} diff --git a/vendor/illuminate/database/Connectors/ConnectorInterface.php b/vendor/illuminate/database/Connectors/ConnectorInterface.php new file mode 100644 index 0000000..08597ac --- /dev/null +++ b/vendor/illuminate/database/Connectors/ConnectorInterface.php @@ -0,0 +1,14 @@ +getDsn($config); + + $options = $this->getOptions($config); + + // We need to grab the PDO options that should be used while making the brand + // new connection instance. The PDO options control various aspects of the + // connection's behavior, and some might be specified by the developers. + $connection = $this->createConnection($dsn, $config, $options); + + if (! empty($config['database'])) { + $connection->exec("use `{$config['database']}`;"); + } + + $this->configureIsolationLevel($connection, $config); + + $this->configureEncoding($connection, $config); + + // Next, we will check to see if a timezone has been specified in this config + // and if it has we will issue a statement to modify the timezone with the + // database. Setting this DB timezone is an optional configuration item. + $this->configureTimezone($connection, $config); + + $this->setModes($connection, $config); + + return $connection; + } + + /** + * Set the connection transaction isolation level. + * + * @param \PDO $connection + * @param array $config + * @return void + */ + protected function configureIsolationLevel($connection, array $config) + { + if (! isset($config['isolation_level'])) { + return; + } + + $connection->prepare( + "SET SESSION TRANSACTION ISOLATION LEVEL {$config['isolation_level']}" + )->execute(); + } + + /** + * Set the connection character set and collation. + * + * @param \PDO $connection + * @param array $config + * @return void|\PDO + */ + protected function configureEncoding($connection, array $config) + { + if (! isset($config['charset'])) { + return $connection; + } + + $connection->prepare( + "set names '{$config['charset']}'".$this->getCollation($config) + )->execute(); + } + + /** + * Get the collation for the configuration. + * + * @param array $config + * @return string + */ + protected function getCollation(array $config) + { + return isset($config['collation']) ? " collate '{$config['collation']}'" : ''; + } + + /** + * Set the timezone on the connection. + * + * @param \PDO $connection + * @param array $config + * @return void + */ + protected function configureTimezone($connection, array $config) + { + if (isset($config['timezone'])) { + $connection->prepare('set time_zone="'.$config['timezone'].'"')->execute(); + } + } + + /** + * Create a DSN string from a configuration. + * + * Chooses socket or host/port based on the 'unix_socket' config value. + * + * @param array $config + * @return string + */ + protected function getDsn(array $config) + { + return $this->hasSocket($config) + ? $this->getSocketDsn($config) + : $this->getHostDsn($config); + } + + /** + * Determine if the given configuration array has a UNIX socket value. + * + * @param array $config + * @return bool + */ + protected function hasSocket(array $config) + { + return isset($config['unix_socket']) && ! empty($config['unix_socket']); + } + + /** + * Get the DSN string for a socket configuration. + * + * @param array $config + * @return string + */ + protected function getSocketDsn(array $config) + { + return "mysql:unix_socket={$config['unix_socket']};dbname={$config['database']}"; + } + + /** + * Get the DSN string for a host / port configuration. + * + * @param array $config + * @return string + */ + protected function getHostDsn(array $config) + { + extract($config, EXTR_SKIP); + + return isset($port) + ? "mysql:host={$host};port={$port};dbname={$database}" + : "mysql:host={$host};dbname={$database}"; + } + + /** + * Set the modes for the connection. + * + * @param \PDO $connection + * @param array $config + * @return void + */ + protected function setModes(PDO $connection, array $config) + { + if (isset($config['modes'])) { + $this->setCustomModes($connection, $config); + } elseif (isset($config['strict'])) { + if ($config['strict']) { + $connection->prepare($this->strictMode($connection, $config))->execute(); + } else { + $connection->prepare("set session sql_mode='NO_ENGINE_SUBSTITUTION'")->execute(); + } + } + } + + /** + * Set the custom modes on the connection. + * + * @param \PDO $connection + * @param array $config + * @return void + */ + protected function setCustomModes(PDO $connection, array $config) + { + $modes = implode(',', $config['modes']); + + $connection->prepare("set session sql_mode='{$modes}'")->execute(); + } + + /** + * Get the query to enable strict mode. + * + * @param \PDO $connection + * @param array $config + * @return string + */ + protected function strictMode(PDO $connection, $config) + { + $version = $config['version'] ?? $connection->getAttribute(PDO::ATTR_SERVER_VERSION); + + if (version_compare($version, '8.0.11') >= 0) { + return "set session sql_mode='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'"; + } + + return "set session sql_mode='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'"; + } +} diff --git a/vendor/illuminate/database/Connectors/PostgresConnector.php b/vendor/illuminate/database/Connectors/PostgresConnector.php new file mode 100644 index 0000000..a507d9a --- /dev/null +++ b/vendor/illuminate/database/Connectors/PostgresConnector.php @@ -0,0 +1,210 @@ + PDO::CASE_NATURAL, + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL, + PDO::ATTR_STRINGIFY_FETCHES => false, + ]; + + /** + * Establish a database connection. + * + * @param array $config + * @return \PDO + */ + public function connect(array $config) + { + // First we'll create the basic DSN and connection instance connecting to the + // using the configuration option specified by the developer. We will also + // set the default character set on the connections to UTF-8 by default. + $connection = $this->createConnection( + $this->getDsn($config), $config, $this->getOptions($config) + ); + + $this->configureIsolationLevel($connection, $config); + + $this->configureEncoding($connection, $config); + + // Next, we will check to see if a timezone has been specified in this config + // and if it has we will issue a statement to modify the timezone with the + // database. Setting this DB timezone is an optional configuration item. + $this->configureTimezone($connection, $config); + + $this->configureSchema($connection, $config); + + // Postgres allows an application_name to be set by the user and this name is + // used to when monitoring the application with pg_stat_activity. So we'll + // determine if the option has been specified and run a statement if so. + $this->configureApplicationName($connection, $config); + + $this->configureSynchronousCommit($connection, $config); + + return $connection; + } + + /** + * Set the connection transaction isolation level. + * + * @param \PDO $connection + * @param array $config + * @return void + */ + protected function configureIsolationLevel($connection, array $config) + { + if (isset($config['isolation_level'])) { + $connection->prepare("set session characteristics as transaction isolation level {$config['isolation_level']}")->execute(); + } + } + + /** + * Set the connection character set and collation. + * + * @param \PDO $connection + * @param array $config + * @return void + */ + protected function configureEncoding($connection, $config) + { + if (! isset($config['charset'])) { + return; + } + + $connection->prepare("set names '{$config['charset']}'")->execute(); + } + + /** + * Set the timezone on the connection. + * + * @param \PDO $connection + * @param array $config + * @return void + */ + protected function configureTimezone($connection, array $config) + { + if (isset($config['timezone'])) { + $timezone = $config['timezone']; + + $connection->prepare("set time zone '{$timezone}'")->execute(); + } + } + + /** + * Set the schema on the connection. + * + * @param \PDO $connection + * @param array $config + * @return void + */ + protected function configureSchema($connection, $config) + { + if (isset($config['schema'])) { + $schema = $this->formatSchema($config['schema']); + + $connection->prepare("set search_path to {$schema}")->execute(); + } + } + + /** + * Format the schema for the DSN. + * + * @param array|string $schema + * @return string + */ + protected function formatSchema($schema) + { + if (is_array($schema)) { + return '"'.implode('", "', $schema).'"'; + } + + return '"'.$schema.'"'; + } + + /** + * Set the schema on the connection. + * + * @param \PDO $connection + * @param array $config + * @return void + */ + protected function configureApplicationName($connection, $config) + { + if (isset($config['application_name'])) { + $applicationName = $config['application_name']; + + $connection->prepare("set application_name to '$applicationName'")->execute(); + } + } + + /** + * Create a DSN string from a configuration. + * + * @param array $config + * @return string + */ + protected function getDsn(array $config) + { + // First we will create the basic DSN setup as well as the port if it is in + // in the configuration options. This will give us the basic DSN we will + // need to establish the PDO connections and return them back for use. + extract($config, EXTR_SKIP); + + $host = isset($host) ? "host={$host};" : ''; + + $dsn = "pgsql:{$host}dbname='{$database}'"; + + // If a port was specified, we will add it to this Postgres DSN connections + // format. Once we have done that we are ready to return this connection + // string back out for usage, as this has been fully constructed here. + if (isset($config['port'])) { + $dsn .= ";port={$port}"; + } + + return $this->addSslOptions($dsn, $config); + } + + /** + * Add the SSL options to the DSN. + * + * @param string $dsn + * @param array $config + * @return string + */ + protected function addSslOptions($dsn, array $config) + { + foreach (['sslmode', 'sslcert', 'sslkey', 'sslrootcert'] as $option) { + if (isset($config[$option])) { + $dsn .= ";{$option}={$config[$option]}"; + } + } + + return $dsn; + } + + /** + * Configure the synchronous_commit setting. + * + * @param \PDO $connection + * @param array $config + * @return void + */ + protected function configureSynchronousCommit($connection, array $config) + { + if (! isset($config['synchronous_commit'])) { + return; + } + + $connection->prepare("set synchronous_commit to '{$config['synchronous_commit']}'")->execute(); + } +} diff --git a/vendor/illuminate/database/Connectors/SQLiteConnector.php b/vendor/illuminate/database/Connectors/SQLiteConnector.php new file mode 100644 index 0000000..90dc16b --- /dev/null +++ b/vendor/illuminate/database/Connectors/SQLiteConnector.php @@ -0,0 +1,39 @@ +getOptions($config); + + // SQLite supports "in-memory" databases that only last as long as the owning + // connection does. These are useful for tests or for short lifetime store + // querying. In-memory databases may only have a single open connection. + if ($config['database'] === ':memory:') { + return $this->createConnection('sqlite::memory:', $config, $options); + } + + $path = realpath($config['database']); + + // Here we'll verify that the SQLite database exists before going any further + // as the developer probably wants to know if the database exists and this + // SQLite driver will not throw any exception if it does not by default. + if ($path === false) { + throw new InvalidArgumentException("Database ({$config['database']}) does not exist."); + } + + return $this->createConnection("sqlite:{$path}", $config, $options); + } +} diff --git a/vendor/illuminate/database/Connectors/SqlServerConnector.php b/vendor/illuminate/database/Connectors/SqlServerConnector.php new file mode 100644 index 0000000..caefa68 --- /dev/null +++ b/vendor/illuminate/database/Connectors/SqlServerConnector.php @@ -0,0 +1,205 @@ + PDO::CASE_NATURAL, + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL, + PDO::ATTR_STRINGIFY_FETCHES => false, + ]; + + /** + * Establish a database connection. + * + * @param array $config + * @return \PDO + */ + public function connect(array $config) + { + $options = $this->getOptions($config); + + return $this->createConnection($this->getDsn($config), $config, $options); + } + + /** + * Create a DSN string from a configuration. + * + * @param array $config + * @return string + */ + protected function getDsn(array $config) + { + // First we will create the basic DSN setup as well as the port if it is in + // in the configuration options. This will give us the basic DSN we will + // need to establish the PDO connections and return them back for use. + if ($this->prefersOdbc($config)) { + return $this->getOdbcDsn($config); + } + + if (in_array('sqlsrv', $this->getAvailableDrivers())) { + return $this->getSqlSrvDsn($config); + } else { + return $this->getDblibDsn($config); + } + } + + /** + * Determine if the database configuration prefers ODBC. + * + * @param array $config + * @return bool + */ + protected function prefersOdbc(array $config) + { + return in_array('odbc', $this->getAvailableDrivers()) && + ($config['odbc'] ?? null) === true; + } + + /** + * Get the DSN string for a DbLib connection. + * + * @param array $config + * @return string + */ + protected function getDblibDsn(array $config) + { + return $this->buildConnectString('dblib', array_merge([ + 'host' => $this->buildHostString($config, ':'), + 'dbname' => $config['database'], + ], Arr::only($config, ['appname', 'charset', 'version']))); + } + + /** + * Get the DSN string for an ODBC connection. + * + * @param array $config + * @return string + */ + protected function getOdbcDsn(array $config) + { + return isset($config['odbc_datasource_name']) + ? 'odbc:'.$config['odbc_datasource_name'] : ''; + } + + /** + * Get the DSN string for a SqlSrv connection. + * + * @param array $config + * @return string + */ + protected function getSqlSrvDsn(array $config) + { + $arguments = [ + 'Server' => $this->buildHostString($config, ','), + ]; + + if (isset($config['database'])) { + $arguments['Database'] = $config['database']; + } + + if (isset($config['readonly'])) { + $arguments['ApplicationIntent'] = 'ReadOnly'; + } + + if (isset($config['pooling']) && $config['pooling'] === false) { + $arguments['ConnectionPooling'] = '0'; + } + + if (isset($config['appname'])) { + $arguments['APP'] = $config['appname']; + } + + if (isset($config['encrypt'])) { + $arguments['Encrypt'] = $config['encrypt']; + } + + if (isset($config['trust_server_certificate'])) { + $arguments['TrustServerCertificate'] = $config['trust_server_certificate']; + } + + if (isset($config['multiple_active_result_sets']) && $config['multiple_active_result_sets'] === false) { + $arguments['MultipleActiveResultSets'] = 'false'; + } + + if (isset($config['transaction_isolation'])) { + $arguments['TransactionIsolation'] = $config['transaction_isolation']; + } + + if (isset($config['multi_subnet_failover'])) { + $arguments['MultiSubnetFailover'] = $config['multi_subnet_failover']; + } + + if (isset($config['column_encryption'])) { + $arguments['ColumnEncryption'] = $config['column_encryption']; + } + + if (isset($config['key_store_authentication'])) { + $arguments['KeyStoreAuthentication'] = $config['key_store_authentication']; + } + + if (isset($config['key_store_principal_id'])) { + $arguments['KeyStorePrincipalId'] = $config['key_store_principal_id']; + } + + if (isset($config['key_store_secret'])) { + $arguments['KeyStoreSecret'] = $config['key_store_secret']; + } + + if (isset($config['login_timeout'])) { + $arguments['LoginTimeout'] = $config['login_timeout']; + } + + return $this->buildConnectString('sqlsrv', $arguments); + } + + /** + * Build a connection string from the given arguments. + * + * @param string $driver + * @param array $arguments + * @return string + */ + protected function buildConnectString($driver, array $arguments) + { + return $driver.':'.implode(';', array_map(function ($key) use ($arguments) { + return sprintf('%s=%s', $key, $arguments[$key]); + }, array_keys($arguments))); + } + + /** + * Build a host string from the given configuration. + * + * @param array $config + * @param string $separator + * @return string + */ + protected function buildHostString(array $config, $separator) + { + if (empty($config['port'])) { + return $config['host']; + } + + return $config['host'].$separator.$config['port']; + } + + /** + * Get the available PDO drivers. + * + * @return array + */ + protected function getAvailableDrivers() + { + return PDO::getAvailableDrivers(); + } +} diff --git a/vendor/illuminate/database/Console/DbCommand.php b/vendor/illuminate/database/Console/DbCommand.php new file mode 100644 index 0000000..c2c4593 --- /dev/null +++ b/vendor/illuminate/database/Console/DbCommand.php @@ -0,0 +1,219 @@ +getConnection(); + + (new Process( + array_merge([$this->getCommand($connection)], $this->commandArguments($connection)), + null, + $this->commandEnvironment($connection) + ))->setTimeout(null)->setTty(true)->mustRun(function ($type, $buffer) { + $this->output->write($buffer); + }); + + return 0; + } + + /** + * Get the database connection configuration. + * + * @return array + * + * @throws \UnexpectedValueException + */ + public function getConnection() + { + $connection = $this->laravel['config']['database.connections.'. + (($db = $this->argument('connection')) ?? $this->laravel['config']['database.default']) + ]; + + if (empty($connection)) { + throw new UnexpectedValueException("Invalid database connection [{$db}]."); + } + + if (! empty($connection['url'])) { + $connection = (new ConfigurationUrlParser)->parseConfiguration($connection); + } + + if ($this->option('read')) { + if (is_array($connection['read']['host'])) { + $connection['read']['host'] = $connection['read']['host'][0]; + } + + $connection = array_merge($connection, $connection['read']); + } elseif ($this->option('write')) { + if (is_array($connection['write']['host'])) { + $connection['write']['host'] = $connection['write']['host'][0]; + } + + $connection = array_merge($connection, $connection['write']); + } + + return $connection; + } + + /** + * Get the arguments for the database client command. + * + * @param array $connection + * @return array + */ + public function commandArguments(array $connection) + { + $driver = ucfirst($connection['driver']); + + return $this->{"get{$driver}Arguments"}($connection); + } + + /** + * Get the environment variables for the database client command. + * + * @param array $connection + * @return array|null + */ + public function commandEnvironment(array $connection) + { + $driver = ucfirst($connection['driver']); + + if (method_exists($this, "get{$driver}Environment")) { + return $this->{"get{$driver}Environment"}($connection); + } + + return null; + } + + /** + * Get the database client command to run. + * + * @param array $connection + * @return string + */ + public function getCommand(array $connection) + { + return [ + 'mysql' => 'mysql', + 'pgsql' => 'psql', + 'sqlite' => 'sqlite3', + 'sqlsrv' => 'sqlcmd', + ][$connection['driver']]; + } + + /** + * Get the arguments for the MySQL CLI. + * + * @param array $connection + * @return array + */ + protected function getMysqlArguments(array $connection) + { + return array_merge([ + '--host='.$connection['host'], + '--port='.$connection['port'], + '--user='.$connection['username'], + ], $this->getOptionalArguments([ + 'password' => '--password='.$connection['password'], + 'unix_socket' => '--socket='.($connection['unix_socket'] ?? ''), + 'charset' => '--default-character-set='.($connection['charset'] ?? ''), + ], $connection), [$connection['database']]); + } + + /** + * Get the arguments for the Postgres CLI. + * + * @param array $connection + * @return array + */ + protected function getPgsqlArguments(array $connection) + { + return [$connection['database']]; + } + + /** + * Get the arguments for the SQLite CLI. + * + * @param array $connection + * @return array + */ + protected function getSqliteArguments(array $connection) + { + return [$connection['database']]; + } + + /** + * Get the arguments for the SQL Server CLI. + * + * @param array $connection + * @return array + */ + protected function getSqlsrvArguments(array $connection) + { + return array_merge(...$this->getOptionalArguments([ + 'database' => ['-d', $connection['database']], + 'username' => ['-U', $connection['username']], + 'password' => ['-P', $connection['password']], + 'host' => ['-S', 'tcp:'.$connection['host'] + .($connection['port'] ? ','.$connection['port'] : ''), ], + ], $connection)); + } + + /** + * Get the environment variables for the Postgres CLI. + * + * @param array $connection + * @return array|null + */ + protected function getPgsqlEnvironment(array $connection) + { + return array_merge(...$this->getOptionalArguments([ + 'username' => ['PGUSER' => $connection['username']], + 'host' => ['PGHOST' => $connection['host']], + 'port' => ['PGPORT' => $connection['port']], + 'password' => ['PGPASSWORD' => $connection['password']], + ], $connection)); + } + + /** + * Get the optional arguments based on the connection configuration. + * + * @param array $args + * @param array $connection + * @return array + */ + protected function getOptionalArguments(array $args, array $connection) + { + return array_values(array_filter($args, function ($key) use ($connection) { + return ! empty($connection[$key]); + }, ARRAY_FILTER_USE_KEY)); + } +} diff --git a/vendor/illuminate/database/Console/DumpCommand.php b/vendor/illuminate/database/Console/DumpCommand.php new file mode 100644 index 0000000..fe73fb2 --- /dev/null +++ b/vendor/illuminate/database/Console/DumpCommand.php @@ -0,0 +1,86 @@ +connection($database = $this->input->getOption('database')); + + $this->schemaState($connection)->dump( + $connection, $path = $this->path($connection) + ); + + $dispatcher->dispatch(new SchemaDumped($connection, $path)); + + $this->info('Database schema dumped successfully.'); + + if ($this->option('prune')) { + (new Filesystem)->deleteDirectory( + database_path('migrations'), $preserve = false + ); + + $this->info('Migrations pruned successfully.'); + } + } + + /** + * Create a schema state instance for the given connection. + * + * @param \Illuminate\Database\Connection $connection + * @return mixed + */ + protected function schemaState(Connection $connection) + { + return $connection->getSchemaState() + ->withMigrationTable($connection->getTablePrefix().Config::get('database.migrations', 'migrations')) + ->handleOutputUsing(function ($type, $buffer) { + $this->output->write($buffer); + }); + } + + /** + * Get the path that the dump should be written to. + * + * @param \Illuminate\Database\Connection $connection + */ + protected function path(Connection $connection) + { + return tap($this->option('path') ?: database_path('schema/'.$connection->getName().'-schema.dump'), function ($path) { + (new Filesystem)->ensureDirectoryExists(dirname($path)); + }); + } +} diff --git a/vendor/illuminate/database/Console/Factories/FactoryMakeCommand.php b/vendor/illuminate/database/Console/Factories/FactoryMakeCommand.php new file mode 100644 index 0000000..6233fe2 --- /dev/null +++ b/vendor/illuminate/database/Console/Factories/FactoryMakeCommand.php @@ -0,0 +1,143 @@ +resolveStubPath('/stubs/factory.stub'); + } + + /** + * Resolve the fully-qualified path to the stub. + * + * @param string $stub + * @return string + */ + protected function resolveStubPath($stub) + { + return file_exists($customPath = $this->laravel->basePath(trim($stub, '/'))) + ? $customPath + : __DIR__.$stub; + } + + /** + * Build the class with the given name. + * + * @param string $name + * @return string + */ + protected function buildClass($name) + { + $factory = class_basename(Str::ucfirst(str_replace('Factory', '', $name))); + + $namespaceModel = $this->option('model') + ? $this->qualifyModel($this->option('model')) + : $this->qualifyModel($this->guessModelName($name)); + + $model = class_basename($namespaceModel); + + if (Str::startsWith($namespaceModel, $this->rootNamespace().'Models')) { + $namespace = Str::beforeLast('Database\\Factories\\'.Str::after($namespaceModel, $this->rootNamespace().'Models\\'), '\\'); + } else { + $namespace = 'Database\\Factories'; + } + + $replace = [ + '{{ factoryNamespace }}' => $namespace, + 'NamespacedDummyModel' => $namespaceModel, + '{{ namespacedModel }}' => $namespaceModel, + '{{namespacedModel}}' => $namespaceModel, + 'DummyModel' => $model, + '{{ model }}' => $model, + '{{model}}' => $model, + '{{ factory }}' => $factory, + '{{factory}}' => $factory, + ]; + + return str_replace( + array_keys($replace), array_values($replace), parent::buildClass($name) + ); + } + + /** + * Get the destination class path. + * + * @param string $name + * @return string + */ + protected function getPath($name) + { + $name = (string) Str::of($name)->replaceFirst($this->rootNamespace(), '')->finish('Factory'); + + return $this->laravel->databasePath().'/factories/'.str_replace('\\', '/', $name).'.php'; + } + + /** + * Guess the model name from the Factory name or return a default model name. + * + * @param string $name + * @return string + */ + protected function guessModelName($name) + { + if (Str::endsWith($name, 'Factory')) { + $name = substr($name, 0, -7); + } + + $modelName = $this->qualifyModel(Str::after($name, $this->rootNamespace())); + + if (class_exists($modelName)) { + return $modelName; + } + + if (is_dir(app_path('Models/'))) { + return $this->rootNamespace().'Models\Model'; + } + + return $this->rootNamespace().'Model'; + } + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return [ + ['model', 'm', InputOption::VALUE_OPTIONAL, 'The name of the model'], + ]; + } +} diff --git a/vendor/illuminate/database/Console/Factories/stubs/factory.stub b/vendor/illuminate/database/Console/Factories/stubs/factory.stub new file mode 100644 index 0000000..3e00f36 --- /dev/null +++ b/vendor/illuminate/database/Console/Factories/stubs/factory.stub @@ -0,0 +1,20 @@ +input->hasOption('path') && $this->option('path')) { + return collect($this->option('path'))->map(function ($path) { + return ! $this->usingRealPath() + ? $this->laravel->basePath().'/'.$path + : $path; + })->all(); + } + + return array_merge( + $this->migrator->paths(), [$this->getMigrationPath()] + ); + } + + /** + * Determine if the given path(s) are pre-resolved "real" paths. + * + * @return bool + */ + protected function usingRealPath() + { + return $this->input->hasOption('realpath') && $this->option('realpath'); + } + + /** + * Get the path to the migration directory. + * + * @return string + */ + protected function getMigrationPath() + { + return $this->laravel->databasePath().DIRECTORY_SEPARATOR.'migrations'; + } +} diff --git a/vendor/illuminate/database/Console/Migrations/FreshCommand.php b/vendor/illuminate/database/Console/Migrations/FreshCommand.php new file mode 100644 index 0000000..7bfba0d --- /dev/null +++ b/vendor/illuminate/database/Console/Migrations/FreshCommand.php @@ -0,0 +1,116 @@ +confirmToProceed()) { + return 1; + } + + $database = $this->input->getOption('database'); + + $this->call('db:wipe', array_filter([ + '--database' => $database, + '--drop-views' => $this->option('drop-views'), + '--drop-types' => $this->option('drop-types'), + '--force' => true, + ])); + + $this->call('migrate', array_filter([ + '--database' => $database, + '--path' => $this->input->getOption('path'), + '--realpath' => $this->input->getOption('realpath'), + '--schema-path' => $this->input->getOption('schema-path'), + '--force' => true, + '--step' => $this->option('step'), + ])); + + if ($this->laravel->bound(Dispatcher::class)) { + $this->laravel[Dispatcher::class]->dispatch( + new DatabaseRefreshed + ); + } + + if ($this->needsSeeding()) { + $this->runSeeder($database); + } + + return 0; + } + + /** + * Determine if the developer has requested database seeding. + * + * @return bool + */ + protected function needsSeeding() + { + return $this->option('seed') || $this->option('seeder'); + } + + /** + * Run the database seeder command. + * + * @param string $database + * @return void + */ + protected function runSeeder($database) + { + $this->call('db:seed', array_filter([ + '--database' => $database, + '--class' => $this->option('seeder') ?: 'Database\\Seeders\\DatabaseSeeder', + '--force' => true, + ])); + } + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return [ + ['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use'], + ['drop-views', null, InputOption::VALUE_NONE, 'Drop all tables and views'], + ['drop-types', null, InputOption::VALUE_NONE, 'Drop all tables and types (Postgres only)'], + ['force', null, InputOption::VALUE_NONE, 'Force the operation to run when in production'], + ['path', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'The path(s) to the migrations files to be executed'], + ['realpath', null, InputOption::VALUE_NONE, 'Indicate any provided migration file paths are pre-resolved absolute paths'], + ['schema-path', null, InputOption::VALUE_OPTIONAL, 'The path to a schema dump file'], + ['seed', null, InputOption::VALUE_NONE, 'Indicates if the seed task should be re-run'], + ['seeder', null, InputOption::VALUE_OPTIONAL, 'The class name of the root seeder'], + ['step', null, InputOption::VALUE_NONE, 'Force the migrations to be run so they can be rolled back individually'], + ]; + } +} diff --git a/vendor/illuminate/database/Console/Migrations/InstallCommand.php b/vendor/illuminate/database/Console/Migrations/InstallCommand.php new file mode 100644 index 0000000..d69c2ab --- /dev/null +++ b/vendor/illuminate/database/Console/Migrations/InstallCommand.php @@ -0,0 +1,70 @@ +repository = $repository; + } + + /** + * Execute the console command. + * + * @return void + */ + public function handle() + { + $this->repository->setSource($this->input->getOption('database')); + + $this->repository->createRepository(); + + $this->info('Migration table created successfully.'); + } + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return [ + ['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use'], + ]; + } +} diff --git a/vendor/illuminate/database/Console/Migrations/MigrateCommand.php b/vendor/illuminate/database/Console/Migrations/MigrateCommand.php new file mode 100644 index 0000000..ea379e3 --- /dev/null +++ b/vendor/illuminate/database/Console/Migrations/MigrateCommand.php @@ -0,0 +1,181 @@ +migrator = $migrator; + $this->dispatcher = $dispatcher; + } + + /** + * Execute the console command. + * + * @return int + */ + public function handle() + { + if (! $this->confirmToProceed()) { + return 1; + } + + $this->migrator->usingConnection($this->option('database'), function () { + $this->prepareDatabase(); + + // Next, we will check to see if a path option has been defined. If it has + // we will use the path relative to the root of this installation folder + // so that migrations may be run for any path within the applications. + $this->migrator->setOutput($this->output) + ->run($this->getMigrationPaths(), [ + 'pretend' => $this->option('pretend'), + 'step' => $this->option('step'), + ]); + + // Finally, if the "seed" option has been given, we will re-run the database + // seed task to re-populate the database, which is convenient when adding + // a migration and a seed at the same time, as it is only this command. + if ($this->option('seed') && ! $this->option('pretend')) { + $this->call('db:seed', [ + '--class' => $this->option('seeder') ?: 'Database\\Seeders\\DatabaseSeeder', + '--force' => true, + ]); + } + }); + + return 0; + } + + /** + * Prepare the migration database for running. + * + * @return void + */ + protected function prepareDatabase() + { + if (! $this->migrator->repositoryExists()) { + $this->call('migrate:install', array_filter([ + '--database' => $this->option('database'), + ])); + } + + if (! $this->migrator->hasRunAnyMigrations() && ! $this->option('pretend')) { + $this->loadSchemaState(); + } + } + + /** + * Load the schema state to seed the initial database schema structure. + * + * @return void + */ + protected function loadSchemaState() + { + $connection = $this->migrator->resolveConnection($this->option('database')); + + // First, we will make sure that the connection supports schema loading and that + // the schema file exists before we proceed any further. If not, we will just + // continue with the standard migration operation as normal without errors. + if ($connection instanceof SqlServerConnection || + ! is_file($path = $this->schemaPath($connection))) { + return; + } + + $this->line('Loading stored database schema: '.$path); + + $startTime = microtime(true); + + // Since the schema file will create the "migrations" table and reload it to its + // proper state, we need to delete it here so we don't get an error that this + // table already exists when the stored database schema file gets executed. + $this->migrator->deleteRepository(); + + $connection->getSchemaState()->handleOutputUsing(function ($type, $buffer) { + $this->output->write($buffer); + })->load($path); + + $runTime = number_format((microtime(true) - $startTime) * 1000, 2); + + // Finally, we will fire an event that this schema has been loaded so developers + // can perform any post schema load tasks that are necessary in listeners for + // this event, which may seed the database tables with some necessary data. + $this->dispatcher->dispatch( + new SchemaLoaded($connection, $path) + ); + + $this->line('Loaded stored database schema. ('.$runTime.'ms)'); + } + + /** + * Get the path to the stored schema for the given connection. + * + * @param \Illuminate\Database\Connection $connection + * @return string + */ + protected function schemaPath($connection) + { + if ($this->option('schema-path')) { + return $this->option('schema-path'); + } + + if (file_exists($path = database_path('schema/'.$connection->getName().'-schema.dump'))) { + return $path; + } + + return database_path('schema/'.$connection->getName().'-schema.sql'); + } +} diff --git a/vendor/illuminate/database/Console/Migrations/MigrateMakeCommand.php b/vendor/illuminate/database/Console/Migrations/MigrateMakeCommand.php new file mode 100644 index 0000000..95c3a20 --- /dev/null +++ b/vendor/illuminate/database/Console/Migrations/MigrateMakeCommand.php @@ -0,0 +1,135 @@ +creator = $creator; + $this->composer = $composer; + } + + /** + * Execute the console command. + * + * @return void + */ + public function handle() + { + // It's possible for the developer to specify the tables to modify in this + // schema operation. The developer may also specify if this table needs + // to be freshly created so we can create the appropriate migrations. + $name = Str::snake(trim($this->input->getArgument('name'))); + + $table = $this->input->getOption('table'); + + $create = $this->input->getOption('create') ?: false; + + // If no table was given as an option but a create option is given then we + // will use the "create" option as the table name. This allows the devs + // to pass a table name into this option as a short-cut for creating. + if (! $table && is_string($create)) { + $table = $create; + + $create = true; + } + + // Next, we will attempt to guess the table name if this the migration has + // "create" in the name. This will allow us to provide a convenient way + // of creating migrations that create new tables for the application. + if (! $table) { + [$table, $create] = TableGuesser::guess($name); + } + + // Now we are ready to write the migration out to disk. Once we've written + // the migration out, we will dump-autoload for the entire framework to + // make sure that the migrations are registered by the class loaders. + $this->writeMigration($name, $table, $create); + + $this->composer->dumpAutoloads(); + } + + /** + * Write the migration file to disk. + * + * @param string $name + * @param string $table + * @param bool $create + * @return string + */ + protected function writeMigration($name, $table, $create) + { + $file = $this->creator->create( + $name, $this->getMigrationPath(), $table, $create + ); + + if (! $this->option('fullpath')) { + $file = pathinfo($file, PATHINFO_FILENAME); + } + + $this->line("Created Migration: {$file}"); + } + + /** + * Get migration path (either specified by '--path' option or default location). + * + * @return string + */ + protected function getMigrationPath() + { + if (! is_null($targetPath = $this->input->getOption('path'))) { + return ! $this->usingRealPath() + ? $this->laravel->basePath().'/'.$targetPath + : $targetPath; + } + + return parent::getMigrationPath(); + } +} diff --git a/vendor/illuminate/database/Console/Migrations/RefreshCommand.php b/vendor/illuminate/database/Console/Migrations/RefreshCommand.php new file mode 100644 index 0000000..2073cd9 --- /dev/null +++ b/vendor/illuminate/database/Console/Migrations/RefreshCommand.php @@ -0,0 +1,159 @@ +confirmToProceed()) { + return 1; + } + + // Next we'll gather some of the options so that we can have the right options + // to pass to the commands. This includes options such as which database to + // use and the path to use for the migration. Then we'll run the command. + $database = $this->input->getOption('database'); + + $path = $this->input->getOption('path'); + + // If the "step" option is specified it means we only want to rollback a small + // number of migrations before migrating again. For example, the user might + // only rollback and remigrate the latest four migrations instead of all. + $step = $this->input->getOption('step') ?: 0; + + if ($step > 0) { + $this->runRollback($database, $path, $step); + } else { + $this->runReset($database, $path); + } + + // The refresh command is essentially just a brief aggregate of a few other of + // the migration commands and just provides a convenient wrapper to execute + // them in succession. We'll also see if we need to re-seed the database. + $this->call('migrate', array_filter([ + '--database' => $database, + '--path' => $path, + '--realpath' => $this->input->getOption('realpath'), + '--force' => true, + ])); + + if ($this->laravel->bound(Dispatcher::class)) { + $this->laravel[Dispatcher::class]->dispatch( + new DatabaseRefreshed + ); + } + + if ($this->needsSeeding()) { + $this->runSeeder($database); + } + + return 0; + } + + /** + * Run the rollback command. + * + * @param string $database + * @param string $path + * @param int $step + * @return void + */ + protected function runRollback($database, $path, $step) + { + $this->call('migrate:rollback', array_filter([ + '--database' => $database, + '--path' => $path, + '--realpath' => $this->input->getOption('realpath'), + '--step' => $step, + '--force' => true, + ])); + } + + /** + * Run the reset command. + * + * @param string $database + * @param string $path + * @return void + */ + protected function runReset($database, $path) + { + $this->call('migrate:reset', array_filter([ + '--database' => $database, + '--path' => $path, + '--realpath' => $this->input->getOption('realpath'), + '--force' => true, + ])); + } + + /** + * Determine if the developer has requested database seeding. + * + * @return bool + */ + protected function needsSeeding() + { + return $this->option('seed') || $this->option('seeder'); + } + + /** + * Run the database seeder command. + * + * @param string $database + * @return void + */ + protected function runSeeder($database) + { + $this->call('db:seed', array_filter([ + '--database' => $database, + '--class' => $this->option('seeder') ?: 'Database\\Seeders\\DatabaseSeeder', + '--force' => true, + ])); + } + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return [ + ['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use'], + ['force', null, InputOption::VALUE_NONE, 'Force the operation to run when in production'], + ['path', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'The path(s) to the migrations files to be executed'], + ['realpath', null, InputOption::VALUE_NONE, 'Indicate any provided migration file paths are pre-resolved absolute paths'], + ['seed', null, InputOption::VALUE_NONE, 'Indicates if the seed task should be re-run'], + ['seeder', null, InputOption::VALUE_OPTIONAL, 'The class name of the root seeder'], + ['step', null, InputOption::VALUE_OPTIONAL, 'The number of migrations to be reverted & re-run'], + ]; + } +} diff --git a/vendor/illuminate/database/Console/Migrations/ResetCommand.php b/vendor/illuminate/database/Console/Migrations/ResetCommand.php new file mode 100644 index 0000000..1f2babb --- /dev/null +++ b/vendor/illuminate/database/Console/Migrations/ResetCommand.php @@ -0,0 +1,91 @@ +migrator = $migrator; + } + + /** + * Execute the console command. + * + * @return int + */ + public function handle() + { + if (! $this->confirmToProceed()) { + return 1; + } + + return $this->migrator->usingConnection($this->option('database'), function () { + // First, we'll make sure that the migration table actually exists before we + // start trying to rollback and re-run all of the migrations. If it's not + // present we'll just bail out with an info message for the developers. + if (! $this->migrator->repositoryExists()) { + return $this->comment('Migration table not found.'); + } + + $this->migrator->setOutput($this->output)->reset( + $this->getMigrationPaths(), $this->option('pretend') + ); + }); + } + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return [ + ['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use'], + + ['force', null, InputOption::VALUE_NONE, 'Force the operation to run when in production'], + + ['path', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'The path(s) to the migrations files to be executed'], + + ['realpath', null, InputOption::VALUE_NONE, 'Indicate any provided migration file paths are pre-resolved absolute paths'], + + ['pretend', null, InputOption::VALUE_NONE, 'Dump the SQL queries that would be run'], + ]; + } +} diff --git a/vendor/illuminate/database/Console/Migrations/RollbackCommand.php b/vendor/illuminate/database/Console/Migrations/RollbackCommand.php new file mode 100644 index 0000000..c851360 --- /dev/null +++ b/vendor/illuminate/database/Console/Migrations/RollbackCommand.php @@ -0,0 +1,91 @@ +migrator = $migrator; + } + + /** + * Execute the console command. + * + * @return int + */ + public function handle() + { + if (! $this->confirmToProceed()) { + return 1; + } + + $this->migrator->usingConnection($this->option('database'), function () { + $this->migrator->setOutput($this->output)->rollback( + $this->getMigrationPaths(), [ + 'pretend' => $this->option('pretend'), + 'step' => (int) $this->option('step'), + ] + ); + }); + + return 0; + } + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return [ + ['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use'], + + ['force', null, InputOption::VALUE_NONE, 'Force the operation to run when in production'], + + ['path', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'The path(s) to the migrations files to be executed'], + + ['realpath', null, InputOption::VALUE_NONE, 'Indicate any provided migration file paths are pre-resolved absolute paths'], + + ['pretend', null, InputOption::VALUE_NONE, 'Dump the SQL queries that would be run'], + + ['step', null, InputOption::VALUE_OPTIONAL, 'The number of migrations to be reverted'], + ]; + } +} diff --git a/vendor/illuminate/database/Console/Migrations/StatusCommand.php b/vendor/illuminate/database/Console/Migrations/StatusCommand.php new file mode 100644 index 0000000..2cf82f9 --- /dev/null +++ b/vendor/illuminate/database/Console/Migrations/StatusCommand.php @@ -0,0 +1,115 @@ +migrator = $migrator; + } + + /** + * Execute the console command. + * + * @return int|null + */ + public function handle() + { + return $this->migrator->usingConnection($this->option('database'), function () { + if (! $this->migrator->repositoryExists()) { + $this->error('Migration table not found.'); + + return 1; + } + + $ran = $this->migrator->getRepository()->getRan(); + + $batches = $this->migrator->getRepository()->getMigrationBatches(); + + if (count($migrations = $this->getStatusFor($ran, $batches)) > 0) { + $this->table(['Ran?', 'Migration', 'Batch'], $migrations); + } else { + $this->error('No migrations found'); + } + }); + } + + /** + * Get the status for the given ran migrations. + * + * @param array $ran + * @param array $batches + * @return \Illuminate\Support\Collection + */ + protected function getStatusFor(array $ran, array $batches) + { + return Collection::make($this->getAllMigrationFiles()) + ->map(function ($migration) use ($ran, $batches) { + $migrationName = $this->migrator->getMigrationName($migration); + + return in_array($migrationName, $ran) + ? ['Yes', $migrationName, $batches[$migrationName]] + : ['No', $migrationName]; + }); + } + + /** + * Get an array of all of the migration files. + * + * @return array + */ + protected function getAllMigrationFiles() + { + return $this->migrator->getMigrationFiles($this->getMigrationPaths()); + } + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return [ + ['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use'], + + ['path', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'The path(s) to the migrations files to use'], + + ['realpath', null, InputOption::VALUE_NONE, 'Indicate any provided migration file paths are pre-resolved absolute paths'], + ]; + } +} diff --git a/vendor/illuminate/database/Console/Migrations/TableGuesser.php b/vendor/illuminate/database/Console/Migrations/TableGuesser.php new file mode 100644 index 0000000..82dfbdd --- /dev/null +++ b/vendor/illuminate/database/Console/Migrations/TableGuesser.php @@ -0,0 +1,37 @@ +models(); + + if ($models->isEmpty()) { + $this->info('No prunable models found.'); + + return; + } + + if ($this->option('pretend')) { + $models->each(function ($model) { + $this->pretendToPrune($model); + }); + + return; + } + + $events->listen(ModelsPruned::class, function ($event) { + $this->info("{$event->count} [{$event->model}] records have been pruned."); + }); + + $models->each(function ($model) { + $instance = new $model; + + $chunkSize = property_exists($instance, 'prunableChunkSize') + ? $instance->prunableChunkSize + : $this->option('chunk'); + + $total = $this->isPrunable($model) + ? $instance->pruneAll($chunkSize) + : 0; + + if ($total == 0) { + $this->info("No prunable [$model] records found."); + } + }); + + $events->forget(ModelsPruned::class); + } + + /** + * Determine the models that should be pruned. + * + * @return \Illuminate\Support\Collection + */ + protected function models() + { + if (! empty($models = $this->option('model'))) { + return collect($models)->filter(function ($model) { + return class_exists($model); + })->values(); + } + + $except = $this->option('except'); + + if (! empty($models) && ! empty($except)) { + throw new InvalidArgumentException('The --models and --except options cannot be combined.'); + } + + return collect((new Finder)->in($this->getDefaultPath())->files()->name('*.php')) + ->map(function ($model) { + $namespace = $this->laravel->getNamespace(); + + return $namespace.str_replace( + ['/', '.php'], + ['\\', ''], + Str::after($model->getRealPath(), realpath(app_path()).DIRECTORY_SEPARATOR) + ); + })->when(! empty($except), function ($models) use ($except) { + return $models->reject(function ($model) use ($except) { + return in_array($model, $except); + }); + })->filter(function ($model) { + return $this->isPrunable($model); + })->filter(function ($model) { + return class_exists($model); + })->values(); + } + + /** + * Get the default path where models are located. + * + * @return string + */ + protected function getDefaultPath() + { + return app_path('Models'); + } + + /** + * Determine if the given model class is prunable. + * + * @param string $model + * @return bool + */ + protected function isPrunable($model) + { + $uses = class_uses_recursive($model); + + return in_array(Prunable::class, $uses) || in_array(MassPrunable::class, $uses); + } + + /** + * Display how many models will be pruned. + * + * @param string $model + * @return void + */ + protected function pretendToPrune($model) + { + $instance = new $model; + + $count = $instance->prunable() + ->when(in_array(SoftDeletes::class, class_uses_recursive(get_class($instance))), function ($query) { + $query->withTrashed(); + })->count(); + + if ($count === 0) { + $this->info("No prunable [$model] records found."); + } else { + $this->info("{$count} [{$model}] records will be pruned."); + } + } +} diff --git a/vendor/illuminate/database/Console/Seeds/SeedCommand.php b/vendor/illuminate/database/Console/Seeds/SeedCommand.php new file mode 100644 index 0000000..058e545 --- /dev/null +++ b/vendor/illuminate/database/Console/Seeds/SeedCommand.php @@ -0,0 +1,138 @@ +resolver = $resolver; + } + + /** + * Execute the console command. + * + * @return int + */ + public function handle() + { + if (! $this->confirmToProceed()) { + return 1; + } + + $previousConnection = $this->resolver->getDefaultConnection(); + + $this->resolver->setDefaultConnection($this->getDatabase()); + + Model::unguarded(function () { + $this->getSeeder()->__invoke(); + }); + + if ($previousConnection) { + $this->resolver->setDefaultConnection($previousConnection); + } + + $this->info('Database seeding completed successfully.'); + + return 0; + } + + /** + * Get a seeder instance from the container. + * + * @return \Illuminate\Database\Seeder + */ + protected function getSeeder() + { + $class = $this->input->getArgument('class') ?? $this->input->getOption('class'); + + if (strpos($class, '\\') === false) { + $class = 'Database\\Seeders\\'.$class; + } + + if ($class === 'Database\\Seeders\\DatabaseSeeder' && + ! class_exists($class)) { + $class = 'DatabaseSeeder'; + } + + return $this->laravel->make($class) + ->setContainer($this->laravel) + ->setCommand($this); + } + + /** + * Get the name of the database connection to use. + * + * @return string + */ + protected function getDatabase() + { + $database = $this->input->getOption('database'); + + return $database ?: $this->laravel['config']['database.default']; + } + + /** + * Get the console command arguments. + * + * @return array + */ + protected function getArguments() + { + return [ + ['class', InputArgument::OPTIONAL, 'The class name of the root seeder', null], + ]; + } + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return [ + ['class', null, InputOption::VALUE_OPTIONAL, 'The class name of the root seeder', 'Database\\Seeders\\DatabaseSeeder'], + ['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to seed'], + ['force', null, InputOption::VALUE_NONE, 'Force the operation to run when in production'], + ]; + } +} diff --git a/vendor/illuminate/database/Console/Seeds/SeederMakeCommand.php b/vendor/illuminate/database/Console/Seeds/SeederMakeCommand.php new file mode 100644 index 0000000..aef7a77 --- /dev/null +++ b/vendor/illuminate/database/Console/Seeds/SeederMakeCommand.php @@ -0,0 +1,88 @@ +resolveStubPath('/stubs/seeder.stub'); + } + + /** + * Resolve the fully-qualified path to the stub. + * + * @param string $stub + * @return string + */ + protected function resolveStubPath($stub) + { + return is_file($customPath = $this->laravel->basePath(trim($stub, '/'))) + ? $customPath + : __DIR__.$stub; + } + + /** + * Get the destination class path. + * + * @param string $name + * @return string + */ + protected function getPath($name) + { + if (is_dir($this->laravel->databasePath().'/seeds')) { + return $this->laravel->databasePath().'/seeds/'.$name.'.php'; + } else { + return $this->laravel->databasePath().'/seeders/'.$name.'.php'; + } + } + + /** + * Parse the class name and format according to the root namespace. + * + * @param string $name + * @return string + */ + protected function qualifyClass($name) + { + return $name; + } +} diff --git a/vendor/illuminate/database/Console/Seeds/stubs/seeder.stub b/vendor/illuminate/database/Console/Seeds/stubs/seeder.stub new file mode 100644 index 0000000..5662969 --- /dev/null +++ b/vendor/illuminate/database/Console/Seeds/stubs/seeder.stub @@ -0,0 +1,18 @@ +confirmToProceed()) { + return 1; + } + + $database = $this->input->getOption('database'); + + if ($this->option('drop-views')) { + $this->dropAllViews($database); + + $this->info('Dropped all views successfully.'); + } + + $this->dropAllTables($database); + + $this->info('Dropped all tables successfully.'); + + if ($this->option('drop-types')) { + $this->dropAllTypes($database); + + $this->info('Dropped all types successfully.'); + } + + return 0; + } + + /** + * Drop all of the database tables. + * + * @param string $database + * @return void + */ + protected function dropAllTables($database) + { + $this->laravel['db']->connection($database) + ->getSchemaBuilder() + ->dropAllTables(); + } + + /** + * Drop all of the database views. + * + * @param string $database + * @return void + */ + protected function dropAllViews($database) + { + $this->laravel['db']->connection($database) + ->getSchemaBuilder() + ->dropAllViews(); + } + + /** + * Drop all of the database types. + * + * @param string $database + * @return void + */ + protected function dropAllTypes($database) + { + $this->laravel['db']->connection($database) + ->getSchemaBuilder() + ->dropAllTypes(); + } + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return [ + ['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use'], + ['drop-views', null, InputOption::VALUE_NONE, 'Drop all tables and views'], + ['drop-types', null, InputOption::VALUE_NONE, 'Drop all tables and types (Postgres only)'], + ['force', null, InputOption::VALUE_NONE, 'Force the operation to run when in production'], + ]; + } +} diff --git a/vendor/illuminate/database/DBAL/TimestampType.php b/vendor/illuminate/database/DBAL/TimestampType.php new file mode 100644 index 0000000..1557f12 --- /dev/null +++ b/vendor/illuminate/database/DBAL/TimestampType.php @@ -0,0 +1,110 @@ +getName(); + + switch ($name) { + case 'mysql': + case 'mysql2': + return $this->getMySqlPlatformSQLDeclaration($fieldDeclaration); + + case 'postgresql': + case 'pgsql': + case 'postgres': + return $this->getPostgresPlatformSQLDeclaration($fieldDeclaration); + + case 'mssql': + return $this->getSqlServerPlatformSQLDeclaration($fieldDeclaration); + + case 'sqlite': + case 'sqlite3': + return $this->getSQLitePlatformSQLDeclaration($fieldDeclaration); + + default: + throw new RuntimeException('Invalid platform: '.$name); + } + } + + /** + * Get the SQL declaration for MySQL. + * + * @param array $fieldDeclaration + * @return string + */ + protected function getMySqlPlatformSQLDeclaration(array $fieldDeclaration) + { + $columnType = 'TIMESTAMP'; + + if ($fieldDeclaration['precision']) { + $columnType = 'TIMESTAMP('.$fieldDeclaration['precision'].')'; + } + + $notNull = $fieldDeclaration['notnull'] ?? false; + + if (! $notNull) { + return $columnType.' NULL'; + } + + return $columnType; + } + + /** + * Get the SQL declaration for PostgreSQL. + * + * @param array $fieldDeclaration + * @return string + */ + protected function getPostgresPlatformSQLDeclaration(array $fieldDeclaration) + { + return 'TIMESTAMP('.(int) $fieldDeclaration['precision'].')'; + } + + /** + * Get the SQL declaration for SQL Server. + * + * @param array $fieldDeclaration + * @return string + */ + protected function getSqlServerPlatformSQLDeclaration(array $fieldDeclaration) + { + return $fieldDeclaration['precision'] ?? false + ? 'DATETIME2('.$fieldDeclaration['precision'].')' + : 'DATETIME'; + } + + /** + * Get the SQL declaration for SQLite. + * + * @param array $fieldDeclaration + * @return string + */ + protected function getSQLitePlatformSQLDeclaration(array $fieldDeclaration) + { + return 'DATETIME'; + } + + /** + * {@inheritdoc} + * + * @return string + */ + public function getName() + { + return 'timestamp'; + } +} diff --git a/vendor/illuminate/database/DatabaseManager.php b/vendor/illuminate/database/DatabaseManager.php new file mode 100644 index 0000000..cb823bf --- /dev/null +++ b/vendor/illuminate/database/DatabaseManager.php @@ -0,0 +1,444 @@ +app = $app; + $this->factory = $factory; + + $this->reconnector = function ($connection) { + $this->reconnect($connection->getNameWithReadWriteType()); + }; + } + + /** + * Get a database connection instance. + * + * @param string|null $name + * @return \Illuminate\Database\Connection + */ + public function connection($name = null) + { + [$database, $type] = $this->parseConnectionName($name); + + $name = $name ?: $database; + + // If we haven't created this connection, we'll create it based on the config + // provided in the application. Once we've created the connections we will + // set the "fetch mode" for PDO which determines the query return types. + if (! isset($this->connections[$name])) { + $this->connections[$name] = $this->configure( + $this->makeConnection($database), $type + ); + } + + return $this->connections[$name]; + } + + /** + * Parse the connection into an array of the name and read / write type. + * + * @param string $name + * @return array + */ + protected function parseConnectionName($name) + { + $name = $name ?: $this->getDefaultConnection(); + + return Str::endsWith($name, ['::read', '::write']) + ? explode('::', $name, 2) : [$name, null]; + } + + /** + * Make the database connection instance. + * + * @param string $name + * @return \Illuminate\Database\Connection + */ + protected function makeConnection($name) + { + $config = $this->configuration($name); + + // First we will check by the connection name to see if an extension has been + // registered specifically for that connection. If it has we will call the + // Closure and pass it the config allowing it to resolve the connection. + if (isset($this->extensions[$name])) { + return call_user_func($this->extensions[$name], $config, $name); + } + + // Next we will check to see if an extension has been registered for a driver + // and will call the Closure if so, which allows us to have a more generic + // resolver for the drivers themselves which applies to all connections. + if (isset($this->extensions[$driver = $config['driver']])) { + return call_user_func($this->extensions[$driver], $config, $name); + } + + return $this->factory->make($config, $name); + } + + /** + * Get the configuration for a connection. + * + * @param string $name + * @return array + * + * @throws \InvalidArgumentException + */ + protected function configuration($name) + { + $name = $name ?: $this->getDefaultConnection(); + + // To get the database connection configuration, we will just pull each of the + // connection configurations and get the configurations for the given name. + // If the configuration doesn't exist, we'll throw an exception and bail. + $connections = $this->app['config']['database.connections']; + + if (is_null($config = Arr::get($connections, $name))) { + throw new InvalidArgumentException("Database connection [{$name}] not configured."); + } + + return (new ConfigurationUrlParser) + ->parseConfiguration($config); + } + + /** + * Prepare the database connection instance. + * + * @param \Illuminate\Database\Connection $connection + * @param string $type + * @return \Illuminate\Database\Connection + */ + protected function configure(Connection $connection, $type) + { + $connection = $this->setPdoForType($connection, $type)->setReadWriteType($type); + + // First we'll set the fetch mode and a few other dependencies of the database + // connection. This method basically just configures and prepares it to get + // used by the application. Once we're finished we'll return it back out. + if ($this->app->bound('events')) { + $connection->setEventDispatcher($this->app['events']); + } + + if ($this->app->bound('db.transactions')) { + $connection->setTransactionManager($this->app['db.transactions']); + } + + // Here we'll set a reconnector callback. This reconnector can be any callable + // so we will set a Closure to reconnect from this manager with the name of + // the connection, which will allow us to reconnect from the connections. + $connection->setReconnector($this->reconnector); + + $this->registerConfiguredDoctrineTypes($connection); + + return $connection; + } + + /** + * Prepare the read / write mode for database connection instance. + * + * @param \Illuminate\Database\Connection $connection + * @param string|null $type + * @return \Illuminate\Database\Connection + */ + protected function setPdoForType(Connection $connection, $type = null) + { + if ($type === 'read') { + $connection->setPdo($connection->getReadPdo()); + } elseif ($type === 'write') { + $connection->setReadPdo($connection->getPdo()); + } + + return $connection; + } + + /** + * Register custom Doctrine types with the connection. + * + * @param \Illuminate\Database\Connection $connection + * @return void + */ + protected function registerConfiguredDoctrineTypes(Connection $connection): void + { + foreach ($this->app['config']->get('database.dbal.types', []) as $name => $class) { + $this->registerDoctrineType($class, $name, $name); + } + + foreach ($this->doctrineTypes as $name => [$type, $class]) { + $connection->registerDoctrineType($class, $name, $type); + } + } + + /** + * Register a custom Doctrine type. + * + * @param string $class + * @param string $name + * @param string $type + * @return void + * + * @throws \Doctrine\DBAL\DBALException + * @throws \RuntimeException + */ + public function registerDoctrineType(string $class, string $name, string $type): void + { + if (! class_exists('Doctrine\DBAL\Connection')) { + throw new RuntimeException( + 'Registering a custom Doctrine type requires Doctrine DBAL (doctrine/dbal).' + ); + } + + if (! Type::hasType($name)) { + Type::addType($name, $class); + } + + $this->doctrineTypes[$name] = [$type, $class]; + } + + /** + * Disconnect from the given database and remove from local cache. + * + * @param string|null $name + * @return void + */ + public function purge($name = null) + { + $name = $name ?: $this->getDefaultConnection(); + + $this->disconnect($name); + + unset($this->connections[$name]); + } + + /** + * Disconnect from the given database. + * + * @param string|null $name + * @return void + */ + public function disconnect($name = null) + { + if (isset($this->connections[$name = $name ?: $this->getDefaultConnection()])) { + $this->connections[$name]->disconnect(); + } + } + + /** + * Reconnect to the given database. + * + * @param string|null $name + * @return \Illuminate\Database\Connection + */ + public function reconnect($name = null) + { + $this->disconnect($name = $name ?: $this->getDefaultConnection()); + + if (! isset($this->connections[$name])) { + return $this->connection($name); + } + + return $this->refreshPdoConnections($name); + } + + /** + * Set the default database connection for the callback execution. + * + * @param string $name + * @param callable $callback + * @return mixed + */ + public function usingConnection($name, callable $callback) + { + $previousName = $this->getDefaultConnection(); + + $this->setDefaultConnection($name); + + return tap($callback(), function () use ($previousName) { + $this->setDefaultConnection($previousName); + }); + } + + /** + * Refresh the PDO connections on a given connection. + * + * @param string $name + * @return \Illuminate\Database\Connection + */ + protected function refreshPdoConnections($name) + { + [$database, $type] = $this->parseConnectionName($name); + + $fresh = $this->configure( + $this->makeConnection($database), $type + ); + + return $this->connections[$name] + ->setPdo($fresh->getRawPdo()) + ->setReadPdo($fresh->getRawReadPdo()); + } + + /** + * Get the default connection name. + * + * @return string + */ + public function getDefaultConnection() + { + return $this->app['config']['database.default']; + } + + /** + * Set the default connection name. + * + * @param string $name + * @return void + */ + public function setDefaultConnection($name) + { + $this->app['config']['database.default'] = $name; + } + + /** + * Get all of the support drivers. + * + * @return array + */ + public function supportedDrivers() + { + return ['mysql', 'pgsql', 'sqlite', 'sqlsrv']; + } + + /** + * Get all of the drivers that are actually available. + * + * @return array + */ + public function availableDrivers() + { + return array_intersect( + $this->supportedDrivers(), + str_replace('dblib', 'sqlsrv', PDO::getAvailableDrivers()) + ); + } + + /** + * Register an extension connection resolver. + * + * @param string $name + * @param callable $resolver + * @return void + */ + public function extend($name, callable $resolver) + { + $this->extensions[$name] = $resolver; + } + + /** + * Return all of the created connections. + * + * @return array + */ + public function getConnections() + { + return $this->connections; + } + + /** + * Set the database reconnector callback. + * + * @param callable $reconnector + * @return void + */ + public function setReconnector(callable $reconnector) + { + $this->reconnector = $reconnector; + } + + /** + * Set the application instance used by the manager. + * + * @param \Illuminate\Contracts\Foundation\Application $app + * @return $this + */ + public function setApplication($app) + { + $this->app = $app; + + return $this; + } + + /** + * Dynamically pass methods to the default connection. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + return $this->connection()->$method(...$parameters); + } +} diff --git a/vendor/illuminate/database/DatabaseServiceProvider.php b/vendor/illuminate/database/DatabaseServiceProvider.php new file mode 100644 index 0000000..4b6521b --- /dev/null +++ b/vendor/illuminate/database/DatabaseServiceProvider.php @@ -0,0 +1,109 @@ +app['db']); + + Model::setEventDispatcher($this->app['events']); + } + + /** + * Register the service provider. + * + * @return void + */ + public function register() + { + Model::clearBootedModels(); + + $this->registerConnectionServices(); + $this->registerEloquentFactory(); + $this->registerQueueableEntityResolver(); + } + + /** + * Register the primary database bindings. + * + * @return void + */ + protected function registerConnectionServices() + { + // The connection factory is used to create the actual connection instances on + // the database. We will inject the factory into the manager so that it may + // make the connections while they are actually needed and not of before. + $this->app->singleton('db.factory', function ($app) { + return new ConnectionFactory($app); + }); + + // The database manager is used to resolve various connections, since multiple + // connections might be managed. It also implements the connection resolver + // interface which may be used by other components requiring connections. + $this->app->singleton('db', function ($app) { + return new DatabaseManager($app, $app['db.factory']); + }); + + $this->app->bind('db.connection', function ($app) { + return $app['db']->connection(); + }); + + $this->app->singleton('db.transactions', function ($app) { + return new DatabaseTransactionsManager; + }); + } + + /** + * Register the Eloquent factory instance in the container. + * + * @return void + */ + protected function registerEloquentFactory() + { + $this->app->singleton(FakerGenerator::class, function ($app, $parameters) { + $locale = $parameters['locale'] ?? $app['config']->get('app.faker_locale', 'en_US'); + + if (! isset(static::$fakers[$locale])) { + static::$fakers[$locale] = FakerFactory::create($locale); + } + + static::$fakers[$locale]->unique(true); + + return static::$fakers[$locale]; + }); + } + + /** + * Register the queueable entity resolver implementation. + * + * @return void + */ + protected function registerQueueableEntityResolver() + { + $this->app->singleton(EntityResolver::class, function () { + return new QueueEntityResolver; + }); + } +} diff --git a/vendor/illuminate/database/DatabaseTransactionRecord.php b/vendor/illuminate/database/DatabaseTransactionRecord.php new file mode 100644 index 0000000..3259552 --- /dev/null +++ b/vendor/illuminate/database/DatabaseTransactionRecord.php @@ -0,0 +1,73 @@ +connection = $connection; + $this->level = $level; + } + + /** + * Register a callback to be executed after committing. + * + * @param callable $callback + * @return void + */ + public function addCallback($callback) + { + $this->callbacks[] = $callback; + } + + /** + * Execute all of the callbacks. + * + * @return void + */ + public function executeCallbacks() + { + foreach ($this->callbacks as $callback) { + call_user_func($callback); + } + } + + /** + * Get all of the callbacks. + * + * @return array + */ + public function getCallbacks() + { + return $this->callbacks; + } +} diff --git a/vendor/illuminate/database/DatabaseTransactionsManager.php b/vendor/illuminate/database/DatabaseTransactionsManager.php new file mode 100644 index 0000000..add2f7c --- /dev/null +++ b/vendor/illuminate/database/DatabaseTransactionsManager.php @@ -0,0 +1,96 @@ +transactions = collect(); + } + + /** + * Start a new database transaction. + * + * @param string $connection + * @param int $level + * @return void + */ + public function begin($connection, $level) + { + $this->transactions->push( + new DatabaseTransactionRecord($connection, $level) + ); + } + + /** + * Rollback the active database transaction. + * + * @param string $connection + * @param int $level + * @return void + */ + public function rollback($connection, $level) + { + $this->transactions = $this->transactions->reject(function ($transaction) use ($connection, $level) { + return $transaction->connection == $connection && + $transaction->level > $level; + })->values(); + } + + /** + * Commit the active database transaction. + * + * @param string $connection + * @return void + */ + public function commit($connection) + { + [$forThisConnection, $forOtherConnections] = $this->transactions->partition( + function ($transaction) use ($connection) { + return $transaction->connection == $connection; + } + ); + + $this->transactions = $forOtherConnections->values(); + + $forThisConnection->map->executeCallbacks(); + } + + /** + * Register a transaction callback. + * + * @param callable $callback + * @return void + */ + public function addCallback($callback) + { + if ($current = $this->transactions->last()) { + return $current->addCallback($callback); + } + + call_user_func($callback); + } + + /** + * Get all the transactions. + * + * @return \Illuminate\Support\Collection + */ + public function getTransactions() + { + return $this->transactions; + } +} diff --git a/vendor/illuminate/database/DetectsConcurrencyErrors.php b/vendor/illuminate/database/DetectsConcurrencyErrors.php new file mode 100644 index 0000000..c6c66f4 --- /dev/null +++ b/vendor/illuminate/database/DetectsConcurrencyErrors.php @@ -0,0 +1,37 @@ +getCode() === 40001 || $e->getCode() === '40001')) { + return true; + } + + $message = $e->getMessage(); + + return Str::contains($message, [ + 'Deadlock found when trying to get lock', + 'deadlock detected', + 'The database file is locked', + 'database is locked', + 'database table is locked', + 'A table in the database is locked', + 'has been chosen as the deadlock victim', + 'Lock wait timeout exceeded; try restarting transaction', + 'WSREP detected deadlock/conflict and aborted the transaction. Try restarting the transaction', + ]); + } +} diff --git a/vendor/illuminate/database/DetectsLostConnections.php b/vendor/illuminate/database/DetectsLostConnections.php new file mode 100644 index 0000000..16c8687 --- /dev/null +++ b/vendor/illuminate/database/DetectsLostConnections.php @@ -0,0 +1,62 @@ +getMessage(); + + return Str::contains($message, [ + 'server has gone away', + 'no connection to the server', + 'Lost connection', + 'is dead or not enabled', + 'Error while sending', + 'decryption failed or bad record mac', + 'server closed the connection unexpectedly', + 'SSL connection has been closed unexpectedly', + 'Error writing data to the connection', + 'Resource deadlock avoided', + 'Transaction() on null', + 'child connection forced to terminate due to client_idle_limit', + 'query_wait_timeout', + 'reset by peer', + 'Physical connection is not usable', + 'TCP Provider: Error code 0x68', + 'ORA-03114', + 'Packets out of order. Expected', + 'Adaptive Server connection failed', + 'Communication link failure', + 'connection is no longer usable', + 'Login timeout expired', + 'SQLSTATE[HY000] [2002] Connection refused', + 'running with the --read-only option so it cannot execute this statement', + 'The connection is broken and recovery is not possible. The connection is marked by the client driver as unrecoverable. No attempt was made to restore the connection.', + 'SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo failed: Try again', + 'SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo failed: Name or service not known', + 'SQLSTATE[HY000]: General error: 7 SSL SYSCALL error: EOF detected', + 'SQLSTATE[HY000] [2002] Connection timed out', + 'SSL: Connection timed out', + 'SQLSTATE[HY000]: General error: 1105 The last transaction was aborted due to Seamless Scaling. Please retry.', + 'Temporary failure in name resolution', + 'SSL: Broken pipe', + 'SQLSTATE[08S01]: Communication link failure', + 'SQLSTATE[08006] [7] could not connect to server: Connection refused Is the server running on host', + 'SQLSTATE[HY000]: General error: 7 SSL SYSCALL error: No route to host', + 'The client was disconnected by the server because of inactivity. See wait_timeout and interactive_timeout for configuring this behavior.', + 'SQLSTATE[08006] [7] could not translate host name', + 'TCP Provider: Error code 0x274C', + ]); + } +} diff --git a/vendor/illuminate/database/Eloquent/BroadcastableModelEventOccurred.php b/vendor/illuminate/database/Eloquent/BroadcastableModelEventOccurred.php new file mode 100644 index 0000000..14be425 --- /dev/null +++ b/vendor/illuminate/database/Eloquent/BroadcastableModelEventOccurred.php @@ -0,0 +1,137 @@ +model = $model; + $this->event = $event; + } + + /** + * The channels the event should broadcast on. + * + * @return array + */ + public function broadcastOn() + { + $channels = empty($this->channels) + ? ($this->model->broadcastOn($this->event) ?: []) + : $this->channels; + + return collect($channels)->map(function ($channel) { + return $channel instanceof Model ? new PrivateChannel($channel) : $channel; + })->all(); + } + + /** + * The name the event should broadcast as. + * + * @return string + */ + public function broadcastAs() + { + $default = class_basename($this->model).ucfirst($this->event); + + return method_exists($this->model, 'broadcastAs') + ? ($this->model->broadcastAs($this->event) ?: $default) + : $default; + } + + /** + * Get the data that should be sent with the broadcasted event. + * + * @return array|null + */ + public function broadcastWith() + { + return method_exists($this->model, 'broadcastWith') + ? $this->model->broadcastWith($this->event) + : null; + } + + /** + * Manually specify the channels the event should broadcast on. + * + * @param array $channels + * @return $this + */ + public function onChannels(array $channels) + { + $this->channels = $channels; + + return $this; + } + + /** + * Determine if the event should be broadcast synchronously. + * + * @return bool + */ + public function shouldBroadcastNow() + { + return $this->event === 'deleted' && + ! method_exists($this->model, 'bootSoftDeletes'); + } + + /** + * Get the event name. + * + * @return string + */ + public function event() + { + return $this->event; + } +} diff --git a/vendor/illuminate/database/Eloquent/BroadcastsEvents.php b/vendor/illuminate/database/Eloquent/BroadcastsEvents.php new file mode 100644 index 0000000..79dc02d --- /dev/null +++ b/vendor/illuminate/database/Eloquent/BroadcastsEvents.php @@ -0,0 +1,197 @@ +broadcastCreated(); + }); + + static::updated(function ($model) { + $model->broadcastUpdated(); + }); + + if (method_exists(static::class, 'bootSoftDeletes')) { + static::softDeleted(function ($model) { + $model->broadcastTrashed(); + }); + + static::restored(function ($model) { + $model->broadcastRestored(); + }); + } + + static::deleted(function ($model) { + $model->broadcastDeleted(); + }); + } + + /** + * Broadcast that the model was created. + * + * @param \Illuminate\Broadcasting\Channel|\Illuminate\Contracts\Broadcasting\HasBroadcastChannel|array|null $channels + * @return \Illuminate\Broadcasting\PendingBroadcast + */ + public function broadcastCreated($channels = null) + { + return $this->broadcastIfBroadcastChannelsExistForEvent( + $this->newBroadcastableModelEvent('created'), 'created', $channels + ); + } + + /** + * Broadcast that the model was updated. + * + * @param \Illuminate\Broadcasting\Channel|\Illuminate\Contracts\Broadcasting\HasBroadcastChannel|array|null $channels + * @return \Illuminate\Broadcasting\PendingBroadcast + */ + public function broadcastUpdated($channels = null) + { + return $this->broadcastIfBroadcastChannelsExistForEvent( + $this->newBroadcastableModelEvent('updated'), 'updated', $channels + ); + } + + /** + * Broadcast that the model was trashed. + * + * @param \Illuminate\Broadcasting\Channel|\Illuminate\Contracts\Broadcasting\HasBroadcastChannel|array|null $channels + * @return \Illuminate\Broadcasting\PendingBroadcast + */ + public function broadcastTrashed($channels = null) + { + return $this->broadcastIfBroadcastChannelsExistForEvent( + $this->newBroadcastableModelEvent('trashed'), 'trashed', $channels + ); + } + + /** + * Broadcast that the model was restored. + * + * @param \Illuminate\Broadcasting\Channel|\Illuminate\Contracts\Broadcasting\HasBroadcastChannel|array|null $channels + * @return \Illuminate\Broadcasting\PendingBroadcast + */ + public function broadcastRestored($channels = null) + { + return $this->broadcastIfBroadcastChannelsExistForEvent( + $this->newBroadcastableModelEvent('restored'), 'restored', $channels + ); + } + + /** + * Broadcast that the model was deleted. + * + * @param \Illuminate\Broadcasting\Channel|\Illuminate\Contracts\Broadcasting\HasBroadcastChannel|array|null $channels + * @return \Illuminate\Broadcasting\PendingBroadcast + */ + public function broadcastDeleted($channels = null) + { + return $this->broadcastIfBroadcastChannelsExistForEvent( + $this->newBroadcastableModelEvent('deleted'), 'deleted', $channels + ); + } + + /** + * Broadcast the given event instance if channels are configured for the model event. + * + * @param mixed $instance + * @param string $event + * @param mixed $channels + * @return \Illuminate\Broadcasting\PendingBroadcast|null + */ + protected function broadcastIfBroadcastChannelsExistForEvent($instance, $event, $channels = null) + { + if (! static::$isBroadcasting) { + return; + } + + if (! empty($this->broadcastOn($event)) || ! empty($channels)) { + return broadcast($instance->onChannels(Arr::wrap($channels))); + } + } + + /** + * Create a new broadcastable model event event. + * + * @param string $event + * @return mixed + */ + public function newBroadcastableModelEvent($event) + { + return tap($this->newBroadcastableEvent($event), function ($event) { + $event->connection = property_exists($this, 'broadcastConnection') + ? $this->broadcastConnection + : $this->broadcastConnection(); + + $event->queue = property_exists($this, 'broadcastQueue') + ? $this->broadcastQueue + : $this->broadcastQueue(); + + $event->afterCommit = property_exists($this, 'broadcastAfterCommit') + ? $this->broadcastAfterCommit + : $this->broadcastAfterCommit(); + }); + } + + /** + * Create a new broadcastable model event for the model. + * + * @param string $event + * @return \Illuminate\Database\Eloquent\BroadcastableModelEventOccurred + */ + protected function newBroadcastableEvent($event) + { + return new BroadcastableModelEventOccurred($this, $event); + } + + /** + * Get the channels that model events should broadcast on. + * + * @param string $event + * @return \Illuminate\Broadcasting\Channel|array + */ + public function broadcastOn($event) + { + return [$this]; + } + + /** + * Get the queue connection that should be used to broadcast model events. + * + * @return string|null + */ + public function broadcastConnection() + { + // + } + + /** + * Get the queue that should be used to broadcast model events. + * + * @return string|null + */ + public function broadcastQueue() + { + // + } + + /** + * Determine if the model event broadcast queued job should be dispatched after all transactions are committed. + * + * @return bool + */ + public function broadcastAfterCommit() + { + return false; + } +} diff --git a/vendor/illuminate/database/Eloquent/Builder.php b/vendor/illuminate/database/Eloquent/Builder.php new file mode 100644 index 0000000..babd44b --- /dev/null +++ b/vendor/illuminate/database/Eloquent/Builder.php @@ -0,0 +1,1740 @@ +query = $query; + } + + /** + * Create and return an un-saved model instance. + * + * @param array $attributes + * @return \Illuminate\Database\Eloquent\Model|static + */ + public function make(array $attributes = []) + { + return $this->newModelInstance($attributes); + } + + /** + * Register a new global scope. + * + * @param string $identifier + * @param \Illuminate\Database\Eloquent\Scope|\Closure $scope + * @return $this + */ + public function withGlobalScope($identifier, $scope) + { + $this->scopes[$identifier] = $scope; + + if (method_exists($scope, 'extend')) { + $scope->extend($this); + } + + return $this; + } + + /** + * Remove a registered global scope. + * + * @param \Illuminate\Database\Eloquent\Scope|string $scope + * @return $this + */ + public function withoutGlobalScope($scope) + { + if (! is_string($scope)) { + $scope = get_class($scope); + } + + unset($this->scopes[$scope]); + + $this->removedScopes[] = $scope; + + return $this; + } + + /** + * Remove all or passed registered global scopes. + * + * @param array|null $scopes + * @return $this + */ + public function withoutGlobalScopes(array $scopes = null) + { + if (! is_array($scopes)) { + $scopes = array_keys($this->scopes); + } + + foreach ($scopes as $scope) { + $this->withoutGlobalScope($scope); + } + + return $this; + } + + /** + * Get an array of global scopes that were removed from the query. + * + * @return array + */ + public function removedScopes() + { + return $this->removedScopes; + } + + /** + * Add a where clause on the primary key to the query. + * + * @param mixed $id + * @return $this + */ + public function whereKey($id) + { + if ($id instanceof Model) { + $id = $id->getKey(); + } + + if (is_array($id) || $id instanceof Arrayable) { + $this->query->whereIn($this->model->getQualifiedKeyName(), $id); + + return $this; + } + + if ($id !== null && $this->model->getKeyType() === 'string') { + $id = (string) $id; + } + + return $this->where($this->model->getQualifiedKeyName(), '=', $id); + } + + /** + * Add a where clause on the primary key to the query. + * + * @param mixed $id + * @return $this + */ + public function whereKeyNot($id) + { + if ($id instanceof Model) { + $id = $id->getKey(); + } + + if (is_array($id) || $id instanceof Arrayable) { + $this->query->whereNotIn($this->model->getQualifiedKeyName(), $id); + + return $this; + } + + if ($id !== null && $this->model->getKeyType() === 'string') { + $id = (string) $id; + } + + return $this->where($this->model->getQualifiedKeyName(), '!=', $id); + } + + /** + * Add a basic where clause to the query. + * + * @param \Closure|string|array|\Illuminate\Database\Query\Expression $column + * @param mixed $operator + * @param mixed $value + * @param string $boolean + * @return $this + */ + public function where($column, $operator = null, $value = null, $boolean = 'and') + { + if ($column instanceof Closure && is_null($operator)) { + $column($query = $this->model->newQueryWithoutRelationships()); + + $this->query->addNestedWhereQuery($query->getQuery(), $boolean); + } else { + $this->query->where(...func_get_args()); + } + + return $this; + } + + /** + * Add a basic where clause to the query, and return the first result. + * + * @param \Closure|string|array|\Illuminate\Database\Query\Expression $column + * @param mixed $operator + * @param mixed $value + * @param string $boolean + * @return \Illuminate\Database\Eloquent\Model|static|null + */ + public function firstWhere($column, $operator = null, $value = null, $boolean = 'and') + { + return $this->where($column, $operator, $value, $boolean)->first(); + } + + /** + * Add an "or where" clause to the query. + * + * @param \Closure|array|string|\Illuminate\Database\Query\Expression $column + * @param mixed $operator + * @param mixed $value + * @return $this + */ + public function orWhere($column, $operator = null, $value = null) + { + [$value, $operator] = $this->query->prepareValueAndOperator( + $value, $operator, func_num_args() === 2 + ); + + return $this->where($column, $operator, $value, 'or'); + } + + /** + * Add an "order by" clause for a timestamp to the query. + * + * @param string|\Illuminate\Database\Query\Expression $column + * @return $this + */ + public function latest($column = null) + { + if (is_null($column)) { + $column = $this->model->getCreatedAtColumn() ?? 'created_at'; + } + + $this->query->latest($column); + + return $this; + } + + /** + * Add an "order by" clause for a timestamp to the query. + * + * @param string|\Illuminate\Database\Query\Expression $column + * @return $this + */ + public function oldest($column = null) + { + if (is_null($column)) { + $column = $this->model->getCreatedAtColumn() ?? 'created_at'; + } + + $this->query->oldest($column); + + return $this; + } + + /** + * Create a collection of models from plain arrays. + * + * @param array $items + * @return \Illuminate\Database\Eloquent\Collection + */ + public function hydrate(array $items) + { + $instance = $this->newModelInstance(); + + return $instance->newCollection(array_map(function ($item) use ($items, $instance) { + $model = $instance->newFromBuilder($item); + + if (count($items) > 1) { + $model->preventsLazyLoading = Model::preventsLazyLoading(); + } + + return $model; + }, $items)); + } + + /** + * Create a collection of models from a raw query. + * + * @param string $query + * @param array $bindings + * @return \Illuminate\Database\Eloquent\Collection + */ + public function fromQuery($query, $bindings = []) + { + return $this->hydrate( + $this->query->getConnection()->select($query, $bindings) + ); + } + + /** + * Find a model by its primary key. + * + * @param mixed $id + * @param array $columns + * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|static[]|static|null + */ + public function find($id, $columns = ['*']) + { + if (is_array($id) || $id instanceof Arrayable) { + return $this->findMany($id, $columns); + } + + return $this->whereKey($id)->first($columns); + } + + /** + * Find multiple models by their primary keys. + * + * @param \Illuminate\Contracts\Support\Arrayable|array $ids + * @param array $columns + * @return \Illuminate\Database\Eloquent\Collection + */ + public function findMany($ids, $columns = ['*']) + { + $ids = $ids instanceof Arrayable ? $ids->toArray() : $ids; + + if (empty($ids)) { + return $this->model->newCollection(); + } + + return $this->whereKey($ids)->get($columns); + } + + /** + * Find a model by its primary key or throw an exception. + * + * @param mixed $id + * @param array $columns + * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|static|static[] + * + * @throws \Illuminate\Database\Eloquent\ModelNotFoundException + */ + public function findOrFail($id, $columns = ['*']) + { + $result = $this->find($id, $columns); + + $id = $id instanceof Arrayable ? $id->toArray() : $id; + + if (is_array($id)) { + if (count($result) === count(array_unique($id))) { + return $result; + } + } elseif (! is_null($result)) { + return $result; + } + + throw (new ModelNotFoundException)->setModel( + get_class($this->model), $id + ); + } + + /** + * Find a model by its primary key or return fresh model instance. + * + * @param mixed $id + * @param array $columns + * @return \Illuminate\Database\Eloquent\Model|static + */ + public function findOrNew($id, $columns = ['*']) + { + if (! is_null($model = $this->find($id, $columns))) { + return $model; + } + + return $this->newModelInstance(); + } + + /** + * Get the first record matching the attributes or instantiate it. + * + * @param array $attributes + * @param array $values + * @return \Illuminate\Database\Eloquent\Model|static + */ + public function firstOrNew(array $attributes = [], array $values = []) + { + if (! is_null($instance = $this->where($attributes)->first())) { + return $instance; + } + + return $this->newModelInstance(array_merge($attributes, $values)); + } + + /** + * Get the first record matching the attributes or create it. + * + * @param array $attributes + * @param array $values + * @return \Illuminate\Database\Eloquent\Model|static + */ + public function firstOrCreate(array $attributes = [], array $values = []) + { + if (! is_null($instance = $this->where($attributes)->first())) { + return $instance; + } + + return tap($this->newModelInstance(array_merge($attributes, $values)), function ($instance) { + $instance->save(); + }); + } + + /** + * Create or update a record matching the attributes, and fill it with values. + * + * @param array $attributes + * @param array $values + * @return \Illuminate\Database\Eloquent\Model|static + */ + public function updateOrCreate(array $attributes, array $values = []) + { + return tap($this->firstOrNew($attributes), function ($instance) use ($values) { + $instance->fill($values)->save(); + }); + } + + /** + * Execute the query and get the first result or throw an exception. + * + * @param array $columns + * @return \Illuminate\Database\Eloquent\Model|static + * + * @throws \Illuminate\Database\Eloquent\ModelNotFoundException + */ + public function firstOrFail($columns = ['*']) + { + if (! is_null($model = $this->first($columns))) { + return $model; + } + + throw (new ModelNotFoundException)->setModel(get_class($this->model)); + } + + /** + * Execute the query and get the first result or call a callback. + * + * @param \Closure|array $columns + * @param \Closure|null $callback + * @return \Illuminate\Database\Eloquent\Model|static|mixed + */ + public function firstOr($columns = ['*'], Closure $callback = null) + { + if ($columns instanceof Closure) { + $callback = $columns; + + $columns = ['*']; + } + + if (! is_null($model = $this->first($columns))) { + return $model; + } + + return $callback(); + } + + /** + * Execute the query and get the first result if it's the sole matching record. + * + * @param array|string $columns + * @return \Illuminate\Database\Eloquent\Model + * + * @throws \Illuminate\Database\Eloquent\ModelNotFoundException + * @throws \Illuminate\Database\MultipleRecordsFoundException + */ + public function sole($columns = ['*']) + { + try { + return $this->baseSole($columns); + } catch (RecordsNotFoundException $exception) { + throw (new ModelNotFoundException)->setModel(get_class($this->model)); + } + } + + /** + * Get a single column's value from the first result of a query. + * + * @param string|\Illuminate\Database\Query\Expression $column + * @return mixed + */ + public function value($column) + { + if ($result = $this->first([$column])) { + return $result->{Str::afterLast($column, '.')}; + } + } + + /** + * Get a single column's value from the first result of the query or throw an exception. + * + * @param string|\Illuminate\Database\Query\Expression $column + * @return mixed + * + * @throws \Illuminate\Database\Eloquent\ModelNotFoundException + */ + public function valueOrFail($column) + { + return $this->firstOrFail([$column])->{Str::afterLast($column, '.')}; + } + + /** + * Execute the query as a "select" statement. + * + * @param array|string $columns + * @return \Illuminate\Database\Eloquent\Collection|static[] + */ + public function get($columns = ['*']) + { + $builder = $this->applyScopes(); + + // If we actually found models we will also eager load any relationships that + // have been specified as needing to be eager loaded, which will solve the + // n+1 query issue for the developers to avoid running a lot of queries. + if (count($models = $builder->getModels($columns)) > 0) { + $models = $builder->eagerLoadRelations($models); + } + + return $builder->getModel()->newCollection($models); + } + + /** + * Get the hydrated models without eager loading. + * + * @param array|string $columns + * @return \Illuminate\Database\Eloquent\Model[]|static[] + */ + public function getModels($columns = ['*']) + { + return $this->model->hydrate( + $this->query->get($columns)->all() + )->all(); + } + + /** + * Eager load the relationships for the models. + * + * @param array $models + * @return array + */ + public function eagerLoadRelations(array $models) + { + foreach ($this->eagerLoad as $name => $constraints) { + // For nested eager loads we'll skip loading them here and they will be set as an + // eager load on the query to retrieve the relation so that they will be eager + // loaded on that query, because that is where they get hydrated as models. + if (strpos($name, '.') === false) { + $models = $this->eagerLoadRelation($models, $name, $constraints); + } + } + + return $models; + } + + /** + * Eagerly load the relationship on a set of models. + * + * @param array $models + * @param string $name + * @param \Closure $constraints + * @return array + */ + protected function eagerLoadRelation(array $models, $name, Closure $constraints) + { + // First we will "back up" the existing where conditions on the query so we can + // add our eager constraints. Then we will merge the wheres that were on the + // query back to it in order that any where conditions might be specified. + $relation = $this->getRelation($name); + + $relation->addEagerConstraints($models); + + $constraints($relation); + + // Once we have the results, we just match those back up to their parent models + // using the relationship instance. Then we just return the finished arrays + // of models which have been eagerly hydrated and are readied for return. + return $relation->match( + $relation->initRelation($models, $name), + $relation->getEager(), $name + ); + } + + /** + * Get the relation instance for the given relation name. + * + * @param string $name + * @return \Illuminate\Database\Eloquent\Relations\Relation + */ + public function getRelation($name) + { + // We want to run a relationship query without any constrains so that we will + // not have to remove these where clauses manually which gets really hacky + // and error prone. We don't want constraints because we add eager ones. + $relation = Relation::noConstraints(function () use ($name) { + try { + return $this->getModel()->newInstance()->$name(); + } catch (BadMethodCallException $e) { + throw RelationNotFoundException::make($this->getModel(), $name); + } + }); + + $nested = $this->relationsNestedUnder($name); + + // If there are nested relationships set on the query, we will put those onto + // the query instances so that they can be handled after this relationship + // is loaded. In this way they will all trickle down as they are loaded. + if (count($nested) > 0) { + $relation->getQuery()->with($nested); + } + + return $relation; + } + + /** + * Get the deeply nested relations for a given top-level relation. + * + * @param string $relation + * @return array + */ + protected function relationsNestedUnder($relation) + { + $nested = []; + + // We are basically looking for any relationships that are nested deeper than + // the given top-level relationship. We will just check for any relations + // that start with the given top relations and adds them to our arrays. + foreach ($this->eagerLoad as $name => $constraints) { + if ($this->isNestedUnder($relation, $name)) { + $nested[substr($name, strlen($relation.'.'))] = $constraints; + } + } + + return $nested; + } + + /** + * Determine if the relationship is nested. + * + * @param string $relation + * @param string $name + * @return bool + */ + protected function isNestedUnder($relation, $name) + { + return Str::contains($name, '.') && Str::startsWith($name, $relation.'.'); + } + + /** + * Get a lazy collection for the given query. + * + * @return \Illuminate\Support\LazyCollection + */ + public function cursor() + { + return $this->applyScopes()->query->cursor()->map(function ($record) { + return $this->newModelInstance()->newFromBuilder($record); + }); + } + + /** + * Add a generic "order by" clause if the query doesn't already have one. + * + * @return void + */ + protected function enforceOrderBy() + { + if (empty($this->query->orders) && empty($this->query->unionOrders)) { + $this->orderBy($this->model->getQualifiedKeyName(), 'asc'); + } + } + + /** + * Get an array with the values of a given column. + * + * @param string|\Illuminate\Database\Query\Expression $column + * @param string|null $key + * @return \Illuminate\Support\Collection + */ + public function pluck($column, $key = null) + { + $results = $this->toBase()->pluck($column, $key); + + // If the model has a mutator for the requested column, we will spin through + // the results and mutate the values so that the mutated version of these + // columns are returned as you would expect from these Eloquent models. + if (! $this->model->hasGetMutator($column) && + ! $this->model->hasCast($column) && + ! in_array($column, $this->model->getDates())) { + return $results; + } + + return $results->map(function ($value) use ($column) { + return $this->model->newFromBuilder([$column => $value])->{$column}; + }); + } + + /** + * Paginate the given query. + * + * @param int|null $perPage + * @param array $columns + * @param string $pageName + * @param int|null $page + * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator + * + * @throws \InvalidArgumentException + */ + public function paginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null) + { + $page = $page ?: Paginator::resolveCurrentPage($pageName); + + $perPage = $perPage ?: $this->model->getPerPage(); + + $results = ($total = $this->toBase()->getCountForPagination()) + ? $this->forPage($page, $perPage)->get($columns) + : $this->model->newCollection(); + + return $this->paginator($results, $total, $perPage, $page, [ + 'path' => Paginator::resolveCurrentPath(), + 'pageName' => $pageName, + ]); + } + + /** + * Paginate the given query into a simple paginator. + * + * @param int|null $perPage + * @param array $columns + * @param string $pageName + * @param int|null $page + * @return \Illuminate\Contracts\Pagination\Paginator + */ + public function simplePaginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null) + { + $page = $page ?: Paginator::resolveCurrentPage($pageName); + + $perPage = $perPage ?: $this->model->getPerPage(); + + // Next we will set the limit and offset for this query so that when we get the + // results we get the proper section of results. Then, we'll create the full + // paginator instances for these results with the given page and per page. + $this->skip(($page - 1) * $perPage)->take($perPage + 1); + + return $this->simplePaginator($this->get($columns), $perPage, $page, [ + 'path' => Paginator::resolveCurrentPath(), + 'pageName' => $pageName, + ]); + } + + /** + * Paginate the given query into a cursor paginator. + * + * @param int|null $perPage + * @param array $columns + * @param string $cursorName + * @param \Illuminate\Pagination\Cursor|string|null $cursor + * @return \Illuminate\Contracts\Pagination\CursorPaginator + */ + public function cursorPaginate($perPage = null, $columns = ['*'], $cursorName = 'cursor', $cursor = null) + { + $perPage = $perPage ?: $this->model->getPerPage(); + + return $this->paginateUsingCursor($perPage, $columns, $cursorName, $cursor); + } + + /** + * Ensure the proper order by required for cursor pagination. + * + * @param bool $shouldReverse + * @return \Illuminate\Support\Collection + */ + protected function ensureOrderForCursorPagination($shouldReverse = false) + { + if (empty($this->query->orders) && empty($this->query->unionOrders)) { + $this->enforceOrderBy(); + } + + if ($shouldReverse) { + $this->query->orders = collect($this->query->orders)->map(function ($order) { + $order['direction'] = $order['direction'] === 'asc' ? 'desc' : 'asc'; + + return $order; + })->toArray(); + } + + if ($this->query->unionOrders) { + return collect($this->query->unionOrders); + } + + return collect($this->query->orders); + } + + /** + * Save a new model and return the instance. + * + * @param array $attributes + * @return \Illuminate\Database\Eloquent\Model|$this + */ + public function create(array $attributes = []) + { + return tap($this->newModelInstance($attributes), function ($instance) { + $instance->save(); + }); + } + + /** + * Save a new model and return the instance. Allow mass-assignment. + * + * @param array $attributes + * @return \Illuminate\Database\Eloquent\Model|$this + */ + public function forceCreate(array $attributes) + { + return $this->model->unguarded(function () use ($attributes) { + return $this->newModelInstance()->create($attributes); + }); + } + + /** + * Update records in the database. + * + * @param array $values + * @return int + */ + public function update(array $values) + { + return $this->toBase()->update($this->addUpdatedAtColumn($values)); + } + + /** + * Insert new records or update the existing ones. + * + * @param array $values + * @param array|string $uniqueBy + * @param array|null $update + * @return int + */ + public function upsert(array $values, $uniqueBy, $update = null) + { + if (empty($values)) { + return 0; + } + + if (! is_array(reset($values))) { + $values = [$values]; + } + + if (is_null($update)) { + $update = array_keys(reset($values)); + } + + return $this->toBase()->upsert( + $this->addTimestampsToUpsertValues($values), + $uniqueBy, + $this->addUpdatedAtToUpsertColumns($update) + ); + } + + /** + * Increment a column's value by a given amount. + * + * @param string|\Illuminate\Database\Query\Expression $column + * @param float|int $amount + * @param array $extra + * @return int + */ + public function increment($column, $amount = 1, array $extra = []) + { + return $this->toBase()->increment( + $column, $amount, $this->addUpdatedAtColumn($extra) + ); + } + + /** + * Decrement a column's value by a given amount. + * + * @param string|\Illuminate\Database\Query\Expression $column + * @param float|int $amount + * @param array $extra + * @return int + */ + public function decrement($column, $amount = 1, array $extra = []) + { + return $this->toBase()->decrement( + $column, $amount, $this->addUpdatedAtColumn($extra) + ); + } + + /** + * Add the "updated at" column to an array of values. + * + * @param array $values + * @return array + */ + protected function addUpdatedAtColumn(array $values) + { + if (! $this->model->usesTimestamps() || + is_null($this->model->getUpdatedAtColumn())) { + return $values; + } + + $column = $this->model->getUpdatedAtColumn(); + + $values = array_merge( + [$column => $this->model->freshTimestampString()], + $values + ); + + $segments = preg_split('/\s+as\s+/i', $this->query->from); + + $qualifiedColumn = end($segments).'.'.$column; + + $values[$qualifiedColumn] = Arr::get($values, $qualifiedColumn, $values[$column]); + + unset($values[$column]); + + return $values; + } + + /** + * Add timestamps to the inserted values. + * + * @param array $values + * @return array + */ + protected function addTimestampsToUpsertValues(array $values) + { + if (! $this->model->usesTimestamps()) { + return $values; + } + + $timestamp = $this->model->freshTimestampString(); + + $columns = array_filter([ + $this->model->getCreatedAtColumn(), + $this->model->getUpdatedAtColumn(), + ]); + + foreach ($columns as $column) { + foreach ($values as &$row) { + $row = array_merge([$column => $timestamp], $row); + } + } + + return $values; + } + + /** + * Add the "updated at" column to the updated columns. + * + * @param array $update + * @return array + */ + protected function addUpdatedAtToUpsertColumns(array $update) + { + if (! $this->model->usesTimestamps()) { + return $update; + } + + $column = $this->model->getUpdatedAtColumn(); + + if (! is_null($column) && + ! array_key_exists($column, $update) && + ! in_array($column, $update)) { + $update[] = $column; + } + + return $update; + } + + /** + * Delete records from the database. + * + * @return mixed + */ + public function delete() + { + if (isset($this->onDelete)) { + return call_user_func($this->onDelete, $this); + } + + return $this->toBase()->delete(); + } + + /** + * Run the default delete function on the builder. + * + * Since we do not apply scopes here, the row will actually be deleted. + * + * @return mixed + */ + public function forceDelete() + { + return $this->query->delete(); + } + + /** + * Register a replacement for the default delete function. + * + * @param \Closure $callback + * @return void + */ + public function onDelete(Closure $callback) + { + $this->onDelete = $callback; + } + + /** + * Determine if the given model has a scope. + * + * @param string $scope + * @return bool + */ + public function hasNamedScope($scope) + { + return $this->model && $this->model->hasNamedScope($scope); + } + + /** + * Call the given local model scopes. + * + * @param array|string $scopes + * @return static|mixed + */ + public function scopes($scopes) + { + $builder = $this; + + foreach (Arr::wrap($scopes) as $scope => $parameters) { + // If the scope key is an integer, then the scope was passed as the value and + // the parameter list is empty, so we will format the scope name and these + // parameters here. Then, we'll be ready to call the scope on the model. + if (is_int($scope)) { + [$scope, $parameters] = [$parameters, []]; + } + + // Next we'll pass the scope callback to the callScope method which will take + // care of grouping the "wheres" properly so the logical order doesn't get + // messed up when adding scopes. Then we'll return back out the builder. + $builder = $builder->callNamedScope( + $scope, Arr::wrap($parameters) + ); + } + + return $builder; + } + + /** + * Apply the scopes to the Eloquent builder instance and return it. + * + * @return static + */ + public function applyScopes() + { + if (! $this->scopes) { + return $this; + } + + $builder = clone $this; + + foreach ($this->scopes as $identifier => $scope) { + if (! isset($builder->scopes[$identifier])) { + continue; + } + + $builder->callScope(function (self $builder) use ($scope) { + // If the scope is a Closure we will just go ahead and call the scope with the + // builder instance. The "callScope" method will properly group the clauses + // that are added to this query so "where" clauses maintain proper logic. + if ($scope instanceof Closure) { + $scope($builder); + } + + // If the scope is a scope object, we will call the apply method on this scope + // passing in the builder and the model instance. After we run all of these + // scopes we will return back the builder instance to the outside caller. + if ($scope instanceof Scope) { + $scope->apply($builder, $this->getModel()); + } + }); + } + + return $builder; + } + + /** + * Apply the given scope on the current builder instance. + * + * @param callable $scope + * @param array $parameters + * @return mixed + */ + protected function callScope(callable $scope, array $parameters = []) + { + array_unshift($parameters, $this); + + $query = $this->getQuery(); + + // We will keep track of how many wheres are on the query before running the + // scope so that we can properly group the added scope constraints in the + // query as their own isolated nested where statement and avoid issues. + $originalWhereCount = is_null($query->wheres) + ? 0 : count($query->wheres); + + $result = $scope(...array_values($parameters)) ?? $this; + + if (count((array) $query->wheres) > $originalWhereCount) { + $this->addNewWheresWithinGroup($query, $originalWhereCount); + } + + return $result; + } + + /** + * Apply the given named scope on the current builder instance. + * + * @param string $scope + * @param array $parameters + * @return mixed + */ + protected function callNamedScope($scope, array $parameters = []) + { + return $this->callScope(function (...$parameters) use ($scope) { + return $this->model->callNamedScope($scope, $parameters); + }, $parameters); + } + + /** + * Nest where conditions by slicing them at the given where count. + * + * @param \Illuminate\Database\Query\Builder $query + * @param int $originalWhereCount + * @return void + */ + protected function addNewWheresWithinGroup(QueryBuilder $query, $originalWhereCount) + { + // Here, we totally remove all of the where clauses since we are going to + // rebuild them as nested queries by slicing the groups of wheres into + // their own sections. This is to prevent any confusing logic order. + $allWheres = $query->wheres; + + $query->wheres = []; + + $this->groupWhereSliceForScope( + $query, array_slice($allWheres, 0, $originalWhereCount) + ); + + $this->groupWhereSliceForScope( + $query, array_slice($allWheres, $originalWhereCount) + ); + } + + /** + * Slice where conditions at the given offset and add them to the query as a nested condition. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $whereSlice + * @return void + */ + protected function groupWhereSliceForScope(QueryBuilder $query, $whereSlice) + { + $whereBooleans = collect($whereSlice)->pluck('boolean'); + + // Here we'll check if the given subset of where clauses contains any "or" + // booleans and in this case create a nested where expression. That way + // we don't add any unnecessary nesting thus keeping the query clean. + if ($whereBooleans->contains('or')) { + $query->wheres[] = $this->createNestedWhere( + $whereSlice, $whereBooleans->first() + ); + } else { + $query->wheres = array_merge($query->wheres, $whereSlice); + } + } + + /** + * Create a where array with nested where conditions. + * + * @param array $whereSlice + * @param string $boolean + * @return array + */ + protected function createNestedWhere($whereSlice, $boolean = 'and') + { + $whereGroup = $this->getQuery()->forNestedWhere(); + + $whereGroup->wheres = $whereSlice; + + return ['type' => 'Nested', 'query' => $whereGroup, 'boolean' => $boolean]; + } + + /** + * Set the relationships that should be eager loaded. + * + * @param string|array $relations + * @param string|\Closure|null $callback + * @return $this + */ + public function with($relations, $callback = null) + { + if ($callback instanceof Closure) { + $eagerLoad = $this->parseWithRelations([$relations => $callback]); + } else { + $eagerLoad = $this->parseWithRelations(is_string($relations) ? func_get_args() : $relations); + } + + $this->eagerLoad = array_merge($this->eagerLoad, $eagerLoad); + + return $this; + } + + /** + * Prevent the specified relations from being eager loaded. + * + * @param mixed $relations + * @return $this + */ + public function without($relations) + { + $this->eagerLoad = array_diff_key($this->eagerLoad, array_flip( + is_string($relations) ? func_get_args() : $relations + )); + + return $this; + } + + /** + * Set the relationships that should be eager loaded while removing any previously added eager loading specifications. + * + * @param mixed $relations + * @return $this + */ + public function withOnly($relations) + { + $this->eagerLoad = []; + + return $this->with($relations); + } + + /** + * Create a new instance of the model being queried. + * + * @param array $attributes + * @return \Illuminate\Database\Eloquent\Model|static + */ + public function newModelInstance($attributes = []) + { + return $this->model->newInstance($attributes)->setConnection( + $this->query->getConnection()->getName() + ); + } + + /** + * Parse a list of relations into individuals. + * + * @param array $relations + * @return array + */ + protected function parseWithRelations(array $relations) + { + $results = []; + + foreach ($relations as $name => $constraints) { + // If the "name" value is a numeric key, we can assume that no constraints + // have been specified. We will just put an empty Closure there so that + // we can treat these all the same while we are looping through them. + if (is_numeric($name)) { + $name = $constraints; + + [$name, $constraints] = Str::contains($name, ':') + ? $this->createSelectWithConstraint($name) + : [$name, static function () { + // + }]; + } + + // We need to separate out any nested includes, which allows the developers + // to load deep relationships using "dots" without stating each level of + // the relationship with its own key in the array of eager-load names. + $results = $this->addNestedWiths($name, $results); + + $results[$name] = $constraints; + } + + return $results; + } + + /** + * Create a constraint to select the given columns for the relation. + * + * @param string $name + * @return array + */ + protected function createSelectWithConstraint($name) + { + return [explode(':', $name)[0], static function ($query) use ($name) { + $query->select(array_map(static function ($column) use ($query) { + if (Str::contains($column, '.')) { + return $column; + } + + return $query instanceof BelongsToMany + ? $query->getRelated()->getTable().'.'.$column + : $column; + }, explode(',', explode(':', $name)[1]))); + }]; + } + + /** + * Parse the nested relationships in a relation. + * + * @param string $name + * @param array $results + * @return array + */ + protected function addNestedWiths($name, $results) + { + $progress = []; + + // If the relation has already been set on the result array, we will not set it + // again, since that would override any constraints that were already placed + // on the relationships. We will only set the ones that are not specified. + foreach (explode('.', $name) as $segment) { + $progress[] = $segment; + + if (! isset($results[$last = implode('.', $progress)])) { + $results[$last] = static function () { + // + }; + } + } + + return $results; + } + + /** + * Apply query-time casts to the model instance. + * + * @param array $casts + * @return $this + */ + public function withCasts($casts) + { + $this->model->mergeCasts($casts); + + return $this; + } + + /** + * Get the underlying query builder instance. + * + * @return \Illuminate\Database\Query\Builder + */ + public function getQuery() + { + return $this->query; + } + + /** + * Set the underlying query builder instance. + * + * @param \Illuminate\Database\Query\Builder $query + * @return $this + */ + public function setQuery($query) + { + $this->query = $query; + + return $this; + } + + /** + * Get a base query builder instance. + * + * @return \Illuminate\Database\Query\Builder + */ + public function toBase() + { + return $this->applyScopes()->getQuery(); + } + + /** + * Get the relationships being eagerly loaded. + * + * @return array + */ + public function getEagerLoads() + { + return $this->eagerLoad; + } + + /** + * Set the relationships being eagerly loaded. + * + * @param array $eagerLoad + * @return $this + */ + public function setEagerLoads(array $eagerLoad) + { + $this->eagerLoad = $eagerLoad; + + return $this; + } + + /** + * Get the default key name of the table. + * + * @return string + */ + protected function defaultKeyName() + { + return $this->getModel()->getKeyName(); + } + + /** + * Get the model instance being queried. + * + * @return \Illuminate\Database\Eloquent\Model|static + */ + public function getModel() + { + return $this->model; + } + + /** + * Set a model instance for the model being queried. + * + * @param \Illuminate\Database\Eloquent\Model $model + * @return $this + */ + public function setModel(Model $model) + { + $this->model = $model; + + $this->query->from($model->getTable()); + + return $this; + } + + /** + * Qualify the given column name by the model's table. + * + * @param string|\Illuminate\Database\Query\Expression $column + * @return string + */ + public function qualifyColumn($column) + { + return $this->model->qualifyColumn($column); + } + + /** + * Qualify the given columns with the model's table. + * + * @param array|\Illuminate\Database\Query\Expression $columns + * @return array + */ + public function qualifyColumns($columns) + { + return $this->model->qualifyColumns($columns); + } + + /** + * Get the given macro by name. + * + * @param string $name + * @return \Closure + */ + public function getMacro($name) + { + return Arr::get($this->localMacros, $name); + } + + /** + * Checks if a macro is registered. + * + * @param string $name + * @return bool + */ + public function hasMacro($name) + { + return isset($this->localMacros[$name]); + } + + /** + * Get the given global macro by name. + * + * @param string $name + * @return \Closure + */ + public static function getGlobalMacro($name) + { + return Arr::get(static::$macros, $name); + } + + /** + * Checks if a global macro is registered. + * + * @param string $name + * @return bool + */ + public static function hasGlobalMacro($name) + { + return isset(static::$macros[$name]); + } + + /** + * Dynamically access builder proxies. + * + * @param string $key + * @return mixed + * + * @throws \Exception + */ + public function __get($key) + { + if ($key === 'orWhere') { + return new HigherOrderBuilderProxy($this, $key); + } + + if (in_array($key, $this->propertyPassthru)) { + return $this->toBase()->{$key}; + } + + throw new Exception("Property [{$key}] does not exist on the Eloquent builder instance."); + } + + /** + * Dynamically handle calls into the query instance. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + if ($method === 'macro') { + $this->localMacros[$parameters[0]] = $parameters[1]; + + return; + } + + if ($this->hasMacro($method)) { + array_unshift($parameters, $this); + + return $this->localMacros[$method](...$parameters); + } + + if (static::hasGlobalMacro($method)) { + $callable = static::$macros[$method]; + + if ($callable instanceof Closure) { + $callable = $callable->bindTo($this, static::class); + } + + return $callable(...$parameters); + } + + if ($this->hasNamedScope($method)) { + return $this->callNamedScope($method, $parameters); + } + + if (in_array($method, $this->passthru)) { + return $this->toBase()->{$method}(...$parameters); + } + + $this->forwardCallTo($this->query, $method, $parameters); + + return $this; + } + + /** + * Dynamically handle calls into the query instance. + * + * @param string $method + * @param array $parameters + * @return mixed + * + * @throws \BadMethodCallException + */ + public static function __callStatic($method, $parameters) + { + if ($method === 'macro') { + static::$macros[$parameters[0]] = $parameters[1]; + + return; + } + + if ($method === 'mixin') { + return static::registerMixin($parameters[0], $parameters[1] ?? true); + } + + if (! static::hasGlobalMacro($method)) { + static::throwBadMethodCallException($method); + } + + $callable = static::$macros[$method]; + + if ($callable instanceof Closure) { + $callable = $callable->bindTo(null, static::class); + } + + return $callable(...$parameters); + } + + /** + * Register the given mixin with the builder. + * + * @param string $mixin + * @param bool $replace + * @return void + */ + protected static function registerMixin($mixin, $replace) + { + $methods = (new ReflectionClass($mixin))->getMethods( + ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED + ); + + foreach ($methods as $method) { + if ($replace || ! static::hasGlobalMacro($method->name)) { + $method->setAccessible(true); + + static::macro($method->name, $method->invoke($mixin)); + } + } + } + + /** + * Clone the Eloquent query builder. + * + * @return static + */ + public function clone() + { + return clone $this; + } + + /** + * Force a clone of the underlying query builder when cloning. + * + * @return void + */ + public function __clone() + { + $this->query = clone $this->query; + } +} diff --git a/vendor/illuminate/database/Eloquent/Casts/ArrayObject.php b/vendor/illuminate/database/Eloquent/Casts/ArrayObject.php new file mode 100644 index 0000000..6f1713c --- /dev/null +++ b/vendor/illuminate/database/Eloquent/Casts/ArrayObject.php @@ -0,0 +1,41 @@ +getArrayCopy()); + } + + /** + * Get the instance as an array. + * + * @return array + */ + public function toArray() + { + return $this->getArrayCopy(); + } + + /** + * Get the array that should be JSON serialized. + * + * @return array + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return $this->getArrayCopy(); + } +} diff --git a/vendor/illuminate/database/Eloquent/Casts/AsArrayObject.php b/vendor/illuminate/database/Eloquent/Casts/AsArrayObject.php new file mode 100644 index 0000000..db9a21b --- /dev/null +++ b/vendor/illuminate/database/Eloquent/Casts/AsArrayObject.php @@ -0,0 +1,36 @@ + json_encode($value)]; + } + + public function serialize($model, string $key, $value, array $attributes) + { + return $value->getArrayCopy(); + } + }; + } +} diff --git a/vendor/illuminate/database/Eloquent/Casts/AsCollection.php b/vendor/illuminate/database/Eloquent/Casts/AsCollection.php new file mode 100644 index 0000000..585b6cf --- /dev/null +++ b/vendor/illuminate/database/Eloquent/Casts/AsCollection.php @@ -0,0 +1,32 @@ + json_encode($value)]; + } + }; + } +} diff --git a/vendor/illuminate/database/Eloquent/Casts/AsEncryptedArrayObject.php b/vendor/illuminate/database/Eloquent/Casts/AsEncryptedArrayObject.php new file mode 100644 index 0000000..cd65624 --- /dev/null +++ b/vendor/illuminate/database/Eloquent/Casts/AsEncryptedArrayObject.php @@ -0,0 +1,45 @@ + Crypt::encryptString(json_encode($value))]; + } + + return null; + } + + public function serialize($model, string $key, $value, array $attributes) + { + return ! is_null($value) ? $value->getArrayCopy() : null; + } + }; + } +} diff --git a/vendor/illuminate/database/Eloquent/Casts/AsEncryptedCollection.php b/vendor/illuminate/database/Eloquent/Casts/AsEncryptedCollection.php new file mode 100644 index 0000000..4d9fee7 --- /dev/null +++ b/vendor/illuminate/database/Eloquent/Casts/AsEncryptedCollection.php @@ -0,0 +1,41 @@ + Crypt::encryptString(json_encode($value))]; + } + + return null; + } + }; + } +} diff --git a/vendor/illuminate/database/Eloquent/Casts/AsStringable.php b/vendor/illuminate/database/Eloquent/Casts/AsStringable.php new file mode 100644 index 0000000..912659f --- /dev/null +++ b/vendor/illuminate/database/Eloquent/Casts/AsStringable.php @@ -0,0 +1,32 @@ +get = $get; + $this->set = $set; + } + + /** + * Create a new attribute accessor. + * + * @param callable $get + * @return static + */ + public static function get(callable $get) + { + return new static($get); + } + + /** + * Create a new attribute mutator. + * + * @param callable $set + * @return static + */ + public static function set(callable $set) + { + return new static(null, $set); + } + + /** + * Disable object caching for the attribute. + * + * @return static + */ + public function withoutObjectCaching() + { + $this->withObjectCaching = false; + + return $this; + } +} diff --git a/vendor/illuminate/database/Eloquent/Collection.php b/vendor/illuminate/database/Eloquent/Collection.php new file mode 100644 index 0000000..cdd972c --- /dev/null +++ b/vendor/illuminate/database/Eloquent/Collection.php @@ -0,0 +1,750 @@ +getKey(); + } + + if ($key instanceof Arrayable) { + $key = $key->toArray(); + } + + if (is_array($key)) { + if ($this->isEmpty()) { + return new static; + } + + return $this->whereIn($this->first()->getKeyName(), $key); + } + + return Arr::first($this->items, function ($model) use ($key) { + return $model->getKey() == $key; + }, $default); + } + + /** + * Load a set of relationships onto the collection. + * + * @param array|string $relations + * @return $this + */ + public function load($relations) + { + if ($this->isNotEmpty()) { + if (is_string($relations)) { + $relations = func_get_args(); + } + + $query = $this->first()->newQueryWithoutRelationships()->with($relations); + + $this->items = $query->eagerLoadRelations($this->items); + } + + return $this; + } + + /** + * Load a set of aggregations over relationship's column onto the collection. + * + * @param array|string $relations + * @param string $column + * @param string $function + * @return $this + */ + public function loadAggregate($relations, $column, $function = null) + { + if ($this->isEmpty()) { + return $this; + } + + $models = $this->first()->newModelQuery() + ->whereKey($this->modelKeys()) + ->select($this->first()->getKeyName()) + ->withAggregate($relations, $column, $function) + ->get() + ->keyBy($this->first()->getKeyName()); + + $attributes = Arr::except( + array_keys($models->first()->getAttributes()), + $models->first()->getKeyName() + ); + + $this->each(function ($model) use ($models, $attributes) { + $extraAttributes = Arr::only($models->get($model->getKey())->getAttributes(), $attributes); + + $model->forceFill($extraAttributes) + ->syncOriginalAttributes($attributes) + ->mergeCasts($models->get($model->getKey())->getCasts()); + }); + + return $this; + } + + /** + * Load a set of relationship counts onto the collection. + * + * @param array|string $relations + * @return $this + */ + public function loadCount($relations) + { + return $this->loadAggregate($relations, '*', 'count'); + } + + /** + * Load a set of relationship's max column values onto the collection. + * + * @param array|string $relations + * @param string $column + * @return $this + */ + public function loadMax($relations, $column) + { + return $this->loadAggregate($relations, $column, 'max'); + } + + /** + * Load a set of relationship's min column values onto the collection. + * + * @param array|string $relations + * @param string $column + * @return $this + */ + public function loadMin($relations, $column) + { + return $this->loadAggregate($relations, $column, 'min'); + } + + /** + * Load a set of relationship's column summations onto the collection. + * + * @param array|string $relations + * @param string $column + * @return $this + */ + public function loadSum($relations, $column) + { + return $this->loadAggregate($relations, $column, 'sum'); + } + + /** + * Load a set of relationship's average column values onto the collection. + * + * @param array|string $relations + * @param string $column + * @return $this + */ + public function loadAvg($relations, $column) + { + return $this->loadAggregate($relations, $column, 'avg'); + } + + /** + * Load a set of related existences onto the collection. + * + * @param array|string $relations + * @return $this + */ + public function loadExists($relations) + { + return $this->loadAggregate($relations, '*', 'exists'); + } + + /** + * Load a set of relationships onto the collection if they are not already eager loaded. + * + * @param array|string $relations + * @return $this + */ + public function loadMissing($relations) + { + if (is_string($relations)) { + $relations = func_get_args(); + } + + foreach ($relations as $key => $value) { + if (is_numeric($key)) { + $key = $value; + } + + $segments = explode('.', explode(':', $key)[0]); + + if (Str::contains($key, ':')) { + $segments[count($segments) - 1] .= ':'.explode(':', $key)[1]; + } + + $path = []; + + foreach ($segments as $segment) { + $path[] = [$segment => $segment]; + } + + if (is_callable($value)) { + $path[count($segments) - 1][end($segments)] = $value; + } + + $this->loadMissingRelation($this, $path); + } + + return $this; + } + + /** + * Load a relationship path if it is not already eager loaded. + * + * @param \Illuminate\Database\Eloquent\Collection $models + * @param array $path + * @return void + */ + protected function loadMissingRelation(self $models, array $path) + { + $relation = array_shift($path); + + $name = explode(':', key($relation))[0]; + + if (is_string(reset($relation))) { + $relation = reset($relation); + } + + $models->filter(function ($model) use ($name) { + return ! is_null($model) && ! $model->relationLoaded($name); + })->load($relation); + + if (empty($path)) { + return; + } + + $models = $models->pluck($name)->whereNotNull(); + + if ($models->first() instanceof BaseCollection) { + $models = $models->collapse(); + } + + $this->loadMissingRelation(new static($models), $path); + } + + /** + * Load a set of relationships onto the mixed relationship collection. + * + * @param string $relation + * @param array $relations + * @return $this + */ + public function loadMorph($relation, $relations) + { + $this->pluck($relation) + ->filter() + ->groupBy(function ($model) { + return get_class($model); + }) + ->each(function ($models, $className) use ($relations) { + static::make($models)->load($relations[$className] ?? []); + }); + + return $this; + } + + /** + * Load a set of relationship counts onto the mixed relationship collection. + * + * @param string $relation + * @param array $relations + * @return $this + */ + public function loadMorphCount($relation, $relations) + { + $this->pluck($relation) + ->filter() + ->groupBy(function ($model) { + return get_class($model); + }) + ->each(function ($models, $className) use ($relations) { + static::make($models)->loadCount($relations[$className] ?? []); + }); + + return $this; + } + + /** + * Determine if a key exists in the collection. + * + * @param mixed $key + * @param mixed $operator + * @param mixed $value + * @return bool + */ + public function contains($key, $operator = null, $value = null) + { + if (func_num_args() > 1 || $this->useAsCallable($key)) { + return parent::contains(...func_get_args()); + } + + if ($key instanceof Model) { + return parent::contains(function ($model) use ($key) { + return $model->is($key); + }); + } + + return parent::contains(function ($model) use ($key) { + return $model->getKey() == $key; + }); + } + + /** + * Get the array of primary keys. + * + * @return array + */ + public function modelKeys() + { + return array_map(function ($model) { + return $model->getKey(); + }, $this->items); + } + + /** + * Merge the collection with the given items. + * + * @param \ArrayAccess|array $items + * @return static + */ + public function merge($items) + { + $dictionary = $this->getDictionary(); + + foreach ($items as $item) { + $dictionary[$item->getKey()] = $item; + } + + return new static(array_values($dictionary)); + } + + /** + * Run a map over each of the items. + * + * @param callable $callback + * @return \Illuminate\Support\Collection|static + */ + public function map(callable $callback) + { + $result = parent::map($callback); + + return $result->contains(function ($item) { + return ! $item instanceof Model; + }) ? $result->toBase() : $result; + } + + /** + * Run an associative map over each of the items. + * + * The callback should return an associative array with a single key / value pair. + * + * @param callable $callback + * @return \Illuminate\Support\Collection|static + */ + public function mapWithKeys(callable $callback) + { + $result = parent::mapWithKeys($callback); + + return $result->contains(function ($item) { + return ! $item instanceof Model; + }) ? $result->toBase() : $result; + } + + /** + * Reload a fresh model instance from the database for all the entities. + * + * @param array|string $with + * @return static + */ + public function fresh($with = []) + { + if ($this->isEmpty()) { + return new static; + } + + $model = $this->first(); + + $freshModels = $model->newQueryWithoutScopes() + ->with(is_string($with) ? func_get_args() : $with) + ->whereIn($model->getKeyName(), $this->modelKeys()) + ->get() + ->getDictionary(); + + return $this->filter(function ($model) use ($freshModels) { + return $model->exists && isset($freshModels[$model->getKey()]); + }) + ->map(function ($model) use ($freshModels) { + return $freshModels[$model->getKey()]; + }); + } + + /** + * Diff the collection with the given items. + * + * @param \ArrayAccess|array $items + * @return static + */ + public function diff($items) + { + $diff = new static; + + $dictionary = $this->getDictionary($items); + + foreach ($this->items as $item) { + if (! isset($dictionary[$item->getKey()])) { + $diff->add($item); + } + } + + return $diff; + } + + /** + * Intersect the collection with the given items. + * + * @param \ArrayAccess|array $items + * @return static + */ + public function intersect($items) + { + $intersect = new static; + + if (empty($items)) { + return $intersect; + } + + $dictionary = $this->getDictionary($items); + + foreach ($this->items as $item) { + if (isset($dictionary[$item->getKey()])) { + $intersect->add($item); + } + } + + return $intersect; + } + + /** + * Return only unique items from the collection. + * + * @param string|callable|null $key + * @param bool $strict + * @return static + */ + public function unique($key = null, $strict = false) + { + if (! is_null($key)) { + return parent::unique($key, $strict); + } + + return new static(array_values($this->getDictionary())); + } + + /** + * Returns only the models from the collection with the specified keys. + * + * @param mixed $keys + * @return static + */ + public function only($keys) + { + if (is_null($keys)) { + return new static($this->items); + } + + $dictionary = Arr::only($this->getDictionary(), $keys); + + return new static(array_values($dictionary)); + } + + /** + * Returns all models in the collection except the models with specified keys. + * + * @param mixed $keys + * @return static + */ + public function except($keys) + { + $dictionary = Arr::except($this->getDictionary(), $keys); + + return new static(array_values($dictionary)); + } + + /** + * Make the given, typically visible, attributes hidden across the entire collection. + * + * @param array|string $attributes + * @return $this + */ + public function makeHidden($attributes) + { + return $this->each->makeHidden($attributes); + } + + /** + * Make the given, typically hidden, attributes visible across the entire collection. + * + * @param array|string $attributes + * @return $this + */ + public function makeVisible($attributes) + { + return $this->each->makeVisible($attributes); + } + + /** + * Append an attribute across the entire collection. + * + * @param array|string $attributes + * @return $this + */ + public function append($attributes) + { + return $this->each->append($attributes); + } + + /** + * Get a dictionary keyed by primary keys. + * + * @param \ArrayAccess|array|null $items + * @return array + */ + public function getDictionary($items = null) + { + $items = is_null($items) ? $this->items : $items; + + $dictionary = []; + + foreach ($items as $value) { + $dictionary[$value->getKey()] = $value; + } + + return $dictionary; + } + + /** + * The following methods are intercepted to always return base collections. + */ + + /** + * Get an array with the values of a given key. + * + * @param string|array $value + * @param string|null $key + * @return \Illuminate\Support\Collection + */ + public function pluck($value, $key = null) + { + return $this->toBase()->pluck($value, $key); + } + + /** + * Get the keys of the collection items. + * + * @return \Illuminate\Support\Collection + */ + public function keys() + { + return $this->toBase()->keys(); + } + + /** + * Zip the collection together with one or more arrays. + * + * @param mixed ...$items + * @return \Illuminate\Support\Collection + */ + public function zip($items) + { + return $this->toBase()->zip(...func_get_args()); + } + + /** + * Collapse the collection of items into a single array. + * + * @return \Illuminate\Support\Collection + */ + public function collapse() + { + return $this->toBase()->collapse(); + } + + /** + * Get a flattened array of the items in the collection. + * + * @param int $depth + * @return \Illuminate\Support\Collection + */ + public function flatten($depth = INF) + { + return $this->toBase()->flatten($depth); + } + + /** + * Flip the items in the collection. + * + * @return \Illuminate\Support\Collection + */ + public function flip() + { + return $this->toBase()->flip(); + } + + /** + * Pad collection to the specified length with a value. + * + * @param int $size + * @param mixed $value + * @return \Illuminate\Support\Collection + */ + public function pad($size, $value) + { + return $this->toBase()->pad($size, $value); + } + + /** + * Get the comparison function to detect duplicates. + * + * @param bool $strict + * @return \Closure + */ + protected function duplicateComparator($strict) + { + return function ($a, $b) { + return $a->is($b); + }; + } + + /** + * Get the type of the entities being queued. + * + * @return string|null + * + * @throws \LogicException + */ + public function getQueueableClass() + { + if ($this->isEmpty()) { + return; + } + + $class = get_class($this->first()); + + $this->each(function ($model) use ($class) { + if (get_class($model) !== $class) { + throw new LogicException('Queueing collections with multiple model types is not supported.'); + } + }); + + return $class; + } + + /** + * Get the identifiers for all of the entities. + * + * @return array + */ + public function getQueueableIds() + { + if ($this->isEmpty()) { + return []; + } + + return $this->first() instanceof QueueableEntity + ? $this->map->getQueueableId()->all() + : $this->modelKeys(); + } + + /** + * Get the relationships of the entities being queued. + * + * @return array + */ + public function getQueueableRelations() + { + if ($this->isEmpty()) { + return []; + } + + $relations = $this->map->getQueueableRelations()->all(); + + if (count($relations) === 0 || $relations === [[]]) { + return []; + } elseif (count($relations) === 1) { + return reset($relations); + } else { + return array_intersect(...array_values($relations)); + } + } + + /** + * Get the connection of the entities being queued. + * + * @return string|null + * + * @throws \LogicException + */ + public function getQueueableConnection() + { + if ($this->isEmpty()) { + return; + } + + $connection = $this->first()->getConnectionName(); + + $this->each(function ($model) use ($connection) { + if ($model->getConnectionName() !== $connection) { + throw new LogicException('Queueing collections with multiple model connections is not supported.'); + } + }); + + return $connection; + } + + /** + * Get the Eloquent query builder from the collection. + * + * @return \Illuminate\Database\Eloquent\Builder + * + * @throws \LogicException + */ + public function toQuery() + { + $model = $this->first(); + + if (! $model) { + throw new LogicException('Unable to create query for empty collection.'); + } + + $class = get_class($model); + + if ($this->filter(function ($model) use ($class) { + return ! $model instanceof $class; + })->isNotEmpty()) { + throw new LogicException('Unable to create query for collection with mixed types.'); + } + + return $model->newModelQuery()->whereKey($this->modelKeys()); + } +} diff --git a/vendor/illuminate/database/Eloquent/Concerns/GuardsAttributes.php b/vendor/illuminate/database/Eloquent/Concerns/GuardsAttributes.php new file mode 100644 index 0000000..3e85fb9 --- /dev/null +++ b/vendor/illuminate/database/Eloquent/Concerns/GuardsAttributes.php @@ -0,0 +1,257 @@ +fillable; + } + + /** + * Set the fillable attributes for the model. + * + * @param array $fillable + * @return $this + */ + public function fillable(array $fillable) + { + $this->fillable = $fillable; + + return $this; + } + + /** + * Merge new fillable attributes with existing fillable attributes on the model. + * + * @param array $fillable + * @return $this + */ + public function mergeFillable(array $fillable) + { + $this->fillable = array_merge($this->fillable, $fillable); + + return $this; + } + + /** + * Get the guarded attributes for the model. + * + * @return array + */ + public function getGuarded() + { + return $this->guarded === false + ? [] + : $this->guarded; + } + + /** + * Set the guarded attributes for the model. + * + * @param array $guarded + * @return $this + */ + public function guard(array $guarded) + { + $this->guarded = $guarded; + + return $this; + } + + /** + * Merge new guarded attributes with existing guarded attributes on the model. + * + * @param array $guarded + * @return $this + */ + public function mergeGuarded(array $guarded) + { + $this->guarded = array_merge($this->guarded, $guarded); + + return $this; + } + + /** + * Disable all mass assignable restrictions. + * + * @param bool $state + * @return void + */ + public static function unguard($state = true) + { + static::$unguarded = $state; + } + + /** + * Enable the mass assignment restrictions. + * + * @return void + */ + public static function reguard() + { + static::$unguarded = false; + } + + /** + * Determine if the current state is "unguarded". + * + * @return bool + */ + public static function isUnguarded() + { + return static::$unguarded; + } + + /** + * Run the given callable while being unguarded. + * + * @param callable $callback + * @return mixed + */ + public static function unguarded(callable $callback) + { + if (static::$unguarded) { + return $callback(); + } + + static::unguard(); + + try { + return $callback(); + } finally { + static::reguard(); + } + } + + /** + * Determine if the given attribute may be mass assigned. + * + * @param string $key + * @return bool + */ + public function isFillable($key) + { + if (static::$unguarded) { + return true; + } + + // If the key is in the "fillable" array, we can of course assume that it's + // a fillable attribute. Otherwise, we will check the guarded array when + // we need to determine if the attribute is black-listed on the model. + if (in_array($key, $this->getFillable())) { + return true; + } + + // If the attribute is explicitly listed in the "guarded" array then we can + // return false immediately. This means this attribute is definitely not + // fillable and there is no point in going any further in this method. + if ($this->isGuarded($key)) { + return false; + } + + return empty($this->getFillable()) && + strpos($key, '.') === false && + ! Str::startsWith($key, '_'); + } + + /** + * Determine if the given key is guarded. + * + * @param string $key + * @return bool + */ + public function isGuarded($key) + { + if (empty($this->getGuarded())) { + return false; + } + + return $this->getGuarded() == ['*'] || + ! empty(preg_grep('/^'.preg_quote($key).'$/i', $this->getGuarded())) || + ! $this->isGuardableColumn($key); + } + + /** + * Determine if the given column is a valid, guardable column. + * + * @param string $key + * @return bool + */ + protected function isGuardableColumn($key) + { + if (! isset(static::$guardableColumns[get_class($this)])) { + $columns = $this->getConnection() + ->getSchemaBuilder() + ->getColumnListing($this->getTable()); + + if (empty($columns)) { + return true; + } + static::$guardableColumns[get_class($this)] = $columns; + } + + return in_array($key, static::$guardableColumns[get_class($this)]); + } + + /** + * Determine if the model is totally guarded. + * + * @return bool + */ + public function totallyGuarded() + { + return count($this->getFillable()) === 0 && $this->getGuarded() == ['*']; + } + + /** + * Get the fillable attributes of a given array. + * + * @param array $attributes + * @return array + */ + protected function fillableFromArray(array $attributes) + { + if (count($this->getFillable()) > 0 && ! static::$unguarded) { + return array_intersect_key($attributes, array_flip($this->getFillable())); + } + + return $attributes; + } +} diff --git a/vendor/illuminate/database/Eloquent/Concerns/HasAttributes.php b/vendor/illuminate/database/Eloquent/Concerns/HasAttributes.php new file mode 100644 index 0000000..95d07e7 --- /dev/null +++ b/vendor/illuminate/database/Eloquent/Concerns/HasAttributes.php @@ -0,0 +1,2129 @@ +addDateAttributesToArray( + $attributes = $this->getArrayableAttributes() + ); + + $attributes = $this->addMutatedAttributesToArray( + $attributes, $mutatedAttributes = $this->getMutatedAttributes() + ); + + // Next we will handle any casts that have been setup for this model and cast + // the values to their appropriate type. If the attribute has a mutator we + // will not perform the cast on those attributes to avoid any confusion. + $attributes = $this->addCastAttributesToArray( + $attributes, $mutatedAttributes + ); + + // Here we will grab all of the appended, calculated attributes to this model + // as these attributes are not really in the attributes array, but are run + // when we need to array or JSON the model for convenience to the coder. + foreach ($this->getArrayableAppends() as $key) { + $attributes[$key] = $this->mutateAttributeForArray($key, null); + } + + return $attributes; + } + + /** + * Add the date attributes to the attributes array. + * + * @param array $attributes + * @return array + */ + protected function addDateAttributesToArray(array $attributes) + { + foreach ($this->getDates() as $key) { + if (! isset($attributes[$key])) { + continue; + } + + $attributes[$key] = $this->serializeDate( + $this->asDateTime($attributes[$key]) + ); + } + + return $attributes; + } + + /** + * Add the mutated attributes to the attributes array. + * + * @param array $attributes + * @param array $mutatedAttributes + * @return array + */ + protected function addMutatedAttributesToArray(array $attributes, array $mutatedAttributes) + { + foreach ($mutatedAttributes as $key) { + // We want to spin through all the mutated attributes for this model and call + // the mutator for the attribute. We cache off every mutated attributes so + // we don't have to constantly check on attributes that actually change. + if (! array_key_exists($key, $attributes)) { + continue; + } + + // Next, we will call the mutator for this attribute so that we can get these + // mutated attribute's actual values. After we finish mutating each of the + // attributes we will return this final array of the mutated attributes. + $attributes[$key] = $this->mutateAttributeForArray( + $key, $attributes[$key] + ); + } + + return $attributes; + } + + /** + * Add the casted attributes to the attributes array. + * + * @param array $attributes + * @param array $mutatedAttributes + * @return array + */ + protected function addCastAttributesToArray(array $attributes, array $mutatedAttributes) + { + foreach ($this->getCasts() as $key => $value) { + if (! array_key_exists($key, $attributes) || + in_array($key, $mutatedAttributes)) { + continue; + } + + // Here we will cast the attribute. Then, if the cast is a date or datetime cast + // then we will serialize the date for the array. This will convert the dates + // to strings based on the date format specified for these Eloquent models. + $attributes[$key] = $this->castAttribute( + $key, $attributes[$key] + ); + + // If the attribute cast was a date or a datetime, we will serialize the date as + // a string. This allows the developers to customize how dates are serialized + // into an array without affecting how they are persisted into the storage. + if ($attributes[$key] && in_array($value, ['date', 'datetime', 'immutable_date', 'immutable_datetime'])) { + $attributes[$key] = $this->serializeDate($attributes[$key]); + } + + if ($attributes[$key] && ($this->isCustomDateTimeCast($value) || + $this->isImmutableCustomDateTimeCast($value))) { + $attributes[$key] = $attributes[$key]->format(explode(':', $value, 2)[1]); + } + + if ($attributes[$key] && $attributes[$key] instanceof DateTimeInterface && + $this->isClassCastable($key)) { + $attributes[$key] = $this->serializeDate($attributes[$key]); + } + + if ($attributes[$key] && $this->isClassSerializable($key)) { + $attributes[$key] = $this->serializeClassCastableAttribute($key, $attributes[$key]); + } + + if ($this->isEnumCastable($key) && (! ($attributes[$key] ?? null) instanceof Arrayable)) { + $attributes[$key] = isset($attributes[$key]) ? $attributes[$key]->value : null; + } + + if ($attributes[$key] instanceof Arrayable) { + $attributes[$key] = $attributes[$key]->toArray(); + } + } + + return $attributes; + } + + /** + * Get an attribute array of all arrayable attributes. + * + * @return array + */ + protected function getArrayableAttributes() + { + return $this->getArrayableItems($this->getAttributes()); + } + + /** + * Get all of the appendable values that are arrayable. + * + * @return array + */ + protected function getArrayableAppends() + { + if (! count($this->appends)) { + return []; + } + + return $this->getArrayableItems( + array_combine($this->appends, $this->appends) + ); + } + + /** + * Get the model's relationships in array form. + * + * @return array + */ + public function relationsToArray() + { + $attributes = []; + + foreach ($this->getArrayableRelations() as $key => $value) { + // If the values implements the Arrayable interface we can just call this + // toArray method on the instances which will convert both models and + // collections to their proper array form and we'll set the values. + if ($value instanceof Arrayable) { + $relation = $value->toArray(); + } + + // If the value is null, we'll still go ahead and set it in this list of + // attributes since null is used to represent empty relationships if + // if it a has one or belongs to type relationships on the models. + elseif (is_null($value)) { + $relation = $value; + } + + // If the relationships snake-casing is enabled, we will snake case this + // key so that the relation attribute is snake cased in this returned + // array to the developers, making this consistent with attributes. + if (static::$snakeAttributes) { + $key = Str::snake($key); + } + + // If the relation value has been set, we will set it on this attributes + // list for returning. If it was not arrayable or null, we'll not set + // the value on the array because it is some type of invalid value. + if (isset($relation) || is_null($value)) { + $attributes[$key] = $relation; + } + + unset($relation); + } + + return $attributes; + } + + /** + * Get an attribute array of all arrayable relations. + * + * @return array + */ + protected function getArrayableRelations() + { + return $this->getArrayableItems($this->relations); + } + + /** + * Get an attribute array of all arrayable values. + * + * @param array $values + * @return array + */ + protected function getArrayableItems(array $values) + { + if (count($this->getVisible()) > 0) { + $values = array_intersect_key($values, array_flip($this->getVisible())); + } + + if (count($this->getHidden()) > 0) { + $values = array_diff_key($values, array_flip($this->getHidden())); + } + + return $values; + } + + /** + * Get an attribute from the model. + * + * @param string $key + * @return mixed + */ + public function getAttribute($key) + { + if (! $key) { + return; + } + + // If the attribute exists in the attribute array or has a "get" mutator we will + // get the attribute's value. Otherwise, we will proceed as if the developers + // are asking for a relationship's value. This covers both types of values. + if (array_key_exists($key, $this->attributes) || + array_key_exists($key, $this->casts) || + $this->hasGetMutator($key) || + $this->hasAttributeMutator($key) || + $this->isClassCastable($key)) { + return $this->getAttributeValue($key); + } + + // Here we will determine if the model base class itself contains this given key + // since we don't want to treat any of those methods as relationships because + // they are all intended as helper methods and none of these are relations. + if (method_exists(self::class, $key)) { + return; + } + + return $this->getRelationValue($key); + } + + /** + * Get a plain attribute (not a relationship). + * + * @param string $key + * @return mixed + */ + public function getAttributeValue($key) + { + return $this->transformModelValue($key, $this->getAttributeFromArray($key)); + } + + /** + * Get an attribute from the $attributes array. + * + * @param string $key + * @return mixed + */ + protected function getAttributeFromArray($key) + { + return $this->getAttributes()[$key] ?? null; + } + + /** + * Get a relationship. + * + * @param string $key + * @return mixed + */ + public function getRelationValue($key) + { + // If the key already exists in the relationships array, it just means the + // relationship has already been loaded, so we'll just return it out of + // here because there is no need to query within the relations twice. + if ($this->relationLoaded($key)) { + return $this->relations[$key]; + } + + if (! $this->isRelation($key)) { + return; + } + + if ($this->preventsLazyLoading) { + $this->handleLazyLoadingViolation($key); + } + + // If the "attribute" exists as a method on the model, we will just assume + // it is a relationship and will load and return results from the query + // and hydrate the relationship's value on the "relationships" array. + return $this->getRelationshipFromMethod($key); + } + + /** + * Determine if the given key is a relationship method on the model. + * + * @param string $key + * @return bool + */ + public function isRelation($key) + { + if ($this->hasAttributeMutator($key)) { + return false; + } + + return method_exists($this, $key) || + (static::$relationResolvers[get_class($this)][$key] ?? null); + } + + /** + * Handle a lazy loading violation. + * + * @param string $key + * @return mixed + */ + protected function handleLazyLoadingViolation($key) + { + if (isset(static::$lazyLoadingViolationCallback)) { + return call_user_func(static::$lazyLoadingViolationCallback, $this, $key); + } + + if (! $this->exists || $this->wasRecentlyCreated) { + return; + } + + throw new LazyLoadingViolationException($this, $key); + } + + /** + * Get a relationship value from a method. + * + * @param string $method + * @return mixed + * + * @throws \LogicException + */ + protected function getRelationshipFromMethod($method) + { + $relation = $this->$method(); + + if (! $relation instanceof Relation) { + if (is_null($relation)) { + throw new LogicException(sprintf( + '%s::%s must return a relationship instance, but "null" was returned. Was the "return" keyword used?', static::class, $method + )); + } + + throw new LogicException(sprintf( + '%s::%s must return a relationship instance.', static::class, $method + )); + } + + return tap($relation->getResults(), function ($results) use ($method) { + $this->setRelation($method, $results); + }); + } + + /** + * Determine if a get mutator exists for an attribute. + * + * @param string $key + * @return bool + */ + public function hasGetMutator($key) + { + return method_exists($this, 'get'.Str::studly($key).'Attribute'); + } + + /** + * Determine if a "Attribute" return type marked mutator exists for an attribute. + * + * @param string $key + * @return bool + */ + public function hasAttributeMutator($key) + { + if (isset(static::$attributeMutatorCache[get_class($this)][$key])) { + return static::$attributeMutatorCache[get_class($this)][$key]; + } + + if (! method_exists($this, $method = Str::camel($key))) { + return static::$attributeMutatorCache[get_class($this)][$key] = false; + } + + $returnType = (new ReflectionMethod($this, $method))->getReturnType(); + + return static::$attributeMutatorCache[get_class($this)][$key] = $returnType && + $returnType instanceof ReflectionNamedType && + $returnType->getName() === Attribute::class; + } + + /** + * Determine if a "Attribute" return type marked get mutator exists for an attribute. + * + * @param string $key + * @return bool + */ + public function hasAttributeGetMutator($key) + { + if (isset(static::$getAttributeMutatorCache[get_class($this)][$key])) { + return static::$getAttributeMutatorCache[get_class($this)][$key]; + } + + if (! $this->hasAttributeMutator($key)) { + return static::$getAttributeMutatorCache[get_class($this)][$key] = false; + } + + return static::$getAttributeMutatorCache[get_class($this)][$key] = is_callable($this->{Str::camel($key)}()->get); + } + + /** + * Get the value of an attribute using its mutator. + * + * @param string $key + * @param mixed $value + * @return mixed + */ + protected function mutateAttribute($key, $value) + { + return $this->{'get'.Str::studly($key).'Attribute'}($value); + } + + /** + * Get the value of an "Attribute" return type marked attribute using its mutator. + * + * @param string $key + * @param mixed $value + * @return mixed + */ + protected function mutateAttributeMarkedAttribute($key, $value) + { + if (isset($this->attributeCastCache[$key])) { + return $this->attributeCastCache[$key]; + } + + $attribute = $this->{Str::camel($key)}(); + + $value = call_user_func($attribute->get ?: function ($value) { + return $value; + }, $value, $this->attributes); + + if (! is_object($value) || ! $attribute->withObjectCaching) { + unset($this->attributeCastCache[$key]); + } else { + $this->attributeCastCache[$key] = $value; + } + + return $value; + } + + /** + * Get the value of an attribute using its mutator for array conversion. + * + * @param string $key + * @param mixed $value + * @return mixed + */ + protected function mutateAttributeForArray($key, $value) + { + if ($this->isClassCastable($key)) { + $value = $this->getClassCastableAttributeValue($key, $value); + } elseif (isset(static::$getAttributeMutatorCache[get_class($this)][$key]) && + static::$getAttributeMutatorCache[get_class($this)][$key] === true) { + $value = $this->mutateAttributeMarkedAttribute($key, $value); + + $value = $value instanceof DateTimeInterface + ? $this->serializeDate($value) + : $value; + } else { + $value = $this->mutateAttribute($key, $value); + } + + return $value instanceof Arrayable ? $value->toArray() : $value; + } + + /** + * Merge new casts with existing casts on the model. + * + * @param array $casts + * @return $this + */ + public function mergeCasts($casts) + { + $this->casts = array_merge($this->casts, $casts); + + return $this; + } + + /** + * Cast an attribute to a native PHP type. + * + * @param string $key + * @param mixed $value + * @return mixed + */ + protected function castAttribute($key, $value) + { + $castType = $this->getCastType($key); + + if (is_null($value) && in_array($castType, static::$primitiveCastTypes)) { + return $value; + } + + // If the key is one of the encrypted castable types, we'll first decrypt + // the value and update the cast type so we may leverage the following + // logic for casting this value to any additionally specified types. + if ($this->isEncryptedCastable($key)) { + $value = $this->fromEncryptedString($value); + + $castType = Str::after($castType, 'encrypted:'); + } + + switch ($castType) { + case 'int': + case 'integer': + return (int) $value; + case 'real': + case 'float': + case 'double': + return $this->fromFloat($value); + case 'decimal': + return $this->asDecimal($value, explode(':', $this->getCasts()[$key], 2)[1]); + case 'string': + return (string) $value; + case 'bool': + case 'boolean': + return (bool) $value; + case 'object': + return $this->fromJson($value, true); + case 'array': + case 'json': + return $this->fromJson($value); + case 'collection': + return new BaseCollection($this->fromJson($value)); + case 'date': + return $this->asDate($value); + case 'datetime': + case 'custom_datetime': + return $this->asDateTime($value); + case 'immutable_date': + return $this->asDate($value)->toImmutable(); + case 'immutable_custom_datetime': + case 'immutable_datetime': + return $this->asDateTime($value)->toImmutable(); + case 'timestamp': + return $this->asTimestamp($value); + } + + if ($this->isEnumCastable($key)) { + return $this->getEnumCastableAttributeValue($key, $value); + } + + if ($this->isClassCastable($key)) { + return $this->getClassCastableAttributeValue($key, $value); + } + + return $value; + } + + /** + * Cast the given attribute using a custom cast class. + * + * @param string $key + * @param mixed $value + * @return mixed + */ + protected function getClassCastableAttributeValue($key, $value) + { + if (isset($this->classCastCache[$key])) { + return $this->classCastCache[$key]; + } else { + $caster = $this->resolveCasterClass($key); + + $value = $caster instanceof CastsInboundAttributes + ? $value + : $caster->get($this, $key, $value, $this->attributes); + + if ($caster instanceof CastsInboundAttributes || ! is_object($value)) { + unset($this->classCastCache[$key]); + } else { + $this->classCastCache[$key] = $value; + } + + return $value; + } + } + + /** + * Cast the given attribute to an enum. + * + * @param string $key + * @param mixed $value + * @return mixed + */ + protected function getEnumCastableAttributeValue($key, $value) + { + if (is_null($value)) { + return; + } + + $castType = $this->getCasts()[$key]; + + if ($value instanceof $castType) { + return $value; + } + + return $castType::from($value); + } + + /** + * Get the type of cast for a model attribute. + * + * @param string $key + * @return string + */ + protected function getCastType($key) + { + if ($this->isCustomDateTimeCast($this->getCasts()[$key])) { + return 'custom_datetime'; + } + + if ($this->isImmutableCustomDateTimeCast($this->getCasts()[$key])) { + return 'immutable_custom_datetime'; + } + + if ($this->isDecimalCast($this->getCasts()[$key])) { + return 'decimal'; + } + + return trim(strtolower($this->getCasts()[$key])); + } + + /** + * Increment or decrement the given attribute using the custom cast class. + * + * @param string $method + * @param string $key + * @param mixed $value + * @return mixed + */ + protected function deviateClassCastableAttribute($method, $key, $value) + { + return $this->resolveCasterClass($key)->{$method}( + $this, $key, $value, $this->attributes + ); + } + + /** + * Serialize the given attribute using the custom cast class. + * + * @param string $key + * @param mixed $value + * @return mixed + */ + protected function serializeClassCastableAttribute($key, $value) + { + return $this->resolveCasterClass($key)->serialize( + $this, $key, $value, $this->attributes + ); + } + + /** + * Determine if the cast type is a custom date time cast. + * + * @param string $cast + * @return bool + */ + protected function isCustomDateTimeCast($cast) + { + return strncmp($cast, 'date:', 5) === 0 || + strncmp($cast, 'datetime:', 9) === 0; + } + + /** + * Determine if the cast type is an immutable custom date time cast. + * + * @param string $cast + * @return bool + */ + protected function isImmutableCustomDateTimeCast($cast) + { + return strncmp($cast, 'immutable_date:', 15) === 0 || + strncmp($cast, 'immutable_datetime:', 19) === 0; + } + + /** + * Determine if the cast type is a decimal cast. + * + * @param string $cast + * @return bool + */ + protected function isDecimalCast($cast) + { + return strncmp($cast, 'decimal:', 8) === 0; + } + + /** + * Set a given attribute on the model. + * + * @param string $key + * @param mixed $value + * @return mixed + */ + public function setAttribute($key, $value) + { + // First we will check for the presence of a mutator for the set operation + // which simply lets the developers tweak the attribute as it is set on + // this model, such as "json_encoding" a listing of data for storage. + if ($this->hasSetMutator($key)) { + return $this->setMutatedAttributeValue($key, $value); + } elseif ($this->hasAttributeSetMutator($key)) { + return $this->setAttributeMarkedMutatedAttributeValue($key, $value); + } + + // If an attribute is listed as a "date", we'll convert it from a DateTime + // instance into a form proper for storage on the database tables using + // the connection grammar's date format. We will auto set the values. + elseif ($value && $this->isDateAttribute($key)) { + $value = $this->fromDateTime($value); + } + + if ($this->isEnumCastable($key)) { + $this->setEnumCastableAttribute($key, $value); + + return $this; + } + + if ($this->isClassCastable($key)) { + $this->setClassCastableAttribute($key, $value); + + return $this; + } + + if (! is_null($value) && $this->isJsonCastable($key)) { + $value = $this->castAttributeAsJson($key, $value); + } + + // If this attribute contains a JSON ->, we'll set the proper value in the + // attribute's underlying array. This takes care of properly nesting an + // attribute in the array's value in the case of deeply nested items. + if (Str::contains($key, '->')) { + return $this->fillJsonAttribute($key, $value); + } + + if (! is_null($value) && $this->isEncryptedCastable($key)) { + $value = $this->castAttributeAsEncryptedString($key, $value); + } + + $this->attributes[$key] = $value; + + return $this; + } + + /** + * Determine if a set mutator exists for an attribute. + * + * @param string $key + * @return bool + */ + public function hasSetMutator($key) + { + return method_exists($this, 'set'.Str::studly($key).'Attribute'); + } + + /** + * Determine if an "Attribute" return type marked set mutator exists for an attribute. + * + * @param string $key + * @return bool + */ + public function hasAttributeSetMutator($key) + { + $class = get_class($this); + + if (isset(static::$setAttributeMutatorCache[$class][$key])) { + return static::$setAttributeMutatorCache[$class][$key]; + } + + if (! method_exists($this, $method = Str::camel($key))) { + return static::$setAttributeMutatorCache[$class][$key] = false; + } + + $returnType = (new ReflectionMethod($this, $method))->getReturnType(); + + return static::$setAttributeMutatorCache[$class][$key] = $returnType && + $returnType instanceof ReflectionNamedType && + $returnType->getName() === Attribute::class && + is_callable($this->{$method}()->set); + } + + /** + * Set the value of an attribute using its mutator. + * + * @param string $key + * @param mixed $value + * @return mixed + */ + protected function setMutatedAttributeValue($key, $value) + { + return $this->{'set'.Str::studly($key).'Attribute'}($value); + } + + /** + * Set the value of a "Attribute" return type marked attribute using its mutator. + * + * @param string $key + * @param mixed $value + * @return mixed + */ + protected function setAttributeMarkedMutatedAttributeValue($key, $value) + { + $attribute = $this->{Str::camel($key)}(); + + $callback = $attribute->set ?: function ($value) use ($key) { + $this->attributes[$key] = $value; + }; + + $this->attributes = array_merge( + $this->attributes, + $this->normalizeCastClassResponse( + $key, call_user_func($callback, $value, $this->attributes) + ) + ); + + if (! is_object($value) || ! $attribute->withObjectCaching) { + unset($this->attributeCastCache[$key]); + } else { + $this->attributeCastCache[$key] = $value; + } + } + + /** + * Determine if the given attribute is a date or date castable. + * + * @param string $key + * @return bool + */ + protected function isDateAttribute($key) + { + return in_array($key, $this->getDates(), true) || + $this->isDateCastable($key); + } + + /** + * Set a given JSON attribute on the model. + * + * @param string $key + * @param mixed $value + * @return $this + */ + public function fillJsonAttribute($key, $value) + { + [$key, $path] = explode('->', $key, 2); + + $value = $this->asJson($this->getArrayAttributeWithValue( + $path, $key, $value + )); + + $this->attributes[$key] = $this->isEncryptedCastable($key) + ? $this->castAttributeAsEncryptedString($key, $value) + : $value; + + return $this; + } + + /** + * Set the value of a class castable attribute. + * + * @param string $key + * @param mixed $value + * @return void + */ + protected function setClassCastableAttribute($key, $value) + { + $caster = $this->resolveCasterClass($key); + + if (is_null($value)) { + $this->attributes = array_merge($this->attributes, array_map( + function () { + }, + $this->normalizeCastClassResponse($key, $caster->set( + $this, $key, $this->{$key}, $this->attributes + )) + )); + } else { + $this->attributes = array_merge( + $this->attributes, + $this->normalizeCastClassResponse($key, $caster->set( + $this, $key, $value, $this->attributes + )) + ); + } + + if ($caster instanceof CastsInboundAttributes || ! is_object($value)) { + unset($this->classCastCache[$key]); + } else { + $this->classCastCache[$key] = $value; + } + } + + /** + * Set the value of an enum castable attribute. + * + * @param string $key + * @param \BackedEnum $value + * @return void + */ + protected function setEnumCastableAttribute($key, $value) + { + $enumClass = $this->getCasts()[$key]; + + if (! isset($value)) { + $this->attributes[$key] = null; + } elseif ($value instanceof $enumClass) { + $this->attributes[$key] = $value->value; + } else { + $this->attributes[$key] = $enumClass::from($value)->value; + } + } + + /** + * Get an array attribute with the given key and value set. + * + * @param string $path + * @param string $key + * @param mixed $value + * @return $this + */ + protected function getArrayAttributeWithValue($path, $key, $value) + { + return tap($this->getArrayAttributeByKey($key), function (&$array) use ($path, $value) { + Arr::set($array, str_replace('->', '.', $path), $value); + }); + } + + /** + * Get an array attribute or return an empty array if it is not set. + * + * @param string $key + * @return array + */ + protected function getArrayAttributeByKey($key) + { + if (! isset($this->attributes[$key])) { + return []; + } + + return $this->fromJson( + $this->isEncryptedCastable($key) + ? $this->fromEncryptedString($this->attributes[$key]) + : $this->attributes[$key] + ); + } + + /** + * Cast the given attribute to JSON. + * + * @param string $key + * @param mixed $value + * @return string + */ + protected function castAttributeAsJson($key, $value) + { + $value = $this->asJson($value); + + if ($value === false) { + throw JsonEncodingException::forAttribute( + $this, $key, json_last_error_msg() + ); + } + + return $value; + } + + /** + * Encode the given value as JSON. + * + * @param mixed $value + * @return string + */ + protected function asJson($value) + { + return json_encode($value); + } + + /** + * Decode the given JSON back into an array or object. + * + * @param string $value + * @param bool $asObject + * @return mixed + */ + public function fromJson($value, $asObject = false) + { + return json_decode($value, ! $asObject); + } + + /** + * Decrypt the given encrypted string. + * + * @param string $value + * @return mixed + */ + public function fromEncryptedString($value) + { + return (static::$encrypter ?? Crypt::getFacadeRoot())->decrypt($value, false); + } + + /** + * Cast the given attribute to an encrypted string. + * + * @param string $key + * @param mixed $value + * @return string + */ + protected function castAttributeAsEncryptedString($key, $value) + { + return (static::$encrypter ?? Crypt::getFacadeRoot())->encrypt($value, false); + } + + /** + * Set the encrypter instance that will be used to encrypt attributes. + * + * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter + * @return void + */ + public static function encryptUsing($encrypter) + { + static::$encrypter = $encrypter; + } + + /** + * Decode the given float. + * + * @param mixed $value + * @return mixed + */ + public function fromFloat($value) + { + switch ((string) $value) { + case 'Infinity': + return INF; + case '-Infinity': + return -INF; + case 'NaN': + return NAN; + default: + return (float) $value; + } + } + + /** + * Return a decimal as string. + * + * @param float $value + * @param int $decimals + * @return string + */ + protected function asDecimal($value, $decimals) + { + return number_format($value, $decimals, '.', ''); + } + + /** + * Return a timestamp as DateTime object with time set to 00:00:00. + * + * @param mixed $value + * @return \Illuminate\Support\Carbon + */ + protected function asDate($value) + { + return $this->asDateTime($value)->startOfDay(); + } + + /** + * Return a timestamp as DateTime object. + * + * @param mixed $value + * @return \Illuminate\Support\Carbon + */ + protected function asDateTime($value) + { + // If this value is already a Carbon instance, we shall just return it as is. + // This prevents us having to re-instantiate a Carbon instance when we know + // it already is one, which wouldn't be fulfilled by the DateTime check. + if ($value instanceof CarbonInterface) { + return Date::instance($value); + } + + // If the value is already a DateTime instance, we will just skip the rest of + // these checks since they will be a waste of time, and hinder performance + // when checking the field. We will just return the DateTime right away. + if ($value instanceof DateTimeInterface) { + return Date::parse( + $value->format('Y-m-d H:i:s.u'), $value->getTimezone() + ); + } + + // If this value is an integer, we will assume it is a UNIX timestamp's value + // and format a Carbon object from this timestamp. This allows flexibility + // when defining your date fields as they might be UNIX timestamps here. + if (is_numeric($value)) { + return Date::createFromTimestamp($value); + } + + // If the value is in simply year, month, day format, we will instantiate the + // Carbon instances from that format. Again, this provides for simple date + // fields on the database, while still supporting Carbonized conversion. + if ($this->isStandardDateFormat($value)) { + return Date::instance(Carbon::createFromFormat('Y-m-d', $value)->startOfDay()); + } + + $format = $this->getDateFormat(); + + // Finally, we will just assume this date is in the format used by default on + // the database connection and use that format to create the Carbon object + // that is returned back out to the developers after we convert it here. + try { + $date = Date::createFromFormat($format, $value); + } catch (InvalidArgumentException $e) { + $date = false; + } + + return $date ?: Date::parse($value); + } + + /** + * Determine if the given value is a standard date format. + * + * @param string $value + * @return bool + */ + protected function isStandardDateFormat($value) + { + return preg_match('/^(\d{4})-(\d{1,2})-(\d{1,2})$/', $value); + } + + /** + * Convert a DateTime to a storable string. + * + * @param mixed $value + * @return string|null + */ + public function fromDateTime($value) + { + return empty($value) ? $value : $this->asDateTime($value)->format( + $this->getDateFormat() + ); + } + + /** + * Return a timestamp as unix timestamp. + * + * @param mixed $value + * @return int + */ + protected function asTimestamp($value) + { + return $this->asDateTime($value)->getTimestamp(); + } + + /** + * Prepare a date for array / JSON serialization. + * + * @param \DateTimeInterface $date + * @return string + */ + protected function serializeDate(DateTimeInterface $date) + { + return $date instanceof \DateTimeImmutable ? + CarbonImmutable::instance($date)->toJSON() : + Carbon::instance($date)->toJSON(); + } + + /** + * Get the attributes that should be converted to dates. + * + * @return array + */ + public function getDates() + { + if (! $this->usesTimestamps()) { + return $this->dates; + } + + $defaults = [ + $this->getCreatedAtColumn(), + $this->getUpdatedAtColumn(), + ]; + + return array_unique(array_merge($this->dates, $defaults)); + } + + /** + * Get the format for database stored dates. + * + * @return string + */ + public function getDateFormat() + { + return $this->dateFormat ?: $this->getConnection()->getQueryGrammar()->getDateFormat(); + } + + /** + * Set the date format used by the model. + * + * @param string $format + * @return $this + */ + public function setDateFormat($format) + { + $this->dateFormat = $format; + + return $this; + } + + /** + * Determine whether an attribute should be cast to a native type. + * + * @param string $key + * @param array|string|null $types + * @return bool + */ + public function hasCast($key, $types = null) + { + if (array_key_exists($key, $this->getCasts())) { + return $types ? in_array($this->getCastType($key), (array) $types, true) : true; + } + + return false; + } + + /** + * Get the casts array. + * + * @return array + */ + public function getCasts() + { + if ($this->getIncrementing()) { + return array_merge([$this->getKeyName() => $this->getKeyType()], $this->casts); + } + + return $this->casts; + } + + /** + * Determine whether a value is Date / DateTime castable for inbound manipulation. + * + * @param string $key + * @return bool + */ + protected function isDateCastable($key) + { + return $this->hasCast($key, ['date', 'datetime', 'immutable_date', 'immutable_datetime']); + } + + /** + * Determine whether a value is Date / DateTime custom-castable for inbound manipulation. + * + * @param string $key + * @return bool + */ + protected function isDateCastableWithCustomFormat($key) + { + return $this->hasCast($key, ['custom_datetime', 'immutable_custom_datetime']); + } + + /** + * Determine whether a value is JSON castable for inbound manipulation. + * + * @param string $key + * @return bool + */ + protected function isJsonCastable($key) + { + return $this->hasCast($key, ['array', 'json', 'object', 'collection', 'encrypted:array', 'encrypted:collection', 'encrypted:json', 'encrypted:object']); + } + + /** + * Determine whether a value is an encrypted castable for inbound manipulation. + * + * @param string $key + * @return bool + */ + protected function isEncryptedCastable($key) + { + return $this->hasCast($key, ['encrypted', 'encrypted:array', 'encrypted:collection', 'encrypted:json', 'encrypted:object']); + } + + /** + * Determine if the given key is cast using a custom class. + * + * @param string $key + * @return bool + * + * @throws \Illuminate\Database\Eloquent\InvalidCastException + */ + protected function isClassCastable($key) + { + if (! array_key_exists($key, $this->getCasts())) { + return false; + } + + $castType = $this->parseCasterClass($this->getCasts()[$key]); + + if (in_array($castType, static::$primitiveCastTypes)) { + return false; + } + + if (class_exists($castType)) { + return true; + } + + throw new InvalidCastException($this->getModel(), $key, $castType); + } + + /** + * Determine if the given key is cast using an enum. + * + * @param string $key + * @return bool + */ + protected function isEnumCastable($key) + { + if (! array_key_exists($key, $this->getCasts())) { + return false; + } + + $castType = $this->getCasts()[$key]; + + if (in_array($castType, static::$primitiveCastTypes)) { + return false; + } + + if (function_exists('enum_exists') && enum_exists($castType)) { + return true; + } + } + + /** + * Determine if the key is deviable using a custom class. + * + * @param string $key + * @return bool + * + * @throws \Illuminate\Database\Eloquent\InvalidCastException + */ + protected function isClassDeviable($key) + { + return $this->isClassCastable($key) && + method_exists($castType = $this->parseCasterClass($this->getCasts()[$key]), 'increment') && + method_exists($castType, 'decrement'); + } + + /** + * Determine if the key is serializable using a custom class. + * + * @param string $key + * @return bool + * + * @throws \Illuminate\Database\Eloquent\InvalidCastException + */ + protected function isClassSerializable($key) + { + return ! $this->isEnumCastable($key) && + $this->isClassCastable($key) && + method_exists($this->resolveCasterClass($key), 'serialize'); + } + + /** + * Resolve the custom caster class for a given key. + * + * @param string $key + * @return mixed + */ + protected function resolveCasterClass($key) + { + $castType = $this->getCasts()[$key]; + + $arguments = []; + + if (is_string($castType) && strpos($castType, ':') !== false) { + $segments = explode(':', $castType, 2); + + $castType = $segments[0]; + $arguments = explode(',', $segments[1]); + } + + if (is_subclass_of($castType, Castable::class)) { + $castType = $castType::castUsing($arguments); + } + + if (is_object($castType)) { + return $castType; + } + + return new $castType(...$arguments); + } + + /** + * Parse the given caster class, removing any arguments. + * + * @param string $class + * @return string + */ + protected function parseCasterClass($class) + { + return strpos($class, ':') === false + ? $class + : explode(':', $class, 2)[0]; + } + + /** + * Merge the cast class and attribute cast attributes back into the model. + * + * @return void + */ + protected function mergeAttributesFromCachedCasts() + { + $this->mergeAttributesFromClassCasts(); + $this->mergeAttributesFromAttributeCasts(); + } + + /** + * Merge the cast class attributes back into the model. + * + * @return void + */ + protected function mergeAttributesFromClassCasts() + { + foreach ($this->classCastCache as $key => $value) { + $caster = $this->resolveCasterClass($key); + + $this->attributes = array_merge( + $this->attributes, + $caster instanceof CastsInboundAttributes + ? [$key => $value] + : $this->normalizeCastClassResponse($key, $caster->set($this, $key, $value, $this->attributes)) + ); + } + } + + /** + * Merge the cast class attributes back into the model. + * + * @return void + */ + protected function mergeAttributesFromAttributeCasts() + { + foreach ($this->attributeCastCache as $key => $value) { + $attribute = $this->{Str::camel($key)}(); + + if ($attribute->get && ! $attribute->set) { + continue; + } + + $callback = $attribute->set ?: function ($value) use ($key) { + $this->attributes[$key] = $value; + }; + + $this->attributes = array_merge( + $this->attributes, + $this->normalizeCastClassResponse( + $key, call_user_func($callback, $value, $this->attributes) + ) + ); + } + } + + /** + * Normalize the response from a custom class caster. + * + * @param string $key + * @param mixed $value + * @return array + */ + protected function normalizeCastClassResponse($key, $value) + { + return is_array($value) ? $value : [$key => $value]; + } + + /** + * Get all of the current attributes on the model. + * + * @return array + */ + public function getAttributes() + { + $this->mergeAttributesFromCachedCasts(); + + return $this->attributes; + } + + /** + * Get all of the current attributes on the model for an insert operation. + * + * @return array + */ + protected function getAttributesForInsert() + { + return $this->getAttributes(); + } + + /** + * Set the array of model attributes. No checking is done. + * + * @param array $attributes + * @param bool $sync + * @return $this + */ + public function setRawAttributes(array $attributes, $sync = false) + { + $this->attributes = $attributes; + + if ($sync) { + $this->syncOriginal(); + } + + $this->classCastCache = []; + $this->attributeCastCache = []; + + return $this; + } + + /** + * Get the model's original attribute values. + * + * @param string|null $key + * @param mixed $default + * @return mixed|array + */ + public function getOriginal($key = null, $default = null) + { + return (new static)->setRawAttributes( + $this->original, $sync = true + )->getOriginalWithoutRewindingModel($key, $default); + } + + /** + * Get the model's original attribute values. + * + * @param string|null $key + * @param mixed $default + * @return mixed|array + */ + protected function getOriginalWithoutRewindingModel($key = null, $default = null) + { + if ($key) { + return $this->transformModelValue( + $key, Arr::get($this->original, $key, $default) + ); + } + + return collect($this->original)->mapWithKeys(function ($value, $key) { + return [$key => $this->transformModelValue($key, $value)]; + })->all(); + } + + /** + * Get the model's raw original attribute values. + * + * @param string|null $key + * @param mixed $default + * @return mixed|array + */ + public function getRawOriginal($key = null, $default = null) + { + return Arr::get($this->original, $key, $default); + } + + /** + * Get a subset of the model's attributes. + * + * @param array|mixed $attributes + * @return array + */ + public function only($attributes) + { + $results = []; + + foreach (is_array($attributes) ? $attributes : func_get_args() as $attribute) { + $results[$attribute] = $this->getAttribute($attribute); + } + + return $results; + } + + /** + * Sync the original attributes with the current. + * + * @return $this + */ + public function syncOriginal() + { + $this->original = $this->getAttributes(); + + return $this; + } + + /** + * Sync a single original attribute with its current value. + * + * @param string $attribute + * @return $this + */ + public function syncOriginalAttribute($attribute) + { + return $this->syncOriginalAttributes($attribute); + } + + /** + * Sync multiple original attribute with their current values. + * + * @param array|string $attributes + * @return $this + */ + public function syncOriginalAttributes($attributes) + { + $attributes = is_array($attributes) ? $attributes : func_get_args(); + + $modelAttributes = $this->getAttributes(); + + foreach ($attributes as $attribute) { + $this->original[$attribute] = $modelAttributes[$attribute]; + } + + return $this; + } + + /** + * Sync the changed attributes. + * + * @return $this + */ + public function syncChanges() + { + $this->changes = $this->getDirty(); + + return $this; + } + + /** + * Determine if the model or any of the given attribute(s) have been modified. + * + * @param array|string|null $attributes + * @return bool + */ + public function isDirty($attributes = null) + { + return $this->hasChanges( + $this->getDirty(), is_array($attributes) ? $attributes : func_get_args() + ); + } + + /** + * Determine if the model or all the given attribute(s) have remained the same. + * + * @param array|string|null $attributes + * @return bool + */ + public function isClean($attributes = null) + { + return ! $this->isDirty(...func_get_args()); + } + + /** + * Determine if the model or any of the given attribute(s) have been modified. + * + * @param array|string|null $attributes + * @return bool + */ + public function wasChanged($attributes = null) + { + return $this->hasChanges( + $this->getChanges(), is_array($attributes) ? $attributes : func_get_args() + ); + } + + /** + * Determine if any of the given attributes were changed. + * + * @param array $changes + * @param array|string|null $attributes + * @return bool + */ + protected function hasChanges($changes, $attributes = null) + { + // If no specific attributes were provided, we will just see if the dirty array + // already contains any attributes. If it does we will just return that this + // count is greater than zero. Else, we need to check specific attributes. + if (empty($attributes)) { + return count($changes) > 0; + } + + // Here we will spin through every attribute and see if this is in the array of + // dirty attributes. If it is, we will return true and if we make it through + // all of the attributes for the entire array we will return false at end. + foreach (Arr::wrap($attributes) as $attribute) { + if (array_key_exists($attribute, $changes)) { + return true; + } + } + + return false; + } + + /** + * Get the attributes that have been changed since the last sync. + * + * @return array + */ + public function getDirty() + { + $dirty = []; + + foreach ($this->getAttributes() as $key => $value) { + if (! $this->originalIsEquivalent($key)) { + $dirty[$key] = $value; + } + } + + return $dirty; + } + + /** + * Get the attributes that were changed. + * + * @return array + */ + public function getChanges() + { + return $this->changes; + } + + /** + * Determine if the new and old values for a given key are equivalent. + * + * @param string $key + * @return bool + */ + public function originalIsEquivalent($key) + { + if (! array_key_exists($key, $this->original)) { + return false; + } + + $attribute = Arr::get($this->attributes, $key); + $original = Arr::get($this->original, $key); + + if ($attribute === $original) { + return true; + } elseif (is_null($attribute)) { + return false; + } elseif ($this->isDateAttribute($key) || $this->isDateCastableWithCustomFormat($key)) { + return $this->fromDateTime($attribute) === + $this->fromDateTime($original); + } elseif ($this->hasCast($key, ['object', 'collection'])) { + return $this->fromJson($attribute) === + $this->fromJson($original); + } elseif ($this->hasCast($key, ['real', 'float', 'double'])) { + if (($attribute === null && $original !== null) || ($attribute !== null && $original === null)) { + return false; + } + + return abs($this->castAttribute($key, $attribute) - $this->castAttribute($key, $original)) < PHP_FLOAT_EPSILON * 4; + } elseif ($this->hasCast($key, static::$primitiveCastTypes)) { + return $this->castAttribute($key, $attribute) === + $this->castAttribute($key, $original); + } elseif ($this->isClassCastable($key) && in_array($this->getCasts()[$key], [AsArrayObject::class, AsCollection::class])) { + return $this->fromJson($attribute) === $this->fromJson($original); + } + + return is_numeric($attribute) && is_numeric($original) + && strcmp((string) $attribute, (string) $original) === 0; + } + + /** + * Transform a raw model value using mutators, casts, etc. + * + * @param string $key + * @param mixed $value + * @return mixed + */ + protected function transformModelValue($key, $value) + { + // If the attribute has a get mutator, we will call that then return what + // it returns as the value, which is useful for transforming values on + // retrieval from the model to a form that is more useful for usage. + if ($this->hasGetMutator($key)) { + return $this->mutateAttribute($key, $value); + } elseif ($this->hasAttributeGetMutator($key)) { + return $this->mutateAttributeMarkedAttribute($key, $value); + } + + // If the attribute exists within the cast array, we will convert it to + // an appropriate native PHP type dependent upon the associated value + // given with the key in the pair. Dayle made this comment line up. + if ($this->hasCast($key)) { + return $this->castAttribute($key, $value); + } + + // If the attribute is listed as a date, we will convert it to a DateTime + // instance on retrieval, which makes it quite convenient to work with + // date fields without having to create a mutator for each property. + if ($value !== null + && \in_array($key, $this->getDates(), false)) { + return $this->asDateTime($value); + } + + return $value; + } + + /** + * Append attributes to query when building a query. + * + * @param array|string $attributes + * @return $this + */ + public function append($attributes) + { + $this->appends = array_unique( + array_merge($this->appends, is_string($attributes) ? func_get_args() : $attributes) + ); + + return $this; + } + + /** + * Set the accessors to append to model arrays. + * + * @param array $appends + * @return $this + */ + public function setAppends(array $appends) + { + $this->appends = $appends; + + return $this; + } + + /** + * Return whether the accessor attribute has been appended. + * + * @param string $attribute + * @return bool + */ + public function hasAppended($attribute) + { + return in_array($attribute, $this->appends); + } + + /** + * Get the mutated attributes for a given instance. + * + * @return array + */ + public function getMutatedAttributes() + { + $class = static::class; + + if (! isset(static::$mutatorCache[$class])) { + static::cacheMutatedAttributes($class); + } + + return static::$mutatorCache[$class]; + } + + /** + * Extract and cache all the mutated attributes of a class. + * + * @param string $class + * @return void + */ + public static function cacheMutatedAttributes($class) + { + static::$getAttributeMutatorCache[$class] = + collect($attributeMutatorMethods = static::getAttributeMarkedMutatorMethods($class)) + ->mapWithKeys(function ($match) { + return [lcfirst(static::$snakeAttributes ? Str::snake($match) : $match) => true]; + })->all(); + + static::$mutatorCache[$class] = collect(static::getMutatorMethods($class)) + ->merge($attributeMutatorMethods) + ->map(function ($match) { + return lcfirst(static::$snakeAttributes ? Str::snake($match) : $match); + })->all(); + } + + /** + * Get all of the attribute mutator methods. + * + * @param mixed $class + * @return array + */ + protected static function getMutatorMethods($class) + { + preg_match_all('/(?<=^|;)get([^;]+?)Attribute(;|$)/', implode(';', get_class_methods($class)), $matches); + + return $matches[1]; + } + + /** + * Get all of the "Attribute" return typed attribute mutator methods. + * + * @param mixed $class + * @return array + */ + protected static function getAttributeMarkedMutatorMethods($class) + { + $instance = is_object($class) ? $class : new $class; + + return collect((new ReflectionClass($instance))->getMethods())->filter(function ($method) use ($instance) { + $returnType = $method->getReturnType(); + + if ($returnType && + $returnType instanceof ReflectionNamedType && + $returnType->getName() === Attribute::class) { + $method->setAccessible(true); + + if (is_callable($method->invoke($instance)->get)) { + return true; + } + } + + return false; + })->map->name->values()->all(); + } +} diff --git a/vendor/illuminate/database/Eloquent/Concerns/HasEvents.php b/vendor/illuminate/database/Eloquent/Concerns/HasEvents.php new file mode 100644 index 0000000..eb6a970 --- /dev/null +++ b/vendor/illuminate/database/Eloquent/Concerns/HasEvents.php @@ -0,0 +1,415 @@ +registerObserver($class); + } + } + + /** + * Register a single observer with the model. + * + * @param object|string $class + * @return void + * + * @throws \RuntimeException + */ + protected function registerObserver($class) + { + $className = $this->resolveObserverClassName($class); + + // When registering a model observer, we will spin through the possible events + // and determine if this observer has that method. If it does, we will hook + // it into the model's event system, making it convenient to watch these. + foreach ($this->getObservableEvents() as $event) { + if (method_exists($class, $event)) { + static::registerModelEvent($event, $className.'@'.$event); + } + } + } + + /** + * Resolve the observer's class name from an object or string. + * + * @param object|string $class + * @return string + * + * @throws \InvalidArgumentException + */ + private function resolveObserverClassName($class) + { + if (is_object($class)) { + return get_class($class); + } + + if (class_exists($class)) { + return $class; + } + + throw new InvalidArgumentException('Unable to find observer: '.$class); + } + + /** + * Get the observable event names. + * + * @return array + */ + public function getObservableEvents() + { + return array_merge( + [ + 'retrieved', 'creating', 'created', 'updating', 'updated', + 'saving', 'saved', 'restoring', 'restored', 'replicating', + 'deleting', 'deleted', 'forceDeleted', + ], + $this->observables + ); + } + + /** + * Set the observable event names. + * + * @param array $observables + * @return $this + */ + public function setObservableEvents(array $observables) + { + $this->observables = $observables; + + return $this; + } + + /** + * Add an observable event name. + * + * @param array|mixed $observables + * @return void + */ + public function addObservableEvents($observables) + { + $this->observables = array_unique(array_merge( + $this->observables, is_array($observables) ? $observables : func_get_args() + )); + } + + /** + * Remove an observable event name. + * + * @param array|mixed $observables + * @return void + */ + public function removeObservableEvents($observables) + { + $this->observables = array_diff( + $this->observables, is_array($observables) ? $observables : func_get_args() + ); + } + + /** + * Register a model event with the dispatcher. + * + * @param string $event + * @param \Illuminate\Events\QueuedClosure|\Closure|string $callback + * @return void + */ + protected static function registerModelEvent($event, $callback) + { + if (isset(static::$dispatcher)) { + $name = static::class; + + static::$dispatcher->listen("eloquent.{$event}: {$name}", $callback); + } + } + + /** + * Fire the given event for the model. + * + * @param string $event + * @param bool $halt + * @return mixed + */ + protected function fireModelEvent($event, $halt = true) + { + if (! isset(static::$dispatcher)) { + return true; + } + + // First, we will get the proper method to call on the event dispatcher, and then we + // will attempt to fire a custom, object based event for the given event. If that + // returns a result we can return that result, or we'll call the string events. + $method = $halt ? 'until' : 'dispatch'; + + $result = $this->filterModelEventResults( + $this->fireCustomModelEvent($event, $method) + ); + + if ($result === false) { + return false; + } + + return ! empty($result) ? $result : static::$dispatcher->{$method}( + "eloquent.{$event}: ".static::class, $this + ); + } + + /** + * Fire a custom model event for the given event. + * + * @param string $event + * @param string $method + * @return mixed|null + */ + protected function fireCustomModelEvent($event, $method) + { + if (! isset($this->dispatchesEvents[$event])) { + return; + } + + $result = static::$dispatcher->$method(new $this->dispatchesEvents[$event]($this)); + + if (! is_null($result)) { + return $result; + } + } + + /** + * Filter the model event results. + * + * @param mixed $result + * @return mixed + */ + protected function filterModelEventResults($result) + { + if (is_array($result)) { + $result = array_filter($result, function ($response) { + return ! is_null($response); + }); + } + + return $result; + } + + /** + * Register a retrieved model event with the dispatcher. + * + * @param \Illuminate\Events\QueuedClosure|\Closure|string $callback + * @return void + */ + public static function retrieved($callback) + { + static::registerModelEvent('retrieved', $callback); + } + + /** + * Register a saving model event with the dispatcher. + * + * @param \Illuminate\Events\QueuedClosure|\Closure|string $callback + * @return void + */ + public static function saving($callback) + { + static::registerModelEvent('saving', $callback); + } + + /** + * Register a saved model event with the dispatcher. + * + * @param \Illuminate\Events\QueuedClosure|\Closure|string $callback + * @return void + */ + public static function saved($callback) + { + static::registerModelEvent('saved', $callback); + } + + /** + * Register an updating model event with the dispatcher. + * + * @param \Illuminate\Events\QueuedClosure|\Closure|string $callback + * @return void + */ + public static function updating($callback) + { + static::registerModelEvent('updating', $callback); + } + + /** + * Register an updated model event with the dispatcher. + * + * @param \Illuminate\Events\QueuedClosure|\Closure|string $callback + * @return void + */ + public static function updated($callback) + { + static::registerModelEvent('updated', $callback); + } + + /** + * Register a creating model event with the dispatcher. + * + * @param \Illuminate\Events\QueuedClosure|\Closure|string $callback + * @return void + */ + public static function creating($callback) + { + static::registerModelEvent('creating', $callback); + } + + /** + * Register a created model event with the dispatcher. + * + * @param \Illuminate\Events\QueuedClosure|\Closure|string $callback + * @return void + */ + public static function created($callback) + { + static::registerModelEvent('created', $callback); + } + + /** + * Register a replicating model event with the dispatcher. + * + * @param \Illuminate\Events\QueuedClosure|\Closure|string $callback + * @return void + */ + public static function replicating($callback) + { + static::registerModelEvent('replicating', $callback); + } + + /** + * Register a deleting model event with the dispatcher. + * + * @param \Illuminate\Events\QueuedClosure|\Closure|string $callback + * @return void + */ + public static function deleting($callback) + { + static::registerModelEvent('deleting', $callback); + } + + /** + * Register a deleted model event with the dispatcher. + * + * @param \Illuminate\Events\QueuedClosure|\Closure|string $callback + * @return void + */ + public static function deleted($callback) + { + static::registerModelEvent('deleted', $callback); + } + + /** + * Remove all of the event listeners for the model. + * + * @return void + */ + public static function flushEventListeners() + { + if (! isset(static::$dispatcher)) { + return; + } + + $instance = new static; + + foreach ($instance->getObservableEvents() as $event) { + static::$dispatcher->forget("eloquent.{$event}: ".static::class); + } + + foreach (array_values($instance->dispatchesEvents) as $event) { + static::$dispatcher->forget($event); + } + } + + /** + * Get the event dispatcher instance. + * + * @return \Illuminate\Contracts\Events\Dispatcher + */ + public static function getEventDispatcher() + { + return static::$dispatcher; + } + + /** + * Set the event dispatcher instance. + * + * @param \Illuminate\Contracts\Events\Dispatcher $dispatcher + * @return void + */ + public static function setEventDispatcher(Dispatcher $dispatcher) + { + static::$dispatcher = $dispatcher; + } + + /** + * Unset the event dispatcher for models. + * + * @return void + */ + public static function unsetEventDispatcher() + { + static::$dispatcher = null; + } + + /** + * Execute a callback without firing any model events for any model type. + * + * @param callable $callback + * @return mixed + */ + public static function withoutEvents(callable $callback) + { + $dispatcher = static::getEventDispatcher(); + + if ($dispatcher) { + static::setEventDispatcher(new NullDispatcher($dispatcher)); + } + + try { + return $callback(); + } finally { + if ($dispatcher) { + static::setEventDispatcher($dispatcher); + } + } + } +} diff --git a/vendor/illuminate/database/Eloquent/Concerns/HasGlobalScopes.php b/vendor/illuminate/database/Eloquent/Concerns/HasGlobalScopes.php new file mode 100644 index 0000000..1742679 --- /dev/null +++ b/vendor/illuminate/database/Eloquent/Concerns/HasGlobalScopes.php @@ -0,0 +1,71 @@ + [$name => $callback]] + ); + } + + /** + * Define a one-to-one relationship. + * + * @param string $related + * @param string|null $foreignKey + * @param string|null $localKey + * @return \Illuminate\Database\Eloquent\Relations\HasOne + */ + public function hasOne($related, $foreignKey = null, $localKey = null) + { + $instance = $this->newRelatedInstance($related); + + $foreignKey = $foreignKey ?: $this->getForeignKey(); + + $localKey = $localKey ?: $this->getKeyName(); + + return $this->newHasOne($instance->newQuery(), $this, $instance->getTable().'.'.$foreignKey, $localKey); + } + + /** + * Instantiate a new HasOne relationship. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Model $parent + * @param string $foreignKey + * @param string $localKey + * @return \Illuminate\Database\Eloquent\Relations\HasOne + */ + protected function newHasOne(Builder $query, Model $parent, $foreignKey, $localKey) + { + return new HasOne($query, $parent, $foreignKey, $localKey); + } + + /** + * Define a has-one-through relationship. + * + * @param string $related + * @param string $through + * @param string|null $firstKey + * @param string|null $secondKey + * @param string|null $localKey + * @param string|null $secondLocalKey + * @return \Illuminate\Database\Eloquent\Relations\HasOneThrough + */ + public function hasOneThrough($related, $through, $firstKey = null, $secondKey = null, $localKey = null, $secondLocalKey = null) + { + $through = new $through; + + $firstKey = $firstKey ?: $this->getForeignKey(); + + $secondKey = $secondKey ?: $through->getForeignKey(); + + return $this->newHasOneThrough( + $this->newRelatedInstance($related)->newQuery(), $this, $through, + $firstKey, $secondKey, $localKey ?: $this->getKeyName(), + $secondLocalKey ?: $through->getKeyName() + ); + } + + /** + * Instantiate a new HasOneThrough relationship. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Model $farParent + * @param \Illuminate\Database\Eloquent\Model $throughParent + * @param string $firstKey + * @param string $secondKey + * @param string $localKey + * @param string $secondLocalKey + * @return \Illuminate\Database\Eloquent\Relations\HasOneThrough + */ + protected function newHasOneThrough(Builder $query, Model $farParent, Model $throughParent, $firstKey, $secondKey, $localKey, $secondLocalKey) + { + return new HasOneThrough($query, $farParent, $throughParent, $firstKey, $secondKey, $localKey, $secondLocalKey); + } + + /** + * Define a polymorphic one-to-one relationship. + * + * @param string $related + * @param string $name + * @param string|null $type + * @param string|null $id + * @param string|null $localKey + * @return \Illuminate\Database\Eloquent\Relations\MorphOne + */ + public function morphOne($related, $name, $type = null, $id = null, $localKey = null) + { + $instance = $this->newRelatedInstance($related); + + [$type, $id] = $this->getMorphs($name, $type, $id); + + $table = $instance->getTable(); + + $localKey = $localKey ?: $this->getKeyName(); + + return $this->newMorphOne($instance->newQuery(), $this, $table.'.'.$type, $table.'.'.$id, $localKey); + } + + /** + * Instantiate a new MorphOne relationship. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Model $parent + * @param string $type + * @param string $id + * @param string $localKey + * @return \Illuminate\Database\Eloquent\Relations\MorphOne + */ + protected function newMorphOne(Builder $query, Model $parent, $type, $id, $localKey) + { + return new MorphOne($query, $parent, $type, $id, $localKey); + } + + /** + * Define an inverse one-to-one or many relationship. + * + * @param string $related + * @param string|null $foreignKey + * @param string|null $ownerKey + * @param string|null $relation + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function belongsTo($related, $foreignKey = null, $ownerKey = null, $relation = null) + { + // If no relation name was given, we will use this debug backtrace to extract + // the calling method's name and use that as the relationship name as most + // of the time this will be what we desire to use for the relationships. + if (is_null($relation)) { + $relation = $this->guessBelongsToRelation(); + } + + $instance = $this->newRelatedInstance($related); + + // If no foreign key was supplied, we can use a backtrace to guess the proper + // foreign key name by using the name of the relationship function, which + // when combined with an "_id" should conventionally match the columns. + if (is_null($foreignKey)) { + $foreignKey = Str::snake($relation).'_'.$instance->getKeyName(); + } + + // Once we have the foreign key names, we'll just create a new Eloquent query + // for the related models and returns the relationship instance which will + // actually be responsible for retrieving and hydrating every relations. + $ownerKey = $ownerKey ?: $instance->getKeyName(); + + return $this->newBelongsTo( + $instance->newQuery(), $this, $foreignKey, $ownerKey, $relation + ); + } + + /** + * Instantiate a new BelongsTo relationship. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Model $child + * @param string $foreignKey + * @param string $ownerKey + * @param string $relation + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + protected function newBelongsTo(Builder $query, Model $child, $foreignKey, $ownerKey, $relation) + { + return new BelongsTo($query, $child, $foreignKey, $ownerKey, $relation); + } + + /** + * Define a polymorphic, inverse one-to-one or many relationship. + * + * @param string|null $name + * @param string|null $type + * @param string|null $id + * @param string|null $ownerKey + * @return \Illuminate\Database\Eloquent\Relations\MorphTo + */ + public function morphTo($name = null, $type = null, $id = null, $ownerKey = null) + { + // If no name is provided, we will use the backtrace to get the function name + // since that is most likely the name of the polymorphic interface. We can + // use that to get both the class and foreign key that will be utilized. + $name = $name ?: $this->guessBelongsToRelation(); + + [$type, $id] = $this->getMorphs( + Str::snake($name), $type, $id + ); + + // If the type value is null it is probably safe to assume we're eager loading + // the relationship. In this case we'll just pass in a dummy query where we + // need to remove any eager loads that may already be defined on a model. + return is_null($class = $this->getAttributeFromArray($type)) || $class === '' + ? $this->morphEagerTo($name, $type, $id, $ownerKey) + : $this->morphInstanceTo($class, $name, $type, $id, $ownerKey); + } + + /** + * Define a polymorphic, inverse one-to-one or many relationship. + * + * @param string $name + * @param string $type + * @param string $id + * @param string $ownerKey + * @return \Illuminate\Database\Eloquent\Relations\MorphTo + */ + protected function morphEagerTo($name, $type, $id, $ownerKey) + { + return $this->newMorphTo( + $this->newQuery()->setEagerLoads([]), $this, $id, $ownerKey, $type, $name + ); + } + + /** + * Define a polymorphic, inverse one-to-one or many relationship. + * + * @param string $target + * @param string $name + * @param string $type + * @param string $id + * @param string $ownerKey + * @return \Illuminate\Database\Eloquent\Relations\MorphTo + */ + protected function morphInstanceTo($target, $name, $type, $id, $ownerKey) + { + $instance = $this->newRelatedInstance( + static::getActualClassNameForMorph($target) + ); + + return $this->newMorphTo( + $instance->newQuery(), $this, $id, $ownerKey ?? $instance->getKeyName(), $type, $name + ); + } + + /** + * Instantiate a new MorphTo relationship. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Model $parent + * @param string $foreignKey + * @param string $ownerKey + * @param string $type + * @param string $relation + * @return \Illuminate\Database\Eloquent\Relations\MorphTo + */ + protected function newMorphTo(Builder $query, Model $parent, $foreignKey, $ownerKey, $type, $relation) + { + return new MorphTo($query, $parent, $foreignKey, $ownerKey, $type, $relation); + } + + /** + * Retrieve the actual class name for a given morph class. + * + * @param string $class + * @return string + */ + public static function getActualClassNameForMorph($class) + { + return Arr::get(Relation::morphMap() ?: [], $class, $class); + } + + /** + * Guess the "belongs to" relationship name. + * + * @return string + */ + protected function guessBelongsToRelation() + { + [$one, $two, $caller] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3); + + return $caller['function']; + } + + /** + * Define a one-to-many relationship. + * + * @param string $related + * @param string|null $foreignKey + * @param string|null $localKey + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function hasMany($related, $foreignKey = null, $localKey = null) + { + $instance = $this->newRelatedInstance($related); + + $foreignKey = $foreignKey ?: $this->getForeignKey(); + + $localKey = $localKey ?: $this->getKeyName(); + + return $this->newHasMany( + $instance->newQuery(), $this, $instance->getTable().'.'.$foreignKey, $localKey + ); + } + + /** + * Instantiate a new HasMany relationship. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Model $parent + * @param string $foreignKey + * @param string $localKey + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + protected function newHasMany(Builder $query, Model $parent, $foreignKey, $localKey) + { + return new HasMany($query, $parent, $foreignKey, $localKey); + } + + /** + * Define a has-many-through relationship. + * + * @param string $related + * @param string $through + * @param string|null $firstKey + * @param string|null $secondKey + * @param string|null $localKey + * @param string|null $secondLocalKey + * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough + */ + public function hasManyThrough($related, $through, $firstKey = null, $secondKey = null, $localKey = null, $secondLocalKey = null) + { + $through = new $through; + + $firstKey = $firstKey ?: $this->getForeignKey(); + + $secondKey = $secondKey ?: $through->getForeignKey(); + + return $this->newHasManyThrough( + $this->newRelatedInstance($related)->newQuery(), + $this, + $through, + $firstKey, + $secondKey, + $localKey ?: $this->getKeyName(), + $secondLocalKey ?: $through->getKeyName() + ); + } + + /** + * Instantiate a new HasManyThrough relationship. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Model $farParent + * @param \Illuminate\Database\Eloquent\Model $throughParent + * @param string $firstKey + * @param string $secondKey + * @param string $localKey + * @param string $secondLocalKey + * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough + */ + protected function newHasManyThrough(Builder $query, Model $farParent, Model $throughParent, $firstKey, $secondKey, $localKey, $secondLocalKey) + { + return new HasManyThrough($query, $farParent, $throughParent, $firstKey, $secondKey, $localKey, $secondLocalKey); + } + + /** + * Define a polymorphic one-to-many relationship. + * + * @param string $related + * @param string $name + * @param string|null $type + * @param string|null $id + * @param string|null $localKey + * @return \Illuminate\Database\Eloquent\Relations\MorphMany + */ + public function morphMany($related, $name, $type = null, $id = null, $localKey = null) + { + $instance = $this->newRelatedInstance($related); + + // Here we will gather up the morph type and ID for the relationship so that we + // can properly query the intermediate table of a relation. Finally, we will + // get the table and create the relationship instances for the developers. + [$type, $id] = $this->getMorphs($name, $type, $id); + + $table = $instance->getTable(); + + $localKey = $localKey ?: $this->getKeyName(); + + return $this->newMorphMany($instance->newQuery(), $this, $table.'.'.$type, $table.'.'.$id, $localKey); + } + + /** + * Instantiate a new MorphMany relationship. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Model $parent + * @param string $type + * @param string $id + * @param string $localKey + * @return \Illuminate\Database\Eloquent\Relations\MorphMany + */ + protected function newMorphMany(Builder $query, Model $parent, $type, $id, $localKey) + { + return new MorphMany($query, $parent, $type, $id, $localKey); + } + + /** + * Define a many-to-many relationship. + * + * @param string $related + * @param string|null $table + * @param string|null $foreignPivotKey + * @param string|null $relatedPivotKey + * @param string|null $parentKey + * @param string|null $relatedKey + * @param string|null $relation + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + */ + public function belongsToMany($related, $table = null, $foreignPivotKey = null, $relatedPivotKey = null, + $parentKey = null, $relatedKey = null, $relation = null) + { + // If no relationship name was passed, we will pull backtraces to get the + // name of the calling function. We will use that function name as the + // title of this relation since that is a great convention to apply. + if (is_null($relation)) { + $relation = $this->guessBelongsToManyRelation(); + } + + // First, we'll need to determine the foreign key and "other key" for the + // relationship. Once we have determined the keys we'll make the query + // instances as well as the relationship instances we need for this. + $instance = $this->newRelatedInstance($related); + + $foreignPivotKey = $foreignPivotKey ?: $this->getForeignKey(); + + $relatedPivotKey = $relatedPivotKey ?: $instance->getForeignKey(); + + // If no table name was provided, we can guess it by concatenating the two + // models using underscores in alphabetical order. The two model names + // are transformed to snake case from their default CamelCase also. + if (is_null($table)) { + $table = $this->joiningTable($related, $instance); + } + + return $this->newBelongsToMany( + $instance->newQuery(), $this, $table, $foreignPivotKey, + $relatedPivotKey, $parentKey ?: $this->getKeyName(), + $relatedKey ?: $instance->getKeyName(), $relation + ); + } + + /** + * Instantiate a new BelongsToMany relationship. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Model $parent + * @param string $table + * @param string $foreignPivotKey + * @param string $relatedPivotKey + * @param string $parentKey + * @param string $relatedKey + * @param string|null $relationName + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + */ + protected function newBelongsToMany(Builder $query, Model $parent, $table, $foreignPivotKey, $relatedPivotKey, + $parentKey, $relatedKey, $relationName = null) + { + return new BelongsToMany($query, $parent, $table, $foreignPivotKey, $relatedPivotKey, $parentKey, $relatedKey, $relationName); + } + + /** + * Define a polymorphic many-to-many relationship. + * + * @param string $related + * @param string $name + * @param string|null $table + * @param string|null $foreignPivotKey + * @param string|null $relatedPivotKey + * @param string|null $parentKey + * @param string|null $relatedKey + * @param bool $inverse + * @return \Illuminate\Database\Eloquent\Relations\MorphToMany + */ + public function morphToMany($related, $name, $table = null, $foreignPivotKey = null, + $relatedPivotKey = null, $parentKey = null, + $relatedKey = null, $inverse = false) + { + $caller = $this->guessBelongsToManyRelation(); + + // First, we will need to determine the foreign key and "other key" for the + // relationship. Once we have determined the keys we will make the query + // instances, as well as the relationship instances we need for these. + $instance = $this->newRelatedInstance($related); + + $foreignPivotKey = $foreignPivotKey ?: $name.'_id'; + + $relatedPivotKey = $relatedPivotKey ?: $instance->getForeignKey(); + + // Now we're ready to create a new query builder for this related model and + // the relationship instances for this relation. This relations will set + // appropriate query constraints then entirely manages the hydrations. + if (! $table) { + $words = preg_split('/(_)/u', $name, -1, PREG_SPLIT_DELIM_CAPTURE); + + $lastWord = array_pop($words); + + $table = implode('', $words).Str::plural($lastWord); + } + + return $this->newMorphToMany( + $instance->newQuery(), $this, $name, $table, + $foreignPivotKey, $relatedPivotKey, $parentKey ?: $this->getKeyName(), + $relatedKey ?: $instance->getKeyName(), $caller, $inverse + ); + } + + /** + * Instantiate a new MorphToMany relationship. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Model $parent + * @param string $name + * @param string $table + * @param string $foreignPivotKey + * @param string $relatedPivotKey + * @param string $parentKey + * @param string $relatedKey + * @param string|null $relationName + * @param bool $inverse + * @return \Illuminate\Database\Eloquent\Relations\MorphToMany + */ + protected function newMorphToMany(Builder $query, Model $parent, $name, $table, $foreignPivotKey, + $relatedPivotKey, $parentKey, $relatedKey, + $relationName = null, $inverse = false) + { + return new MorphToMany($query, $parent, $name, $table, $foreignPivotKey, $relatedPivotKey, $parentKey, $relatedKey, + $relationName, $inverse); + } + + /** + * Define a polymorphic, inverse many-to-many relationship. + * + * @param string $related + * @param string $name + * @param string|null $table + * @param string|null $foreignPivotKey + * @param string|null $relatedPivotKey + * @param string|null $parentKey + * @param string|null $relatedKey + * @return \Illuminate\Database\Eloquent\Relations\MorphToMany + */ + public function morphedByMany($related, $name, $table = null, $foreignPivotKey = null, + $relatedPivotKey = null, $parentKey = null, $relatedKey = null) + { + $foreignPivotKey = $foreignPivotKey ?: $this->getForeignKey(); + + // For the inverse of the polymorphic many-to-many relations, we will change + // the way we determine the foreign and other keys, as it is the opposite + // of the morph-to-many method since we're figuring out these inverses. + $relatedPivotKey = $relatedPivotKey ?: $name.'_id'; + + return $this->morphToMany( + $related, $name, $table, $foreignPivotKey, + $relatedPivotKey, $parentKey, $relatedKey, true + ); + } + + /** + * Get the relationship name of the belongsToMany relationship. + * + * @return string|null + */ + protected function guessBelongsToManyRelation() + { + $caller = Arr::first(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), function ($trace) { + return ! in_array( + $trace['function'], + array_merge(static::$manyMethods, ['guessBelongsToManyRelation']) + ); + }); + + return ! is_null($caller) ? $caller['function'] : null; + } + + /** + * Get the joining table name for a many-to-many relation. + * + * @param string $related + * @param \Illuminate\Database\Eloquent\Model|null $instance + * @return string + */ + public function joiningTable($related, $instance = null) + { + // The joining table name, by convention, is simply the snake cased models + // sorted alphabetically and concatenated with an underscore, so we can + // just sort the models and join them together to get the table name. + $segments = [ + $instance ? $instance->joiningTableSegment() + : Str::snake(class_basename($related)), + $this->joiningTableSegment(), + ]; + + // Now that we have the model names in an array we can just sort them and + // use the implode function to join them together with an underscores, + // which is typically used by convention within the database system. + sort($segments); + + return strtolower(implode('_', $segments)); + } + + /** + * Get this model's half of the intermediate table name for belongsToMany relationships. + * + * @return string + */ + public function joiningTableSegment() + { + return Str::snake(class_basename($this)); + } + + /** + * Determine if the model touches a given relation. + * + * @param string $relation + * @return bool + */ + public function touches($relation) + { + return in_array($relation, $this->getTouchedRelations()); + } + + /** + * Touch the owning relations of the model. + * + * @return void + */ + public function touchOwners() + { + foreach ($this->getTouchedRelations() as $relation) { + $this->$relation()->touch(); + + if ($this->$relation instanceof self) { + $this->$relation->fireModelEvent('saved', false); + + $this->$relation->touchOwners(); + } elseif ($this->$relation instanceof Collection) { + $this->$relation->each->touchOwners(); + } + } + } + + /** + * Get the polymorphic relationship columns. + * + * @param string $name + * @param string $type + * @param string $id + * @return array + */ + protected function getMorphs($name, $type, $id) + { + return [$type ?: $name.'_type', $id ?: $name.'_id']; + } + + /** + * Get the class name for polymorphic relations. + * + * @return string + */ + public function getMorphClass() + { + $morphMap = Relation::morphMap(); + + if (! empty($morphMap) && in_array(static::class, $morphMap)) { + return array_search(static::class, $morphMap, true); + } + + if (Relation::requiresMorphMap()) { + throw new ClassMorphViolationException($this); + } + + return static::class; + } + + /** + * Create a new model instance for a related model. + * + * @param string $class + * @return mixed + */ + protected function newRelatedInstance($class) + { + return tap(new $class, function ($instance) { + if (! $instance->getConnectionName()) { + $instance->setConnection($this->connection); + } + }); + } + + /** + * Get all the loaded relations for the instance. + * + * @return array + */ + public function getRelations() + { + return $this->relations; + } + + /** + * Get a specified relationship. + * + * @param string $relation + * @return mixed + */ + public function getRelation($relation) + { + return $this->relations[$relation]; + } + + /** + * Determine if the given relation is loaded. + * + * @param string $key + * @return bool + */ + public function relationLoaded($key) + { + return array_key_exists($key, $this->relations); + } + + /** + * Set the given relationship on the model. + * + * @param string $relation + * @param mixed $value + * @return $this + */ + public function setRelation($relation, $value) + { + $this->relations[$relation] = $value; + + return $this; + } + + /** + * Unset a loaded relationship. + * + * @param string $relation + * @return $this + */ + public function unsetRelation($relation) + { + unset($this->relations[$relation]); + + return $this; + } + + /** + * Set the entire relations array on the model. + * + * @param array $relations + * @return $this + */ + public function setRelations(array $relations) + { + $this->relations = $relations; + + return $this; + } + + /** + * Duplicate the instance and unset all the loaded relations. + * + * @return $this + */ + public function withoutRelations() + { + $model = clone $this; + + return $model->unsetRelations(); + } + + /** + * Unset all the loaded relations for the instance. + * + * @return $this + */ + public function unsetRelations() + { + $this->relations = []; + + return $this; + } + + /** + * Get the relationships that are touched on save. + * + * @return array + */ + public function getTouchedRelations() + { + return $this->touches; + } + + /** + * Set the relationships that are touched on save. + * + * @param array $touches + * @return $this + */ + public function setTouchedRelations(array $touches) + { + $this->touches = $touches; + + return $this; + } +} diff --git a/vendor/illuminate/database/Eloquent/Concerns/HasTimestamps.php b/vendor/illuminate/database/Eloquent/Concerns/HasTimestamps.php new file mode 100644 index 0000000..13ebd31 --- /dev/null +++ b/vendor/illuminate/database/Eloquent/Concerns/HasTimestamps.php @@ -0,0 +1,149 @@ +usesTimestamps()) { + return false; + } + + $this->updateTimestamps(); + + return $this->save(); + } + + /** + * Update the creation and update timestamps. + * + * @return void + */ + public function updateTimestamps() + { + $time = $this->freshTimestamp(); + + $updatedAtColumn = $this->getUpdatedAtColumn(); + + if (! is_null($updatedAtColumn) && ! $this->isDirty($updatedAtColumn)) { + $this->setUpdatedAt($time); + } + + $createdAtColumn = $this->getCreatedAtColumn(); + + if (! $this->exists && ! is_null($createdAtColumn) && ! $this->isDirty($createdAtColumn)) { + $this->setCreatedAt($time); + } + } + + /** + * Set the value of the "created at" attribute. + * + * @param mixed $value + * @return $this + */ + public function setCreatedAt($value) + { + $this->{$this->getCreatedAtColumn()} = $value; + + return $this; + } + + /** + * Set the value of the "updated at" attribute. + * + * @param mixed $value + * @return $this + */ + public function setUpdatedAt($value) + { + $this->{$this->getUpdatedAtColumn()} = $value; + + return $this; + } + + /** + * Get a fresh timestamp for the model. + * + * @return \Illuminate\Support\Carbon + */ + public function freshTimestamp() + { + return Date::now(); + } + + /** + * Get a fresh timestamp for the model. + * + * @return string + */ + public function freshTimestampString() + { + return $this->fromDateTime($this->freshTimestamp()); + } + + /** + * Determine if the model uses timestamps. + * + * @return bool + */ + public function usesTimestamps() + { + return $this->timestamps; + } + + /** + * Get the name of the "created at" column. + * + * @return string|null + */ + public function getCreatedAtColumn() + { + return static::CREATED_AT; + } + + /** + * Get the name of the "updated at" column. + * + * @return string|null + */ + public function getUpdatedAtColumn() + { + return static::UPDATED_AT; + } + + /** + * Get the fully qualified "created at" column. + * + * @return string|null + */ + public function getQualifiedCreatedAtColumn() + { + return $this->qualifyColumn($this->getCreatedAtColumn()); + } + + /** + * Get the fully qualified "updated at" column. + * + * @return string|null + */ + public function getQualifiedUpdatedAtColumn() + { + return $this->qualifyColumn($this->getUpdatedAtColumn()); + } +} diff --git a/vendor/illuminate/database/Eloquent/Concerns/HidesAttributes.php b/vendor/illuminate/database/Eloquent/Concerns/HidesAttributes.php new file mode 100644 index 0000000..065d48a --- /dev/null +++ b/vendor/illuminate/database/Eloquent/Concerns/HidesAttributes.php @@ -0,0 +1,126 @@ +hidden; + } + + /** + * Set the hidden attributes for the model. + * + * @param array $hidden + * @return $this + */ + public function setHidden(array $hidden) + { + $this->hidden = $hidden; + + return $this; + } + + /** + * Get the visible attributes for the model. + * + * @return array + */ + public function getVisible() + { + return $this->visible; + } + + /** + * Set the visible attributes for the model. + * + * @param array $visible + * @return $this + */ + public function setVisible(array $visible) + { + $this->visible = $visible; + + return $this; + } + + /** + * Make the given, typically hidden, attributes visible. + * + * @param array|string|null $attributes + * @return $this + */ + public function makeVisible($attributes) + { + $attributes = is_array($attributes) ? $attributes : func_get_args(); + + $this->hidden = array_diff($this->hidden, $attributes); + + if (! empty($this->visible)) { + $this->visible = array_merge($this->visible, $attributes); + } + + return $this; + } + + /** + * Make the given, typically hidden, attributes visible if the given truth test passes. + * + * @param bool|Closure $condition + * @param array|string|null $attributes + * @return $this + */ + public function makeVisibleIf($condition, $attributes) + { + return value($condition, $this) ? $this->makeVisible($attributes) : $this; + } + + /** + * Make the given, typically visible, attributes hidden. + * + * @param array|string|null $attributes + * @return $this + */ + public function makeHidden($attributes) + { + $this->hidden = array_merge( + $this->hidden, is_array($attributes) ? $attributes : func_get_args() + ); + + return $this; + } + + /** + * Make the given, typically visible, attributes hidden if the given truth test passes. + * + * @param bool|Closure $condition + * @param array|string|null $attributes + * @return $this + */ + public function makeHiddenIf($condition, $attributes) + { + return value($condition, $this) ? $this->makeHidden($attributes) : $this; + } +} diff --git a/vendor/illuminate/database/Eloquent/Concerns/QueriesRelationships.php b/vendor/illuminate/database/Eloquent/Concerns/QueriesRelationships.php new file mode 100644 index 0000000..c16af1f --- /dev/null +++ b/vendor/illuminate/database/Eloquent/Concerns/QueriesRelationships.php @@ -0,0 +1,758 @@ +=', $count = 1, $boolean = 'and', Closure $callback = null) + { + if (is_string($relation)) { + if (strpos($relation, '.') !== false) { + return $this->hasNested($relation, $operator, $count, $boolean, $callback); + } + + $relation = $this->getRelationWithoutConstraints($relation); + } + + if ($relation instanceof MorphTo) { + return $this->hasMorph($relation, ['*'], $operator, $count, $boolean, $callback); + } + + // If we only need to check for the existence of the relation, then we can optimize + // the subquery to only run a "where exists" clause instead of this full "count" + // clause. This will make these queries run much faster compared with a count. + $method = $this->canUseExistsForExistenceCheck($operator, $count) + ? 'getRelationExistenceQuery' + : 'getRelationExistenceCountQuery'; + + $hasQuery = $relation->{$method}( + $relation->getRelated()->newQueryWithoutRelationships(), $this + ); + + // Next we will call any given callback as an "anonymous" scope so they can get the + // proper logical grouping of the where clauses if needed by this Eloquent query + // builder. Then, we will be ready to finalize and return this query instance. + if ($callback) { + $hasQuery->callScope($callback); + } + + return $this->addHasWhere( + $hasQuery, $relation, $operator, $count, $boolean + ); + } + + /** + * Add nested relationship count / exists conditions to the query. + * + * Sets up recursive call to whereHas until we finish the nested relation. + * + * @param string $relations + * @param string $operator + * @param int $count + * @param string $boolean + * @param \Closure|null $callback + * @return \Illuminate\Database\Eloquent\Builder|static + */ + protected function hasNested($relations, $operator = '>=', $count = 1, $boolean = 'and', $callback = null) + { + $relations = explode('.', $relations); + + $doesntHave = $operator === '<' && $count === 1; + + if ($doesntHave) { + $operator = '>='; + $count = 1; + } + + $closure = function ($q) use (&$closure, &$relations, $operator, $count, $callback) { + // In order to nest "has", we need to add count relation constraints on the + // callback Closure. We'll do this by simply passing the Closure its own + // reference to itself so it calls itself recursively on each segment. + count($relations) > 1 + ? $q->whereHas(array_shift($relations), $closure) + : $q->has(array_shift($relations), $operator, $count, 'and', $callback); + }; + + return $this->has(array_shift($relations), $doesntHave ? '<' : '>=', 1, $boolean, $closure); + } + + /** + * Add a relationship count / exists condition to the query with an "or". + * + * @param string $relation + * @param string $operator + * @param int $count + * @return \Illuminate\Database\Eloquent\Builder|static + */ + public function orHas($relation, $operator = '>=', $count = 1) + { + return $this->has($relation, $operator, $count, 'or'); + } + + /** + * Add a relationship count / exists condition to the query. + * + * @param string $relation + * @param string $boolean + * @param \Closure|null $callback + * @return \Illuminate\Database\Eloquent\Builder|static + */ + public function doesntHave($relation, $boolean = 'and', Closure $callback = null) + { + return $this->has($relation, '<', 1, $boolean, $callback); + } + + /** + * Add a relationship count / exists condition to the query with an "or". + * + * @param string $relation + * @return \Illuminate\Database\Eloquent\Builder|static + */ + public function orDoesntHave($relation) + { + return $this->doesntHave($relation, 'or'); + } + + /** + * Add a relationship count / exists condition to the query with where clauses. + * + * @param string $relation + * @param \Closure|null $callback + * @param string $operator + * @param int $count + * @return \Illuminate\Database\Eloquent\Builder|static + */ + public function whereHas($relation, Closure $callback = null, $operator = '>=', $count = 1) + { + return $this->has($relation, $operator, $count, 'and', $callback); + } + + /** + * Add a relationship count / exists condition to the query with where clauses and an "or". + * + * @param string $relation + * @param \Closure|null $callback + * @param string $operator + * @param int $count + * @return \Illuminate\Database\Eloquent\Builder|static + */ + public function orWhereHas($relation, Closure $callback = null, $operator = '>=', $count = 1) + { + return $this->has($relation, $operator, $count, 'or', $callback); + } + + /** + * Add a relationship count / exists condition to the query with where clauses. + * + * @param string $relation + * @param \Closure|null $callback + * @return \Illuminate\Database\Eloquent\Builder|static + */ + public function whereDoesntHave($relation, Closure $callback = null) + { + return $this->doesntHave($relation, 'and', $callback); + } + + /** + * Add a relationship count / exists condition to the query with where clauses and an "or". + * + * @param string $relation + * @param \Closure|null $callback + * @return \Illuminate\Database\Eloquent\Builder|static + */ + public function orWhereDoesntHave($relation, Closure $callback = null) + { + return $this->doesntHave($relation, 'or', $callback); + } + + /** + * Add a polymorphic relationship count / exists condition to the query. + * + * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation + * @param string|array $types + * @param string $operator + * @param int $count + * @param string $boolean + * @param \Closure|null $callback + * @return \Illuminate\Database\Eloquent\Builder|static + */ + public function hasMorph($relation, $types, $operator = '>=', $count = 1, $boolean = 'and', Closure $callback = null) + { + if (is_string($relation)) { + $relation = $this->getRelationWithoutConstraints($relation); + } + + $types = (array) $types; + + if ($types === ['*']) { + $types = $this->model->newModelQuery()->distinct()->pluck($relation->getMorphType())->filter()->all(); + } + + foreach ($types as &$type) { + $type = Relation::getMorphedModel($type) ?? $type; + } + + return $this->where(function ($query) use ($relation, $callback, $operator, $count, $types) { + foreach ($types as $type) { + $query->orWhere(function ($query) use ($relation, $callback, $operator, $count, $type) { + $belongsTo = $this->getBelongsToRelation($relation, $type); + + if ($callback) { + $callback = function ($query) use ($callback, $type) { + return $callback($query, $type); + }; + } + + $query->where($this->qualifyColumn($relation->getMorphType()), '=', (new $type)->getMorphClass()) + ->whereHas($belongsTo, $callback, $operator, $count); + }); + } + }, null, null, $boolean); + } + + /** + * Get the BelongsTo relationship for a single polymorphic type. + * + * @param \Illuminate\Database\Eloquent\Relations\MorphTo $relation + * @param string $type + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + protected function getBelongsToRelation(MorphTo $relation, $type) + { + $belongsTo = Relation::noConstraints(function () use ($relation, $type) { + return $this->model->belongsTo( + $type, + $relation->getForeignKeyName(), + $relation->getOwnerKeyName() + ); + }); + + $belongsTo->getQuery()->mergeConstraintsFrom($relation->getQuery()); + + return $belongsTo; + } + + /** + * Add a polymorphic relationship count / exists condition to the query with an "or". + * + * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation + * @param string|array $types + * @param string $operator + * @param int $count + * @return \Illuminate\Database\Eloquent\Builder|static + */ + public function orHasMorph($relation, $types, $operator = '>=', $count = 1) + { + return $this->hasMorph($relation, $types, $operator, $count, 'or'); + } + + /** + * Add a polymorphic relationship count / exists condition to the query. + * + * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation + * @param string|array $types + * @param string $boolean + * @param \Closure|null $callback + * @return \Illuminate\Database\Eloquent\Builder|static + */ + public function doesntHaveMorph($relation, $types, $boolean = 'and', Closure $callback = null) + { + return $this->hasMorph($relation, $types, '<', 1, $boolean, $callback); + } + + /** + * Add a polymorphic relationship count / exists condition to the query with an "or". + * + * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation + * @param string|array $types + * @return \Illuminate\Database\Eloquent\Builder|static + */ + public function orDoesntHaveMorph($relation, $types) + { + return $this->doesntHaveMorph($relation, $types, 'or'); + } + + /** + * Add a polymorphic relationship count / exists condition to the query with where clauses. + * + * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation + * @param string|array $types + * @param \Closure|null $callback + * @param string $operator + * @param int $count + * @return \Illuminate\Database\Eloquent\Builder|static + */ + public function whereHasMorph($relation, $types, Closure $callback = null, $operator = '>=', $count = 1) + { + return $this->hasMorph($relation, $types, $operator, $count, 'and', $callback); + } + + /** + * Add a polymorphic relationship count / exists condition to the query with where clauses and an "or". + * + * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation + * @param string|array $types + * @param \Closure|null $callback + * @param string $operator + * @param int $count + * @return \Illuminate\Database\Eloquent\Builder|static + */ + public function orWhereHasMorph($relation, $types, Closure $callback = null, $operator = '>=', $count = 1) + { + return $this->hasMorph($relation, $types, $operator, $count, 'or', $callback); + } + + /** + * Add a polymorphic relationship count / exists condition to the query with where clauses. + * + * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation + * @param string|array $types + * @param \Closure|null $callback + * @return \Illuminate\Database\Eloquent\Builder|static + */ + public function whereDoesntHaveMorph($relation, $types, Closure $callback = null) + { + return $this->doesntHaveMorph($relation, $types, 'and', $callback); + } + + /** + * Add a polymorphic relationship count / exists condition to the query with where clauses and an "or". + * + * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation + * @param string|array $types + * @param \Closure|null $callback + * @return \Illuminate\Database\Eloquent\Builder|static + */ + public function orWhereDoesntHaveMorph($relation, $types, Closure $callback = null) + { + return $this->doesntHaveMorph($relation, $types, 'or', $callback); + } + + /** + * Add a basic where clause to a relationship query. + * + * @param string $relation + * @param \Closure|string|array|\Illuminate\Database\Query\Expression $column + * @param mixed $operator + * @param mixed $value + * @return \Illuminate\Database\Eloquent\Builder|static + */ + public function whereRelation($relation, $column, $operator = null, $value = null) + { + return $this->whereHas($relation, function ($query) use ($column, $operator, $value) { + $query->where($column, $operator, $value); + }); + } + + /** + * Add an "or where" clause to a relationship query. + * + * @param string $relation + * @param \Closure|string|array|\Illuminate\Database\Query\Expression $column + * @param mixed $operator + * @param mixed $value + * @return \Illuminate\Database\Eloquent\Builder|static + */ + public function orWhereRelation($relation, $column, $operator = null, $value = null) + { + return $this->orWhereHas($relation, function ($query) use ($column, $operator, $value) { + $query->where($column, $operator, $value); + }); + } + + /** + * Add a polymorphic relationship condition to the query with a where clause. + * + * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation + * @param string|array $types + * @param \Closure|string|array|\Illuminate\Database\Query\Expression $column + * @param mixed $operator + * @param mixed $value + * @return \Illuminate\Database\Eloquent\Builder|static + */ + public function whereMorphRelation($relation, $types, $column, $operator = null, $value = null) + { + return $this->whereHasMorph($relation, $types, function ($query) use ($column, $operator, $value) { + $query->where($column, $operator, $value); + }); + } + + /** + * Add a polymorphic relationship condition to the query with an "or where" clause. + * + * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation + * @param string|array $types + * @param \Closure|string|array|\Illuminate\Database\Query\Expression $column + * @param mixed $operator + * @param mixed $value + * @return \Illuminate\Database\Eloquent\Builder|static + */ + public function orWhereMorphRelation($relation, $types, $column, $operator = null, $value = null) + { + return $this->orWhereHasMorph($relation, $types, function ($query) use ($column, $operator, $value) { + $query->where($column, $operator, $value); + }); + } + + /** + * Add a morph-to relationship condition to the query. + * + * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation + * @param \Illuminate\Database\Eloquent\Model|string $model + * @return \Illuminate\Database\Eloquent\Builder|static + */ + public function whereMorphedTo($relation, $model, $boolean = 'and') + { + if (is_string($relation)) { + $relation = $this->getRelationWithoutConstraints($relation); + } + + if (is_string($model)) { + $morphMap = Relation::morphMap(); + + if (! empty($morphMap) && in_array($model, $morphMap)) { + $model = array_search($model, $morphMap, true); + } + + return $this->where($relation->getMorphType(), $model, null, $boolean); + } + + return $this->where(function ($query) use ($relation, $model) { + $query->where($relation->getMorphType(), $model->getMorphClass()) + ->where($relation->getForeignKeyName(), $model->getKey()); + }, null, null, $boolean); + } + + /** + * Add a morph-to relationship condition to the query with an "or where" clause. + * + * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation + * @param \Illuminate\Database\Eloquent\Model|string $model + * @return \Illuminate\Database\Eloquent\Builder|static + */ + public function orWhereMorphedTo($relation, $model) + { + return $this->whereMorphedTo($relation, $model, 'or'); + } + + /** + * Add a "belongs to" relationship where clause to the query. + * + * @param \Illuminate\Database\Eloquent\Model $related + * @param string $relationship + * @param string $boolean + * @return $this + * + * @throws \RuntimeException + */ + public function whereBelongsTo($related, $relationshipName = null, $boolean = 'and') + { + if ($relationshipName === null) { + $relationshipName = Str::camel(class_basename($related)); + } + + try { + $relationship = $this->model->{$relationshipName}(); + } catch (BadMethodCallException $exception) { + throw RelationNotFoundException::make($this->model, $relationshipName); + } + + if (! $relationship instanceof BelongsTo) { + throw RelationNotFoundException::make($this->model, $relationshipName, BelongsTo::class); + } + + $this->where( + $relationship->getQualifiedForeignKeyName(), + '=', + $related->getAttributeValue($relationship->getOwnerKeyName()), + $boolean, + ); + + return $this; + } + + /** + * Add an "BelongsTo" relationship with an "or where" clause to the query. + * + * @param \Illuminate\Database\Eloquent\Model $related + * @param string $relationship + * @return $this + * + * @throws \RuntimeException + */ + public function orWhereBelongsTo($related, $relationshipName = null) + { + return $this->whereBelongsTo($related, $relationshipName, 'or'); + } + + /** + * Add subselect queries to include an aggregate value for a relationship. + * + * @param mixed $relations + * @param string $column + * @param string $function + * @return $this + */ + public function withAggregate($relations, $column, $function = null) + { + if (empty($relations)) { + return $this; + } + + if (is_null($this->query->columns)) { + $this->query->select([$this->query->from.'.*']); + } + + $relations = is_array($relations) ? $relations : [$relations]; + + foreach ($this->parseWithRelations($relations) as $name => $constraints) { + // First we will determine if the name has been aliased using an "as" clause on the name + // and if it has we will extract the actual relationship name and the desired name of + // the resulting column. This allows multiple aggregates on the same relationships. + $segments = explode(' ', $name); + + unset($alias); + + if (count($segments) === 3 && Str::lower($segments[1]) === 'as') { + [$name, $alias] = [$segments[0], $segments[2]]; + } + + $relation = $this->getRelationWithoutConstraints($name); + + if ($function) { + $hashedColumn = $this->getQuery()->from === $relation->getQuery()->getQuery()->from + ? "{$relation->getRelationCountHash(false)}.$column" + : $column; + + $wrappedColumn = $this->getQuery()->getGrammar()->wrap( + $column === '*' ? $column : $relation->getRelated()->qualifyColumn($hashedColumn) + ); + + $expression = $function === 'exists' ? $wrappedColumn : sprintf('%s(%s)', $function, $wrappedColumn); + } else { + $expression = $column; + } + + // Here, we will grab the relationship sub-query and prepare to add it to the main query + // as a sub-select. First, we'll get the "has" query and use that to get the relation + // sub-query. We'll format this relationship name and append this column if needed. + $query = $relation->getRelationExistenceQuery( + $relation->getRelated()->newQuery(), $this, new Expression($expression) + )->setBindings([], 'select'); + + $query->callScope($constraints); + + $query = $query->mergeConstraintsFrom($relation->getQuery())->toBase(); + + // If the query contains certain elements like orderings / more than one column selected + // then we will remove those elements from the query so that it will execute properly + // when given to the database. Otherwise, we may receive SQL errors or poor syntax. + $query->orders = null; + $query->setBindings([], 'order'); + + if (count($query->columns) > 1) { + $query->columns = [$query->columns[0]]; + $query->bindings['select'] = []; + } + + // Finally, we will make the proper column alias to the query and run this sub-select on + // the query builder. Then, we will return the builder instance back to the developer + // for further constraint chaining that needs to take place on the query as needed. + $alias = $alias ?? Str::snake( + preg_replace('/[^[:alnum:][:space:]_]/u', '', "$name $function $column") + ); + + if ($function === 'exists') { + $this->selectRaw( + sprintf('exists(%s) as %s', $query->toSql(), $this->getQuery()->grammar->wrap($alias)), + $query->getBindings() + )->withCasts([$alias => 'bool']); + } else { + $this->selectSub( + $function ? $query : $query->limit(1), + $alias + ); + } + } + + return $this; + } + + /** + * Add subselect queries to count the relations. + * + * @param mixed $relations + * @return $this + */ + public function withCount($relations) + { + return $this->withAggregate(is_array($relations) ? $relations : func_get_args(), '*', 'count'); + } + + /** + * Add subselect queries to include the max of the relation's column. + * + * @param string|array $relation + * @param string $column + * @return $this + */ + public function withMax($relation, $column) + { + return $this->withAggregate($relation, $column, 'max'); + } + + /** + * Add subselect queries to include the min of the relation's column. + * + * @param string|array $relation + * @param string $column + * @return $this + */ + public function withMin($relation, $column) + { + return $this->withAggregate($relation, $column, 'min'); + } + + /** + * Add subselect queries to include the sum of the relation's column. + * + * @param string|array $relation + * @param string $column + * @return $this + */ + public function withSum($relation, $column) + { + return $this->withAggregate($relation, $column, 'sum'); + } + + /** + * Add subselect queries to include the average of the relation's column. + * + * @param string|array $relation + * @param string $column + * @return $this + */ + public function withAvg($relation, $column) + { + return $this->withAggregate($relation, $column, 'avg'); + } + + /** + * Add subselect queries to include the existence of related models. + * + * @param string|array $relation + * @return $this + */ + public function withExists($relation) + { + return $this->withAggregate($relation, '*', 'exists'); + } + + /** + * Add the "has" condition where clause to the query. + * + * @param \Illuminate\Database\Eloquent\Builder $hasQuery + * @param \Illuminate\Database\Eloquent\Relations\Relation $relation + * @param string $operator + * @param int $count + * @param string $boolean + * @return \Illuminate\Database\Eloquent\Builder|static + */ + protected function addHasWhere(Builder $hasQuery, Relation $relation, $operator, $count, $boolean) + { + $hasQuery->mergeConstraintsFrom($relation->getQuery()); + + return $this->canUseExistsForExistenceCheck($operator, $count) + ? $this->addWhereExistsQuery($hasQuery->toBase(), $boolean, $operator === '<' && $count === 1) + : $this->addWhereCountQuery($hasQuery->toBase(), $operator, $count, $boolean); + } + + /** + * Merge the where constraints from another query to the current query. + * + * @param \Illuminate\Database\Eloquent\Builder $from + * @return \Illuminate\Database\Eloquent\Builder|static + */ + public function mergeConstraintsFrom(Builder $from) + { + $whereBindings = $from->getQuery()->getRawBindings()['where'] ?? []; + + // Here we have some other query that we want to merge the where constraints from. We will + // copy over any where constraints on the query as well as remove any global scopes the + // query might have removed. Then we will return ourselves with the finished merging. + return $this->withoutGlobalScopes( + $from->removedScopes() + )->mergeWheres( + $from->getQuery()->wheres, $whereBindings + ); + } + + /** + * Add a sub-query count clause to this query. + * + * @param \Illuminate\Database\Query\Builder $query + * @param string $operator + * @param int $count + * @param string $boolean + * @return $this + */ + protected function addWhereCountQuery(QueryBuilder $query, $operator = '>=', $count = 1, $boolean = 'and') + { + $this->query->addBinding($query->getBindings(), 'where'); + + return $this->where( + new Expression('('.$query->toSql().')'), + $operator, + is_numeric($count) ? new Expression($count) : $count, + $boolean + ); + } + + /** + * Get the "has relation" base query instance. + * + * @param string $relation + * @return \Illuminate\Database\Eloquent\Relations\Relation + */ + protected function getRelationWithoutConstraints($relation) + { + return Relation::noConstraints(function () use ($relation) { + return $this->getModel()->{$relation}(); + }); + } + + /** + * Check if we can run an "exists" query to optimize performance. + * + * @param string $operator + * @param int $count + * @return bool + */ + protected function canUseExistsForExistenceCheck($operator, $count) + { + return ($operator === '>=' || $operator === '<') && $count === 1; + } +} diff --git a/vendor/illuminate/database/Eloquent/Factories/BelongsToManyRelationship.php b/vendor/illuminate/database/Eloquent/Factories/BelongsToManyRelationship.php new file mode 100644 index 0000000..e0c42c4 --- /dev/null +++ b/vendor/illuminate/database/Eloquent/Factories/BelongsToManyRelationship.php @@ -0,0 +1,61 @@ +factory = $factory; + $this->pivot = $pivot; + $this->relationship = $relationship; + } + + /** + * Create the attached relationship for the given model. + * + * @param \Illuminate\Database\Eloquent\Model $model + * @return void + */ + public function createFor(Model $model) + { + Collection::wrap($this->factory instanceof Factory ? $this->factory->create([], $model) : $this->factory)->each(function ($attachable) use ($model) { + $model->{$this->relationship}()->attach( + $attachable, + is_callable($this->pivot) ? call_user_func($this->pivot, $model) : $this->pivot + ); + }); + } +} diff --git a/vendor/illuminate/database/Eloquent/Factories/BelongsToRelationship.php b/vendor/illuminate/database/Eloquent/Factories/BelongsToRelationship.php new file mode 100644 index 0000000..55747fd --- /dev/null +++ b/vendor/illuminate/database/Eloquent/Factories/BelongsToRelationship.php @@ -0,0 +1,80 @@ +factory = $factory; + $this->relationship = $relationship; + } + + /** + * Get the parent model attributes and resolvers for the given child model. + * + * @param \Illuminate\Database\Eloquent\Model $model + * @return array + */ + public function attributesFor(Model $model) + { + $relationship = $model->{$this->relationship}(); + + return $relationship instanceof MorphTo ? [ + $relationship->getMorphType() => $this->factory instanceof Factory ? $this->factory->newModel()->getMorphClass() : $this->factory->getMorphClass(), + $relationship->getForeignKeyName() => $this->resolver($relationship->getOwnerKeyName()), + ] : [ + $relationship->getForeignKeyName() => $this->resolver($relationship->getOwnerKeyName()), + ]; + } + + /** + * Get the deferred resolver for this relationship's parent ID. + * + * @param string|null $key + * @return \Closure + */ + protected function resolver($key) + { + return function () use ($key) { + if (! $this->resolved) { + $instance = $this->factory instanceof Factory ? $this->factory->create() : $this->factory; + + return $this->resolved = $key ? $instance->{$key} : $instance->getKey(); + } + + return $this->resolved; + }; + } +} diff --git a/vendor/illuminate/database/Eloquent/Factories/CrossJoinSequence.php b/vendor/illuminate/database/Eloquent/Factories/CrossJoinSequence.php new file mode 100644 index 0000000..b0efbd0 --- /dev/null +++ b/vendor/illuminate/database/Eloquent/Factories/CrossJoinSequence.php @@ -0,0 +1,26 @@ +count = $count; + $this->states = $states ?: new Collection; + $this->has = $has ?: new Collection; + $this->for = $for ?: new Collection; + $this->afterMaking = $afterMaking ?: new Collection; + $this->afterCreating = $afterCreating ?: new Collection; + $this->connection = $connection; + $this->faker = $this->withFaker(); + } + + /** + * Define the model's default state. + * + * @return array + */ + abstract public function definition(); + + /** + * Get a new factory instance for the given attributes. + * + * @param callable|array $attributes + * @return static + */ + public static function new($attributes = []) + { + return (new static)->state($attributes)->configure(); + } + + /** + * Get a new factory instance for the given number of models. + * + * @param int $count + * @return static + */ + public static function times(int $count) + { + return static::new()->count($count); + } + + /** + * Configure the factory. + * + * @return $this + */ + public function configure() + { + return $this; + } + + /** + * Get the raw attributes generated by the factory. + * + * @param array $attributes + * @param \Illuminate\Database\Eloquent\Model|null $parent + * @return array + */ + public function raw($attributes = [], ?Model $parent = null) + { + if ($this->count === null) { + return $this->state($attributes)->getExpandedAttributes($parent); + } + + return array_map(function () use ($attributes, $parent) { + return $this->state($attributes)->getExpandedAttributes($parent); + }, range(1, $this->count)); + } + + /** + * Create a single model and persist it to the database. + * + * @param array $attributes + * @return \Illuminate\Database\Eloquent\Model + */ + public function createOne($attributes = []) + { + return $this->count(null)->create($attributes); + } + + /** + * Create a single model and persist it to the database. + * + * @param array $attributes + * @return \Illuminate\Database\Eloquent\Model + */ + public function createOneQuietly($attributes = []) + { + return $this->count(null)->createQuietly($attributes); + } + + /** + * Create a collection of models and persist them to the database. + * + * @param iterable $records + * @return \Illuminate\Database\Eloquent\Collection + */ + public function createMany(iterable $records) + { + return new EloquentCollection( + collect($records)->map(function ($record) { + return $this->state($record)->create(); + }) + ); + } + + /** + * Create a collection of models and persist them to the database. + * + * @param iterable $records + * @return \Illuminate\Database\Eloquent\Collection + */ + public function createManyQuietly(iterable $records) + { + return Model::withoutEvents(function () use ($records) { + return $this->createMany($records); + }); + } + + /** + * Create a collection of models and persist them to the database. + * + * @param array $attributes + * @param \Illuminate\Database\Eloquent\Model|null $parent + * @return \Illuminate\Database\Eloquent\Collection|\Illuminate\Database\Eloquent\Model + */ + public function create($attributes = [], ?Model $parent = null) + { + if (! empty($attributes)) { + return $this->state($attributes)->create([], $parent); + } + + $results = $this->make($attributes, $parent); + + if ($results instanceof Model) { + $this->store(collect([$results])); + + $this->callAfterCreating(collect([$results]), $parent); + } else { + $this->store($results); + + $this->callAfterCreating($results, $parent); + } + + return $results; + } + + /** + * Create a collection of models and persist them to the database. + * + * @param array $attributes + * @param \Illuminate\Database\Eloquent\Model|null $parent + * @return \Illuminate\Database\Eloquent\Collection|\Illuminate\Database\Eloquent\Model + */ + public function createQuietly($attributes = [], ?Model $parent = null) + { + return Model::withoutEvents(function () use ($attributes, $parent) { + return $this->create($attributes, $parent); + }); + } + + /** + * Create a callback that persists a model in the database when invoked. + * + * @param array $attributes + * @param \Illuminate\Database\Eloquent\Model|null $parent + * @return \Closure + */ + public function lazy(array $attributes = [], ?Model $parent = null) + { + return function () use ($attributes, $parent) { + return $this->create($attributes, $parent); + }; + } + + /** + * Set the connection name on the results and store them. + * + * @param \Illuminate\Support\Collection $results + * @return void + */ + protected function store(Collection $results) + { + $results->each(function ($model) { + if (! isset($this->connection)) { + $model->setConnection($model->newQueryWithoutScopes()->getConnection()->getName()); + } + + $model->save(); + + $this->createChildren($model); + }); + } + + /** + * Create the children for the given model. + * + * @param \Illuminate\Database\Eloquent\Model $model + * @return void + */ + protected function createChildren(Model $model) + { + Model::unguarded(function () use ($model) { + $this->has->each(function ($has) use ($model) { + $has->createFor($model); + }); + }); + } + + /** + * Make a single instance of the model. + * + * @param callable|array $attributes + * @return \Illuminate\Database\Eloquent\Model + */ + public function makeOne($attributes = []) + { + return $this->count(null)->make($attributes); + } + + /** + * Create a collection of models. + * + * @param array $attributes + * @param \Illuminate\Database\Eloquent\Model|null $parent + * @return \Illuminate\Database\Eloquent\Collection|\Illuminate\Database\Eloquent\Model + */ + public function make($attributes = [], ?Model $parent = null) + { + if (! empty($attributes)) { + return $this->state($attributes)->make([], $parent); + } + + if ($this->count === null) { + return tap($this->makeInstance($parent), function ($instance) { + $this->callAfterMaking(collect([$instance])); + }); + } + + if ($this->count < 1) { + return $this->newModel()->newCollection(); + } + + $instances = $this->newModel()->newCollection(array_map(function () use ($parent) { + return $this->makeInstance($parent); + }, range(1, $this->count))); + + $this->callAfterMaking($instances); + + return $instances; + } + + /** + * Make an instance of the model with the given attributes. + * + * @param \Illuminate\Database\Eloquent\Model|null $parent + * @return \Illuminate\Database\Eloquent\Model + */ + protected function makeInstance(?Model $parent) + { + return Model::unguarded(function () use ($parent) { + return tap($this->newModel($this->getExpandedAttributes($parent)), function ($instance) { + if (isset($this->connection)) { + $instance->setConnection($this->connection); + } + }); + }); + } + + /** + * Get a raw attributes array for the model. + * + * @param \Illuminate\Database\Eloquent\Model|null $parent + * @return mixed + */ + protected function getExpandedAttributes(?Model $parent) + { + return $this->expandAttributes($this->getRawAttributes($parent)); + } + + /** + * Get the raw attributes for the model as an array. + * + * @param \Illuminate\Database\Eloquent\Model|null $parent + * @return array + */ + protected function getRawAttributes(?Model $parent) + { + return $this->states->pipe(function ($states) { + return $this->for->isEmpty() ? $states : new Collection(array_merge([function () { + return $this->parentResolvers(); + }], $states->all())); + })->reduce(function ($carry, $state) use ($parent) { + if ($state instanceof Closure) { + $state = $state->bindTo($this); + } + + return array_merge($carry, $state($carry, $parent)); + }, $this->definition()); + } + + /** + * Create the parent relationship resolvers (as deferred Closures). + * + * @return array + */ + protected function parentResolvers() + { + $model = $this->newModel(); + + return $this->for->map(function (BelongsToRelationship $for) use ($model) { + return $for->attributesFor($model); + })->collapse()->all(); + } + + /** + * Expand all attributes to their underlying values. + * + * @param array $definition + * @return array + */ + protected function expandAttributes(array $definition) + { + return collect($definition)->map(function ($attribute, $key) use (&$definition) { + if (is_callable($attribute) && ! is_string($attribute) && ! is_array($attribute)) { + $attribute = $attribute($definition); + } + + if ($attribute instanceof self) { + $attribute = $attribute->create()->getKey(); + } elseif ($attribute instanceof Model) { + $attribute = $attribute->getKey(); + } + + $definition[$key] = $attribute; + + return $attribute; + })->all(); + } + + /** + * Add a new state transformation to the model definition. + * + * @param callable|array $state + * @return static + */ + public function state($state) + { + return $this->newInstance([ + 'states' => $this->states->concat([ + is_callable($state) ? $state : function () use ($state) { + return $state; + }, + ]), + ]); + } + + /** + * Add a new sequenced state transformation to the model definition. + * + * @param array $sequence + * @return static + */ + public function sequence(...$sequence) + { + return $this->state(new Sequence(...$sequence)); + } + + /** + * Add a new cross joined sequenced state transformation to the model definition. + * + * @param array $sequence + * @return static + */ + public function crossJoinSequence(...$sequence) + { + return $this->state(new CrossJoinSequence(...$sequence)); + } + + /** + * Define a child relationship for the model. + * + * @param \Illuminate\Database\Eloquent\Factories\Factory $factory + * @param string|null $relationship + * @return static + */ + public function has(self $factory, $relationship = null) + { + return $this->newInstance([ + 'has' => $this->has->concat([new Relationship( + $factory, $relationship ?: $this->guessRelationship($factory->modelName()) + )]), + ]); + } + + /** + * Attempt to guess the relationship name for a "has" relationship. + * + * @param string $related + * @return string + */ + protected function guessRelationship(string $related) + { + $guess = Str::camel(Str::plural(class_basename($related))); + + return method_exists($this->modelName(), $guess) ? $guess : Str::singular($guess); + } + + /** + * Define an attached relationship for the model. + * + * @param \Illuminate\Database\Eloquent\Factories\Factory|\Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model $factory + * @param callable|array $pivot + * @param string|null $relationship + * @return static + */ + public function hasAttached($factory, $pivot = [], $relationship = null) + { + return $this->newInstance([ + 'has' => $this->has->concat([new BelongsToManyRelationship( + $factory, + $pivot, + $relationship ?: Str::camel(Str::plural(class_basename( + $factory instanceof Factory + ? $factory->modelName() + : Collection::wrap($factory)->first() + ))) + )]), + ]); + } + + /** + * Define a parent relationship for the model. + * + * @param \Illuminate\Database\Eloquent\Factories\Factory|\Illuminate\Database\Eloquent\Model $factory + * @param string|null $relationship + * @return static + */ + public function for($factory, $relationship = null) + { + return $this->newInstance(['for' => $this->for->concat([new BelongsToRelationship( + $factory, + $relationship ?: Str::camel(class_basename( + $factory instanceof Factory ? $factory->modelName() : $factory + )) + )])]); + } + + /** + * Add a new "after making" callback to the model definition. + * + * @param \Closure $callback + * @return static + */ + public function afterMaking(Closure $callback) + { + return $this->newInstance(['afterMaking' => $this->afterMaking->concat([$callback])]); + } + + /** + * Add a new "after creating" callback to the model definition. + * + * @param \Closure $callback + * @return static + */ + public function afterCreating(Closure $callback) + { + return $this->newInstance(['afterCreating' => $this->afterCreating->concat([$callback])]); + } + + /** + * Call the "after making" callbacks for the given model instances. + * + * @param \Illuminate\Support\Collection $instances + * @return void + */ + protected function callAfterMaking(Collection $instances) + { + $instances->each(function ($model) { + $this->afterMaking->each(function ($callback) use ($model) { + $callback($model); + }); + }); + } + + /** + * Call the "after creating" callbacks for the given model instances. + * + * @param \Illuminate\Support\Collection $instances + * @param \Illuminate\Database\Eloquent\Model|null $parent + * @return void + */ + protected function callAfterCreating(Collection $instances, ?Model $parent = null) + { + $instances->each(function ($model) use ($parent) { + $this->afterCreating->each(function ($callback) use ($model, $parent) { + $callback($model, $parent); + }); + }); + } + + /** + * Specify how many models should be generated. + * + * @param int|null $count + * @return static + */ + public function count(?int $count) + { + return $this->newInstance(['count' => $count]); + } + + /** + * Specify the database connection that should be used to generate models. + * + * @param string $connection + * @return static + */ + public function connection(string $connection) + { + return $this->newInstance(['connection' => $connection]); + } + + /** + * Create a new instance of the factory builder with the given mutated properties. + * + * @param array $arguments + * @return static + */ + protected function newInstance(array $arguments = []) + { + return new static(...array_values(array_merge([ + 'count' => $this->count, + 'states' => $this->states, + 'has' => $this->has, + 'for' => $this->for, + 'afterMaking' => $this->afterMaking, + 'afterCreating' => $this->afterCreating, + 'connection' => $this->connection, + ], $arguments))); + } + + /** + * Get a new model instance. + * + * @param array $attributes + * @return \Illuminate\Database\Eloquent\Model + */ + public function newModel(array $attributes = []) + { + $model = $this->modelName(); + + return new $model($attributes); + } + + /** + * Get the name of the model that is generated by the factory. + * + * @return string + */ + public function modelName() + { + $resolver = static::$modelNameResolver ?: function (self $factory) { + $namespacedFactoryBasename = Str::replaceLast( + 'Factory', '', Str::replaceFirst(static::$namespace, '', get_class($factory)) + ); + + $factoryBasename = Str::replaceLast('Factory', '', class_basename($factory)); + + $appNamespace = static::appNamespace(); + + return class_exists($appNamespace.'Models\\'.$namespacedFactoryBasename) + ? $appNamespace.'Models\\'.$namespacedFactoryBasename + : $appNamespace.$factoryBasename; + }; + + return $this->model ?: $resolver($this); + } + + /** + * Specify the callback that should be invoked to guess model names based on factory names. + * + * @param callable $callback + * @return void + */ + public static function guessModelNamesUsing(callable $callback) + { + static::$modelNameResolver = $callback; + } + + /** + * Specify the default namespace that contains the application's model factories. + * + * @param string $namespace + * @return void + */ + public static function useNamespace(string $namespace) + { + static::$namespace = $namespace; + } + + /** + * Get a new factory instance for the given model name. + * + * @param string $modelName + * @return static + */ + public static function factoryForModel(string $modelName) + { + $factory = static::resolveFactoryName($modelName); + + return $factory::new(); + } + + /** + * Specify the callback that should be invoked to guess factory names based on dynamic relationship names. + * + * @param callable $callback + * @return void + */ + public static function guessFactoryNamesUsing(callable $callback) + { + static::$factoryNameResolver = $callback; + } + + /** + * Get a new Faker instance. + * + * @return \Faker\Generator + */ + protected function withFaker() + { + return Container::getInstance()->make(Generator::class); + } + + /** + * Get the factory name for the given model name. + * + * @param string $modelName + * @return string + */ + public static function resolveFactoryName(string $modelName) + { + $resolver = static::$factoryNameResolver ?: function (string $modelName) { + $appNamespace = static::appNamespace(); + + $modelName = Str::startsWith($modelName, $appNamespace.'Models\\') + ? Str::after($modelName, $appNamespace.'Models\\') + : Str::after($modelName, $appNamespace); + + return static::$namespace.$modelName.'Factory'; + }; + + return $resolver($modelName); + } + + /** + * Get the application namespace for the application. + * + * @return string + */ + protected static function appNamespace() + { + try { + return Container::getInstance() + ->make(Application::class) + ->getNamespace(); + } catch (Throwable $e) { + return 'App\\'; + } + } + + /** + * Proxy dynamic factory methods onto their proper methods. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + if (static::hasMacro($method)) { + return $this->macroCall($method, $parameters); + } + + if (! Str::startsWith($method, ['for', 'has'])) { + static::throwBadMethodCallException($method); + } + + $relationship = Str::camel(Str::substr($method, 3)); + + $relatedModel = get_class($this->newModel()->{$relationship}()->getRelated()); + + if (method_exists($relatedModel, 'newFactory')) { + $factory = $relatedModel::newFactory() ?: static::factoryForModel($relatedModel); + } else { + $factory = static::factoryForModel($relatedModel); + } + + if (Str::startsWith($method, 'for')) { + return $this->for($factory->state($parameters[0] ?? []), $relationship); + } elseif (Str::startsWith($method, 'has')) { + return $this->has( + $factory + ->count(is_numeric($parameters[0] ?? null) ? $parameters[0] : 1) + ->state((is_callable($parameters[0] ?? null) || is_array($parameters[0] ?? null)) ? $parameters[0] : ($parameters[1] ?? [])), + $relationship + ); + } + } +} diff --git a/vendor/illuminate/database/Eloquent/Factories/HasFactory.php b/vendor/illuminate/database/Eloquent/Factories/HasFactory.php new file mode 100644 index 0000000..7d2be22 --- /dev/null +++ b/vendor/illuminate/database/Eloquent/Factories/HasFactory.php @@ -0,0 +1,31 @@ +count(is_numeric($parameters[0] ?? null) ? $parameters[0] : null) + ->state(is_array($parameters[0] ?? null) ? $parameters[0] : ($parameters[1] ?? [])); + } + + /** + * Create a new factory instance for the model. + * + * @return \Illuminate\Database\Eloquent\Factories\Factory + */ + protected static function newFactory() + { + // + } +} diff --git a/vendor/illuminate/database/Eloquent/Factories/Relationship.php b/vendor/illuminate/database/Eloquent/Factories/Relationship.php new file mode 100644 index 0000000..788f6bc --- /dev/null +++ b/vendor/illuminate/database/Eloquent/Factories/Relationship.php @@ -0,0 +1,62 @@ +factory = $factory; + $this->relationship = $relationship; + } + + /** + * Create the child relationship for the given parent model. + * + * @param \Illuminate\Database\Eloquent\Model $parent + * @return void + */ + public function createFor(Model $parent) + { + $relationship = $parent->{$this->relationship}(); + + if ($relationship instanceof MorphOneOrMany) { + $this->factory->state([ + $relationship->getMorphType() => $relationship->getMorphClass(), + $relationship->getForeignKeyName() => $relationship->getParentKey(), + ])->create([], $parent); + } elseif ($relationship instanceof HasOneOrMany) { + $this->factory->state([ + $relationship->getForeignKeyName() => $relationship->getParentKey(), + ])->create([], $parent); + } elseif ($relationship instanceof BelongsToMany) { + $relationship->attach($this->factory->create([], $parent)); + } + } +} diff --git a/vendor/illuminate/database/Eloquent/Factories/Sequence.php b/vendor/illuminate/database/Eloquent/Factories/Sequence.php new file mode 100644 index 0000000..064cc4a --- /dev/null +++ b/vendor/illuminate/database/Eloquent/Factories/Sequence.php @@ -0,0 +1,63 @@ +sequence = $sequence; + $this->count = count($sequence); + } + + /** + * Get the current count of the sequence items. + * + * @return int + */ + public function count(): int + { + return $this->count; + } + + /** + * Get the next value in the sequence. + * + * @return mixed + */ + public function __invoke() + { + return tap(value($this->sequence[$this->index % $this->count], $this), function () { + $this->index = $this->index + 1; + }); + } +} diff --git a/vendor/illuminate/database/Eloquent/HigherOrderBuilderProxy.php b/vendor/illuminate/database/Eloquent/HigherOrderBuilderProxy.php new file mode 100644 index 0000000..16b49a1 --- /dev/null +++ b/vendor/illuminate/database/Eloquent/HigherOrderBuilderProxy.php @@ -0,0 +1,50 @@ +method = $method; + $this->builder = $builder; + } + + /** + * Proxy a scope call onto the query builder. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + return $this->builder->{$this->method}(function ($value) use ($method, $parameters) { + return $value->{$method}(...$parameters); + }); + } +} diff --git a/vendor/illuminate/database/Eloquent/InvalidCastException.php b/vendor/illuminate/database/Eloquent/InvalidCastException.php new file mode 100644 index 0000000..9d00eb3 --- /dev/null +++ b/vendor/illuminate/database/Eloquent/InvalidCastException.php @@ -0,0 +1,48 @@ +model = $class; + $this->column = $column; + $this->castType = $castType; + } +} diff --git a/vendor/illuminate/database/Eloquent/JsonEncodingException.php b/vendor/illuminate/database/Eloquent/JsonEncodingException.php new file mode 100644 index 0000000..f62abd4 --- /dev/null +++ b/vendor/illuminate/database/Eloquent/JsonEncodingException.php @@ -0,0 +1,49 @@ +getKey().'] to JSON: '.$message); + } + + /** + * Create a new JSON encoding exception for the resource. + * + * @param \Illuminate\Http\Resources\Json\JsonResource $resource + * @param string $message + * @return static + */ + public static function forResource($resource, $message) + { + $model = $resource->resource; + + return new static('Error encoding resource ['.get_class($resource).'] with model ['.get_class($model).'] with ID ['.$model->getKey().'] to JSON: '.$message); + } + + /** + * Create a new JSON encoding exception for an attribute. + * + * @param mixed $model + * @param mixed $key + * @param string $message + * @return static + */ + public static function forAttribute($model, $key, $message) + { + $class = get_class($model); + + return new static("Unable to encode attribute [{$key}] for model [{$class}] to JSON: {$message}."); + } +} diff --git a/vendor/illuminate/database/Eloquent/MassAssignmentException.php b/vendor/illuminate/database/Eloquent/MassAssignmentException.php new file mode 100644 index 0000000..7c81aae --- /dev/null +++ b/vendor/illuminate/database/Eloquent/MassAssignmentException.php @@ -0,0 +1,10 @@ +prunable(), function ($query) use ($chunkSize) { + $query->when(! $query->getQuery()->limit, function ($query) use ($chunkSize) { + $query->limit($chunkSize); + }); + }); + + $total = 0; + + do { + $total += $count = in_array(SoftDeletes::class, class_uses_recursive(get_class($this))) + ? $query->forceDelete() + : $query->delete(); + + if ($count > 0) { + event(new ModelsPruned(static::class, $total)); + } + } while ($count > 0); + + return $total; + } + + /** + * Get the prunable model query. + * + * @return \Illuminate\Database\Eloquent\Builder + */ + public function prunable() + { + throw new LogicException('Please implement the prunable method on your model.'); + } +} diff --git a/vendor/illuminate/database/Eloquent/Model.php b/vendor/illuminate/database/Eloquent/Model.php new file mode 100644 index 0000000..d1742bb --- /dev/null +++ b/vendor/illuminate/database/Eloquent/Model.php @@ -0,0 +1,2198 @@ +bootIfNotBooted(); + + $this->initializeTraits(); + + $this->syncOriginal(); + + $this->fill($attributes); + } + + /** + * Check if the model needs to be booted and if so, do it. + * + * @return void + */ + protected function bootIfNotBooted() + { + if (! isset(static::$booted[static::class])) { + static::$booted[static::class] = true; + + $this->fireModelEvent('booting', false); + + static::booting(); + static::boot(); + static::booted(); + + $this->fireModelEvent('booted', false); + } + } + + /** + * Perform any actions required before the model boots. + * + * @return void + */ + protected static function booting() + { + // + } + + /** + * Bootstrap the model and its traits. + * + * @return void + */ + protected static function boot() + { + static::bootTraits(); + } + + /** + * Boot all of the bootable traits on the model. + * + * @return void + */ + protected static function bootTraits() + { + $class = static::class; + + $booted = []; + + static::$traitInitializers[$class] = []; + + foreach (class_uses_recursive($class) as $trait) { + $method = 'boot'.class_basename($trait); + + if (method_exists($class, $method) && ! in_array($method, $booted)) { + forward_static_call([$class, $method]); + + $booted[] = $method; + } + + if (method_exists($class, $method = 'initialize'.class_basename($trait))) { + static::$traitInitializers[$class][] = $method; + + static::$traitInitializers[$class] = array_unique( + static::$traitInitializers[$class] + ); + } + } + } + + /** + * Initialize any initializable traits on the model. + * + * @return void + */ + protected function initializeTraits() + { + foreach (static::$traitInitializers[static::class] as $method) { + $this->{$method}(); + } + } + + /** + * Perform any actions required after the model boots. + * + * @return void + */ + protected static function booted() + { + // + } + + /** + * Clear the list of booted models so they will be re-booted. + * + * @return void + */ + public static function clearBootedModels() + { + static::$booted = []; + + static::$globalScopes = []; + } + + /** + * Disables relationship model touching for the current class during given callback scope. + * + * @param callable $callback + * @return void + */ + public static function withoutTouching(callable $callback) + { + static::withoutTouchingOn([static::class], $callback); + } + + /** + * Disables relationship model touching for the given model classes during given callback scope. + * + * @param array $models + * @param callable $callback + * @return void + */ + public static function withoutTouchingOn(array $models, callable $callback) + { + static::$ignoreOnTouch = array_values(array_merge(static::$ignoreOnTouch, $models)); + + try { + $callback(); + } finally { + static::$ignoreOnTouch = array_values(array_diff(static::$ignoreOnTouch, $models)); + } + } + + /** + * Determine if the given model is ignoring touches. + * + * @param string|null $class + * @return bool + */ + public static function isIgnoringTouch($class = null) + { + $class = $class ?: static::class; + + if (! get_class_vars($class)['timestamps'] || ! $class::UPDATED_AT) { + return true; + } + + foreach (static::$ignoreOnTouch as $ignoredClass) { + if ($class === $ignoredClass || is_subclass_of($class, $ignoredClass)) { + return true; + } + } + + return false; + } + + /** + * Prevent model relationships from being lazy loaded. + * + * @param bool $value + * @return void + */ + public static function preventLazyLoading($value = true) + { + static::$modelsShouldPreventLazyLoading = $value; + } + + /** + * Register a callback that is responsible for handling lazy loading violations. + * + * @param callable|null $callback + * @return void + */ + public static function handleLazyLoadingViolationUsing(?callable $callback) + { + static::$lazyLoadingViolationCallback = $callback; + } + + /** + * Execute a callback without broadcasting any model events for all model types. + * + * @param callable $callback + * @return mixed + */ + public static function withoutBroadcasting(callable $callback) + { + $isBroadcasting = static::$isBroadcasting; + + static::$isBroadcasting = false; + + try { + return $callback(); + } finally { + static::$isBroadcasting = $isBroadcasting; + } + } + + /** + * Fill the model with an array of attributes. + * + * @param array $attributes + * @return $this + * + * @throws \Illuminate\Database\Eloquent\MassAssignmentException + */ + public function fill(array $attributes) + { + $totallyGuarded = $this->totallyGuarded(); + + foreach ($this->fillableFromArray($attributes) as $key => $value) { + // The developers may choose to place some attributes in the "fillable" array + // which means only those attributes may be set through mass assignment to + // the model, and all others will just get ignored for security reasons. + if ($this->isFillable($key)) { + $this->setAttribute($key, $value); + } elseif ($totallyGuarded) { + throw new MassAssignmentException(sprintf( + 'Add [%s] to fillable property to allow mass assignment on [%s].', + $key, get_class($this) + )); + } + } + + return $this; + } + + /** + * Fill the model with an array of attributes. Force mass assignment. + * + * @param array $attributes + * @return $this + */ + public function forceFill(array $attributes) + { + return static::unguarded(function () use ($attributes) { + return $this->fill($attributes); + }); + } + + /** + * Qualify the given column name by the model's table. + * + * @param string $column + * @return string + */ + public function qualifyColumn($column) + { + if (Str::contains($column, '.')) { + return $column; + } + + return $this->getTable().'.'.$column; + } + + /** + * Qualify the given columns with the model's table. + * + * @param array $columns + * @return array + */ + public function qualifyColumns($columns) + { + return collect($columns)->map(function ($column) { + return $this->qualifyColumn($column); + })->all(); + } + + /** + * Create a new instance of the given model. + * + * @param array $attributes + * @param bool $exists + * @return static + */ + public function newInstance($attributes = [], $exists = false) + { + // This method just provides a convenient way for us to generate fresh model + // instances of this current model. It is particularly useful during the + // hydration of new objects via the Eloquent query builder instances. + $model = new static((array) $attributes); + + $model->exists = $exists; + + $model->setConnection( + $this->getConnectionName() + ); + + $model->setTable($this->getTable()); + + $model->mergeCasts($this->casts); + + return $model; + } + + /** + * Create a new model instance that is existing. + * + * @param array $attributes + * @param string|null $connection + * @return static + */ + public function newFromBuilder($attributes = [], $connection = null) + { + $model = $this->newInstance([], true); + + $model->setRawAttributes((array) $attributes, true); + + $model->setConnection($connection ?: $this->getConnectionName()); + + $model->fireModelEvent('retrieved', false); + + return $model; + } + + /** + * Begin querying the model on a given connection. + * + * @param string|null $connection + * @return \Illuminate\Database\Eloquent\Builder + */ + public static function on($connection = null) + { + // First we will just create a fresh instance of this model, and then we can set the + // connection on the model so that it is used for the queries we execute, as well + // as being set on every relation we retrieve without a custom connection name. + $instance = new static; + + $instance->setConnection($connection); + + return $instance->newQuery(); + } + + /** + * Begin querying the model on the write connection. + * + * @return \Illuminate\Database\Query\Builder + */ + public static function onWriteConnection() + { + return static::query()->useWritePdo(); + } + + /** + * Get all of the models from the database. + * + * @param array|mixed $columns + * @return \Illuminate\Database\Eloquent\Collection|static[] + */ + public static function all($columns = ['*']) + { + return static::query()->get( + is_array($columns) ? $columns : func_get_args() + ); + } + + /** + * Begin querying a model with eager loading. + * + * @param array|string $relations + * @return \Illuminate\Database\Eloquent\Builder + */ + public static function with($relations) + { + return static::query()->with( + is_string($relations) ? func_get_args() : $relations + ); + } + + /** + * Eager load relations on the model. + * + * @param array|string $relations + * @return $this + */ + public function load($relations) + { + $query = $this->newQueryWithoutRelationships()->with( + is_string($relations) ? func_get_args() : $relations + ); + + $query->eagerLoadRelations([$this]); + + return $this; + } + + /** + * Eager load relationships on the polymorphic relation of a model. + * + * @param string $relation + * @param array $relations + * @return $this + */ + public function loadMorph($relation, $relations) + { + if (! $this->{$relation}) { + return $this; + } + + $className = get_class($this->{$relation}); + + $this->{$relation}->load($relations[$className] ?? []); + + return $this; + } + + /** + * Eager load relations on the model if they are not already eager loaded. + * + * @param array|string $relations + * @return $this + */ + public function loadMissing($relations) + { + $relations = is_string($relations) ? func_get_args() : $relations; + + $this->newCollection([$this])->loadMissing($relations); + + return $this; + } + + /** + * Eager load relation's column aggregations on the model. + * + * @param array|string $relations + * @param string $column + * @param string $function + * @return $this + */ + public function loadAggregate($relations, $column, $function = null) + { + $this->newCollection([$this])->loadAggregate($relations, $column, $function); + + return $this; + } + + /** + * Eager load relation counts on the model. + * + * @param array|string $relations + * @return $this + */ + public function loadCount($relations) + { + $relations = is_string($relations) ? func_get_args() : $relations; + + return $this->loadAggregate($relations, '*', 'count'); + } + + /** + * Eager load relation max column values on the model. + * + * @param array|string $relations + * @param string $column + * @return $this + */ + public function loadMax($relations, $column) + { + return $this->loadAggregate($relations, $column, 'max'); + } + + /** + * Eager load relation min column values on the model. + * + * @param array|string $relations + * @param string $column + * @return $this + */ + public function loadMin($relations, $column) + { + return $this->loadAggregate($relations, $column, 'min'); + } + + /** + * Eager load relation's column summations on the model. + * + * @param array|string $relations + * @param string $column + * @return $this + */ + public function loadSum($relations, $column) + { + return $this->loadAggregate($relations, $column, 'sum'); + } + + /** + * Eager load relation average column values on the model. + * + * @param array|string $relations + * @param string $column + * @return $this + */ + public function loadAvg($relations, $column) + { + return $this->loadAggregate($relations, $column, 'avg'); + } + + /** + * Eager load related model existence values on the model. + * + * @param array|string $relations + * @return $this + */ + public function loadExists($relations) + { + return $this->loadAggregate($relations, '*', 'exists'); + } + + /** + * Eager load relationship column aggregation on the polymorphic relation of a model. + * + * @param string $relation + * @param array $relations + * @param string $column + * @param string $function + * @return $this + */ + public function loadMorphAggregate($relation, $relations, $column, $function = null) + { + if (! $this->{$relation}) { + return $this; + } + + $className = get_class($this->{$relation}); + + $this->{$relation}->loadAggregate($relations[$className] ?? [], $column, $function); + + return $this; + } + + /** + * Eager load relationship counts on the polymorphic relation of a model. + * + * @param string $relation + * @param array $relations + * @return $this + */ + public function loadMorphCount($relation, $relations) + { + return $this->loadMorphAggregate($relation, $relations, '*', 'count'); + } + + /** + * Eager load relationship max column values on the polymorphic relation of a model. + * + * @param string $relation + * @param array $relations + * @param string $column + * @return $this + */ + public function loadMorphMax($relation, $relations, $column) + { + return $this->loadMorphAggregate($relation, $relations, $column, 'max'); + } + + /** + * Eager load relationship min column values on the polymorphic relation of a model. + * + * @param string $relation + * @param array $relations + * @param string $column + * @return $this + */ + public function loadMorphMin($relation, $relations, $column) + { + return $this->loadMorphAggregate($relation, $relations, $column, 'min'); + } + + /** + * Eager load relationship column summations on the polymorphic relation of a model. + * + * @param string $relation + * @param array $relations + * @param string $column + * @return $this + */ + public function loadMorphSum($relation, $relations, $column) + { + return $this->loadMorphAggregate($relation, $relations, $column, 'sum'); + } + + /** + * Eager load relationship average column values on the polymorphic relation of a model. + * + * @param string $relation + * @param array $relations + * @param string $column + * @return $this + */ + public function loadMorphAvg($relation, $relations, $column) + { + return $this->loadMorphAggregate($relation, $relations, $column, 'avg'); + } + + /** + * Increment a column's value by a given amount. + * + * @param string $column + * @param float|int $amount + * @param array $extra + * @return int + */ + protected function increment($column, $amount = 1, array $extra = []) + { + return $this->incrementOrDecrement($column, $amount, $extra, 'increment'); + } + + /** + * Decrement a column's value by a given amount. + * + * @param string $column + * @param float|int $amount + * @param array $extra + * @return int + */ + protected function decrement($column, $amount = 1, array $extra = []) + { + return $this->incrementOrDecrement($column, $amount, $extra, 'decrement'); + } + + /** + * Run the increment or decrement method on the model. + * + * @param string $column + * @param float|int $amount + * @param array $extra + * @param string $method + * @return int + */ + protected function incrementOrDecrement($column, $amount, $extra, $method) + { + $query = $this->newQueryWithoutRelationships(); + + if (! $this->exists) { + return $query->{$method}($column, $amount, $extra); + } + + $this->{$column} = $this->isClassDeviable($column) + ? $this->deviateClassCastableAttribute($method, $column, $amount) + : $this->{$column} + ($method === 'increment' ? $amount : $amount * -1); + + $this->forceFill($extra); + + if ($this->fireModelEvent('updating') === false) { + return false; + } + + return tap($this->setKeysForSaveQuery($query)->{$method}($column, $amount, $extra), function () use ($column) { + $this->syncChanges(); + + $this->fireModelEvent('updated', false); + + $this->syncOriginalAttribute($column); + }); + } + + /** + * Update the model in the database. + * + * @param array $attributes + * @param array $options + * @return bool + */ + public function update(array $attributes = [], array $options = []) + { + if (! $this->exists) { + return false; + } + + return $this->fill($attributes)->save($options); + } + + /** + * Update the model in the database within a transaction. + * + * @param array $attributes + * @param array $options + * @return bool + * + * @throws \Throwable + */ + public function updateOrFail(array $attributes = [], array $options = []) + { + if (! $this->exists) { + return false; + } + + return $this->fill($attributes)->saveOrFail($options); + } + + /** + * Update the model in the database without raising any events. + * + * @param array $attributes + * @param array $options + * @return bool + */ + public function updateQuietly(array $attributes = [], array $options = []) + { + if (! $this->exists) { + return false; + } + + return $this->fill($attributes)->saveQuietly($options); + } + + /** + * Save the model and all of its relationships. + * + * @return bool + */ + public function push() + { + if (! $this->save()) { + return false; + } + + // To sync all of the relationships to the database, we will simply spin through + // the relationships and save each model via this "push" method, which allows + // us to recurse into all of these nested relations for the model instance. + foreach ($this->relations as $models) { + $models = $models instanceof Collection + ? $models->all() : [$models]; + + foreach (array_filter($models) as $model) { + if (! $model->push()) { + return false; + } + } + } + + return true; + } + + /** + * Save the model to the database without raising any events. + * + * @param array $options + * @return bool + */ + public function saveQuietly(array $options = []) + { + return static::withoutEvents(function () use ($options) { + return $this->save($options); + }); + } + + /** + * Save the model to the database. + * + * @param array $options + * @return bool + */ + public function save(array $options = []) + { + $this->mergeAttributesFromCachedCasts(); + + $query = $this->newModelQuery(); + + // If the "saving" event returns false we'll bail out of the save and return + // false, indicating that the save failed. This provides a chance for any + // listeners to cancel save operations if validations fail or whatever. + if ($this->fireModelEvent('saving') === false) { + return false; + } + + // If the model already exists in the database we can just update our record + // that is already in this database using the current IDs in this "where" + // clause to only update this model. Otherwise, we'll just insert them. + if ($this->exists) { + $saved = $this->isDirty() ? + $this->performUpdate($query) : true; + } + + // If the model is brand new, we'll insert it into our database and set the + // ID attribute on the model to the value of the newly inserted row's ID + // which is typically an auto-increment value managed by the database. + else { + $saved = $this->performInsert($query); + + if (! $this->getConnectionName() && + $connection = $query->getConnection()) { + $this->setConnection($connection->getName()); + } + } + + // If the model is successfully saved, we need to do a few more things once + // that is done. We will call the "saved" method here to run any actions + // we need to happen after a model gets successfully saved right here. + if ($saved) { + $this->finishSave($options); + } + + return $saved; + } + + /** + * Save the model to the database within a transaction. + * + * @param array $options + * @return bool + * + * @throws \Throwable + */ + public function saveOrFail(array $options = []) + { + return $this->getConnection()->transaction(function () use ($options) { + return $this->save($options); + }); + } + + /** + * Perform any actions that are necessary after the model is saved. + * + * @param array $options + * @return void + */ + protected function finishSave(array $options) + { + $this->fireModelEvent('saved', false); + + if ($this->isDirty() && ($options['touch'] ?? true)) { + $this->touchOwners(); + } + + $this->syncOriginal(); + } + + /** + * Perform a model update operation. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @return bool + */ + protected function performUpdate(Builder $query) + { + // If the updating event returns false, we will cancel the update operation so + // developers can hook Validation systems into their models and cancel this + // operation if the model does not pass validation. Otherwise, we update. + if ($this->fireModelEvent('updating') === false) { + return false; + } + + // First we need to create a fresh query instance and touch the creation and + // update timestamp on the model which are maintained by us for developer + // convenience. Then we will just continue saving the model instances. + if ($this->usesTimestamps()) { + $this->updateTimestamps(); + } + + // Once we have run the update operation, we will fire the "updated" event for + // this model instance. This will allow developers to hook into these after + // models are updated, giving them a chance to do any special processing. + $dirty = $this->getDirty(); + + if (count($dirty) > 0) { + $this->setKeysForSaveQuery($query)->update($dirty); + + $this->syncChanges(); + + $this->fireModelEvent('updated', false); + } + + return true; + } + + /** + * Set the keys for a select query. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @return \Illuminate\Database\Eloquent\Builder + */ + protected function setKeysForSelectQuery($query) + { + $query->where($this->getKeyName(), '=', $this->getKeyForSelectQuery()); + + return $query; + } + + /** + * Get the primary key value for a select query. + * + * @return mixed + */ + protected function getKeyForSelectQuery() + { + return $this->original[$this->getKeyName()] ?? $this->getKey(); + } + + /** + * Set the keys for a save update query. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @return \Illuminate\Database\Eloquent\Builder + */ + protected function setKeysForSaveQuery($query) + { + $query->where($this->getKeyName(), '=', $this->getKeyForSaveQuery()); + + return $query; + } + + /** + * Get the primary key value for a save query. + * + * @return mixed + */ + protected function getKeyForSaveQuery() + { + return $this->original[$this->getKeyName()] ?? $this->getKey(); + } + + /** + * Perform a model insert operation. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @return bool + */ + protected function performInsert(Builder $query) + { + if ($this->fireModelEvent('creating') === false) { + return false; + } + + // First we'll need to create a fresh query instance and touch the creation and + // update timestamps on this model, which are maintained by us for developer + // convenience. After, we will just continue saving these model instances. + if ($this->usesTimestamps()) { + $this->updateTimestamps(); + } + + // If the model has an incrementing key, we can use the "insertGetId" method on + // the query builder, which will give us back the final inserted ID for this + // table from the database. Not all tables have to be incrementing though. + $attributes = $this->getAttributesForInsert(); + + if ($this->getIncrementing()) { + $this->insertAndSetId($query, $attributes); + } + + // If the table isn't incrementing we'll simply insert these attributes as they + // are. These attribute arrays must contain an "id" column previously placed + // there by the developer as the manually determined key for these models. + else { + if (empty($attributes)) { + return true; + } + + $query->insert($attributes); + } + + // We will go ahead and set the exists property to true, so that it is set when + // the created event is fired, just in case the developer tries to update it + // during the event. This will allow them to do so and run an update here. + $this->exists = true; + + $this->wasRecentlyCreated = true; + + $this->fireModelEvent('created', false); + + return true; + } + + /** + * Insert the given attributes and set the ID on the model. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param array $attributes + * @return void + */ + protected function insertAndSetId(Builder $query, $attributes) + { + $id = $query->insertGetId($attributes, $keyName = $this->getKeyName()); + + $this->setAttribute($keyName, $id); + } + + /** + * Destroy the models for the given IDs. + * + * @param \Illuminate\Support\Collection|array|int|string $ids + * @return int + */ + public static function destroy($ids) + { + if ($ids instanceof EloquentCollection) { + $ids = $ids->modelKeys(); + } + + if ($ids instanceof BaseCollection) { + $ids = $ids->all(); + } + + $ids = is_array($ids) ? $ids : func_get_args(); + + if (count($ids) === 0) { + return 0; + } + + // We will actually pull the models from the database table and call delete on + // each of them individually so that their events get fired properly with a + // correct set of attributes in case the developers wants to check these. + $key = ($instance = new static)->getKeyName(); + + $count = 0; + + foreach ($instance->whereIn($key, $ids)->get() as $model) { + if ($model->delete()) { + $count++; + } + } + + return $count; + } + + /** + * Delete the model from the database. + * + * @return bool|null + * + * @throws \LogicException + */ + public function delete() + { + $this->mergeAttributesFromCachedCasts(); + + if (is_null($this->getKeyName())) { + throw new LogicException('No primary key defined on model.'); + } + + // If the model doesn't exist, there is nothing to delete so we'll just return + // immediately and not do anything else. Otherwise, we will continue with a + // deletion process on the model, firing the proper events, and so forth. + if (! $this->exists) { + return; + } + + if ($this->fireModelEvent('deleting') === false) { + return false; + } + + // Here, we'll touch the owning models, verifying these timestamps get updated + // for the models. This will allow any caching to get broken on the parents + // by the timestamp. Then we will go ahead and delete the model instance. + $this->touchOwners(); + + $this->performDeleteOnModel(); + + // Once the model has been deleted, we will fire off the deleted event so that + // the developers may hook into post-delete operations. We will then return + // a boolean true as the delete is presumably successful on the database. + $this->fireModelEvent('deleted', false); + + return true; + } + + /** + * Delete the model from the database within a transaction. + * + * @return bool|null + * + * @throws \Throwable + */ + public function deleteOrFail() + { + if (! $this->exists) { + return false; + } + + return $this->getConnection()->transaction(function () { + return $this->delete(); + }); + } + + /** + * Force a hard delete on a soft deleted model. + * + * This method protects developers from running forceDelete when the trait is missing. + * + * @return bool|null + */ + public function forceDelete() + { + return $this->delete(); + } + + /** + * Perform the actual delete query on this model instance. + * + * @return void + */ + protected function performDeleteOnModel() + { + $this->setKeysForSaveQuery($this->newModelQuery())->delete(); + + $this->exists = false; + } + + /** + * Begin querying the model. + * + * @return \Illuminate\Database\Eloquent\Builder + */ + public static function query() + { + return (new static)->newQuery(); + } + + /** + * Get a new query builder for the model's table. + * + * @return \Illuminate\Database\Eloquent\Builder + */ + public function newQuery() + { + return $this->registerGlobalScopes($this->newQueryWithoutScopes()); + } + + /** + * Get a new query builder that doesn't have any global scopes or eager loading. + * + * @return \Illuminate\Database\Eloquent\Builder|static + */ + public function newModelQuery() + { + return $this->newEloquentBuilder( + $this->newBaseQueryBuilder() + )->setModel($this); + } + + /** + * Get a new query builder with no relationships loaded. + * + * @return \Illuminate\Database\Eloquent\Builder + */ + public function newQueryWithoutRelationships() + { + return $this->registerGlobalScopes($this->newModelQuery()); + } + + /** + * Register the global scopes for this builder instance. + * + * @param \Illuminate\Database\Eloquent\Builder $builder + * @return \Illuminate\Database\Eloquent\Builder + */ + public function registerGlobalScopes($builder) + { + foreach ($this->getGlobalScopes() as $identifier => $scope) { + $builder->withGlobalScope($identifier, $scope); + } + + return $builder; + } + + /** + * Get a new query builder that doesn't have any global scopes. + * + * @return \Illuminate\Database\Eloquent\Builder|static + */ + public function newQueryWithoutScopes() + { + return $this->newModelQuery() + ->with($this->with) + ->withCount($this->withCount); + } + + /** + * Get a new query instance without a given scope. + * + * @param \Illuminate\Database\Eloquent\Scope|string $scope + * @return \Illuminate\Database\Eloquent\Builder + */ + public function newQueryWithoutScope($scope) + { + return $this->newQuery()->withoutGlobalScope($scope); + } + + /** + * Get a new query to restore one or more models by their queueable IDs. + * + * @param array|int $ids + * @return \Illuminate\Database\Eloquent\Builder + */ + public function newQueryForRestoration($ids) + { + return is_array($ids) + ? $this->newQueryWithoutScopes()->whereIn($this->getQualifiedKeyName(), $ids) + : $this->newQueryWithoutScopes()->whereKey($ids); + } + + /** + * Create a new Eloquent query builder for the model. + * + * @param \Illuminate\Database\Query\Builder $query + * @return \Illuminate\Database\Eloquent\Builder|static + */ + public function newEloquentBuilder($query) + { + return new Builder($query); + } + + /** + * Get a new query builder instance for the connection. + * + * @return \Illuminate\Database\Query\Builder + */ + protected function newBaseQueryBuilder() + { + return $this->getConnection()->query(); + } + + /** + * Create a new Eloquent Collection instance. + * + * @param array $models + * @return \Illuminate\Database\Eloquent\Collection + */ + public function newCollection(array $models = []) + { + return new Collection($models); + } + + /** + * Create a new pivot model instance. + * + * @param \Illuminate\Database\Eloquent\Model $parent + * @param array $attributes + * @param string $table + * @param bool $exists + * @param string|null $using + * @return \Illuminate\Database\Eloquent\Relations\Pivot + */ + public function newPivot(self $parent, array $attributes, $table, $exists, $using = null) + { + return $using ? $using::fromRawAttributes($parent, $attributes, $table, $exists) + : Pivot::fromAttributes($parent, $attributes, $table, $exists); + } + + /** + * Determine if the model has a given scope. + * + * @param string $scope + * @return bool + */ + public function hasNamedScope($scope) + { + return method_exists($this, 'scope'.ucfirst($scope)); + } + + /** + * Apply the given named scope if possible. + * + * @param string $scope + * @param array $parameters + * @return mixed + */ + public function callNamedScope($scope, array $parameters = []) + { + return $this->{'scope'.ucfirst($scope)}(...$parameters); + } + + /** + * Convert the model instance to an array. + * + * @return array + */ + public function toArray() + { + return array_merge($this->attributesToArray(), $this->relationsToArray()); + } + + /** + * Convert the model instance to JSON. + * + * @param int $options + * @return string + * + * @throws \Illuminate\Database\Eloquent\JsonEncodingException + */ + public function toJson($options = 0) + { + $json = json_encode($this->jsonSerialize(), $options); + + if (JSON_ERROR_NONE !== json_last_error()) { + throw JsonEncodingException::forModel($this, json_last_error_msg()); + } + + return $json; + } + + /** + * Convert the object into something JSON serializable. + * + * @return array + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return $this->toArray(); + } + + /** + * Reload a fresh model instance from the database. + * + * @param array|string $with + * @return static|null + */ + public function fresh($with = []) + { + if (! $this->exists) { + return; + } + + return $this->setKeysForSelectQuery($this->newQueryWithoutScopes()) + ->with(is_string($with) ? func_get_args() : $with) + ->first(); + } + + /** + * Reload the current model instance with fresh attributes from the database. + * + * @return $this + */ + public function refresh() + { + if (! $this->exists) { + return $this; + } + + $this->setRawAttributes( + $this->setKeysForSelectQuery($this->newQueryWithoutScopes())->firstOrFail()->attributes + ); + + $this->load(collect($this->relations)->reject(function ($relation) { + return $relation instanceof Pivot + || (is_object($relation) && in_array(AsPivot::class, class_uses_recursive($relation), true)); + })->keys()->all()); + + $this->syncOriginal(); + + return $this; + } + + /** + * Clone the model into a new, non-existing instance. + * + * @param array|null $except + * @return static + */ + public function replicate(array $except = null) + { + $defaults = [ + $this->getKeyName(), + $this->getCreatedAtColumn(), + $this->getUpdatedAtColumn(), + ]; + + $attributes = Arr::except( + $this->getAttributes(), $except ? array_unique(array_merge($except, $defaults)) : $defaults + ); + + return tap(new static, function ($instance) use ($attributes) { + $instance->setRawAttributes($attributes); + + $instance->setRelations($this->relations); + + $instance->fireModelEvent('replicating', false); + }); + } + + /** + * Determine if two models have the same ID and belong to the same table. + * + * @param \Illuminate\Database\Eloquent\Model|null $model + * @return bool + */ + public function is($model) + { + return ! is_null($model) && + $this->getKey() === $model->getKey() && + $this->getTable() === $model->getTable() && + $this->getConnectionName() === $model->getConnectionName(); + } + + /** + * Determine if two models are not the same. + * + * @param \Illuminate\Database\Eloquent\Model|null $model + * @return bool + */ + public function isNot($model) + { + return ! $this->is($model); + } + + /** + * Get the database connection for the model. + * + * @return \Illuminate\Database\Connection + */ + public function getConnection() + { + return static::resolveConnection($this->getConnectionName()); + } + + /** + * Get the current connection name for the model. + * + * @return string|null + */ + public function getConnectionName() + { + return $this->connection; + } + + /** + * Set the connection associated with the model. + * + * @param string|null $name + * @return $this + */ + public function setConnection($name) + { + $this->connection = $name; + + return $this; + } + + /** + * Resolve a connection instance. + * + * @param string|null $connection + * @return \Illuminate\Database\Connection + */ + public static function resolveConnection($connection = null) + { + return static::$resolver->connection($connection); + } + + /** + * Get the connection resolver instance. + * + * @return \Illuminate\Database\ConnectionResolverInterface + */ + public static function getConnectionResolver() + { + return static::$resolver; + } + + /** + * Set the connection resolver instance. + * + * @param \Illuminate\Database\ConnectionResolverInterface $resolver + * @return void + */ + public static function setConnectionResolver(Resolver $resolver) + { + static::$resolver = $resolver; + } + + /** + * Unset the connection resolver for models. + * + * @return void + */ + public static function unsetConnectionResolver() + { + static::$resolver = null; + } + + /** + * Get the table associated with the model. + * + * @return string + */ + public function getTable() + { + return $this->table ?? Str::snake(Str::pluralStudly(class_basename($this))); + } + + /** + * Set the table associated with the model. + * + * @param string $table + * @return $this + */ + public function setTable($table) + { + $this->table = $table; + + return $this; + } + + /** + * Get the primary key for the model. + * + * @return string + */ + public function getKeyName() + { + return $this->primaryKey; + } + + /** + * Set the primary key for the model. + * + * @param string $key + * @return $this + */ + public function setKeyName($key) + { + $this->primaryKey = $key; + + return $this; + } + + /** + * Get the table qualified key name. + * + * @return string + */ + public function getQualifiedKeyName() + { + return $this->qualifyColumn($this->getKeyName()); + } + + /** + * Get the auto-incrementing key type. + * + * @return string + */ + public function getKeyType() + { + return $this->keyType; + } + + /** + * Set the data type for the primary key. + * + * @param string $type + * @return $this + */ + public function setKeyType($type) + { + $this->keyType = $type; + + return $this; + } + + /** + * Get the value indicating whether the IDs are incrementing. + * + * @return bool + */ + public function getIncrementing() + { + return $this->incrementing; + } + + /** + * Set whether IDs are incrementing. + * + * @param bool $value + * @return $this + */ + public function setIncrementing($value) + { + $this->incrementing = $value; + + return $this; + } + + /** + * Get the value of the model's primary key. + * + * @return mixed + */ + public function getKey() + { + return $this->getAttribute($this->getKeyName()); + } + + /** + * Get the queueable identity for the entity. + * + * @return mixed + */ + public function getQueueableId() + { + return $this->getKey(); + } + + /** + * Get the queueable relationships for the entity. + * + * @return array + */ + public function getQueueableRelations() + { + $relations = []; + + foreach ($this->getRelations() as $key => $relation) { + if (! method_exists($this, $key)) { + continue; + } + + $relations[] = $key; + + if ($relation instanceof QueueableCollection) { + foreach ($relation->getQueueableRelations() as $collectionValue) { + $relations[] = $key.'.'.$collectionValue; + } + } + + if ($relation instanceof QueueableEntity) { + foreach ($relation->getQueueableRelations() as $entityKey => $entityValue) { + $relations[] = $key.'.'.$entityValue; + } + } + } + + return array_unique($relations); + } + + /** + * Get the queueable connection for the entity. + * + * @return string|null + */ + public function getQueueableConnection() + { + return $this->getConnectionName(); + } + + /** + * Get the value of the model's route key. + * + * @return mixed + */ + public function getRouteKey() + { + return $this->getAttribute($this->getRouteKeyName()); + } + + /** + * Get the route key for the model. + * + * @return string + */ + public function getRouteKeyName() + { + return $this->getKeyName(); + } + + /** + * Retrieve the model for a bound value. + * + * @param mixed $value + * @param string|null $field + * @return \Illuminate\Database\Eloquent\Model|null + */ + public function resolveRouteBinding($value, $field = null) + { + return $this->resolveRouteBindingQuery($this, $value, $field)->first(); + } + + /** + * Retrieve the model for a bound value. + * + * @param mixed $value + * @param string|null $field + * @return \Illuminate\Database\Eloquent\Model|null + */ + public function resolveSoftDeletableRouteBinding($value, $field = null) + { + return $this->resolveRouteBindingQuery($this, $value, $field)->withTrashed()->first(); + } + + /** + * Retrieve the child model for a bound value. + * + * @param string $childType + * @param mixed $value + * @param string|null $field + * @return \Illuminate\Database\Eloquent\Model|null + */ + public function resolveChildRouteBinding($childType, $value, $field) + { + return $this->resolveChildRouteBindingQuery($childType, $value, $field)->first(); + } + + /** + * Retrieve the child model for a bound value. + * + * @param string $childType + * @param mixed $value + * @param string|null $field + * @return \Illuminate\Database\Eloquent\Model|null + */ + public function resolveSoftDeletableChildRouteBinding($childType, $value, $field) + { + return $this->resolveChildRouteBindingQuery($childType, $value, $field)->withTrashed()->first(); + } + + /** + * Retrieve the child model query for a bound value. + * + * @param string $childType + * @param mixed $value + * @param string|null $field + * @return \Illuminate\Database\Eloquent\Relations\Relation + */ + protected function resolveChildRouteBindingQuery($childType, $value, $field) + { + $relationship = $this->{Str::plural(Str::camel($childType))}(); + + $field = $field ?: $relationship->getRelated()->getRouteKeyName(); + + if ($relationship instanceof HasManyThrough || + $relationship instanceof BelongsToMany) { + $field = $relationship->getRelated()->getTable().'.'.$field; + } + + return $relationship instanceof Model + ? $relationship->resolveRouteBindingQuery($relationship, $value, $field) + : $relationship->getRelated()->resolveRouteBindingQuery($relationship, $value, $field); + } + + /** + * Retrieve the model for a bound value. + * + * @param \Illuminate\Database\Eloquent\Model|Illuminate\Database\Eloquent\Relations\Relation $query + * @param mixed $value + * @param string|null $field + * @return \Illuminate\Database\Eloquent\Relations\Relation + */ + public function resolveRouteBindingQuery($query, $value, $field = null) + { + return $query->where($field ?? $this->getRouteKeyName(), $value); + } + + /** + * Get the default foreign key name for the model. + * + * @return string + */ + public function getForeignKey() + { + return Str::snake(class_basename($this)).'_'.$this->getKeyName(); + } + + /** + * Get the number of models to return per page. + * + * @return int + */ + public function getPerPage() + { + return $this->perPage; + } + + /** + * Set the number of models to return per page. + * + * @param int $perPage + * @return $this + */ + public function setPerPage($perPage) + { + $this->perPage = $perPage; + + return $this; + } + + /** + * Determine if lazy loading is disabled. + * + * @return bool + */ + public static function preventsLazyLoading() + { + return static::$modelsShouldPreventLazyLoading; + } + + /** + * Get the broadcast channel route definition that is associated with the given entity. + * + * @return string + */ + public function broadcastChannelRoute() + { + return str_replace('\\', '.', get_class($this)).'.{'.Str::camel(class_basename($this)).'}'; + } + + /** + * Get the broadcast channel name that is associated with the given entity. + * + * @return string + */ + public function broadcastChannel() + { + return str_replace('\\', '.', get_class($this)).'.'.$this->getKey(); + } + + /** + * Dynamically retrieve attributes on the model. + * + * @param string $key + * @return mixed + */ + public function __get($key) + { + return $this->getAttribute($key); + } + + /** + * Dynamically set attributes on the model. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function __set($key, $value) + { + $this->setAttribute($key, $value); + } + + /** + * Determine if the given attribute exists. + * + * @param mixed $offset + * @return bool + */ + #[\ReturnTypeWillChange] + public function offsetExists($offset) + { + return ! is_null($this->getAttribute($offset)); + } + + /** + * Get the value for a given offset. + * + * @param mixed $offset + * @return mixed + */ + #[\ReturnTypeWillChange] + public function offsetGet($offset) + { + return $this->getAttribute($offset); + } + + /** + * Set the value for a given offset. + * + * @param mixed $offset + * @param mixed $value + * @return void + */ + #[\ReturnTypeWillChange] + public function offsetSet($offset, $value) + { + $this->setAttribute($offset, $value); + } + + /** + * Unset the value for a given offset. + * + * @param mixed $offset + * @return void + */ + #[\ReturnTypeWillChange] + public function offsetUnset($offset) + { + unset($this->attributes[$offset], $this->relations[$offset]); + } + + /** + * Determine if an attribute or relation exists on the model. + * + * @param string $key + * @return bool + */ + public function __isset($key) + { + return $this->offsetExists($key); + } + + /** + * Unset an attribute on the model. + * + * @param string $key + * @return void + */ + public function __unset($key) + { + $this->offsetUnset($key); + } + + /** + * Handle dynamic method calls into the model. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + if (in_array($method, ['increment', 'decrement'])) { + return $this->$method(...$parameters); + } + + if ($resolver = (static::$relationResolvers[get_class($this)][$method] ?? null)) { + return $resolver($this); + } + + return $this->forwardCallTo($this->newQuery(), $method, $parameters); + } + + /** + * Handle dynamic static method calls into the model. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public static function __callStatic($method, $parameters) + { + return (new static)->$method(...$parameters); + } + + /** + * Convert the model to its string representation. + * + * @return string + */ + public function __toString() + { + return $this->escapeWhenCastingToString + ? e($this->toJson()) + : $this->toJson(); + } + + /** + * Indicate that the object's string representation should be escaped when __toString is invoked. + * + * @param bool $escape + * @return $this + */ + public function escapeWhenCastingToString($escape = true) + { + $this->escapeWhenCastingToString = $escape; + + return $this; + } + + /** + * Prepare the object for serialization. + * + * @return array + */ + public function __sleep() + { + $this->mergeAttributesFromCachedCasts(); + + $this->classCastCache = []; + $this->attributeCastCache = []; + + return array_keys(get_object_vars($this)); + } + + /** + * When a model is being unserialized, check if it needs to be booted. + * + * @return void + */ + public function __wakeup() + { + $this->bootIfNotBooted(); + + $this->initializeTraits(); + } +} diff --git a/vendor/illuminate/database/Eloquent/ModelNotFoundException.php b/vendor/illuminate/database/Eloquent/ModelNotFoundException.php new file mode 100644 index 0000000..c35598b --- /dev/null +++ b/vendor/illuminate/database/Eloquent/ModelNotFoundException.php @@ -0,0 +1,66 @@ +model = $model; + $this->ids = Arr::wrap($ids); + + $this->message = "No query results for model [{$model}]"; + + if (count($this->ids) > 0) { + $this->message .= ' '.implode(', ', $this->ids); + } else { + $this->message .= '.'; + } + + return $this; + } + + /** + * Get the affected Eloquent model. + * + * @return string + */ + public function getModel() + { + return $this->model; + } + + /** + * Get the affected Eloquent model IDs. + * + * @return int|array + */ + public function getIds() + { + return $this->ids; + } +} diff --git a/vendor/illuminate/database/Eloquent/Prunable.php b/vendor/illuminate/database/Eloquent/Prunable.php new file mode 100644 index 0000000..b4ce1b0 --- /dev/null +++ b/vendor/illuminate/database/Eloquent/Prunable.php @@ -0,0 +1,67 @@ +prunable() + ->when(in_array(SoftDeletes::class, class_uses_recursive(get_class($this))), function ($query) { + $query->withTrashed(); + })->chunkById($chunkSize, function ($models) use (&$total) { + $models->each->prune(); + + $total += $models->count(); + + event(new ModelsPruned(static::class, $total)); + }); + + return $total; + } + + /** + * Get the prunable model query. + * + * @return \Illuminate\Database\Eloquent\Builder + */ + public function prunable() + { + throw new LogicException('Please implement the prunable method on your model.'); + } + + /** + * Prune the model in the database. + * + * @return bool|null + */ + public function prune() + { + $this->pruning(); + + return in_array(SoftDeletes::class, class_uses_recursive(get_class($this))) + ? $this->forceDelete() + : $this->delete(); + } + + /** + * Prepare the model for pruning. + * + * @return void + */ + protected function pruning() + { + // + } +} diff --git a/vendor/illuminate/database/Eloquent/QueueEntityResolver.php b/vendor/illuminate/database/Eloquent/QueueEntityResolver.php new file mode 100644 index 0000000..22fccf2 --- /dev/null +++ b/vendor/illuminate/database/Eloquent/QueueEntityResolver.php @@ -0,0 +1,29 @@ +find($id); + + if ($instance) { + return $instance; + } + + throw new EntityNotFoundException($type, $id); + } +} diff --git a/vendor/illuminate/database/Eloquent/RelationNotFoundException.php b/vendor/illuminate/database/Eloquent/RelationNotFoundException.php new file mode 100644 index 0000000..73257bb --- /dev/null +++ b/vendor/illuminate/database/Eloquent/RelationNotFoundException.php @@ -0,0 +1,46 @@ +model = $class; + $instance->relation = $relation; + + return $instance; + } +} diff --git a/vendor/illuminate/database/Eloquent/Relations/BelongsTo.php b/vendor/illuminate/database/Eloquent/Relations/BelongsTo.php new file mode 100644 index 0000000..c17b733 --- /dev/null +++ b/vendor/illuminate/database/Eloquent/Relations/BelongsTo.php @@ -0,0 +1,383 @@ +ownerKey = $ownerKey; + $this->relationName = $relationName; + $this->foreignKey = $foreignKey; + + // In the underlying base relationship class, this variable is referred to as + // the "parent" since most relationships are not inversed. But, since this + // one is we will create a "child" variable for much better readability. + $this->child = $child; + + parent::__construct($query, $child); + } + + /** + * Get the results of the relationship. + * + * @return mixed + */ + public function getResults() + { + if (is_null($this->child->{$this->foreignKey})) { + return $this->getDefaultFor($this->parent); + } + + return $this->query->first() ?: $this->getDefaultFor($this->parent); + } + + /** + * Set the base constraints on the relation query. + * + * @return void + */ + public function addConstraints() + { + if (static::$constraints) { + // For belongs to relationships, which are essentially the inverse of has one + // or has many relationships, we need to actually query on the primary key + // of the related models matching on the foreign key that's on a parent. + $table = $this->related->getTable(); + + $this->query->where($table.'.'.$this->ownerKey, '=', $this->child->{$this->foreignKey}); + } + } + + /** + * Set the constraints for an eager load of the relation. + * + * @param array $models + * @return void + */ + public function addEagerConstraints(array $models) + { + // We'll grab the primary key name of the related models since it could be set to + // a non-standard name and not "id". We will then construct the constraint for + // our eagerly loading query so it returns the proper models from execution. + $key = $this->related->getTable().'.'.$this->ownerKey; + + $whereIn = $this->whereInMethod($this->related, $this->ownerKey); + + $this->query->{$whereIn}($key, $this->getEagerModelKeys($models)); + } + + /** + * Gather the keys from an array of related models. + * + * @param array $models + * @return array + */ + protected function getEagerModelKeys(array $models) + { + $keys = []; + + // First we need to gather all of the keys from the parent models so we know what + // to query for via the eager loading query. We will add them to an array then + // execute a "where in" statement to gather up all of those related records. + foreach ($models as $model) { + if (! is_null($value = $model->{$this->foreignKey})) { + $keys[] = $value; + } + } + + sort($keys); + + return array_values(array_unique($keys)); + } + + /** + * Initialize the relation on a set of models. + * + * @param array $models + * @param string $relation + * @return array + */ + public function initRelation(array $models, $relation) + { + foreach ($models as $model) { + $model->setRelation($relation, $this->getDefaultFor($model)); + } + + return $models; + } + + /** + * Match the eagerly loaded results to their parents. + * + * @param array $models + * @param \Illuminate\Database\Eloquent\Collection $results + * @param string $relation + * @return array + */ + public function match(array $models, Collection $results, $relation) + { + $foreign = $this->foreignKey; + + $owner = $this->ownerKey; + + // First we will get to build a dictionary of the child models by their primary + // key of the relationship, then we can easily match the children back onto + // the parents using that dictionary and the primary key of the children. + $dictionary = []; + + foreach ($results as $result) { + $attribute = $this->getDictionaryKey($result->getAttribute($owner)); + + $dictionary[$attribute] = $result; + } + + // Once we have the dictionary constructed, we can loop through all the parents + // and match back onto their children using these keys of the dictionary and + // the primary key of the children to map them onto the correct instances. + foreach ($models as $model) { + $attribute = $this->getDictionaryKey($model->{$foreign}); + + if (isset($dictionary[$attribute])) { + $model->setRelation($relation, $dictionary[$attribute]); + } + } + + return $models; + } + + /** + * Associate the model instance to the given parent. + * + * @param \Illuminate\Database\Eloquent\Model|int|string|null $model + * @return \Illuminate\Database\Eloquent\Model + */ + public function associate($model) + { + $ownerKey = $model instanceof Model ? $model->getAttribute($this->ownerKey) : $model; + + $this->child->setAttribute($this->foreignKey, $ownerKey); + + if ($model instanceof Model) { + $this->child->setRelation($this->relationName, $model); + } else { + $this->child->unsetRelation($this->relationName); + } + + return $this->child; + } + + /** + * Dissociate previously associated model from the given parent. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function dissociate() + { + $this->child->setAttribute($this->foreignKey, null); + + return $this->child->setRelation($this->relationName, null); + } + + /** + * Alias of "dissociate" method. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function disassociate() + { + return $this->dissociate(); + } + + /** + * Add the constraints for a relationship query. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Builder $parentQuery + * @param array|mixed $columns + * @return \Illuminate\Database\Eloquent\Builder + */ + public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*']) + { + if ($parentQuery->getQuery()->from == $query->getQuery()->from) { + return $this->getRelationExistenceQueryForSelfRelation($query, $parentQuery, $columns); + } + + return $query->select($columns)->whereColumn( + $this->getQualifiedForeignKeyName(), '=', $query->qualifyColumn($this->ownerKey) + ); + } + + /** + * Add the constraints for a relationship query on the same table. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Builder $parentQuery + * @param array|mixed $columns + * @return \Illuminate\Database\Eloquent\Builder + */ + public function getRelationExistenceQueryForSelfRelation(Builder $query, Builder $parentQuery, $columns = ['*']) + { + $query->select($columns)->from( + $query->getModel()->getTable().' as '.$hash = $this->getRelationCountHash() + ); + + $query->getModel()->setTable($hash); + + return $query->whereColumn( + $hash.'.'.$this->ownerKey, '=', $this->getQualifiedForeignKeyName() + ); + } + + /** + * Determine if the related model has an auto-incrementing ID. + * + * @return bool + */ + protected function relationHasIncrementingId() + { + return $this->related->getIncrementing() && + in_array($this->related->getKeyType(), ['int', 'integer']); + } + + /** + * Make a new related instance for the given model. + * + * @param \Illuminate\Database\Eloquent\Model $parent + * @return \Illuminate\Database\Eloquent\Model + */ + protected function newRelatedInstanceFor(Model $parent) + { + return $this->related->newInstance(); + } + + /** + * Get the child of the relationship. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function getChild() + { + return $this->child; + } + + /** + * Get the foreign key of the relationship. + * + * @return string + */ + public function getForeignKeyName() + { + return $this->foreignKey; + } + + /** + * Get the fully qualified foreign key of the relationship. + * + * @return string + */ + public function getQualifiedForeignKeyName() + { + return $this->child->qualifyColumn($this->foreignKey); + } + + /** + * Get the key value of the child's foreign key. + * + * @return mixed + */ + public function getParentKey() + { + return $this->child->{$this->foreignKey}; + } + + /** + * Get the associated key of the relationship. + * + * @return string + */ + public function getOwnerKeyName() + { + return $this->ownerKey; + } + + /** + * Get the fully qualified associated key of the relationship. + * + * @return string + */ + public function getQualifiedOwnerKeyName() + { + return $this->related->qualifyColumn($this->ownerKey); + } + + /** + * Get the value of the model's associated key. + * + * @param \Illuminate\Database\Eloquent\Model $model + * @return mixed + */ + protected function getRelatedKeyFrom(Model $model) + { + return $model->{$this->ownerKey}; + } + + /** + * Get the name of the relationship. + * + * @return string + */ + public function getRelationName() + { + return $this->relationName; + } +} diff --git a/vendor/illuminate/database/Eloquent/Relations/BelongsToMany.php b/vendor/illuminate/database/Eloquent/Relations/BelongsToMany.php new file mode 100644 index 0000000..4cadd74 --- /dev/null +++ b/vendor/illuminate/database/Eloquent/Relations/BelongsToMany.php @@ -0,0 +1,1435 @@ +parentKey = $parentKey; + $this->relatedKey = $relatedKey; + $this->relationName = $relationName; + $this->relatedPivotKey = $relatedPivotKey; + $this->foreignPivotKey = $foreignPivotKey; + $this->table = $this->resolveTableName($table); + + parent::__construct($query, $parent); + } + + /** + * Attempt to resolve the intermediate table name from the given string. + * + * @param string $table + * @return string + */ + protected function resolveTableName($table) + { + if (! Str::contains($table, '\\') || ! class_exists($table)) { + return $table; + } + + $model = new $table; + + if (! $model instanceof Model) { + return $table; + } + + if (in_array(AsPivot::class, class_uses_recursive($model))) { + $this->using($table); + } + + return $model->getTable(); + } + + /** + * Set the base constraints on the relation query. + * + * @return void + */ + public function addConstraints() + { + $this->performJoin(); + + if (static::$constraints) { + $this->addWhereConstraints(); + } + } + + /** + * Set the join clause for the relation query. + * + * @param \Illuminate\Database\Eloquent\Builder|null $query + * @return $this + */ + protected function performJoin($query = null) + { + $query = $query ?: $this->query; + + // We need to join to the intermediate table on the related model's primary + // key column with the intermediate table's foreign key for the related + // model instance. Then we can set the "where" for the parent models. + $query->join( + $this->table, + $this->getQualifiedRelatedKeyName(), + '=', + $this->getQualifiedRelatedPivotKeyName() + ); + + return $this; + } + + /** + * Set the where clause for the relation query. + * + * @return $this + */ + protected function addWhereConstraints() + { + $this->query->where( + $this->getQualifiedForeignPivotKeyName(), '=', $this->parent->{$this->parentKey} + ); + + return $this; + } + + /** + * Set the constraints for an eager load of the relation. + * + * @param array $models + * @return void + */ + public function addEagerConstraints(array $models) + { + $whereIn = $this->whereInMethod($this->parent, $this->parentKey); + + $this->query->{$whereIn}( + $this->getQualifiedForeignPivotKeyName(), + $this->getKeys($models, $this->parentKey) + ); + } + + /** + * Initialize the relation on a set of models. + * + * @param array $models + * @param string $relation + * @return array + */ + public function initRelation(array $models, $relation) + { + foreach ($models as $model) { + $model->setRelation($relation, $this->related->newCollection()); + } + + return $models; + } + + /** + * Match the eagerly loaded results to their parents. + * + * @param array $models + * @param \Illuminate\Database\Eloquent\Collection $results + * @param string $relation + * @return array + */ + public function match(array $models, Collection $results, $relation) + { + $dictionary = $this->buildDictionary($results); + + // Once we have an array dictionary of child objects we can easily match the + // children back to their parent using the dictionary and the keys on the + // parent models. Then we should return these hydrated models back out. + foreach ($models as $model) { + $key = $this->getDictionaryKey($model->{$this->parentKey}); + + if (isset($dictionary[$key])) { + $model->setRelation( + $relation, $this->related->newCollection($dictionary[$key]) + ); + } + } + + return $models; + } + + /** + * Build model dictionary keyed by the relation's foreign key. + * + * @param \Illuminate\Database\Eloquent\Collection $results + * @return array + */ + protected function buildDictionary(Collection $results) + { + // First we will build a dictionary of child models keyed by the foreign key + // of the relation so that we will easily and quickly match them to their + // parents without having a possibly slow inner loops for every models. + $dictionary = []; + + foreach ($results as $result) { + $value = $this->getDictionaryKey($result->{$this->accessor}->{$this->foreignPivotKey}); + + $dictionary[$value][] = $result; + } + + return $dictionary; + } + + /** + * Get the class being used for pivot models. + * + * @return string + */ + public function getPivotClass() + { + return $this->using ?? Pivot::class; + } + + /** + * Specify the custom pivot model to use for the relationship. + * + * @param string $class + * @return $this + */ + public function using($class) + { + $this->using = $class; + + return $this; + } + + /** + * Specify the custom pivot accessor to use for the relationship. + * + * @param string $accessor + * @return $this + */ + public function as($accessor) + { + $this->accessor = $accessor; + + return $this; + } + + /** + * Set a where clause for a pivot table column. + * + * @param string $column + * @param mixed $operator + * @param mixed $value + * @param string $boolean + * @return $this + */ + public function wherePivot($column, $operator = null, $value = null, $boolean = 'and') + { + $this->pivotWheres[] = func_get_args(); + + return $this->where($this->qualifyPivotColumn($column), $operator, $value, $boolean); + } + + /** + * Set a "where between" clause for a pivot table column. + * + * @param string $column + * @param array $values + * @param string $boolean + * @param bool $not + * @return $this + */ + public function wherePivotBetween($column, array $values, $boolean = 'and', $not = false) + { + return $this->whereBetween($this->qualifyPivotColumn($column), $values, $boolean, $not); + } + + /** + * Set a "or where between" clause for a pivot table column. + * + * @param string $column + * @param array $values + * @return $this + */ + public function orWherePivotBetween($column, array $values) + { + return $this->wherePivotBetween($column, $values, 'or'); + } + + /** + * Set a "where pivot not between" clause for a pivot table column. + * + * @param string $column + * @param array $values + * @param string $boolean + * @return $this + */ + public function wherePivotNotBetween($column, array $values, $boolean = 'and') + { + return $this->wherePivotBetween($column, $values, $boolean, true); + } + + /** + * Set a "or where not between" clause for a pivot table column. + * + * @param string $column + * @param array $values + * @return $this + */ + public function orWherePivotNotBetween($column, array $values) + { + return $this->wherePivotBetween($column, $values, 'or', true); + } + + /** + * Set a "where in" clause for a pivot table column. + * + * @param string $column + * @param mixed $values + * @param string $boolean + * @param bool $not + * @return $this + */ + public function wherePivotIn($column, $values, $boolean = 'and', $not = false) + { + $this->pivotWhereIns[] = func_get_args(); + + return $this->whereIn($this->qualifyPivotColumn($column), $values, $boolean, $not); + } + + /** + * Set an "or where" clause for a pivot table column. + * + * @param string $column + * @param mixed $operator + * @param mixed $value + * @return $this + */ + public function orWherePivot($column, $operator = null, $value = null) + { + return $this->wherePivot($column, $operator, $value, 'or'); + } + + /** + * Set a where clause for a pivot table column. + * + * In addition, new pivot records will receive this value. + * + * @param string|array $column + * @param mixed $value + * @return $this + * + * @throws \InvalidArgumentException + */ + public function withPivotValue($column, $value = null) + { + if (is_array($column)) { + foreach ($column as $name => $value) { + $this->withPivotValue($name, $value); + } + + return $this; + } + + if (is_null($value)) { + throw new InvalidArgumentException('The provided value may not be null.'); + } + + $this->pivotValues[] = compact('column', 'value'); + + return $this->wherePivot($column, '=', $value); + } + + /** + * Set an "or where in" clause for a pivot table column. + * + * @param string $column + * @param mixed $values + * @return $this + */ + public function orWherePivotIn($column, $values) + { + return $this->wherePivotIn($column, $values, 'or'); + } + + /** + * Set a "where not in" clause for a pivot table column. + * + * @param string $column + * @param mixed $values + * @param string $boolean + * @return $this + */ + public function wherePivotNotIn($column, $values, $boolean = 'and') + { + return $this->wherePivotIn($column, $values, $boolean, true); + } + + /** + * Set an "or where not in" clause for a pivot table column. + * + * @param string $column + * @param mixed $values + * @return $this + */ + public function orWherePivotNotIn($column, $values) + { + return $this->wherePivotNotIn($column, $values, 'or'); + } + + /** + * Set a "where null" clause for a pivot table column. + * + * @param string $column + * @param string $boolean + * @param bool $not + * @return $this + */ + public function wherePivotNull($column, $boolean = 'and', $not = false) + { + $this->pivotWhereNulls[] = func_get_args(); + + return $this->whereNull($this->qualifyPivotColumn($column), $boolean, $not); + } + + /** + * Set a "where not null" clause for a pivot table column. + * + * @param string $column + * @param string $boolean + * @return $this + */ + public function wherePivotNotNull($column, $boolean = 'and') + { + return $this->wherePivotNull($column, $boolean, true); + } + + /** + * Set a "or where null" clause for a pivot table column. + * + * @param string $column + * @param bool $not + * @return $this + */ + public function orWherePivotNull($column, $not = false) + { + return $this->wherePivotNull($column, 'or', $not); + } + + /** + * Set a "or where not null" clause for a pivot table column. + * + * @param string $column + * @return $this + */ + public function orWherePivotNotNull($column) + { + return $this->orWherePivotNull($column, true); + } + + /** + * Add an "order by" clause for a pivot table column. + * + * @param string $column + * @param string $direction + * @return $this + */ + public function orderByPivot($column, $direction = 'asc') + { + return $this->orderBy($this->qualifyPivotColumn($column), $direction); + } + + /** + * Find a related model by its primary key or return a new instance of the related model. + * + * @param mixed $id + * @param array $columns + * @return \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model + */ + public function findOrNew($id, $columns = ['*']) + { + if (is_null($instance = $this->find($id, $columns))) { + $instance = $this->related->newInstance(); + } + + return $instance; + } + + /** + * Get the first related model record matching the attributes or instantiate it. + * + * @param array $attributes + * @return \Illuminate\Database\Eloquent\Model + */ + public function firstOrNew(array $attributes) + { + if (is_null($instance = $this->where($attributes)->first())) { + $instance = $this->related->newInstance($attributes); + } + + return $instance; + } + + /** + * Get the first related record matching the attributes or create it. + * + * @param array $attributes + * @param array $joining + * @param bool $touch + * @return \Illuminate\Database\Eloquent\Model + */ + public function firstOrCreate(array $attributes, array $joining = [], $touch = true) + { + if (is_null($instance = $this->where($attributes)->first())) { + $instance = $this->create($attributes, $joining, $touch); + } + + return $instance; + } + + /** + * Create or update a related record matching the attributes, and fill it with values. + * + * @param array $attributes + * @param array $values + * @param array $joining + * @param bool $touch + * @return \Illuminate\Database\Eloquent\Model + */ + public function updateOrCreate(array $attributes, array $values = [], array $joining = [], $touch = true) + { + if (is_null($instance = $this->where($attributes)->first())) { + return $this->create($values, $joining, $touch); + } + + $instance->fill($values); + + $instance->save(['touch' => false]); + + return $instance; + } + + /** + * Find a related model by its primary key. + * + * @param mixed $id + * @param array $columns + * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|null + */ + public function find($id, $columns = ['*']) + { + if (! $id instanceof Model && (is_array($id) || $id instanceof Arrayable)) { + return $this->findMany($id, $columns); + } + + return $this->where( + $this->getRelated()->getQualifiedKeyName(), '=', $this->parseId($id) + )->first($columns); + } + + /** + * Find multiple related models by their primary keys. + * + * @param \Illuminate\Contracts\Support\Arrayable|array $ids + * @param array $columns + * @return \Illuminate\Database\Eloquent\Collection + */ + public function findMany($ids, $columns = ['*']) + { + $ids = $ids instanceof Arrayable ? $ids->toArray() : $ids; + + if (empty($ids)) { + return $this->getRelated()->newCollection(); + } + + return $this->whereIn( + $this->getRelated()->getQualifiedKeyName(), $this->parseIds($ids) + )->get($columns); + } + + /** + * Find a related model by its primary key or throw an exception. + * + * @param mixed $id + * @param array $columns + * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection + * + * @throws \Illuminate\Database\Eloquent\ModelNotFoundException + */ + public function findOrFail($id, $columns = ['*']) + { + $result = $this->find($id, $columns); + + $id = $id instanceof Arrayable ? $id->toArray() : $id; + + if (is_array($id)) { + if (count($result) === count(array_unique($id))) { + return $result; + } + } elseif (! is_null($result)) { + return $result; + } + + throw (new ModelNotFoundException)->setModel(get_class($this->related), $id); + } + + /** + * Add a basic where clause to the query, and return the first result. + * + * @param \Closure|string|array $column + * @param mixed $operator + * @param mixed $value + * @param string $boolean + * @return \Illuminate\Database\Eloquent\Model|static + */ + public function firstWhere($column, $operator = null, $value = null, $boolean = 'and') + { + return $this->where($column, $operator, $value, $boolean)->first(); + } + + /** + * Execute the query and get the first result. + * + * @param array $columns + * @return mixed + */ + public function first($columns = ['*']) + { + $results = $this->take(1)->get($columns); + + return count($results) > 0 ? $results->first() : null; + } + + /** + * Execute the query and get the first result or throw an exception. + * + * @param array $columns + * @return \Illuminate\Database\Eloquent\Model|static + * + * @throws \Illuminate\Database\Eloquent\ModelNotFoundException + */ + public function firstOrFail($columns = ['*']) + { + if (! is_null($model = $this->first($columns))) { + return $model; + } + + throw (new ModelNotFoundException)->setModel(get_class($this->related)); + } + + /** + * Execute the query and get the first result or call a callback. + * + * @param \Closure|array $columns + * @param \Closure|null $callback + * @return \Illuminate\Database\Eloquent\Model|static|mixed + */ + public function firstOr($columns = ['*'], Closure $callback = null) + { + if ($columns instanceof Closure) { + $callback = $columns; + + $columns = ['*']; + } + + if (! is_null($model = $this->first($columns))) { + return $model; + } + + return $callback(); + } + + /** + * Get the results of the relationship. + * + * @return mixed + */ + public function getResults() + { + return ! is_null($this->parent->{$this->parentKey}) + ? $this->get() + : $this->related->newCollection(); + } + + /** + * Execute the query as a "select" statement. + * + * @param array $columns + * @return \Illuminate\Database\Eloquent\Collection + */ + public function get($columns = ['*']) + { + // First we'll add the proper select columns onto the query so it is run with + // the proper columns. Then, we will get the results and hydrate our pivot + // models with the result of those columns as a separate model relation. + $builder = $this->query->applyScopes(); + + $columns = $builder->getQuery()->columns ? [] : $columns; + + $models = $builder->addSelect( + $this->shouldSelect($columns) + )->getModels(); + + $this->hydratePivotRelation($models); + + // If we actually found models we will also eager load any relationships that + // have been specified as needing to be eager loaded. This will solve the + // n + 1 query problem for the developer and also increase performance. + if (count($models) > 0) { + $models = $builder->eagerLoadRelations($models); + } + + return $this->related->newCollection($models); + } + + /** + * Get the select columns for the relation query. + * + * @param array $columns + * @return array + */ + protected function shouldSelect(array $columns = ['*']) + { + if ($columns == ['*']) { + $columns = [$this->related->getTable().'.*']; + } + + return array_merge($columns, $this->aliasedPivotColumns()); + } + + /** + * Get the pivot columns for the relation. + * + * "pivot_" is prefixed ot each column for easy removal later. + * + * @return array + */ + protected function aliasedPivotColumns() + { + $defaults = [$this->foreignPivotKey, $this->relatedPivotKey]; + + return collect(array_merge($defaults, $this->pivotColumns))->map(function ($column) { + return $this->qualifyPivotColumn($column).' as pivot_'.$column; + })->unique()->all(); + } + + /** + * Get a paginator for the "select" statement. + * + * @param int|null $perPage + * @param array $columns + * @param string $pageName + * @param int|null $page + * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator + */ + public function paginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null) + { + $this->query->addSelect($this->shouldSelect($columns)); + + return tap($this->query->paginate($perPage, $columns, $pageName, $page), function ($paginator) { + $this->hydratePivotRelation($paginator->items()); + }); + } + + /** + * Paginate the given query into a simple paginator. + * + * @param int|null $perPage + * @param array $columns + * @param string $pageName + * @param int|null $page + * @return \Illuminate\Contracts\Pagination\Paginator + */ + public function simplePaginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null) + { + $this->query->addSelect($this->shouldSelect($columns)); + + return tap($this->query->simplePaginate($perPage, $columns, $pageName, $page), function ($paginator) { + $this->hydratePivotRelation($paginator->items()); + }); + } + + /** + * Paginate the given query into a cursor paginator. + * + * @param int|null $perPage + * @param array $columns + * @param string $cursorName + * @param string|null $cursor + * @return \Illuminate\Contracts\Pagination\CursorPaginator + */ + public function cursorPaginate($perPage = null, $columns = ['*'], $cursorName = 'cursor', $cursor = null) + { + $this->query->addSelect($this->shouldSelect($columns)); + + return tap($this->query->cursorPaginate($perPage, $columns, $cursorName, $cursor), function ($paginator) { + $this->hydratePivotRelation($paginator->items()); + }); + } + + /** + * Chunk the results of the query. + * + * @param int $count + * @param callable $callback + * @return bool + */ + public function chunk($count, callable $callback) + { + return $this->prepareQueryBuilder()->chunk($count, function ($results, $page) use ($callback) { + $this->hydratePivotRelation($results->all()); + + return $callback($results, $page); + }); + } + + /** + * Chunk the results of a query by comparing numeric IDs. + * + * @param int $count + * @param callable $callback + * @param string|null $column + * @param string|null $alias + * @return bool + */ + public function chunkById($count, callable $callback, $column = null, $alias = null) + { + $this->prepareQueryBuilder(); + + $column = $column ?? $this->getRelated()->qualifyColumn( + $this->getRelatedKeyName() + ); + + $alias = $alias ?? $this->getRelatedKeyName(); + + return $this->query->chunkById($count, function ($results) use ($callback) { + $this->hydratePivotRelation($results->all()); + + return $callback($results); + }, $column, $alias); + } + + /** + * Execute a callback over each item while chunking. + * + * @param callable $callback + * @param int $count + * @return bool + */ + public function each(callable $callback, $count = 1000) + { + return $this->chunk($count, function ($results) use ($callback) { + foreach ($results as $key => $value) { + if ($callback($value, $key) === false) { + return false; + } + } + }); + } + + /** + * Query lazily, by chunks of the given size. + * + * @param int $chunkSize + * @return \Illuminate\Support\LazyCollection + */ + public function lazy($chunkSize = 1000) + { + return $this->prepareQueryBuilder()->lazy($chunkSize)->map(function ($model) { + $this->hydratePivotRelation([$model]); + + return $model; + }); + } + + /** + * Query lazily, by chunking the results of a query by comparing IDs. + * + * @param int $chunkSize + * @param string|null $column + * @param string|null $alias + * @return \Illuminate\Support\LazyCollection + */ + public function lazyById($chunkSize = 1000, $column = null, $alias = null) + { + $column = $column ?? $this->getRelated()->qualifyColumn( + $this->getRelatedKeyName() + ); + + $alias = $alias ?? $this->getRelatedKeyName(); + + return $this->prepareQueryBuilder()->lazyById($chunkSize, $column, $alias)->map(function ($model) { + $this->hydratePivotRelation([$model]); + + return $model; + }); + } + + /** + * Get a lazy collection for the given query. + * + * @return \Illuminate\Support\LazyCollection + */ + public function cursor() + { + return $this->prepareQueryBuilder()->cursor()->map(function ($model) { + $this->hydratePivotRelation([$model]); + + return $model; + }); + } + + /** + * Prepare the query builder for query execution. + * + * @return \Illuminate\Database\Eloquent\Builder + */ + protected function prepareQueryBuilder() + { + return $this->query->addSelect($this->shouldSelect()); + } + + /** + * Hydrate the pivot table relationship on the models. + * + * @param array $models + * @return void + */ + protected function hydratePivotRelation(array $models) + { + // To hydrate the pivot relationship, we will just gather the pivot attributes + // and create a new Pivot model, which is basically a dynamic model that we + // will set the attributes, table, and connections on it so it will work. + foreach ($models as $model) { + $model->setRelation($this->accessor, $this->newExistingPivot( + $this->migratePivotAttributes($model) + )); + } + } + + /** + * Get the pivot attributes from a model. + * + * @param \Illuminate\Database\Eloquent\Model $model + * @return array + */ + protected function migratePivotAttributes(Model $model) + { + $values = []; + + foreach ($model->getAttributes() as $key => $value) { + // To get the pivots attributes we will just take any of the attributes which + // begin with "pivot_" and add those to this arrays, as well as unsetting + // them from the parent's models since they exist in a different table. + if (strpos($key, 'pivot_') === 0) { + $values[substr($key, 6)] = $value; + + unset($model->$key); + } + } + + return $values; + } + + /** + * If we're touching the parent model, touch. + * + * @return void + */ + public function touchIfTouching() + { + if ($this->touchingParent()) { + $this->getParent()->touch(); + } + + if ($this->getParent()->touches($this->relationName)) { + $this->touch(); + } + } + + /** + * Determine if we should touch the parent on sync. + * + * @return bool + */ + protected function touchingParent() + { + return $this->getRelated()->touches($this->guessInverseRelation()); + } + + /** + * Attempt to guess the name of the inverse of the relation. + * + * @return string + */ + protected function guessInverseRelation() + { + return Str::camel(Str::pluralStudly(class_basename($this->getParent()))); + } + + /** + * Touch all of the related models for the relationship. + * + * E.g.: Touch all roles associated with this user. + * + * @return void + */ + public function touch() + { + $key = $this->getRelated()->getKeyName(); + + $columns = [ + $this->related->getUpdatedAtColumn() => $this->related->freshTimestampString(), + ]; + + // If we actually have IDs for the relation, we will run the query to update all + // the related model's timestamps, to make sure these all reflect the changes + // to the parent models. This will help us keep any caching synced up here. + if (count($ids = $this->allRelatedIds()) > 0) { + $this->getRelated()->newQueryWithoutRelationships()->whereIn($key, $ids)->update($columns); + } + } + + /** + * Get all of the IDs for the related models. + * + * @return \Illuminate\Support\Collection + */ + public function allRelatedIds() + { + return $this->newPivotQuery()->pluck($this->relatedPivotKey); + } + + /** + * Save a new model and attach it to the parent model. + * + * @param \Illuminate\Database\Eloquent\Model $model + * @param array $pivotAttributes + * @param bool $touch + * @return \Illuminate\Database\Eloquent\Model + */ + public function save(Model $model, array $pivotAttributes = [], $touch = true) + { + $model->save(['touch' => false]); + + $this->attach($model, $pivotAttributes, $touch); + + return $model; + } + + /** + * Save an array of new models and attach them to the parent model. + * + * @param \Illuminate\Support\Collection|array $models + * @param array $pivotAttributes + * @return array + */ + public function saveMany($models, array $pivotAttributes = []) + { + foreach ($models as $key => $model) { + $this->save($model, (array) ($pivotAttributes[$key] ?? []), false); + } + + $this->touchIfTouching(); + + return $models; + } + + /** + * Create a new instance of the related model. + * + * @param array $attributes + * @param array $joining + * @param bool $touch + * @return \Illuminate\Database\Eloquent\Model + */ + public function create(array $attributes = [], array $joining = [], $touch = true) + { + $instance = $this->related->newInstance($attributes); + + // Once we save the related model, we need to attach it to the base model via + // through intermediate table so we'll use the existing "attach" method to + // accomplish this which will insert the record and any more attributes. + $instance->save(['touch' => false]); + + $this->attach($instance, $joining, $touch); + + return $instance; + } + + /** + * Create an array of new instances of the related models. + * + * @param iterable $records + * @param array $joinings + * @return array + */ + public function createMany(iterable $records, array $joinings = []) + { + $instances = []; + + foreach ($records as $key => $record) { + $instances[] = $this->create($record, (array) ($joinings[$key] ?? []), false); + } + + $this->touchIfTouching(); + + return $instances; + } + + /** + * Add the constraints for a relationship query. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Builder $parentQuery + * @param array|mixed $columns + * @return \Illuminate\Database\Eloquent\Builder + */ + public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*']) + { + if ($parentQuery->getQuery()->from == $query->getQuery()->from) { + return $this->getRelationExistenceQueryForSelfJoin($query, $parentQuery, $columns); + } + + $this->performJoin($query); + + return parent::getRelationExistenceQuery($query, $parentQuery, $columns); + } + + /** + * Add the constraints for a relationship query on the same table. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Builder $parentQuery + * @param array|mixed $columns + * @return \Illuminate\Database\Eloquent\Builder + */ + public function getRelationExistenceQueryForSelfJoin(Builder $query, Builder $parentQuery, $columns = ['*']) + { + $query->select($columns); + + $query->from($this->related->getTable().' as '.$hash = $this->getRelationCountHash()); + + $this->related->setTable($hash); + + $this->performJoin($query); + + return parent::getRelationExistenceQuery($query, $parentQuery, $columns); + } + + /** + * Get the key for comparing against the parent key in "has" query. + * + * @return string + */ + public function getExistenceCompareKey() + { + return $this->getQualifiedForeignPivotKeyName(); + } + + /** + * Specify that the pivot table has creation and update timestamps. + * + * @param mixed $createdAt + * @param mixed $updatedAt + * @return $this + */ + public function withTimestamps($createdAt = null, $updatedAt = null) + { + $this->withTimestamps = true; + + $this->pivotCreatedAt = $createdAt; + $this->pivotUpdatedAt = $updatedAt; + + return $this->withPivot($this->createdAt(), $this->updatedAt()); + } + + /** + * Get the name of the "created at" column. + * + * @return string + */ + public function createdAt() + { + return $this->pivotCreatedAt ?: $this->parent->getCreatedAtColumn(); + } + + /** + * Get the name of the "updated at" column. + * + * @return string + */ + public function updatedAt() + { + return $this->pivotUpdatedAt ?: $this->parent->getUpdatedAtColumn(); + } + + /** + * Get the foreign key for the relation. + * + * @return string + */ + public function getForeignPivotKeyName() + { + return $this->foreignPivotKey; + } + + /** + * Get the fully qualified foreign key for the relation. + * + * @return string + */ + public function getQualifiedForeignPivotKeyName() + { + return $this->qualifyPivotColumn($this->foreignPivotKey); + } + + /** + * Get the "related key" for the relation. + * + * @return string + */ + public function getRelatedPivotKeyName() + { + return $this->relatedPivotKey; + } + + /** + * Get the fully qualified "related key" for the relation. + * + * @return string + */ + public function getQualifiedRelatedPivotKeyName() + { + return $this->qualifyPivotColumn($this->relatedPivotKey); + } + + /** + * Get the parent key for the relationship. + * + * @return string + */ + public function getParentKeyName() + { + return $this->parentKey; + } + + /** + * Get the fully qualified parent key name for the relation. + * + * @return string + */ + public function getQualifiedParentKeyName() + { + return $this->parent->qualifyColumn($this->parentKey); + } + + /** + * Get the related key for the relationship. + * + * @return string + */ + public function getRelatedKeyName() + { + return $this->relatedKey; + } + + /** + * Get the fully qualified related key name for the relation. + * + * @return string + */ + public function getQualifiedRelatedKeyName() + { + return $this->related->qualifyColumn($this->relatedKey); + } + + /** + * Get the intermediate table for the relationship. + * + * @return string + */ + public function getTable() + { + return $this->table; + } + + /** + * Get the relationship name for the relationship. + * + * @return string + */ + public function getRelationName() + { + return $this->relationName; + } + + /** + * Get the name of the pivot accessor for this relationship. + * + * @return string + */ + public function getPivotAccessor() + { + return $this->accessor; + } + + /** + * Get the pivot columns for this relationship. + * + * @return array + */ + public function getPivotColumns() + { + return $this->pivotColumns; + } + + /** + * Qualify the given column name by the pivot table. + * + * @param string $column + * @return string + */ + public function qualifyPivotColumn($column) + { + return Str::contains($column, '.') + ? $column + : $this->table.'.'.$column; + } +} diff --git a/vendor/illuminate/database/Eloquent/Relations/Concerns/AsPivot.php b/vendor/illuminate/database/Eloquent/Relations/Concerns/AsPivot.php new file mode 100644 index 0000000..af9defb --- /dev/null +++ b/vendor/illuminate/database/Eloquent/Relations/Concerns/AsPivot.php @@ -0,0 +1,332 @@ +timestamps = $instance->hasTimestampAttributes($attributes); + + // The pivot model is a "dynamic" model since we will set the tables dynamically + // for the instance. This allows it work for any intermediate tables for the + // many to many relationship that are defined by this developer's classes. + $instance->setConnection($parent->getConnectionName()) + ->setTable($table) + ->forceFill($attributes) + ->syncOriginal(); + + // We store off the parent instance so we will access the timestamp column names + // for the model, since the pivot model timestamps aren't easily configurable + // from the developer's point of view. We can use the parents to get these. + $instance->pivotParent = $parent; + + $instance->exists = $exists; + + return $instance; + } + + /** + * Create a new pivot model from raw values returned from a query. + * + * @param \Illuminate\Database\Eloquent\Model $parent + * @param array $attributes + * @param string $table + * @param bool $exists + * @return static + */ + public static function fromRawAttributes(Model $parent, $attributes, $table, $exists = false) + { + $instance = static::fromAttributes($parent, [], $table, $exists); + + $instance->timestamps = $instance->hasTimestampAttributes($attributes); + + $instance->setRawAttributes($attributes, $exists); + + return $instance; + } + + /** + * Set the keys for a select query. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @return \Illuminate\Database\Eloquent\Builder + */ + protected function setKeysForSelectQuery($query) + { + if (isset($this->attributes[$this->getKeyName()])) { + return parent::setKeysForSelectQuery($query); + } + + $query->where($this->foreignKey, $this->getOriginal( + $this->foreignKey, $this->getAttribute($this->foreignKey) + )); + + return $query->where($this->relatedKey, $this->getOriginal( + $this->relatedKey, $this->getAttribute($this->relatedKey) + )); + } + + /** + * Set the keys for a save update query. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @return \Illuminate\Database\Eloquent\Builder + */ + protected function setKeysForSaveQuery($query) + { + return $this->setKeysForSelectQuery($query); + } + + /** + * Delete the pivot model record from the database. + * + * @return int + */ + public function delete() + { + if (isset($this->attributes[$this->getKeyName()])) { + return (int) parent::delete(); + } + + if ($this->fireModelEvent('deleting') === false) { + return 0; + } + + $this->touchOwners(); + + return tap($this->getDeleteQuery()->delete(), function () { + $this->exists = false; + + $this->fireModelEvent('deleted', false); + }); + } + + /** + * Get the query builder for a delete operation on the pivot. + * + * @return \Illuminate\Database\Eloquent\Builder + */ + protected function getDeleteQuery() + { + return $this->newQueryWithoutRelationships()->where([ + $this->foreignKey => $this->getOriginal($this->foreignKey, $this->getAttribute($this->foreignKey)), + $this->relatedKey => $this->getOriginal($this->relatedKey, $this->getAttribute($this->relatedKey)), + ]); + } + + /** + * Get the table associated with the model. + * + * @return string + */ + public function getTable() + { + if (! isset($this->table)) { + $this->setTable(str_replace( + '\\', '', Str::snake(Str::singular(class_basename($this))) + )); + } + + return $this->table; + } + + /** + * Get the foreign key column name. + * + * @return string + */ + public function getForeignKey() + { + return $this->foreignKey; + } + + /** + * Get the "related key" column name. + * + * @return string + */ + public function getRelatedKey() + { + return $this->relatedKey; + } + + /** + * Get the "related key" column name. + * + * @return string + */ + public function getOtherKey() + { + return $this->getRelatedKey(); + } + + /** + * Set the key names for the pivot model instance. + * + * @param string $foreignKey + * @param string $relatedKey + * @return $this + */ + public function setPivotKeys($foreignKey, $relatedKey) + { + $this->foreignKey = $foreignKey; + + $this->relatedKey = $relatedKey; + + return $this; + } + + /** + * Determine if the pivot model or given attributes has timestamp attributes. + * + * @param array|null $attributes + * @return bool + */ + public function hasTimestampAttributes($attributes = null) + { + return array_key_exists($this->getCreatedAtColumn(), $attributes ?? $this->attributes); + } + + /** + * Get the name of the "created at" column. + * + * @return string + */ + public function getCreatedAtColumn() + { + return $this->pivotParent + ? $this->pivotParent->getCreatedAtColumn() + : parent::getCreatedAtColumn(); + } + + /** + * Get the name of the "updated at" column. + * + * @return string + */ + public function getUpdatedAtColumn() + { + return $this->pivotParent + ? $this->pivotParent->getUpdatedAtColumn() + : parent::getUpdatedAtColumn(); + } + + /** + * Get the queueable identity for the entity. + * + * @return mixed + */ + public function getQueueableId() + { + if (isset($this->attributes[$this->getKeyName()])) { + return $this->getKey(); + } + + return sprintf( + '%s:%s:%s:%s', + $this->foreignKey, $this->getAttribute($this->foreignKey), + $this->relatedKey, $this->getAttribute($this->relatedKey) + ); + } + + /** + * Get a new query to restore one or more models by their queueable IDs. + * + * @param int[]|string[]|string $ids + * @return \Illuminate\Database\Eloquent\Builder + */ + public function newQueryForRestoration($ids) + { + if (is_array($ids)) { + return $this->newQueryForCollectionRestoration($ids); + } + + if (! Str::contains($ids, ':')) { + return parent::newQueryForRestoration($ids); + } + + $segments = explode(':', $ids); + + return $this->newQueryWithoutScopes() + ->where($segments[0], $segments[1]) + ->where($segments[2], $segments[3]); + } + + /** + * Get a new query to restore multiple models by their queueable IDs. + * + * @param int[]|string[] $ids + * @return \Illuminate\Database\Eloquent\Builder + */ + protected function newQueryForCollectionRestoration(array $ids) + { + $ids = array_values($ids); + + if (! Str::contains($ids[0], ':')) { + return parent::newQueryForRestoration($ids); + } + + $query = $this->newQueryWithoutScopes(); + + foreach ($ids as $id) { + $segments = explode(':', $id); + + $query->orWhere(function ($query) use ($segments) { + return $query->where($segments[0], $segments[1]) + ->where($segments[2], $segments[3]); + }); + } + + return $query; + } + + /** + * Unset all the loaded relations for the instance. + * + * @return $this + */ + public function unsetRelations() + { + $this->pivotParent = null; + $this->relations = []; + + return $this; + } +} diff --git a/vendor/illuminate/database/Eloquent/Relations/Concerns/CanBeOneOfMany.php b/vendor/illuminate/database/Eloquent/Relations/Concerns/CanBeOneOfMany.php new file mode 100644 index 0000000..6f6b1b7 --- /dev/null +++ b/vendor/illuminate/database/Eloquent/Relations/Concerns/CanBeOneOfMany.php @@ -0,0 +1,313 @@ +isOneOfMany = true; + + $this->relationName = $relation ?: $this->getDefaultOneOfManyJoinAlias( + $this->guessRelationship() + ); + + $keyName = $this->query->getModel()->getKeyName(); + + $columns = is_string($columns = $column) ? [ + $column => $aggregate, + $keyName => $aggregate, + ] : $column; + + if (! array_key_exists($keyName, $columns)) { + $columns[$keyName] = 'MAX'; + } + + if ($aggregate instanceof Closure) { + $closure = $aggregate; + } + + foreach ($columns as $column => $aggregate) { + if (! in_array(strtolower($aggregate), ['min', 'max'])) { + throw new InvalidArgumentException("Invalid aggregate [{$aggregate}] used within ofMany relation. Available aggregates: MIN, MAX"); + } + + $subQuery = $this->newOneOfManySubQuery( + $this->getOneOfManySubQuerySelectColumns(), + $column, $aggregate + ); + + if (isset($previous)) { + $this->addOneOfManyJoinSubQuery($subQuery, $previous['subQuery'], $previous['column']); + } + + if (isset($closure)) { + $closure($subQuery); + } + + if (! isset($previous)) { + $this->oneOfManySubQuery = $subQuery; + } + + if (array_key_last($columns) == $column) { + $this->addOneOfManyJoinSubQuery($this->query, $subQuery, $column); + } + + $previous = [ + 'subQuery' => $subQuery, + 'column' => $column, + ]; + } + + $this->addConstraints(); + + $columns = $this->query->getQuery()->columns; + + if (is_null($columns) || $columns === ['*']) { + $this->select([$this->qualifyColumn('*')]); + } + + return $this; + } + + /** + * Indicate that the relation is the latest single result of a larger one-to-many relationship. + * + * @param string|array|null $column + * @param string|Closure|null $aggregate + * @param string|null $relation + * @return $this + */ + public function latestOfMany($column = 'id', $relation = null) + { + return $this->ofMany(collect(Arr::wrap($column))->mapWithKeys(function ($column) { + return [$column => 'MAX']; + })->all(), 'MAX', $relation); + } + + /** + * Indicate that the relation is the oldest single result of a larger one-to-many relationship. + * + * @param string|array|null $column + * @param string|Closure|null $aggregate + * @param string|null $relation + * @return $this + */ + public function oldestOfMany($column = 'id', $relation = null) + { + return $this->ofMany(collect(Arr::wrap($column))->mapWithKeys(function ($column) { + return [$column => 'MIN']; + })->all(), 'MIN', $relation); + } + + /** + * Get the default alias for the one of many inner join clause. + * + * @param string $relation + * @return string + */ + protected function getDefaultOneOfManyJoinAlias($relation) + { + return $relation == $this->query->getModel()->getTable() + ? $relation.'_of_many' + : $relation; + } + + /** + * Get a new query for the related model, grouping the query by the given column, often the foreign key of the relationship. + * + * @param string|array $groupBy + * @param string|null $column + * @param string|null $aggregate + * @return \Illuminate\Database\Eloquent\Builder + */ + protected function newOneOfManySubQuery($groupBy, $column = null, $aggregate = null) + { + $subQuery = $this->query->getModel() + ->newQuery() + ->withoutGlobalScopes($this->removedScopes()); + + foreach (Arr::wrap($groupBy) as $group) { + $subQuery->groupBy($this->qualifyRelatedColumn($group)); + } + + if (! is_null($column)) { + $subQuery->selectRaw($aggregate.'('.$subQuery->getQuery()->grammar->wrap($subQuery->qualifyColumn($column)).') as '.$subQuery->getQuery()->grammar->wrap($column.'_aggregate')); + } + + $this->addOneOfManySubQueryConstraints($subQuery, $groupBy, $column, $aggregate); + + return $subQuery; + } + + /** + * Add the join subquery to the given query on the given column and the relationship's foreign key. + * + * @param \Illuminate\Database\Eloquent\Builder $parent + * @param \Illuminate\Database\Eloquent\Builder $subQuery + * @param string $on + * @return void + */ + protected function addOneOfManyJoinSubQuery(Builder $parent, Builder $subQuery, $on) + { + $parent->beforeQuery(function ($parent) use ($subQuery, $on) { + $subQuery->applyBeforeQueryCallbacks(); + + $parent->joinSub($subQuery, $this->relationName, function ($join) use ($on) { + $join->on($this->qualifySubSelectColumn($on.'_aggregate'), '=', $this->qualifyRelatedColumn($on)); + + $this->addOneOfManyJoinSubQueryConstraints($join, $on); + }); + }); + } + + /** + * Merge the relationship query joins to the given query builder. + * + * @param \Illuminate\Database\Eloquent\Builder $builder + * @return void + */ + protected function mergeOneOfManyJoinsTo(Builder $query) + { + $query->getQuery()->beforeQueryCallbacks = $this->query->getQuery()->beforeQueryCallbacks; + + $query->applyBeforeQueryCallbacks(); + } + + /** + * Get the query builder that will contain the relationship constraints. + * + * @return \Illuminate\Database\Eloquent\Builder + */ + protected function getRelationQuery() + { + return $this->isOneOfMany() + ? $this->oneOfManySubQuery + : $this->query; + } + + /** + * Get the one of many inner join subselect builder instance. + * + * @return \Illuminate\Database\Eloquent\Builder|void + */ + public function getOneOfManySubQuery() + { + return $this->oneOfManySubQuery; + } + + /** + * Get the qualified column name for the one-of-many relationship using the subselect join query's alias. + * + * @param string $column + * @return string + */ + public function qualifySubSelectColumn($column) + { + return $this->getRelationName().'.'.last(explode('.', $column)); + } + + /** + * Qualify related column using the related table name if it is not already qualified. + * + * @param string $column + * @return string + */ + protected function qualifyRelatedColumn($column) + { + return Str::contains($column, '.') ? $column : $this->query->getModel()->getTable().'.'.$column; + } + + /** + * Guess the "hasOne" relationship's name via backtrace. + * + * @return string + */ + protected function guessRelationship() + { + return debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3)[2]['function']; + } + + /** + * Determine whether the relationship is a one-of-many relationship. + * + * @return bool + */ + public function isOneOfMany() + { + return $this->isOneOfMany; + } + + /** + * Get the name of the relationship. + * + * @return string + */ + public function getRelationName() + { + return $this->relationName; + } +} diff --git a/vendor/illuminate/database/Eloquent/Relations/Concerns/ComparesRelatedModels.php b/vendor/illuminate/database/Eloquent/Relations/Concerns/ComparesRelatedModels.php new file mode 100644 index 0000000..ca06698 --- /dev/null +++ b/vendor/illuminate/database/Eloquent/Relations/Concerns/ComparesRelatedModels.php @@ -0,0 +1,77 @@ +compareKeys($this->getParentKey(), $this->getRelatedKeyFrom($model)) && + $this->related->getTable() === $model->getTable() && + $this->related->getConnectionName() === $model->getConnectionName(); + + if ($match && $this instanceof SupportsPartialRelations && $this->isOneOfMany()) { + return $this->query + ->whereKey($model->getKey()) + ->exists(); + } + + return $match; + } + + /** + * Determine if the model is not the related instance of the relationship. + * + * @param \Illuminate\Database\Eloquent\Model|null $model + * @return bool + */ + public function isNot($model) + { + return ! $this->is($model); + } + + /** + * Get the value of the parent model's key. + * + * @return mixed + */ + abstract public function getParentKey(); + + /** + * Get the value of the model's related key. + * + * @param \Illuminate\Database\Eloquent\Model $model + * @return mixed + */ + abstract protected function getRelatedKeyFrom(Model $model); + + /** + * Compare the parent key with the related key. + * + * @param mixed $parentKey + * @param mixed $relatedKey + * @return bool + */ + protected function compareKeys($parentKey, $relatedKey) + { + if (empty($parentKey) || empty($relatedKey)) { + return false; + } + + if (is_int($parentKey) || is_int($relatedKey)) { + return (int) $parentKey === (int) $relatedKey; + } + + return $parentKey === $relatedKey; + } +} diff --git a/vendor/illuminate/database/Eloquent/Relations/Concerns/InteractsWithDictionary.php b/vendor/illuminate/database/Eloquent/Relations/Concerns/InteractsWithDictionary.php new file mode 100644 index 0000000..ba4ae9a --- /dev/null +++ b/vendor/illuminate/database/Eloquent/Relations/Concerns/InteractsWithDictionary.php @@ -0,0 +1,35 @@ +__toString(); + } + + if (function_exists('enum_exists') && + $attribute instanceof BackedEnum) { + return $attribute->value; + } + + throw new InvalidArgumentException('Model attribute value is an object but does not have a __toString method.'); + } + + return $attribute; + } +} diff --git a/vendor/illuminate/database/Eloquent/Relations/Concerns/InteractsWithPivotTable.php b/vendor/illuminate/database/Eloquent/Relations/Concerns/InteractsWithPivotTable.php new file mode 100644 index 0000000..7a1cbfa --- /dev/null +++ b/vendor/illuminate/database/Eloquent/Relations/Concerns/InteractsWithPivotTable.php @@ -0,0 +1,686 @@ + [], 'detached' => [], + ]; + + $records = $this->formatRecordsList($this->parseIds($ids)); + + // Next, we will determine which IDs should get removed from the join table by + // checking which of the given ID/records is in the list of current records + // and removing all of those rows from this "intermediate" joining table. + $detach = array_values(array_intersect( + $this->newPivotQuery()->pluck($this->relatedPivotKey)->all(), + array_keys($records) + )); + + if (count($detach) > 0) { + $this->detach($detach, false); + + $changes['detached'] = $this->castKeys($detach); + } + + // Finally, for all of the records which were not "detached", we'll attach the + // records into the intermediate table. Then, we will add those attaches to + // this change list and get ready to return these results to the callers. + $attach = array_diff_key($records, array_flip($detach)); + + if (count($attach) > 0) { + $this->attach($attach, [], false); + + $changes['attached'] = array_keys($attach); + } + + // Once we have finished attaching or detaching the records, we will see if we + // have done any attaching or detaching, and if we have we will touch these + // relationships if they are configured to touch on any database updates. + if ($touch && (count($changes['attached']) || + count($changes['detached']))) { + $this->touchIfTouching(); + } + + return $changes; + } + + /** + * Sync the intermediate tables with a list of IDs without detaching. + * + * @param \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model|array $ids + * @return array + */ + public function syncWithoutDetaching($ids) + { + return $this->sync($ids, false); + } + + /** + * Sync the intermediate tables with a list of IDs or collection of models. + * + * @param \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model|array $ids + * @param bool $detaching + * @return array + */ + public function sync($ids, $detaching = true) + { + $changes = [ + 'attached' => [], 'detached' => [], 'updated' => [], + ]; + + // First we need to attach any of the associated models that are not currently + // in this joining table. We'll spin through the given IDs, checking to see + // if they exist in the array of current ones, and if not we will insert. + $current = $this->getCurrentlyAttachedPivots() + ->pluck($this->relatedPivotKey)->all(); + + $detach = array_diff($current, array_keys( + $records = $this->formatRecordsList($this->parseIds($ids)) + )); + + // Next, we will take the differences of the currents and given IDs and detach + // all of the entities that exist in the "current" array but are not in the + // array of the new IDs given to the method which will complete the sync. + if ($detaching && count($detach) > 0) { + $this->detach($detach); + + $changes['detached'] = $this->castKeys($detach); + } + + // Now we are finally ready to attach the new records. Note that we'll disable + // touching until after the entire operation is complete so we don't fire a + // ton of touch operations until we are totally done syncing the records. + $changes = array_merge( + $changes, $this->attachNew($records, $current, false) + ); + + // Once we have finished attaching or detaching the records, we will see if we + // have done any attaching or detaching, and if we have we will touch these + // relationships if they are configured to touch on any database updates. + if (count($changes['attached']) || + count($changes['updated']) || + count($changes['detached'])) { + $this->touchIfTouching(); + } + + return $changes; + } + + /** + * Sync the intermediate tables with a list of IDs or collection of models with the given pivot values. + * + * @param \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model|array $ids + * @param array $values + * @param bool $detaching + * @return array + */ + public function syncWithPivotValues($ids, array $values, bool $detaching = true) + { + return $this->sync(collect($this->parseIds($ids))->mapWithKeys(function ($id) use ($values) { + return [$id => $values]; + }), $detaching); + } + + /** + * Format the sync / toggle record list so that it is keyed by ID. + * + * @param array $records + * @return array + */ + protected function formatRecordsList(array $records) + { + return collect($records)->mapWithKeys(function ($attributes, $id) { + if (! is_array($attributes)) { + [$id, $attributes] = [$attributes, []]; + } + + return [$id => $attributes]; + })->all(); + } + + /** + * Attach all of the records that aren't in the given current records. + * + * @param array $records + * @param array $current + * @param bool $touch + * @return array + */ + protected function attachNew(array $records, array $current, $touch = true) + { + $changes = ['attached' => [], 'updated' => []]; + + foreach ($records as $id => $attributes) { + // If the ID is not in the list of existing pivot IDs, we will insert a new pivot + // record, otherwise, we will just update this existing record on this joining + // table, so that the developers will easily update these records pain free. + if (! in_array($id, $current)) { + $this->attach($id, $attributes, $touch); + + $changes['attached'][] = $this->castKey($id); + } + + // Now we'll try to update an existing pivot record with the attributes that were + // given to the method. If the model is actually updated we will add it to the + // list of updated pivot records so we return them back out to the consumer. + elseif (count($attributes) > 0 && + $this->updateExistingPivot($id, $attributes, $touch)) { + $changes['updated'][] = $this->castKey($id); + } + } + + return $changes; + } + + /** + * Update an existing pivot record on the table. + * + * @param mixed $id + * @param array $attributes + * @param bool $touch + * @return int + */ + public function updateExistingPivot($id, array $attributes, $touch = true) + { + if ($this->using && + empty($this->pivotWheres) && + empty($this->pivotWhereIns) && + empty($this->pivotWhereNulls)) { + return $this->updateExistingPivotUsingCustomClass($id, $attributes, $touch); + } + + if (in_array($this->updatedAt(), $this->pivotColumns)) { + $attributes = $this->addTimestampsToAttachment($attributes, true); + } + + $updated = $this->newPivotStatementForId($this->parseId($id))->update( + $this->castAttributes($attributes) + ); + + if ($touch) { + $this->touchIfTouching(); + } + + return $updated; + } + + /** + * Update an existing pivot record on the table via a custom class. + * + * @param mixed $id + * @param array $attributes + * @param bool $touch + * @return int + */ + protected function updateExistingPivotUsingCustomClass($id, array $attributes, $touch) + { + $pivot = $this->getCurrentlyAttachedPivots() + ->where($this->foreignPivotKey, $this->parent->{$this->parentKey}) + ->where($this->relatedPivotKey, $this->parseId($id)) + ->first(); + + $updated = $pivot ? $pivot->fill($attributes)->isDirty() : false; + + if ($updated) { + $pivot->save(); + } + + if ($touch) { + $this->touchIfTouching(); + } + + return (int) $updated; + } + + /** + * Attach a model to the parent. + * + * @param mixed $id + * @param array $attributes + * @param bool $touch + * @return void + */ + public function attach($id, array $attributes = [], $touch = true) + { + if ($this->using) { + $this->attachUsingCustomClass($id, $attributes); + } else { + // Here we will insert the attachment records into the pivot table. Once we have + // inserted the records, we will touch the relationships if necessary and the + // function will return. We can parse the IDs before inserting the records. + $this->newPivotStatement()->insert($this->formatAttachRecords( + $this->parseIds($id), $attributes + )); + } + + if ($touch) { + $this->touchIfTouching(); + } + } + + /** + * Attach a model to the parent using a custom class. + * + * @param mixed $id + * @param array $attributes + * @return void + */ + protected function attachUsingCustomClass($id, array $attributes) + { + $records = $this->formatAttachRecords( + $this->parseIds($id), $attributes + ); + + foreach ($records as $record) { + $this->newPivot($record, false)->save(); + } + } + + /** + * Create an array of records to insert into the pivot table. + * + * @param array $ids + * @param array $attributes + * @return array + */ + protected function formatAttachRecords($ids, array $attributes) + { + $records = []; + + $hasTimestamps = ($this->hasPivotColumn($this->createdAt()) || + $this->hasPivotColumn($this->updatedAt())); + + // To create the attachment records, we will simply spin through the IDs given + // and create a new record to insert for each ID. Each ID may actually be a + // key in the array, with extra attributes to be placed in other columns. + foreach ($ids as $key => $value) { + $records[] = $this->formatAttachRecord( + $key, $value, $attributes, $hasTimestamps + ); + } + + return $records; + } + + /** + * Create a full attachment record payload. + * + * @param int $key + * @param mixed $value + * @param array $attributes + * @param bool $hasTimestamps + * @return array + */ + protected function formatAttachRecord($key, $value, $attributes, $hasTimestamps) + { + [$id, $attributes] = $this->extractAttachIdAndAttributes($key, $value, $attributes); + + return array_merge( + $this->baseAttachRecord($id, $hasTimestamps), $this->castAttributes($attributes) + ); + } + + /** + * Get the attach record ID and extra attributes. + * + * @param mixed $key + * @param mixed $value + * @param array $attributes + * @return array + */ + protected function extractAttachIdAndAttributes($key, $value, array $attributes) + { + return is_array($value) + ? [$key, array_merge($value, $attributes)] + : [$value, $attributes]; + } + + /** + * Create a new pivot attachment record. + * + * @param int $id + * @param bool $timed + * @return array + */ + protected function baseAttachRecord($id, $timed) + { + $record[$this->relatedPivotKey] = $id; + + $record[$this->foreignPivotKey] = $this->parent->{$this->parentKey}; + + // If the record needs to have creation and update timestamps, we will make + // them by calling the parent model's "freshTimestamp" method which will + // provide us with a fresh timestamp in this model's preferred format. + if ($timed) { + $record = $this->addTimestampsToAttachment($record); + } + + foreach ($this->pivotValues as $value) { + $record[$value['column']] = $value['value']; + } + + return $record; + } + + /** + * Set the creation and update timestamps on an attach record. + * + * @param array $record + * @param bool $exists + * @return array + */ + protected function addTimestampsToAttachment(array $record, $exists = false) + { + $fresh = $this->parent->freshTimestamp(); + + if ($this->using) { + $pivotModel = new $this->using; + + $fresh = $fresh->format($pivotModel->getDateFormat()); + } + + if (! $exists && $this->hasPivotColumn($this->createdAt())) { + $record[$this->createdAt()] = $fresh; + } + + if ($this->hasPivotColumn($this->updatedAt())) { + $record[$this->updatedAt()] = $fresh; + } + + return $record; + } + + /** + * Determine whether the given column is defined as a pivot column. + * + * @param string $column + * @return bool + */ + public function hasPivotColumn($column) + { + return in_array($column, $this->pivotColumns); + } + + /** + * Detach models from the relationship. + * + * @param mixed $ids + * @param bool $touch + * @return int + */ + public function detach($ids = null, $touch = true) + { + if ($this->using && + ! empty($ids) && + empty($this->pivotWheres) && + empty($this->pivotWhereIns) && + empty($this->pivotWhereNulls)) { + $results = $this->detachUsingCustomClass($ids); + } else { + $query = $this->newPivotQuery(); + + // If associated IDs were passed to the method we will only delete those + // associations, otherwise all of the association ties will be broken. + // We'll return the numbers of affected rows when we do the deletes. + if (! is_null($ids)) { + $ids = $this->parseIds($ids); + + if (empty($ids)) { + return 0; + } + + $query->whereIn($this->getQualifiedRelatedPivotKeyName(), (array) $ids); + } + + // Once we have all of the conditions set on the statement, we are ready + // to run the delete on the pivot table. Then, if the touch parameter + // is true, we will go ahead and touch all related models to sync. + $results = $query->delete(); + } + + if ($touch) { + $this->touchIfTouching(); + } + + return $results; + } + + /** + * Detach models from the relationship using a custom class. + * + * @param mixed $ids + * @return int + */ + protected function detachUsingCustomClass($ids) + { + $results = 0; + + foreach ($this->parseIds($ids) as $id) { + $results += $this->newPivot([ + $this->foreignPivotKey => $this->parent->{$this->parentKey}, + $this->relatedPivotKey => $id, + ], true)->delete(); + } + + return $results; + } + + /** + * Get the pivot models that are currently attached. + * + * @return \Illuminate\Support\Collection + */ + protected function getCurrentlyAttachedPivots() + { + return $this->newPivotQuery()->get()->map(function ($record) { + $class = $this->using ?: Pivot::class; + + $pivot = $class::fromRawAttributes($this->parent, (array) $record, $this->getTable(), true); + + return $pivot->setPivotKeys($this->foreignPivotKey, $this->relatedPivotKey); + }); + } + + /** + * Create a new pivot model instance. + * + * @param array $attributes + * @param bool $exists + * @return \Illuminate\Database\Eloquent\Relations\Pivot + */ + public function newPivot(array $attributes = [], $exists = false) + { + $pivot = $this->related->newPivot( + $this->parent, $attributes, $this->table, $exists, $this->using + ); + + return $pivot->setPivotKeys($this->foreignPivotKey, $this->relatedPivotKey); + } + + /** + * Create a new existing pivot model instance. + * + * @param array $attributes + * @return \Illuminate\Database\Eloquent\Relations\Pivot + */ + public function newExistingPivot(array $attributes = []) + { + return $this->newPivot($attributes, true); + } + + /** + * Get a new plain query builder for the pivot table. + * + * @return \Illuminate\Database\Query\Builder + */ + public function newPivotStatement() + { + return $this->query->getQuery()->newQuery()->from($this->table); + } + + /** + * Get a new pivot statement for a given "other" ID. + * + * @param mixed $id + * @return \Illuminate\Database\Query\Builder + */ + public function newPivotStatementForId($id) + { + return $this->newPivotQuery()->whereIn($this->relatedPivotKey, $this->parseIds($id)); + } + + /** + * Create a new query builder for the pivot table. + * + * @return \Illuminate\Database\Query\Builder + */ + public function newPivotQuery() + { + $query = $this->newPivotStatement(); + + foreach ($this->pivotWheres as $arguments) { + $query->where(...$arguments); + } + + foreach ($this->pivotWhereIns as $arguments) { + $query->whereIn(...$arguments); + } + + foreach ($this->pivotWhereNulls as $arguments) { + $query->whereNull(...$arguments); + } + + return $query->where($this->getQualifiedForeignPivotKeyName(), $this->parent->{$this->parentKey}); + } + + /** + * Set the columns on the pivot table to retrieve. + * + * @param array|mixed $columns + * @return $this + */ + public function withPivot($columns) + { + $this->pivotColumns = array_merge( + $this->pivotColumns, is_array($columns) ? $columns : func_get_args() + ); + + return $this; + } + + /** + * Get all of the IDs from the given mixed value. + * + * @param mixed $value + * @return array + */ + protected function parseIds($value) + { + if ($value instanceof Model) { + return [$value->{$this->relatedKey}]; + } + + if ($value instanceof Collection) { + return $value->pluck($this->relatedKey)->all(); + } + + if ($value instanceof BaseCollection) { + return $value->toArray(); + } + + return (array) $value; + } + + /** + * Get the ID from the given mixed value. + * + * @param mixed $value + * @return mixed + */ + protected function parseId($value) + { + return $value instanceof Model ? $value->{$this->relatedKey} : $value; + } + + /** + * Cast the given keys to integers if they are numeric and string otherwise. + * + * @param array $keys + * @return array + */ + protected function castKeys(array $keys) + { + return array_map(function ($v) { + return $this->castKey($v); + }, $keys); + } + + /** + * Cast the given key to convert to primary key type. + * + * @param mixed $key + * @return mixed + */ + protected function castKey($key) + { + return $this->getTypeSwapValue( + $this->related->getKeyType(), + $key + ); + } + + /** + * Cast the given pivot attributes. + * + * @param array $attributes + * @return array + */ + protected function castAttributes($attributes) + { + return $this->using + ? $this->newPivot()->fill($attributes)->getAttributes() + : $attributes; + } + + /** + * Converts a given value to a given type value. + * + * @param string $type + * @param mixed $value + * @return mixed + */ + protected function getTypeSwapValue($type, $value) + { + switch (strtolower($type)) { + case 'int': + case 'integer': + return (int) $value; + case 'real': + case 'float': + case 'double': + return (float) $value; + case 'string': + return (string) $value; + default: + return $value; + } + } +} diff --git a/vendor/illuminate/database/Eloquent/Relations/Concerns/SupportsDefaultModels.php b/vendor/illuminate/database/Eloquent/Relations/Concerns/SupportsDefaultModels.php new file mode 100644 index 0000000..74e758f --- /dev/null +++ b/vendor/illuminate/database/Eloquent/Relations/Concerns/SupportsDefaultModels.php @@ -0,0 +1,63 @@ +withDefault = $callback; + + return $this; + } + + /** + * Get the default value for this relation. + * + * @param \Illuminate\Database\Eloquent\Model $parent + * @return \Illuminate\Database\Eloquent\Model|null + */ + protected function getDefaultFor(Model $parent) + { + if (! $this->withDefault) { + return; + } + + $instance = $this->newRelatedInstanceFor($parent); + + if (is_callable($this->withDefault)) { + return call_user_func($this->withDefault, $instance, $parent) ?: $instance; + } + + if (is_array($this->withDefault)) { + $instance->forceFill($this->withDefault); + } + + return $instance; + } +} diff --git a/vendor/illuminate/database/Eloquent/Relations/HasMany.php b/vendor/illuminate/database/Eloquent/Relations/HasMany.php new file mode 100644 index 0000000..b005d4f --- /dev/null +++ b/vendor/illuminate/database/Eloquent/Relations/HasMany.php @@ -0,0 +1,49 @@ +getParentKey()) + ? $this->query->get() + : $this->related->newCollection(); + } + + /** + * Initialize the relation on a set of models. + * + * @param array $models + * @param string $relation + * @return array + */ + public function initRelation(array $models, $relation) + { + foreach ($models as $model) { + $model->setRelation($relation, $this->related->newCollection()); + } + + return $models; + } + + /** + * Match the eagerly loaded results to their parents. + * + * @param array $models + * @param \Illuminate\Database\Eloquent\Collection $results + * @param string $relation + * @return array + */ + public function match(array $models, Collection $results, $relation) + { + return $this->matchMany($models, $results, $relation); + } +} diff --git a/vendor/illuminate/database/Eloquent/Relations/HasManyThrough.php b/vendor/illuminate/database/Eloquent/Relations/HasManyThrough.php new file mode 100644 index 0000000..2074816 --- /dev/null +++ b/vendor/illuminate/database/Eloquent/Relations/HasManyThrough.php @@ -0,0 +1,718 @@ +localKey = $localKey; + $this->firstKey = $firstKey; + $this->secondKey = $secondKey; + $this->farParent = $farParent; + $this->throughParent = $throughParent; + $this->secondLocalKey = $secondLocalKey; + + parent::__construct($query, $throughParent); + } + + /** + * Set the base constraints on the relation query. + * + * @return void + */ + public function addConstraints() + { + $localValue = $this->farParent[$this->localKey]; + + $this->performJoin(); + + if (static::$constraints) { + $this->query->where($this->getQualifiedFirstKeyName(), '=', $localValue); + } + } + + /** + * Set the join clause on the query. + * + * @param \Illuminate\Database\Eloquent\Builder|null $query + * @return void + */ + protected function performJoin(Builder $query = null) + { + $query = $query ?: $this->query; + + $farKey = $this->getQualifiedFarKeyName(); + + $query->join($this->throughParent->getTable(), $this->getQualifiedParentKeyName(), '=', $farKey); + + if ($this->throughParentSoftDeletes()) { + $query->withGlobalScope('SoftDeletableHasManyThrough', function ($query) { + $query->whereNull($this->throughParent->getQualifiedDeletedAtColumn()); + }); + } + } + + /** + * Get the fully qualified parent key name. + * + * @return string + */ + public function getQualifiedParentKeyName() + { + return $this->parent->qualifyColumn($this->secondLocalKey); + } + + /** + * Determine whether "through" parent of the relation uses Soft Deletes. + * + * @return bool + */ + public function throughParentSoftDeletes() + { + return in_array(SoftDeletes::class, class_uses_recursive($this->throughParent)); + } + + /** + * Indicate that trashed "through" parents should be included in the query. + * + * @return $this + */ + public function withTrashedParents() + { + $this->query->withoutGlobalScope('SoftDeletableHasManyThrough'); + + return $this; + } + + /** + * Set the constraints for an eager load of the relation. + * + * @param array $models + * @return void + */ + public function addEagerConstraints(array $models) + { + $whereIn = $this->whereInMethod($this->farParent, $this->localKey); + + $this->query->{$whereIn}( + $this->getQualifiedFirstKeyName(), $this->getKeys($models, $this->localKey) + ); + } + + /** + * Initialize the relation on a set of models. + * + * @param array $models + * @param string $relation + * @return array + */ + public function initRelation(array $models, $relation) + { + foreach ($models as $model) { + $model->setRelation($relation, $this->related->newCollection()); + } + + return $models; + } + + /** + * Match the eagerly loaded results to their parents. + * + * @param array $models + * @param \Illuminate\Database\Eloquent\Collection $results + * @param string $relation + * @return array + */ + public function match(array $models, Collection $results, $relation) + { + $dictionary = $this->buildDictionary($results); + + // Once we have the dictionary we can simply spin through the parent models to + // link them up with their children using the keyed dictionary to make the + // matching very convenient and easy work. Then we'll just return them. + foreach ($models as $model) { + if (isset($dictionary[$key = $this->getDictionaryKey($model->getAttribute($this->localKey))])) { + $model->setRelation( + $relation, $this->related->newCollection($dictionary[$key]) + ); + } + } + + return $models; + } + + /** + * Build model dictionary keyed by the relation's foreign key. + * + * @param \Illuminate\Database\Eloquent\Collection $results + * @return array + */ + protected function buildDictionary(Collection $results) + { + $dictionary = []; + + // First we will create a dictionary of models keyed by the foreign key of the + // relationship as this will allow us to quickly access all of the related + // models without having to do nested looping which will be quite slow. + foreach ($results as $result) { + $dictionary[$result->laravel_through_key][] = $result; + } + + return $dictionary; + } + + /** + * Get the first related model record matching the attributes or instantiate it. + * + * @param array $attributes + * @return \Illuminate\Database\Eloquent\Model + */ + public function firstOrNew(array $attributes) + { + if (is_null($instance = $this->where($attributes)->first())) { + $instance = $this->related->newInstance($attributes); + } + + return $instance; + } + + /** + * Create or update a related record matching the attributes, and fill it with values. + * + * @param array $attributes + * @param array $values + * @return \Illuminate\Database\Eloquent\Model + */ + public function updateOrCreate(array $attributes, array $values = []) + { + $instance = $this->firstOrNew($attributes); + + $instance->fill($values)->save(); + + return $instance; + } + + /** + * Add a basic where clause to the query, and return the first result. + * + * @param \Closure|string|array $column + * @param mixed $operator + * @param mixed $value + * @param string $boolean + * @return \Illuminate\Database\Eloquent\Model|static + */ + public function firstWhere($column, $operator = null, $value = null, $boolean = 'and') + { + return $this->where($column, $operator, $value, $boolean)->first(); + } + + /** + * Execute the query and get the first related model. + * + * @param array $columns + * @return mixed + */ + public function first($columns = ['*']) + { + $results = $this->take(1)->get($columns); + + return count($results) > 0 ? $results->first() : null; + } + + /** + * Execute the query and get the first result or throw an exception. + * + * @param array $columns + * @return \Illuminate\Database\Eloquent\Model|static + * + * @throws \Illuminate\Database\Eloquent\ModelNotFoundException + */ + public function firstOrFail($columns = ['*']) + { + if (! is_null($model = $this->first($columns))) { + return $model; + } + + throw (new ModelNotFoundException)->setModel(get_class($this->related)); + } + + /** + * Find a related model by its primary key. + * + * @param mixed $id + * @param array $columns + * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|null + */ + public function find($id, $columns = ['*']) + { + if (is_array($id) || $id instanceof Arrayable) { + return $this->findMany($id, $columns); + } + + return $this->where( + $this->getRelated()->getQualifiedKeyName(), '=', $id + )->first($columns); + } + + /** + * Find multiple related models by their primary keys. + * + * @param \Illuminate\Contracts\Support\Arrayable|array $ids + * @param array $columns + * @return \Illuminate\Database\Eloquent\Collection + */ + public function findMany($ids, $columns = ['*']) + { + $ids = $ids instanceof Arrayable ? $ids->toArray() : $ids; + + if (empty($ids)) { + return $this->getRelated()->newCollection(); + } + + return $this->whereIn( + $this->getRelated()->getQualifiedKeyName(), $ids + )->get($columns); + } + + /** + * Find a related model by its primary key or throw an exception. + * + * @param mixed $id + * @param array $columns + * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection + * + * @throws \Illuminate\Database\Eloquent\ModelNotFoundException + */ + public function findOrFail($id, $columns = ['*']) + { + $result = $this->find($id, $columns); + + $id = $id instanceof Arrayable ? $id->toArray() : $id; + + if (is_array($id)) { + if (count($result) === count(array_unique($id))) { + return $result; + } + } elseif (! is_null($result)) { + return $result; + } + + throw (new ModelNotFoundException)->setModel(get_class($this->related), $id); + } + + /** + * Get the results of the relationship. + * + * @return mixed + */ + public function getResults() + { + return ! is_null($this->farParent->{$this->localKey}) + ? $this->get() + : $this->related->newCollection(); + } + + /** + * Execute the query as a "select" statement. + * + * @param array $columns + * @return \Illuminate\Database\Eloquent\Collection + */ + public function get($columns = ['*']) + { + $builder = $this->prepareQueryBuilder($columns); + + $models = $builder->getModels(); + + // If we actually found models we will also eager load any relationships that + // have been specified as needing to be eager loaded. This will solve the + // n + 1 query problem for the developer and also increase performance. + if (count($models) > 0) { + $models = $builder->eagerLoadRelations($models); + } + + return $this->related->newCollection($models); + } + + /** + * Get a paginator for the "select" statement. + * + * @param int|null $perPage + * @param array $columns + * @param string $pageName + * @param int $page + * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator + */ + public function paginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null) + { + $this->query->addSelect($this->shouldSelect($columns)); + + return $this->query->paginate($perPage, $columns, $pageName, $page); + } + + /** + * Paginate the given query into a simple paginator. + * + * @param int|null $perPage + * @param array $columns + * @param string $pageName + * @param int|null $page + * @return \Illuminate\Contracts\Pagination\Paginator + */ + public function simplePaginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null) + { + $this->query->addSelect($this->shouldSelect($columns)); + + return $this->query->simplePaginate($perPage, $columns, $pageName, $page); + } + + /** + * Paginate the given query into a cursor paginator. + * + * @param int|null $perPage + * @param array $columns + * @param string $cursorName + * @param string|null $cursor + * @return \Illuminate\Contracts\Pagination\CursorPaginator + */ + public function cursorPaginate($perPage = null, $columns = ['*'], $cursorName = 'cursor', $cursor = null) + { + $this->query->addSelect($this->shouldSelect($columns)); + + return $this->query->cursorPaginate($perPage, $columns, $cursorName, $cursor); + } + + /** + * Set the select clause for the relation query. + * + * @param array $columns + * @return array + */ + protected function shouldSelect(array $columns = ['*']) + { + if ($columns == ['*']) { + $columns = [$this->related->getTable().'.*']; + } + + return array_merge($columns, [$this->getQualifiedFirstKeyName().' as laravel_through_key']); + } + + /** + * Chunk the results of the query. + * + * @param int $count + * @param callable $callback + * @return bool + */ + public function chunk($count, callable $callback) + { + return $this->prepareQueryBuilder()->chunk($count, $callback); + } + + /** + * Chunk the results of a query by comparing numeric IDs. + * + * @param int $count + * @param callable $callback + * @param string|null $column + * @param string|null $alias + * @return bool + */ + public function chunkById($count, callable $callback, $column = null, $alias = null) + { + $column = $column ?? $this->getRelated()->getQualifiedKeyName(); + + $alias = $alias ?? $this->getRelated()->getKeyName(); + + return $this->prepareQueryBuilder()->chunkById($count, $callback, $column, $alias); + } + + /** + * Get a generator for the given query. + * + * @return \Generator + */ + public function cursor() + { + return $this->prepareQueryBuilder()->cursor(); + } + + /** + * Execute a callback over each item while chunking. + * + * @param callable $callback + * @param int $count + * @return bool + */ + public function each(callable $callback, $count = 1000) + { + return $this->chunk($count, function ($results) use ($callback) { + foreach ($results as $key => $value) { + if ($callback($value, $key) === false) { + return false; + } + } + }); + } + + /** + * Query lazily, by chunks of the given size. + * + * @param int $chunkSize + * @return \Illuminate\Support\LazyCollection + */ + public function lazy($chunkSize = 1000) + { + return $this->prepareQueryBuilder()->lazy($chunkSize); + } + + /** + * Query lazily, by chunking the results of a query by comparing IDs. + * + * @param int $chunkSize + * @param string|null $column + * @param string|null $alias + * @return \Illuminate\Support\LazyCollection + */ + public function lazyById($chunkSize = 1000, $column = null, $alias = null) + { + $column = $column ?? $this->getRelated()->getQualifiedKeyName(); + + $alias = $alias ?? $this->getRelated()->getKeyName(); + + return $this->prepareQueryBuilder()->lazyById($chunkSize, $column, $alias); + } + + /** + * Prepare the query builder for query execution. + * + * @param array $columns + * @return \Illuminate\Database\Eloquent\Builder + */ + protected function prepareQueryBuilder($columns = ['*']) + { + $builder = $this->query->applyScopes(); + + return $builder->addSelect( + $this->shouldSelect($builder->getQuery()->columns ? [] : $columns) + ); + } + + /** + * Add the constraints for a relationship query. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Builder $parentQuery + * @param array|mixed $columns + * @return \Illuminate\Database\Eloquent\Builder + */ + public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*']) + { + if ($parentQuery->getQuery()->from === $query->getQuery()->from) { + return $this->getRelationExistenceQueryForSelfRelation($query, $parentQuery, $columns); + } + + if ($parentQuery->getQuery()->from === $this->throughParent->getTable()) { + return $this->getRelationExistenceQueryForThroughSelfRelation($query, $parentQuery, $columns); + } + + $this->performJoin($query); + + return $query->select($columns)->whereColumn( + $this->getQualifiedLocalKeyName(), '=', $this->getQualifiedFirstKeyName() + ); + } + + /** + * Add the constraints for a relationship query on the same table. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Builder $parentQuery + * @param array|mixed $columns + * @return \Illuminate\Database\Eloquent\Builder + */ + public function getRelationExistenceQueryForSelfRelation(Builder $query, Builder $parentQuery, $columns = ['*']) + { + $query->from($query->getModel()->getTable().' as '.$hash = $this->getRelationCountHash()); + + $query->join($this->throughParent->getTable(), $this->getQualifiedParentKeyName(), '=', $hash.'.'.$this->secondKey); + + if ($this->throughParentSoftDeletes()) { + $query->whereNull($this->throughParent->getQualifiedDeletedAtColumn()); + } + + $query->getModel()->setTable($hash); + + return $query->select($columns)->whereColumn( + $parentQuery->getQuery()->from.'.'.$this->localKey, '=', $this->getQualifiedFirstKeyName() + ); + } + + /** + * Add the constraints for a relationship query on the same table as the through parent. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Builder $parentQuery + * @param array|mixed $columns + * @return \Illuminate\Database\Eloquent\Builder + */ + public function getRelationExistenceQueryForThroughSelfRelation(Builder $query, Builder $parentQuery, $columns = ['*']) + { + $table = $this->throughParent->getTable().' as '.$hash = $this->getRelationCountHash(); + + $query->join($table, $hash.'.'.$this->secondLocalKey, '=', $this->getQualifiedFarKeyName()); + + if ($this->throughParentSoftDeletes()) { + $query->whereNull($hash.'.'.$this->throughParent->getDeletedAtColumn()); + } + + return $query->select($columns)->whereColumn( + $parentQuery->getQuery()->from.'.'.$this->localKey, '=', $hash.'.'.$this->firstKey + ); + } + + /** + * Get the qualified foreign key on the related model. + * + * @return string + */ + public function getQualifiedFarKeyName() + { + return $this->getQualifiedForeignKeyName(); + } + + /** + * Get the foreign key on the "through" model. + * + * @return string + */ + public function getFirstKeyName() + { + return $this->firstKey; + } + + /** + * Get the qualified foreign key on the "through" model. + * + * @return string + */ + public function getQualifiedFirstKeyName() + { + return $this->throughParent->qualifyColumn($this->firstKey); + } + + /** + * Get the foreign key on the related model. + * + * @return string + */ + public function getForeignKeyName() + { + return $this->secondKey; + } + + /** + * Get the qualified foreign key on the related model. + * + * @return string + */ + public function getQualifiedForeignKeyName() + { + return $this->related->qualifyColumn($this->secondKey); + } + + /** + * Get the local key on the far parent model. + * + * @return string + */ + public function getLocalKeyName() + { + return $this->localKey; + } + + /** + * Get the qualified local key on the far parent model. + * + * @return string + */ + public function getQualifiedLocalKeyName() + { + return $this->farParent->qualifyColumn($this->localKey); + } + + /** + * Get the local key on the intermediary model. + * + * @return string + */ + public function getSecondLocalKeyName() + { + return $this->secondLocalKey; + } +} diff --git a/vendor/illuminate/database/Eloquent/Relations/HasOne.php b/vendor/illuminate/database/Eloquent/Relations/HasOne.php new file mode 100644 index 0000000..15c735c --- /dev/null +++ b/vendor/illuminate/database/Eloquent/Relations/HasOne.php @@ -0,0 +1,137 @@ +getParentKey())) { + return $this->getDefaultFor($this->parent); + } + + return $this->query->first() ?: $this->getDefaultFor($this->parent); + } + + /** + * Initialize the relation on a set of models. + * + * @param array $models + * @param string $relation + * @return array + */ + public function initRelation(array $models, $relation) + { + foreach ($models as $model) { + $model->setRelation($relation, $this->getDefaultFor($model)); + } + + return $models; + } + + /** + * Match the eagerly loaded results to their parents. + * + * @param array $models + * @param \Illuminate\Database\Eloquent\Collection $results + * @param string $relation + * @return array + */ + public function match(array $models, Collection $results, $relation) + { + return $this->matchOne($models, $results, $relation); + } + + /** + * Add the constraints for an internal relationship existence query. + * + * Essentially, these queries compare on column names like "whereColumn". + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Builder $parentQuery + * @param array|mixed $columns + * @return \Illuminate\Database\Eloquent\Builder + */ + public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*']) + { + if ($this->isOneOfMany()) { + $this->mergeOneOfManyJoinsTo($query); + } + + return parent::getRelationExistenceQuery($query, $parentQuery, $columns); + } + + /** + * Add constraints for inner join subselect for one of many relationships. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param string|null $column + * @param string|null $aggregate + * @return void + */ + public function addOneOfManySubQueryConstraints(Builder $query, $column = null, $aggregate = null) + { + $query->addSelect($this->foreignKey); + } + + /** + * Get the columns that should be selected by the one of many subquery. + * + * @return array|string + */ + public function getOneOfManySubQuerySelectColumns() + { + return $this->foreignKey; + } + + /** + * Add join query constraints for one of many relationships. + * + * @param \Illuminate\Database\Eloquent\JoinClause $join + * @return void + */ + public function addOneOfManyJoinSubQueryConstraints(JoinClause $join) + { + $join->on($this->qualifySubSelectColumn($this->foreignKey), '=', $this->qualifyRelatedColumn($this->foreignKey)); + } + + /** + * Make a new related instance for the given model. + * + * @param \Illuminate\Database\Eloquent\Model $parent + * @return \Illuminate\Database\Eloquent\Model + */ + public function newRelatedInstanceFor(Model $parent) + { + return $this->related->newInstance()->setAttribute( + $this->getForeignKeyName(), $parent->{$this->localKey} + ); + } + + /** + * Get the value of the model's foreign key. + * + * @param \Illuminate\Database\Eloquent\Model $model + * @return mixed + */ + protected function getRelatedKeyFrom(Model $model) + { + return $model->getAttribute($this->getForeignKeyName()); + } +} diff --git a/vendor/illuminate/database/Eloquent/Relations/HasOneOrMany.php b/vendor/illuminate/database/Eloquent/Relations/HasOneOrMany.php new file mode 100644 index 0000000..ff738b9 --- /dev/null +++ b/vendor/illuminate/database/Eloquent/Relations/HasOneOrMany.php @@ -0,0 +1,438 @@ +localKey = $localKey; + $this->foreignKey = $foreignKey; + + parent::__construct($query, $parent); + } + + /** + * Create and return an un-saved instance of the related model. + * + * @param array $attributes + * @return \Illuminate\Database\Eloquent\Model + */ + public function make(array $attributes = []) + { + return tap($this->related->newInstance($attributes), function ($instance) { + $this->setForeignAttributesForCreate($instance); + }); + } + + /** + * Create and return an un-saved instance of the related models. + * + * @param iterable $records + * @return \Illuminate\Database\Eloquent\Collection + */ + public function makeMany($records) + { + $instances = $this->related->newCollection(); + + foreach ($records as $record) { + $instances->push($this->make($record)); + } + + return $instances; + } + + /** + * Set the base constraints on the relation query. + * + * @return void + */ + public function addConstraints() + { + if (static::$constraints) { + $query = $this->getRelationQuery(); + + $query->where($this->foreignKey, '=', $this->getParentKey()); + + $query->whereNotNull($this->foreignKey); + } + } + + /** + * Set the constraints for an eager load of the relation. + * + * @param array $models + * @return void + */ + public function addEagerConstraints(array $models) + { + $whereIn = $this->whereInMethod($this->parent, $this->localKey); + + $this->getRelationQuery()->{$whereIn}( + $this->foreignKey, $this->getKeys($models, $this->localKey) + ); + } + + /** + * Match the eagerly loaded results to their single parents. + * + * @param array $models + * @param \Illuminate\Database\Eloquent\Collection $results + * @param string $relation + * @return array + */ + public function matchOne(array $models, Collection $results, $relation) + { + return $this->matchOneOrMany($models, $results, $relation, 'one'); + } + + /** + * Match the eagerly loaded results to their many parents. + * + * @param array $models + * @param \Illuminate\Database\Eloquent\Collection $results + * @param string $relation + * @return array + */ + public function matchMany(array $models, Collection $results, $relation) + { + return $this->matchOneOrMany($models, $results, $relation, 'many'); + } + + /** + * Match the eagerly loaded results to their many parents. + * + * @param array $models + * @param \Illuminate\Database\Eloquent\Collection $results + * @param string $relation + * @param string $type + * @return array + */ + protected function matchOneOrMany(array $models, Collection $results, $relation, $type) + { + $dictionary = $this->buildDictionary($results); + + // Once we have the dictionary we can simply spin through the parent models to + // link them up with their children using the keyed dictionary to make the + // matching very convenient and easy work. Then we'll just return them. + foreach ($models as $model) { + if (isset($dictionary[$key = $this->getDictionaryKey($model->getAttribute($this->localKey))])) { + $model->setRelation( + $relation, $this->getRelationValue($dictionary, $key, $type) + ); + } + } + + return $models; + } + + /** + * Get the value of a relationship by one or many type. + * + * @param array $dictionary + * @param string $key + * @param string $type + * @return mixed + */ + protected function getRelationValue(array $dictionary, $key, $type) + { + $value = $dictionary[$key]; + + return $type === 'one' ? reset($value) : $this->related->newCollection($value); + } + + /** + * Build model dictionary keyed by the relation's foreign key. + * + * @param \Illuminate\Database\Eloquent\Collection $results + * @return array + */ + protected function buildDictionary(Collection $results) + { + $foreign = $this->getForeignKeyName(); + + return $results->mapToDictionary(function ($result) use ($foreign) { + return [$this->getDictionaryKey($result->{$foreign}) => $result]; + })->all(); + } + + /** + * Find a model by its primary key or return a new instance of the related model. + * + * @param mixed $id + * @param array $columns + * @return \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model + */ + public function findOrNew($id, $columns = ['*']) + { + if (is_null($instance = $this->find($id, $columns))) { + $instance = $this->related->newInstance(); + + $this->setForeignAttributesForCreate($instance); + } + + return $instance; + } + + /** + * Get the first related model record matching the attributes or instantiate it. + * + * @param array $attributes + * @param array $values + * @return \Illuminate\Database\Eloquent\Model + */ + public function firstOrNew(array $attributes = [], array $values = []) + { + if (is_null($instance = $this->where($attributes)->first())) { + $instance = $this->related->newInstance(array_merge($attributes, $values)); + + $this->setForeignAttributesForCreate($instance); + } + + return $instance; + } + + /** + * Get the first related record matching the attributes or create it. + * + * @param array $attributes + * @param array $values + * @return \Illuminate\Database\Eloquent\Model + */ + public function firstOrCreate(array $attributes = [], array $values = []) + { + if (is_null($instance = $this->where($attributes)->first())) { + $instance = $this->create(array_merge($attributes, $values)); + } + + return $instance; + } + + /** + * Create or update a related record matching the attributes, and fill it with values. + * + * @param array $attributes + * @param array $values + * @return \Illuminate\Database\Eloquent\Model + */ + public function updateOrCreate(array $attributes, array $values = []) + { + return tap($this->firstOrNew($attributes), function ($instance) use ($values) { + $instance->fill($values); + + $instance->save(); + }); + } + + /** + * Attach a model instance to the parent model. + * + * @param \Illuminate\Database\Eloquent\Model $model + * @return \Illuminate\Database\Eloquent\Model|false + */ + public function save(Model $model) + { + $this->setForeignAttributesForCreate($model); + + return $model->save() ? $model : false; + } + + /** + * Attach a collection of models to the parent instance. + * + * @param iterable $models + * @return iterable + */ + public function saveMany($models) + { + foreach ($models as $model) { + $this->save($model); + } + + return $models; + } + + /** + * Create a new instance of the related model. + * + * @param array $attributes + * @return \Illuminate\Database\Eloquent\Model + */ + public function create(array $attributes = []) + { + return tap($this->related->newInstance($attributes), function ($instance) { + $this->setForeignAttributesForCreate($instance); + + $instance->save(); + }); + } + + /** + * Create a new instance of the related model. Allow mass-assignment. + * + * @param array $attributes + * @return \Illuminate\Database\Eloquent\Model + */ + public function forceCreate(array $attributes = []) + { + $attributes[$this->getForeignKeyName()] = $this->getParentKey(); + + return $this->related->forceCreate($attributes); + } + + /** + * Create a Collection of new instances of the related model. + * + * @param iterable $records + * @return \Illuminate\Database\Eloquent\Collection + */ + public function createMany(iterable $records) + { + $instances = $this->related->newCollection(); + + foreach ($records as $record) { + $instances->push($this->create($record)); + } + + return $instances; + } + + /** + * Set the foreign ID for creating a related model. + * + * @param \Illuminate\Database\Eloquent\Model $model + * @return void + */ + protected function setForeignAttributesForCreate(Model $model) + { + $model->setAttribute($this->getForeignKeyName(), $this->getParentKey()); + } + + /** + * Add the constraints for a relationship query. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Builder $parentQuery + * @param array|mixed $columns + * @return \Illuminate\Database\Eloquent\Builder + */ + public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*']) + { + if ($query->getQuery()->from == $parentQuery->getQuery()->from) { + return $this->getRelationExistenceQueryForSelfRelation($query, $parentQuery, $columns); + } + + return parent::getRelationExistenceQuery($query, $parentQuery, $columns); + } + + /** + * Add the constraints for a relationship query on the same table. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Builder $parentQuery + * @param array|mixed $columns + * @return \Illuminate\Database\Eloquent\Builder + */ + public function getRelationExistenceQueryForSelfRelation(Builder $query, Builder $parentQuery, $columns = ['*']) + { + $query->from($query->getModel()->getTable().' as '.$hash = $this->getRelationCountHash()); + + $query->getModel()->setTable($hash); + + return $query->select($columns)->whereColumn( + $this->getQualifiedParentKeyName(), '=', $hash.'.'.$this->getForeignKeyName() + ); + } + + /** + * Get the key for comparing against the parent key in "has" query. + * + * @return string + */ + public function getExistenceCompareKey() + { + return $this->getQualifiedForeignKeyName(); + } + + /** + * Get the key value of the parent's local key. + * + * @return mixed + */ + public function getParentKey() + { + return $this->parent->getAttribute($this->localKey); + } + + /** + * Get the fully qualified parent key name. + * + * @return string + */ + public function getQualifiedParentKeyName() + { + return $this->parent->qualifyColumn($this->localKey); + } + + /** + * Get the plain foreign key. + * + * @return string + */ + public function getForeignKeyName() + { + $segments = explode('.', $this->getQualifiedForeignKeyName()); + + return end($segments); + } + + /** + * Get the foreign key for the relationship. + * + * @return string + */ + public function getQualifiedForeignKeyName() + { + return $this->foreignKey; + } + + /** + * Get the local key for the relationship. + * + * @return string + */ + public function getLocalKeyName() + { + return $this->localKey; + } +} diff --git a/vendor/illuminate/database/Eloquent/Relations/HasOneThrough.php b/vendor/illuminate/database/Eloquent/Relations/HasOneThrough.php new file mode 100644 index 0000000..ed9c7ba --- /dev/null +++ b/vendor/illuminate/database/Eloquent/Relations/HasOneThrough.php @@ -0,0 +1,77 @@ +first() ?: $this->getDefaultFor($this->farParent); + } + + /** + * Initialize the relation on a set of models. + * + * @param array $models + * @param string $relation + * @return array + */ + public function initRelation(array $models, $relation) + { + foreach ($models as $model) { + $model->setRelation($relation, $this->getDefaultFor($model)); + } + + return $models; + } + + /** + * Match the eagerly loaded results to their parents. + * + * @param array $models + * @param \Illuminate\Database\Eloquent\Collection $results + * @param string $relation + * @return array + */ + public function match(array $models, Collection $results, $relation) + { + $dictionary = $this->buildDictionary($results); + + // Once we have the dictionary we can simply spin through the parent models to + // link them up with their children using the keyed dictionary to make the + // matching very convenient and easy work. Then we'll just return them. + foreach ($models as $model) { + if (isset($dictionary[$key = $this->getDictionaryKey($model->getAttribute($this->localKey))])) { + $value = $dictionary[$key]; + $model->setRelation( + $relation, reset($value) + ); + } + } + + return $models; + } + + /** + * Make a new related instance for the given model. + * + * @param \Illuminate\Database\Eloquent\Model $parent + * @return \Illuminate\Database\Eloquent\Model + */ + public function newRelatedInstanceFor(Model $parent) + { + return $this->related->newInstance(); + } +} diff --git a/vendor/illuminate/database/Eloquent/Relations/MorphMany.php b/vendor/illuminate/database/Eloquent/Relations/MorphMany.php new file mode 100644 index 0000000..282ba2e --- /dev/null +++ b/vendor/illuminate/database/Eloquent/Relations/MorphMany.php @@ -0,0 +1,62 @@ +getParentKey()) + ? $this->query->get() + : $this->related->newCollection(); + } + + /** + * Initialize the relation on a set of models. + * + * @param array $models + * @param string $relation + * @return array + */ + public function initRelation(array $models, $relation) + { + foreach ($models as $model) { + $model->setRelation($relation, $this->related->newCollection()); + } + + return $models; + } + + /** + * Match the eagerly loaded results to their parents. + * + * @param array $models + * @param \Illuminate\Database\Eloquent\Collection $results + * @param string $relation + * @return array + */ + public function match(array $models, Collection $results, $relation) + { + return $this->matchMany($models, $results, $relation); + } + + /** + * Create a new instance of the related model. Allow mass-assignment. + * + * @param array $attributes + * @return \Illuminate\Database\Eloquent\Model + */ + public function forceCreate(array $attributes = []) + { + $attributes[$this->getMorphType()] = $this->morphClass; + + return parent::forceCreate($attributes); + } +} diff --git a/vendor/illuminate/database/Eloquent/Relations/MorphOne.php b/vendor/illuminate/database/Eloquent/Relations/MorphOne.php new file mode 100644 index 0000000..ff52684 --- /dev/null +++ b/vendor/illuminate/database/Eloquent/Relations/MorphOne.php @@ -0,0 +1,137 @@ +getParentKey())) { + return $this->getDefaultFor($this->parent); + } + + return $this->query->first() ?: $this->getDefaultFor($this->parent); + } + + /** + * Initialize the relation on a set of models. + * + * @param array $models + * @param string $relation + * @return array + */ + public function initRelation(array $models, $relation) + { + foreach ($models as $model) { + $model->setRelation($relation, $this->getDefaultFor($model)); + } + + return $models; + } + + /** + * Match the eagerly loaded results to their parents. + * + * @param array $models + * @param \Illuminate\Database\Eloquent\Collection $results + * @param string $relation + * @return array + */ + public function match(array $models, Collection $results, $relation) + { + return $this->matchOne($models, $results, $relation); + } + + /** + * Get the relationship query. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Builder $parentQuery + * @param array|mixed $columns + * @return \Illuminate\Database\Eloquent\Builder + */ + public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*']) + { + if ($this->isOneOfMany()) { + $this->mergeOneOfManyJoinsTo($query); + } + + return parent::getRelationExistenceQuery($query, $parentQuery, $columns); + } + + /** + * Add constraints for inner join subselect for one of many relationships. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param string|null $column + * @param string|null $aggregate + * @return void + */ + public function addOneOfManySubQueryConstraints(Builder $query, $column = null, $aggregate = null) + { + $query->addSelect($this->foreignKey, $this->morphType); + } + + /** + * Get the columns that should be selected by the one of many subquery. + * + * @return array|string + */ + public function getOneOfManySubQuerySelectColumns() + { + return [$this->foreignKey, $this->morphType]; + } + + /** + * Add join query constraints for one of many relationships. + * + * @param \Illuminate\Database\Eloquent\JoinClause $join + * @return void + */ + public function addOneOfManyJoinSubQueryConstraints(JoinClause $join) + { + $join + ->on($this->qualifySubSelectColumn($this->morphType), '=', $this->qualifyRelatedColumn($this->morphType)) + ->on($this->qualifySubSelectColumn($this->foreignKey), '=', $this->qualifyRelatedColumn($this->foreignKey)); + } + + /** + * Make a new related instance for the given model. + * + * @param \Illuminate\Database\Eloquent\Model $parent + * @return \Illuminate\Database\Eloquent\Model + */ + public function newRelatedInstanceFor(Model $parent) + { + return $this->related->newInstance() + ->setAttribute($this->getForeignKeyName(), $parent->{$this->localKey}) + ->setAttribute($this->getMorphType(), $this->morphClass); + } + + /** + * Get the value of the model's foreign key. + * + * @param \Illuminate\Database\Eloquent\Model $model + * @return mixed + */ + protected function getRelatedKeyFrom(Model $model) + { + return $model->getAttribute($this->getForeignKeyName()); + } +} diff --git a/vendor/illuminate/database/Eloquent/Relations/MorphOneOrMany.php b/vendor/illuminate/database/Eloquent/Relations/MorphOneOrMany.php new file mode 100644 index 0000000..6e2297f --- /dev/null +++ b/vendor/illuminate/database/Eloquent/Relations/MorphOneOrMany.php @@ -0,0 +1,127 @@ +morphType = $type; + + $this->morphClass = $parent->getMorphClass(); + + parent::__construct($query, $parent, $id, $localKey); + } + + /** + * Set the base constraints on the relation query. + * + * @return void + */ + public function addConstraints() + { + if (static::$constraints) { + $this->getRelationQuery()->where($this->morphType, $this->morphClass); + + parent::addConstraints(); + } + } + + /** + * Set the constraints for an eager load of the relation. + * + * @param array $models + * @return void + */ + public function addEagerConstraints(array $models) + { + parent::addEagerConstraints($models); + + $this->getRelationQuery()->where($this->morphType, $this->morphClass); + } + + /** + * Set the foreign ID and type for creating a related model. + * + * @param \Illuminate\Database\Eloquent\Model $model + * @return void + */ + protected function setForeignAttributesForCreate(Model $model) + { + $model->{$this->getForeignKeyName()} = $this->getParentKey(); + + $model->{$this->getMorphType()} = $this->morphClass; + } + + /** + * Get the relationship query. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Builder $parentQuery + * @param array|mixed $columns + * @return \Illuminate\Database\Eloquent\Builder + */ + public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*']) + { + return parent::getRelationExistenceQuery($query, $parentQuery, $columns)->where( + $query->qualifyColumn($this->getMorphType()), $this->morphClass + ); + } + + /** + * Get the foreign key "type" name. + * + * @return string + */ + public function getQualifiedMorphType() + { + return $this->morphType; + } + + /** + * Get the plain morph type name without the table. + * + * @return string + */ + public function getMorphType() + { + return last(explode('.', $this->morphType)); + } + + /** + * Get the class name of the parent model. + * + * @return string + */ + public function getMorphClass() + { + return $this->morphClass; + } +} diff --git a/vendor/illuminate/database/Eloquent/Relations/MorphPivot.php b/vendor/illuminate/database/Eloquent/Relations/MorphPivot.php new file mode 100644 index 0000000..7fbe484 --- /dev/null +++ b/vendor/illuminate/database/Eloquent/Relations/MorphPivot.php @@ -0,0 +1,184 @@ +where($this->morphType, $this->morphClass); + + return parent::setKeysForSaveQuery($query); + } + + /** + * Set the keys for a select query. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @return \Illuminate\Database\Eloquent\Builder + */ + protected function setKeysForSelectQuery($query) + { + $query->where($this->morphType, $this->morphClass); + + return parent::setKeysForSelectQuery($query); + } + + /** + * Delete the pivot model record from the database. + * + * @return int + */ + public function delete() + { + if (isset($this->attributes[$this->getKeyName()])) { + return (int) parent::delete(); + } + + if ($this->fireModelEvent('deleting') === false) { + return 0; + } + + $query = $this->getDeleteQuery(); + + $query->where($this->morphType, $this->morphClass); + + return tap($query->delete(), function () { + $this->fireModelEvent('deleted', false); + }); + } + + /** + * Get the morph type for the pivot. + * + * @return string + */ + public function getMorphType() + { + return $this->morphType; + } + + /** + * Set the morph type for the pivot. + * + * @param string $morphType + * @return $this + */ + public function setMorphType($morphType) + { + $this->morphType = $morphType; + + return $this; + } + + /** + * Set the morph class for the pivot. + * + * @param string $morphClass + * @return \Illuminate\Database\Eloquent\Relations\MorphPivot + */ + public function setMorphClass($morphClass) + { + $this->morphClass = $morphClass; + + return $this; + } + + /** + * Get the queueable identity for the entity. + * + * @return mixed + */ + public function getQueueableId() + { + if (isset($this->attributes[$this->getKeyName()])) { + return $this->getKey(); + } + + return sprintf( + '%s:%s:%s:%s:%s:%s', + $this->foreignKey, $this->getAttribute($this->foreignKey), + $this->relatedKey, $this->getAttribute($this->relatedKey), + $this->morphType, $this->morphClass + ); + } + + /** + * Get a new query to restore one or more models by their queueable IDs. + * + * @param array|int $ids + * @return \Illuminate\Database\Eloquent\Builder + */ + public function newQueryForRestoration($ids) + { + if (is_array($ids)) { + return $this->newQueryForCollectionRestoration($ids); + } + + if (! Str::contains($ids, ':')) { + return parent::newQueryForRestoration($ids); + } + + $segments = explode(':', $ids); + + return $this->newQueryWithoutScopes() + ->where($segments[0], $segments[1]) + ->where($segments[2], $segments[3]) + ->where($segments[4], $segments[5]); + } + + /** + * Get a new query to restore multiple models by their queueable IDs. + * + * @param array $ids + * @return \Illuminate\Database\Eloquent\Builder + */ + protected function newQueryForCollectionRestoration(array $ids) + { + $ids = array_values($ids); + + if (! Str::contains($ids[0], ':')) { + return parent::newQueryForRestoration($ids); + } + + $query = $this->newQueryWithoutScopes(); + + foreach ($ids as $id) { + $segments = explode(':', $id); + + $query->orWhere(function ($query) use ($segments) { + return $query->where($segments[0], $segments[1]) + ->where($segments[2], $segments[3]) + ->where($segments[4], $segments[5]); + }); + } + + return $query; + } +} diff --git a/vendor/illuminate/database/Eloquent/Relations/MorphTo.php b/vendor/illuminate/database/Eloquent/Relations/MorphTo.php new file mode 100644 index 0000000..262741f --- /dev/null +++ b/vendor/illuminate/database/Eloquent/Relations/MorphTo.php @@ -0,0 +1,396 @@ +morphType = $type; + + parent::__construct($query, $parent, $foreignKey, $ownerKey, $relation); + } + + /** + * Set the constraints for an eager load of the relation. + * + * @param array $models + * @return void + */ + public function addEagerConstraints(array $models) + { + $this->buildDictionary($this->models = Collection::make($models)); + } + + /** + * Build a dictionary with the models. + * + * @param \Illuminate\Database\Eloquent\Collection $models + * @return void + */ + protected function buildDictionary(Collection $models) + { + foreach ($models as $model) { + if ($model->{$this->morphType}) { + $morphTypeKey = $this->getDictionaryKey($model->{$this->morphType}); + $foreignKeyKey = $this->getDictionaryKey($model->{$this->foreignKey}); + + $this->dictionary[$morphTypeKey][$foreignKeyKey][] = $model; + } + } + } + + /** + * Get the results of the relationship. + * + * Called via eager load method of Eloquent query builder. + * + * @return mixed + */ + public function getEager() + { + foreach (array_keys($this->dictionary) as $type) { + $this->matchToMorphParents($type, $this->getResultsByType($type)); + } + + return $this->models; + } + + /** + * Get all of the relation results for a type. + * + * @param string $type + * @return \Illuminate\Database\Eloquent\Collection + */ + protected function getResultsByType($type) + { + $instance = $this->createModelByType($type); + + $ownerKey = $this->ownerKey ?? $instance->getKeyName(); + + $query = $this->replayMacros($instance->newQuery()) + ->mergeConstraintsFrom($this->getQuery()) + ->with(array_merge( + $this->getQuery()->getEagerLoads(), + (array) ($this->morphableEagerLoads[get_class($instance)] ?? []) + )) + ->withCount( + (array) ($this->morphableEagerLoadCounts[get_class($instance)] ?? []) + ); + + if ($callback = ($this->morphableConstraints[get_class($instance)] ?? null)) { + $callback($query); + } + + $whereIn = $this->whereInMethod($instance, $ownerKey); + + return $query->{$whereIn}( + $instance->getTable().'.'.$ownerKey, $this->gatherKeysByType($type, $instance->getKeyType()) + )->get(); + } + + /** + * Gather all of the foreign keys for a given type. + * + * @param string $type + * @param string $keyType + * @return array + */ + protected function gatherKeysByType($type, $keyType) + { + return $keyType !== 'string' + ? array_keys($this->dictionary[$type]) + : array_map(function ($modelId) { + return (string) $modelId; + }, array_filter(array_keys($this->dictionary[$type]))); + } + + /** + * Create a new model instance by type. + * + * @param string $type + * @return \Illuminate\Database\Eloquent\Model + */ + public function createModelByType($type) + { + $class = Model::getActualClassNameForMorph($type); + + return tap(new $class, function ($instance) { + if (! $instance->getConnectionName()) { + $instance->setConnection($this->getConnection()->getName()); + } + }); + } + + /** + * Match the eagerly loaded results to their parents. + * + * @param array $models + * @param \Illuminate\Database\Eloquent\Collection $results + * @param string $relation + * @return array + */ + public function match(array $models, Collection $results, $relation) + { + return $models; + } + + /** + * Match the results for a given type to their parents. + * + * @param string $type + * @param \Illuminate\Database\Eloquent\Collection $results + * @return void + */ + protected function matchToMorphParents($type, Collection $results) + { + foreach ($results as $result) { + $ownerKey = ! is_null($this->ownerKey) ? $this->getDictionaryKey($result->{$this->ownerKey}) : $result->getKey(); + + if (isset($this->dictionary[$type][$ownerKey])) { + foreach ($this->dictionary[$type][$ownerKey] as $model) { + $model->setRelation($this->relationName, $result); + } + } + } + } + + /** + * Associate the model instance to the given parent. + * + * @param \Illuminate\Database\Eloquent\Model $model + * @return \Illuminate\Database\Eloquent\Model + */ + public function associate($model) + { + if ($model instanceof Model) { + $foreignKey = $this->ownerKey && $model->{$this->ownerKey} + ? $this->ownerKey + : $model->getKeyName(); + } + + $this->parent->setAttribute( + $this->foreignKey, $model instanceof Model ? $model->{$foreignKey} : null + ); + + $this->parent->setAttribute( + $this->morphType, $model instanceof Model ? $model->getMorphClass() : null + ); + + return $this->parent->setRelation($this->relationName, $model); + } + + /** + * Dissociate previously associated model from the given parent. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function dissociate() + { + $this->parent->setAttribute($this->foreignKey, null); + + $this->parent->setAttribute($this->morphType, null); + + return $this->parent->setRelation($this->relationName, null); + } + + /** + * Touch all of the related models for the relationship. + * + * @return void + */ + public function touch() + { + if (! is_null($this->child->{$this->foreignKey})) { + parent::touch(); + } + } + + /** + * Make a new related instance for the given model. + * + * @param \Illuminate\Database\Eloquent\Model $parent + * @return \Illuminate\Database\Eloquent\Model + */ + protected function newRelatedInstanceFor(Model $parent) + { + return $parent->{$this->getRelationName()}()->getRelated()->newInstance(); + } + + /** + * Get the foreign key "type" name. + * + * @return string + */ + public function getMorphType() + { + return $this->morphType; + } + + /** + * Get the dictionary used by the relationship. + * + * @return array + */ + public function getDictionary() + { + return $this->dictionary; + } + + /** + * Specify which relations to load for a given morph type. + * + * @param array $with + * @return \Illuminate\Database\Eloquent\Relations\MorphTo + */ + public function morphWith(array $with) + { + $this->morphableEagerLoads = array_merge( + $this->morphableEagerLoads, $with + ); + + return $this; + } + + /** + * Specify which relationship counts to load for a given morph type. + * + * @param array $withCount + * @return \Illuminate\Database\Eloquent\Relations\MorphTo + */ + public function morphWithCount(array $withCount) + { + $this->morphableEagerLoadCounts = array_merge( + $this->morphableEagerLoadCounts, $withCount + ); + + return $this; + } + + /** + * Specify constraints on the query for a given morph type. + * + * @param array $callbacks + * @return \Illuminate\Database\Eloquent\Relations\MorphTo + */ + public function constrain(array $callbacks) + { + $this->morphableConstraints = array_merge( + $this->morphableConstraints, $callbacks + ); + + return $this; + } + + /** + * Replay stored macro calls on the actual related instance. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @return \Illuminate\Database\Eloquent\Builder + */ + protected function replayMacros(Builder $query) + { + foreach ($this->macroBuffer as $macro) { + $query->{$macro['method']}(...$macro['parameters']); + } + + return $query; + } + + /** + * Handle dynamic method calls to the relationship. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + try { + $result = parent::__call($method, $parameters); + + if (in_array($method, ['select', 'selectRaw', 'selectSub', 'addSelect', 'withoutGlobalScopes'])) { + $this->macroBuffer[] = compact('method', 'parameters'); + } + + return $result; + } + + // If we tried to call a method that does not exist on the parent Builder instance, + // we'll assume that we want to call a query macro (e.g. withTrashed) that only + // exists on related models. We will just store the call and replay it later. + catch (BadMethodCallException $e) { + $this->macroBuffer[] = compact('method', 'parameters'); + + return $this; + } + } +} diff --git a/vendor/illuminate/database/Eloquent/Relations/MorphToMany.php b/vendor/illuminate/database/Eloquent/Relations/MorphToMany.php new file mode 100644 index 0000000..c2d5745 --- /dev/null +++ b/vendor/illuminate/database/Eloquent/Relations/MorphToMany.php @@ -0,0 +1,209 @@ +inverse = $inverse; + $this->morphType = $name.'_type'; + $this->morphClass = $inverse ? $query->getModel()->getMorphClass() : $parent->getMorphClass(); + + parent::__construct( + $query, $parent, $table, $foreignPivotKey, + $relatedPivotKey, $parentKey, $relatedKey, $relationName + ); + } + + /** + * Set the where clause for the relation query. + * + * @return $this + */ + protected function addWhereConstraints() + { + parent::addWhereConstraints(); + + $this->query->where($this->qualifyPivotColumn($this->morphType), $this->morphClass); + + return $this; + } + + /** + * Set the constraints for an eager load of the relation. + * + * @param array $models + * @return void + */ + public function addEagerConstraints(array $models) + { + parent::addEagerConstraints($models); + + $this->query->where($this->qualifyPivotColumn($this->morphType), $this->morphClass); + } + + /** + * Create a new pivot attachment record. + * + * @param int $id + * @param bool $timed + * @return array + */ + protected function baseAttachRecord($id, $timed) + { + return Arr::add( + parent::baseAttachRecord($id, $timed), $this->morphType, $this->morphClass + ); + } + + /** + * Add the constraints for a relationship count query. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Builder $parentQuery + * @param array|mixed $columns + * @return \Illuminate\Database\Eloquent\Builder + */ + public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*']) + { + return parent::getRelationExistenceQuery($query, $parentQuery, $columns)->where( + $this->qualifyPivotColumn($this->morphType), $this->morphClass + ); + } + + /** + * Get the pivot models that are currently attached. + * + * @return \Illuminate\Support\Collection + */ + protected function getCurrentlyAttachedPivots() + { + return parent::getCurrentlyAttachedPivots()->map(function ($record) { + return $record instanceof MorphPivot + ? $record->setMorphType($this->morphType) + ->setMorphClass($this->morphClass) + : $record; + }); + } + + /** + * Create a new query builder for the pivot table. + * + * @return \Illuminate\Database\Query\Builder + */ + public function newPivotQuery() + { + return parent::newPivotQuery()->where($this->morphType, $this->morphClass); + } + + /** + * Create a new pivot model instance. + * + * @param array $attributes + * @param bool $exists + * @return \Illuminate\Database\Eloquent\Relations\Pivot + */ + public function newPivot(array $attributes = [], $exists = false) + { + $using = $this->using; + + $pivot = $using ? $using::fromRawAttributes($this->parent, $attributes, $this->table, $exists) + : MorphPivot::fromAttributes($this->parent, $attributes, $this->table, $exists); + + $pivot->setPivotKeys($this->foreignPivotKey, $this->relatedPivotKey) + ->setMorphType($this->morphType) + ->setMorphClass($this->morphClass); + + return $pivot; + } + + /** + * Get the pivot columns for the relation. + * + * "pivot_" is prefixed at each column for easy removal later. + * + * @return array + */ + protected function aliasedPivotColumns() + { + $defaults = [$this->foreignPivotKey, $this->relatedPivotKey, $this->morphType]; + + return collect(array_merge($defaults, $this->pivotColumns))->map(function ($column) { + return $this->qualifyPivotColumn($column).' as pivot_'.$column; + })->unique()->all(); + } + + /** + * Get the foreign key "type" name. + * + * @return string + */ + public function getMorphType() + { + return $this->morphType; + } + + /** + * Get the class name of the parent model. + * + * @return string + */ + public function getMorphClass() + { + return $this->morphClass; + } + + /** + * Get the indicator for a reverse relationship. + * + * @return bool + */ + public function getInverse() + { + return $this->inverse; + } +} diff --git a/vendor/illuminate/database/Eloquent/Relations/Pivot.php b/vendor/illuminate/database/Eloquent/Relations/Pivot.php new file mode 100644 index 0000000..a65ecde --- /dev/null +++ b/vendor/illuminate/database/Eloquent/Relations/Pivot.php @@ -0,0 +1,25 @@ +query = $query; + $this->parent = $parent; + $this->related = $query->getModel(); + + $this->addConstraints(); + } + + /** + * Run a callback with constraints disabled on the relation. + * + * @param \Closure $callback + * @return mixed + */ + public static function noConstraints(Closure $callback) + { + $previous = static::$constraints; + + static::$constraints = false; + + // When resetting the relation where clause, we want to shift the first element + // off of the bindings, leaving only the constraints that the developers put + // as "extra" on the relationships, and not original relation constraints. + try { + return $callback(); + } finally { + static::$constraints = $previous; + } + } + + /** + * Set the base constraints on the relation query. + * + * @return void + */ + abstract public function addConstraints(); + + /** + * Set the constraints for an eager load of the relation. + * + * @param array $models + * @return void + */ + abstract public function addEagerConstraints(array $models); + + /** + * Initialize the relation on a set of models. + * + * @param array $models + * @param string $relation + * @return array + */ + abstract public function initRelation(array $models, $relation); + + /** + * Match the eagerly loaded results to their parents. + * + * @param array $models + * @param \Illuminate\Database\Eloquent\Collection $results + * @param string $relation + * @return array + */ + abstract public function match(array $models, Collection $results, $relation); + + /** + * Get the results of the relationship. + * + * @return mixed + */ + abstract public function getResults(); + + /** + * Get the relationship for eager loading. + * + * @return \Illuminate\Database\Eloquent\Collection + */ + public function getEager() + { + return $this->get(); + } + + /** + * Execute the query and get the first result if it's the sole matching record. + * + * @param array|string $columns + * @return \Illuminate\Database\Eloquent\Model + * + * @throws \Illuminate\Database\Eloquent\ModelNotFoundException + * @throws \Illuminate\Database\MultipleRecordsFoundException + */ + public function sole($columns = ['*']) + { + $result = $this->take(2)->get($columns); + + if ($result->isEmpty()) { + throw (new ModelNotFoundException)->setModel(get_class($this->related)); + } + + if ($result->count() > 1) { + throw new MultipleRecordsFoundException; + } + + return $result->first(); + } + + /** + * Execute the query as a "select" statement. + * + * @param array $columns + * @return \Illuminate\Database\Eloquent\Collection + */ + public function get($columns = ['*']) + { + return $this->query->get($columns); + } + + /** + * Touch all of the related models for the relationship. + * + * @return void + */ + public function touch() + { + $model = $this->getRelated(); + + if (! $model::isIgnoringTouch()) { + $this->rawUpdate([ + $model->getUpdatedAtColumn() => $model->freshTimestampString(), + ]); + } + } + + /** + * Run a raw update against the base query. + * + * @param array $attributes + * @return int + */ + public function rawUpdate(array $attributes = []) + { + return $this->query->withoutGlobalScopes()->update($attributes); + } + + /** + * Add the constraints for a relationship count query. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Builder $parentQuery + * @return \Illuminate\Database\Eloquent\Builder + */ + public function getRelationExistenceCountQuery(Builder $query, Builder $parentQuery) + { + return $this->getRelationExistenceQuery( + $query, $parentQuery, new Expression('count(*)') + )->setBindings([], 'select'); + } + + /** + * Add the constraints for an internal relationship existence query. + * + * Essentially, these queries compare on column names like whereColumn. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Builder $parentQuery + * @param array|mixed $columns + * @return \Illuminate\Database\Eloquent\Builder + */ + public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*']) + { + return $query->select($columns)->whereColumn( + $this->getQualifiedParentKeyName(), '=', $this->getExistenceCompareKey() + ); + } + + /** + * Get a relationship join table hash. + * + * @param bool $incrementJoinCount + * @return string + */ + public function getRelationCountHash($incrementJoinCount = true) + { + return 'laravel_reserved_'.($incrementJoinCount ? static::$selfJoinCount++ : static::$selfJoinCount); + } + + /** + * Get all of the primary keys for an array of models. + * + * @param array $models + * @param string|null $key + * @return array + */ + protected function getKeys(array $models, $key = null) + { + return collect($models)->map(function ($value) use ($key) { + return $key ? $value->getAttribute($key) : $value->getKey(); + })->values()->unique(null, true)->sort()->all(); + } + + /** + * Get the query builder that will contain the relationship constraints. + * + * @return \Illuminate\Database\Eloquent\Builder + */ + protected function getRelationQuery() + { + return $this->query; + } + + /** + * Get the underlying query for the relation. + * + * @return \Illuminate\Database\Eloquent\Builder + */ + public function getQuery() + { + return $this->query; + } + + /** + * Get the base query builder driving the Eloquent builder. + * + * @return \Illuminate\Database\Query\Builder + */ + public function getBaseQuery() + { + return $this->query->getQuery(); + } + + /** + * Get the parent model of the relation. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function getParent() + { + return $this->parent; + } + + /** + * Get the fully qualified parent key name. + * + * @return string + */ + public function getQualifiedParentKeyName() + { + return $this->parent->getQualifiedKeyName(); + } + + /** + * Get the related model of the relation. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function getRelated() + { + return $this->related; + } + + /** + * Get the name of the "created at" column. + * + * @return string + */ + public function createdAt() + { + return $this->parent->getCreatedAtColumn(); + } + + /** + * Get the name of the "updated at" column. + * + * @return string + */ + public function updatedAt() + { + return $this->parent->getUpdatedAtColumn(); + } + + /** + * Get the name of the related model's "updated at" column. + * + * @return string + */ + public function relatedUpdatedAt() + { + return $this->related->getUpdatedAtColumn(); + } + + /** + * Get the name of the "where in" method for eager loading. + * + * @param \Illuminate\Database\Eloquent\Model $model + * @param string $key + * @return string + */ + protected function whereInMethod(Model $model, $key) + { + return $model->getKeyName() === last(explode('.', $key)) + && in_array($model->getKeyType(), ['int', 'integer']) + ? 'whereIntegerInRaw' + : 'whereIn'; + } + + /** + * Prevent polymorphic relationships from being used without model mappings. + * + * @param bool $requireMorphMap + * @return void + */ + public static function requireMorphMap($requireMorphMap = true) + { + static::$requireMorphMap = $requireMorphMap; + } + + /** + * Determine if polymorphic relationships require explicit model mapping. + * + * @return bool + */ + public static function requiresMorphMap() + { + return static::$requireMorphMap; + } + + /** + * Define the morph map for polymorphic relations and require all morphed models to be explicitly mapped. + * + * @param array $map + * @param bool $merge + * @return array + */ + public static function enforceMorphMap(array $map, $merge = true) + { + static::requireMorphMap(); + + return static::morphMap($map, $merge); + } + + /** + * Set or get the morph map for polymorphic relations. + * + * @param array|null $map + * @param bool $merge + * @return array + */ + public static function morphMap(array $map = null, $merge = true) + { + $map = static::buildMorphMapFromModels($map); + + if (is_array($map)) { + static::$morphMap = $merge && static::$morphMap + ? $map + static::$morphMap : $map; + } + + return static::$morphMap; + } + + /** + * Builds a table-keyed array from model class names. + * + * @param string[]|null $models + * @return array|null + */ + protected static function buildMorphMapFromModels(array $models = null) + { + if (is_null($models) || Arr::isAssoc($models)) { + return $models; + } + + return array_combine(array_map(function ($model) { + return (new $model)->getTable(); + }, $models), $models); + } + + /** + * Get the model associated with a custom polymorphic type. + * + * @param string $alias + * @return string|null + */ + public static function getMorphedModel($alias) + { + return static::$morphMap[$alias] ?? null; + } + + /** + * Handle dynamic method calls to the relationship. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + if (static::hasMacro($method)) { + return $this->macroCall($method, $parameters); + } + + return $this->forwardDecoratedCallTo($this->query, $method, $parameters); + } + + /** + * Force a clone of the underlying query builder when cloning. + * + * @return void + */ + public function __clone() + { + $this->query = clone $this->query; + } +} diff --git a/vendor/illuminate/database/Eloquent/Scope.php b/vendor/illuminate/database/Eloquent/Scope.php new file mode 100644 index 0000000..63cba6a --- /dev/null +++ b/vendor/illuminate/database/Eloquent/Scope.php @@ -0,0 +1,15 @@ +casts[$this->getDeletedAtColumn()])) { + $this->casts[$this->getDeletedAtColumn()] = 'datetime'; + } + } + + /** + * Force a hard delete on a soft deleted model. + * + * @return bool|null + */ + public function forceDelete() + { + $this->forceDeleting = true; + + return tap($this->delete(), function ($deleted) { + $this->forceDeleting = false; + + if ($deleted) { + $this->fireModelEvent('forceDeleted', false); + } + }); + } + + /** + * Perform the actual delete query on this model instance. + * + * @return mixed + */ + protected function performDeleteOnModel() + { + if ($this->forceDeleting) { + return tap($this->setKeysForSaveQuery($this->newModelQuery())->forceDelete(), function () { + $this->exists = false; + }); + } + + return $this->runSoftDelete(); + } + + /** + * Perform the actual delete query on this model instance. + * + * @return void + */ + protected function runSoftDelete() + { + $query = $this->setKeysForSaveQuery($this->newModelQuery()); + + $time = $this->freshTimestamp(); + + $columns = [$this->getDeletedAtColumn() => $this->fromDateTime($time)]; + + $this->{$this->getDeletedAtColumn()} = $time; + + if ($this->timestamps && ! is_null($this->getUpdatedAtColumn())) { + $this->{$this->getUpdatedAtColumn()} = $time; + + $columns[$this->getUpdatedAtColumn()] = $this->fromDateTime($time); + } + + $query->update($columns); + + $this->syncOriginalAttributes(array_keys($columns)); + + $this->fireModelEvent('trashed', false); + } + + /** + * Restore a soft-deleted model instance. + * + * @return bool|null + */ + public function restore() + { + // If the restoring event does not return false, we will proceed with this + // restore operation. Otherwise, we bail out so the developer will stop + // the restore totally. We will clear the deleted timestamp and save. + if ($this->fireModelEvent('restoring') === false) { + return false; + } + + $this->{$this->getDeletedAtColumn()} = null; + + // Once we have saved the model, we will fire the "restored" event so this + // developer will do anything they need to after a restore operation is + // totally finished. Then we will return the result of the save call. + $this->exists = true; + + $result = $this->save(); + + $this->fireModelEvent('restored', false); + + return $result; + } + + /** + * Determine if the model instance has been soft-deleted. + * + * @return bool + */ + public function trashed() + { + return ! is_null($this->{$this->getDeletedAtColumn()}); + } + + /** + * Register a "softDeleted" model event callback with the dispatcher. + * + * @param \Closure|string $callback + * @return void + */ + public static function softDeleted($callback) + { + static::registerModelEvent('trashed', $callback); + } + + /** + * Register a "restoring" model event callback with the dispatcher. + * + * @param \Closure|string $callback + * @return void + */ + public static function restoring($callback) + { + static::registerModelEvent('restoring', $callback); + } + + /** + * Register a "restored" model event callback with the dispatcher. + * + * @param \Closure|string $callback + * @return void + */ + public static function restored($callback) + { + static::registerModelEvent('restored', $callback); + } + + /** + * Register a "forceDeleted" model event callback with the dispatcher. + * + * @param \Closure|string $callback + * @return void + */ + public static function forceDeleted($callback) + { + static::registerModelEvent('forceDeleted', $callback); + } + + /** + * Determine if the model is currently force deleting. + * + * @return bool + */ + public function isForceDeleting() + { + return $this->forceDeleting; + } + + /** + * Get the name of the "deleted at" column. + * + * @return string + */ + public function getDeletedAtColumn() + { + return defined('static::DELETED_AT') ? static::DELETED_AT : 'deleted_at'; + } + + /** + * Get the fully qualified "deleted at" column. + * + * @return string + */ + public function getQualifiedDeletedAtColumn() + { + return $this->qualifyColumn($this->getDeletedAtColumn()); + } +} diff --git a/vendor/illuminate/database/Eloquent/SoftDeletingScope.php b/vendor/illuminate/database/Eloquent/SoftDeletingScope.php new file mode 100644 index 0000000..7528964 --- /dev/null +++ b/vendor/illuminate/database/Eloquent/SoftDeletingScope.php @@ -0,0 +1,131 @@ +whereNull($model->getQualifiedDeletedAtColumn()); + } + + /** + * Extend the query builder with the needed functions. + * + * @param \Illuminate\Database\Eloquent\Builder $builder + * @return void + */ + public function extend(Builder $builder) + { + foreach ($this->extensions as $extension) { + $this->{"add{$extension}"}($builder); + } + + $builder->onDelete(function (Builder $builder) { + $column = $this->getDeletedAtColumn($builder); + + return $builder->update([ + $column => $builder->getModel()->freshTimestampString(), + ]); + }); + } + + /** + * Get the "deleted at" column for the builder. + * + * @param \Illuminate\Database\Eloquent\Builder $builder + * @return string + */ + protected function getDeletedAtColumn(Builder $builder) + { + if (count((array) $builder->getQuery()->joins) > 0) { + return $builder->getModel()->getQualifiedDeletedAtColumn(); + } + + return $builder->getModel()->getDeletedAtColumn(); + } + + /** + * Add the restore extension to the builder. + * + * @param \Illuminate\Database\Eloquent\Builder $builder + * @return void + */ + protected function addRestore(Builder $builder) + { + $builder->macro('restore', function (Builder $builder) { + $builder->withTrashed(); + + return $builder->update([$builder->getModel()->getDeletedAtColumn() => null]); + }); + } + + /** + * Add the with-trashed extension to the builder. + * + * @param \Illuminate\Database\Eloquent\Builder $builder + * @return void + */ + protected function addWithTrashed(Builder $builder) + { + $builder->macro('withTrashed', function (Builder $builder, $withTrashed = true) { + if (! $withTrashed) { + return $builder->withoutTrashed(); + } + + return $builder->withoutGlobalScope($this); + }); + } + + /** + * Add the without-trashed extension to the builder. + * + * @param \Illuminate\Database\Eloquent\Builder $builder + * @return void + */ + protected function addWithoutTrashed(Builder $builder) + { + $builder->macro('withoutTrashed', function (Builder $builder) { + $model = $builder->getModel(); + + $builder->withoutGlobalScope($this)->whereNull( + $model->getQualifiedDeletedAtColumn() + ); + + return $builder; + }); + } + + /** + * Add the only-trashed extension to the builder. + * + * @param \Illuminate\Database\Eloquent\Builder $builder + * @return void + */ + protected function addOnlyTrashed(Builder $builder) + { + $builder->macro('onlyTrashed', function (Builder $builder) { + $model = $builder->getModel(); + + $builder->withoutGlobalScope($this)->whereNotNull( + $model->getQualifiedDeletedAtColumn() + ); + + return $builder; + }); + } +} diff --git a/vendor/illuminate/database/Events/ConnectionEvent.php b/vendor/illuminate/database/Events/ConnectionEvent.php new file mode 100644 index 0000000..818c785 --- /dev/null +++ b/vendor/illuminate/database/Events/ConnectionEvent.php @@ -0,0 +1,32 @@ +connection = $connection; + $this->connectionName = $connection->getName(); + } +} diff --git a/vendor/illuminate/database/Events/DatabaseRefreshed.php b/vendor/illuminate/database/Events/DatabaseRefreshed.php new file mode 100644 index 0000000..5b1fb45 --- /dev/null +++ b/vendor/illuminate/database/Events/DatabaseRefreshed.php @@ -0,0 +1,10 @@ +method = $method; + $this->migration = $migration; + } +} diff --git a/vendor/illuminate/database/Events/MigrationStarted.php b/vendor/illuminate/database/Events/MigrationStarted.php new file mode 100644 index 0000000..3f206b4 --- /dev/null +++ b/vendor/illuminate/database/Events/MigrationStarted.php @@ -0,0 +1,8 @@ +method = $method; + } +} diff --git a/vendor/illuminate/database/Events/MigrationsStarted.php b/vendor/illuminate/database/Events/MigrationsStarted.php new file mode 100644 index 0000000..5283b49 --- /dev/null +++ b/vendor/illuminate/database/Events/MigrationsStarted.php @@ -0,0 +1,8 @@ +model = $model; + $this->count = $count; + } +} diff --git a/vendor/illuminate/database/Events/NoPendingMigrations.php b/vendor/illuminate/database/Events/NoPendingMigrations.php new file mode 100644 index 0000000..100f786 --- /dev/null +++ b/vendor/illuminate/database/Events/NoPendingMigrations.php @@ -0,0 +1,24 @@ +method = $method; + } +} diff --git a/vendor/illuminate/database/Events/QueryExecuted.php b/vendor/illuminate/database/Events/QueryExecuted.php new file mode 100644 index 0000000..833a21e --- /dev/null +++ b/vendor/illuminate/database/Events/QueryExecuted.php @@ -0,0 +1,59 @@ +sql = $sql; + $this->time = $time; + $this->bindings = $bindings; + $this->connection = $connection; + $this->connectionName = $connection->getName(); + } +} diff --git a/vendor/illuminate/database/Events/SchemaDumped.php b/vendor/illuminate/database/Events/SchemaDumped.php new file mode 100644 index 0000000..1cbbfff --- /dev/null +++ b/vendor/illuminate/database/Events/SchemaDumped.php @@ -0,0 +1,41 @@ +connection = $connection; + $this->connectionName = $connection->getName(); + $this->path = $path; + } +} diff --git a/vendor/illuminate/database/Events/SchemaLoaded.php b/vendor/illuminate/database/Events/SchemaLoaded.php new file mode 100644 index 0000000..061a079 --- /dev/null +++ b/vendor/illuminate/database/Events/SchemaLoaded.php @@ -0,0 +1,41 @@ +connection = $connection; + $this->connectionName = $connection->getName(); + $this->path = $path; + } +} diff --git a/vendor/illuminate/database/Events/StatementPrepared.php b/vendor/illuminate/database/Events/StatementPrepared.php new file mode 100644 index 0000000..2f60323 --- /dev/null +++ b/vendor/illuminate/database/Events/StatementPrepared.php @@ -0,0 +1,33 @@ +statement = $statement; + $this->connection = $connection; + } +} diff --git a/vendor/illuminate/database/Events/TransactionBeginning.php b/vendor/illuminate/database/Events/TransactionBeginning.php new file mode 100644 index 0000000..3287b5c --- /dev/null +++ b/vendor/illuminate/database/Events/TransactionBeginning.php @@ -0,0 +1,8 @@ +isExpression($table)) { + return $this->wrap($this->tablePrefix.$table, true); + } + + return $this->getValue($table); + } + + /** + * Wrap a value in keyword identifiers. + * + * @param \Illuminate\Database\Query\Expression|string $value + * @param bool $prefixAlias + * @return string + */ + public function wrap($value, $prefixAlias = false) + { + if ($this->isExpression($value)) { + return $this->getValue($value); + } + + // If the value being wrapped has a column alias we will need to separate out + // the pieces so we can wrap each of the segments of the expression on its + // own, and then join these both back together using the "as" connector. + if (stripos($value, ' as ') !== false) { + return $this->wrapAliasedValue($value, $prefixAlias); + } + + return $this->wrapSegments(explode('.', $value)); + } + + /** + * Wrap a value that has an alias. + * + * @param string $value + * @param bool $prefixAlias + * @return string + */ + protected function wrapAliasedValue($value, $prefixAlias = false) + { + $segments = preg_split('/\s+as\s+/i', $value); + + // If we are wrapping a table we need to prefix the alias with the table prefix + // as well in order to generate proper syntax. If this is a column of course + // no prefix is necessary. The condition will be true when from wrapTable. + if ($prefixAlias) { + $segments[1] = $this->tablePrefix.$segments[1]; + } + + return $this->wrap($segments[0]).' as '.$this->wrapValue($segments[1]); + } + + /** + * Wrap the given value segments. + * + * @param array $segments + * @return string + */ + protected function wrapSegments($segments) + { + return collect($segments)->map(function ($segment, $key) use ($segments) { + return $key == 0 && count($segments) > 1 + ? $this->wrapTable($segment) + : $this->wrapValue($segment); + })->implode('.'); + } + + /** + * Wrap a single string in keyword identifiers. + * + * @param string $value + * @return string + */ + protected function wrapValue($value) + { + if ($value !== '*') { + return '"'.str_replace('"', '""', $value).'"'; + } + + return $value; + } + + /** + * Convert an array of column names into a delimited string. + * + * @param array $columns + * @return string + */ + public function columnize(array $columns) + { + return implode(', ', array_map([$this, 'wrap'], $columns)); + } + + /** + * Create query parameter place-holders for an array. + * + * @param array $values + * @return string + */ + public function parameterize(array $values) + { + return implode(', ', array_map([$this, 'parameter'], $values)); + } + + /** + * Get the appropriate query parameter place-holder for a value. + * + * @param mixed $value + * @return string + */ + public function parameter($value) + { + return $this->isExpression($value) ? $this->getValue($value) : '?'; + } + + /** + * Quote the given string literal. + * + * @param string|array $value + * @return string + */ + public function quoteString($value) + { + if (is_array($value)) { + return implode(', ', array_map([$this, __FUNCTION__], $value)); + } + + return "'$value'"; + } + + /** + * Determine if the given value is a raw expression. + * + * @param mixed $value + * @return bool + */ + public function isExpression($value) + { + return $value instanceof Expression; + } + + /** + * Get the value of a raw expression. + * + * @param \Illuminate\Database\Query\Expression $expression + * @return mixed + */ + public function getValue($expression) + { + return $expression->getValue(); + } + + /** + * Get the format for database stored dates. + * + * @return string + */ + public function getDateFormat() + { + return 'Y-m-d H:i:s'; + } + + /** + * Get the grammar's table prefix. + * + * @return string + */ + public function getTablePrefix() + { + return $this->tablePrefix; + } + + /** + * Set the grammar's table prefix. + * + * @param string $prefix + * @return $this + */ + public function setTablePrefix($prefix) + { + $this->tablePrefix = $prefix; + + return $this; + } +} diff --git a/vendor/illuminate/database/LICENSE.md b/vendor/illuminate/database/LICENSE.md new file mode 100644 index 0000000..79810c8 --- /dev/null +++ b/vendor/illuminate/database/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) Taylor Otwell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/illuminate/database/LazyLoadingViolationException.php b/vendor/illuminate/database/LazyLoadingViolationException.php new file mode 100644 index 0000000..1bcd40c --- /dev/null +++ b/vendor/illuminate/database/LazyLoadingViolationException.php @@ -0,0 +1,39 @@ +model = $class; + $this->relation = $relation; + } +} diff --git a/vendor/illuminate/database/MigrationServiceProvider.php b/vendor/illuminate/database/MigrationServiceProvider.php new file mode 100644 index 0000000..9ae7685 --- /dev/null +++ b/vendor/illuminate/database/MigrationServiceProvider.php @@ -0,0 +1,226 @@ + 'command.migrate', + 'MigrateFresh' => 'command.migrate.fresh', + 'MigrateInstall' => 'command.migrate.install', + 'MigrateRefresh' => 'command.migrate.refresh', + 'MigrateReset' => 'command.migrate.reset', + 'MigrateRollback' => 'command.migrate.rollback', + 'MigrateStatus' => 'command.migrate.status', + 'MigrateMake' => 'command.migrate.make', + ]; + + /** + * Register the service provider. + * + * @return void + */ + public function register() + { + $this->registerRepository(); + + $this->registerMigrator(); + + $this->registerCreator(); + + $this->registerCommands($this->commands); + } + + /** + * Register the migration repository service. + * + * @return void + */ + protected function registerRepository() + { + $this->app->singleton('migration.repository', function ($app) { + $table = $app['config']['database.migrations']; + + return new DatabaseMigrationRepository($app['db'], $table); + }); + } + + /** + * Register the migrator service. + * + * @return void + */ + protected function registerMigrator() + { + // The migrator is responsible for actually running and rollback the migration + // files in the application. We'll pass in our database connection resolver + // so the migrator can resolve any of these connections when it needs to. + $this->app->singleton('migrator', function ($app) { + $repository = $app['migration.repository']; + + return new Migrator($repository, $app['db'], $app['files'], $app['events']); + }); + } + + /** + * Register the migration creator. + * + * @return void + */ + protected function registerCreator() + { + $this->app->singleton('migration.creator', function ($app) { + return new MigrationCreator($app['files'], $app->basePath('stubs')); + }); + } + + /** + * Register the given commands. + * + * @param array $commands + * @return void + */ + protected function registerCommands(array $commands) + { + foreach (array_keys($commands) as $command) { + $this->{"register{$command}Command"}(); + } + + $this->commands(array_values($commands)); + } + + /** + * Register the command. + * + * @return void + */ + protected function registerMigrateCommand() + { + $this->app->singleton('command.migrate', function ($app) { + return new MigrateCommand($app['migrator'], $app[Dispatcher::class]); + }); + } + + /** + * Register the command. + * + * @return void + */ + protected function registerMigrateFreshCommand() + { + $this->app->singleton('command.migrate.fresh', function () { + return new FreshCommand; + }); + } + + /** + * Register the command. + * + * @return void + */ + protected function registerMigrateInstallCommand() + { + $this->app->singleton('command.migrate.install', function ($app) { + return new InstallCommand($app['migration.repository']); + }); + } + + /** + * Register the command. + * + * @return void + */ + protected function registerMigrateMakeCommand() + { + $this->app->singleton('command.migrate.make', function ($app) { + // Once we have the migration creator registered, we will create the command + // and inject the creator. The creator is responsible for the actual file + // creation of the migrations, and may be extended by these developers. + $creator = $app['migration.creator']; + + $composer = $app['composer']; + + return new MigrateMakeCommand($creator, $composer); + }); + } + + /** + * Register the command. + * + * @return void + */ + protected function registerMigrateRefreshCommand() + { + $this->app->singleton('command.migrate.refresh', function () { + return new RefreshCommand; + }); + } + + /** + * Register the command. + * + * @return void + */ + protected function registerMigrateResetCommand() + { + $this->app->singleton('command.migrate.reset', function ($app) { + return new ResetCommand($app['migrator']); + }); + } + + /** + * Register the command. + * + * @return void + */ + protected function registerMigrateRollbackCommand() + { + $this->app->singleton('command.migrate.rollback', function ($app) { + return new RollbackCommand($app['migrator']); + }); + } + + /** + * Register the command. + * + * @return void + */ + protected function registerMigrateStatusCommand() + { + $this->app->singleton('command.migrate.status', function ($app) { + return new StatusCommand($app['migrator']); + }); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return array_merge([ + 'migrator', 'migration.repository', 'migration.creator', + ], array_values($this->commands)); + } +} diff --git a/vendor/illuminate/database/Migrations/DatabaseMigrationRepository.php b/vendor/illuminate/database/Migrations/DatabaseMigrationRepository.php new file mode 100644 index 0000000..ed42756 --- /dev/null +++ b/vendor/illuminate/database/Migrations/DatabaseMigrationRepository.php @@ -0,0 +1,224 @@ +table = $table; + $this->resolver = $resolver; + } + + /** + * Get the completed migrations. + * + * @return array + */ + public function getRan() + { + return $this->table() + ->orderBy('batch', 'asc') + ->orderBy('migration', 'asc') + ->pluck('migration')->all(); + } + + /** + * Get list of migrations. + * + * @param int $steps + * @return array + */ + public function getMigrations($steps) + { + $query = $this->table()->where('batch', '>=', '1'); + + return $query->orderBy('batch', 'desc') + ->orderBy('migration', 'desc') + ->take($steps)->get()->all(); + } + + /** + * Get the last migration batch. + * + * @return array + */ + public function getLast() + { + $query = $this->table()->where('batch', $this->getLastBatchNumber()); + + return $query->orderBy('migration', 'desc')->get()->all(); + } + + /** + * Get the completed migrations with their batch numbers. + * + * @return array + */ + public function getMigrationBatches() + { + return $this->table() + ->orderBy('batch', 'asc') + ->orderBy('migration', 'asc') + ->pluck('batch', 'migration')->all(); + } + + /** + * Log that a migration was run. + * + * @param string $file + * @param int $batch + * @return void + */ + public function log($file, $batch) + { + $record = ['migration' => $file, 'batch' => $batch]; + + $this->table()->insert($record); + } + + /** + * Remove a migration from the log. + * + * @param object $migration + * @return void + */ + public function delete($migration) + { + $this->table()->where('migration', $migration->migration)->delete(); + } + + /** + * Get the next migration batch number. + * + * @return int + */ + public function getNextBatchNumber() + { + return $this->getLastBatchNumber() + 1; + } + + /** + * Get the last migration batch number. + * + * @return int + */ + public function getLastBatchNumber() + { + return $this->table()->max('batch'); + } + + /** + * Create the migration repository data store. + * + * @return void + */ + public function createRepository() + { + $schema = $this->getConnection()->getSchemaBuilder(); + + $schema->create($this->table, function ($table) { + // The migrations table is responsible for keeping track of which of the + // migrations have actually run for the application. We'll create the + // table to hold the migration file's path as well as the batch ID. + $table->increments('id'); + $table->string('migration'); + $table->integer('batch'); + }); + } + + /** + * Determine if the migration repository exists. + * + * @return bool + */ + public function repositoryExists() + { + $schema = $this->getConnection()->getSchemaBuilder(); + + return $schema->hasTable($this->table); + } + + /** + * Delete the migration repository data store. + * + * @return void + */ + public function deleteRepository() + { + $schema = $this->getConnection()->getSchemaBuilder(); + + $schema->drop($this->table); + } + + /** + * Get a query builder for the migration table. + * + * @return \Illuminate\Database\Query\Builder + */ + protected function table() + { + return $this->getConnection()->table($this->table)->useWritePdo(); + } + + /** + * Get the connection resolver instance. + * + * @return \Illuminate\Database\ConnectionResolverInterface + */ + public function getConnectionResolver() + { + return $this->resolver; + } + + /** + * Resolve the database connection instance. + * + * @return \Illuminate\Database\Connection + */ + public function getConnection() + { + return $this->resolver->connection($this->connection); + } + + /** + * Set the information source to gather data. + * + * @param string $name + * @return void + */ + public function setSource($name) + { + $this->connection = $name; + } +} diff --git a/vendor/illuminate/database/Migrations/Migration.php b/vendor/illuminate/database/Migrations/Migration.php new file mode 100644 index 0000000..a58f784 --- /dev/null +++ b/vendor/illuminate/database/Migrations/Migration.php @@ -0,0 +1,30 @@ +connection; + } +} diff --git a/vendor/illuminate/database/Migrations/MigrationCreator.php b/vendor/illuminate/database/Migrations/MigrationCreator.php new file mode 100644 index 0000000..a79039a --- /dev/null +++ b/vendor/illuminate/database/Migrations/MigrationCreator.php @@ -0,0 +1,236 @@ +files = $files; + $this->customStubPath = $customStubPath; + } + + /** + * Create a new migration at the given path. + * + * @param string $name + * @param string $path + * @param string|null $table + * @param bool $create + * @return string + * + * @throws \Exception + */ + public function create($name, $path, $table = null, $create = false) + { + $this->ensureMigrationDoesntAlreadyExist($name, $path); + + // First we will get the stub file for the migration, which serves as a type + // of template for the migration. Once we have those we will populate the + // various place-holders, save the file, and run the post create event. + $stub = $this->getStub($table, $create); + + $path = $this->getPath($name, $path); + + $this->files->ensureDirectoryExists(dirname($path)); + + $this->files->put( + $path, $this->populateStub($name, $stub, $table) + ); + + // Next, we will fire any hooks that are supposed to fire after a migration is + // created. Once that is done we'll be ready to return the full path to the + // migration file so it can be used however it's needed by the developer. + $this->firePostCreateHooks($table); + + return $path; + } + + /** + * Ensure that a migration with the given name doesn't already exist. + * + * @param string $name + * @param string $migrationPath + * @return void + * + * @throws \InvalidArgumentException + */ + protected function ensureMigrationDoesntAlreadyExist($name, $migrationPath = null) + { + if (! empty($migrationPath)) { + $migrationFiles = $this->files->glob($migrationPath.'/*.php'); + + foreach ($migrationFiles as $migrationFile) { + $this->files->requireOnce($migrationFile); + } + } + + if (class_exists($className = $this->getClassName($name))) { + throw new InvalidArgumentException("A {$className} class already exists."); + } + } + + /** + * Get the migration stub file. + * + * @param string|null $table + * @param bool $create + * @return string + */ + protected function getStub($table, $create) + { + if (is_null($table)) { + $stub = $this->files->exists($customPath = $this->customStubPath.'/migration.stub') + ? $customPath + : $this->stubPath().'/migration.stub'; + } elseif ($create) { + $stub = $this->files->exists($customPath = $this->customStubPath.'/migration.create.stub') + ? $customPath + : $this->stubPath().'/migration.create.stub'; + } else { + $stub = $this->files->exists($customPath = $this->customStubPath.'/migration.update.stub') + ? $customPath + : $this->stubPath().'/migration.update.stub'; + } + + return $this->files->get($stub); + } + + /** + * Populate the place-holders in the migration stub. + * + * @param string $name + * @param string $stub + * @param string|null $table + * @return string + */ + protected function populateStub($name, $stub, $table) + { + $stub = str_replace( + ['DummyClass', '{{ class }}', '{{class}}'], + $this->getClassName($name), $stub + ); + + // Here we will replace the table place-holders with the table specified by + // the developer, which is useful for quickly creating a tables creation + // or update migration from the console instead of typing it manually. + if (! is_null($table)) { + $stub = str_replace( + ['DummyTable', '{{ table }}', '{{table}}'], + $table, $stub + ); + } + + return $stub; + } + + /** + * Get the class name of a migration name. + * + * @param string $name + * @return string + */ + protected function getClassName($name) + { + return Str::studly($name); + } + + /** + * Get the full path to the migration. + * + * @param string $name + * @param string $path + * @return string + */ + protected function getPath($name, $path) + { + return $path.'/'.$this->getDatePrefix().'_'.$name.'.php'; + } + + /** + * Fire the registered post create hooks. + * + * @param string|null $table + * @return void + */ + protected function firePostCreateHooks($table) + { + foreach ($this->postCreate as $callback) { + $callback($table); + } + } + + /** + * Register a post migration create hook. + * + * @param \Closure $callback + * @return void + */ + public function afterCreate(Closure $callback) + { + $this->postCreate[] = $callback; + } + + /** + * Get the date prefix for the migration. + * + * @return string + */ + protected function getDatePrefix() + { + return date('Y_m_d_His'); + } + + /** + * Get the path to the stubs. + * + * @return string + */ + public function stubPath() + { + return __DIR__.'/stubs'; + } + + /** + * Get the filesystem instance. + * + * @return \Illuminate\Filesystem\Filesystem + */ + public function getFilesystem() + { + return $this->files; + } +} diff --git a/vendor/illuminate/database/Migrations/MigrationRepositoryInterface.php b/vendor/illuminate/database/Migrations/MigrationRepositoryInterface.php new file mode 100644 index 0000000..840a5e1 --- /dev/null +++ b/vendor/illuminate/database/Migrations/MigrationRepositoryInterface.php @@ -0,0 +1,88 @@ +files = $files; + $this->events = $dispatcher; + $this->resolver = $resolver; + $this->repository = $repository; + } + + /** + * Run the pending migrations at a given path. + * + * @param array|string $paths + * @param array $options + * @return array + */ + public function run($paths = [], array $options = []) + { + // Once we grab all of the migration files for the path, we will compare them + // against the migrations that have already been run for this package then + // run each of the outstanding migrations against a database connection. + $files = $this->getMigrationFiles($paths); + + $this->requireFiles($migrations = $this->pendingMigrations( + $files, $this->repository->getRan() + )); + + // Once we have all these migrations that are outstanding we are ready to run + // we will go ahead and run them "up". This will execute each migration as + // an operation against a database. Then we'll return this list of them. + $this->runPending($migrations, $options); + + return $migrations; + } + + /** + * Get the migration files that have not yet run. + * + * @param array $files + * @param array $ran + * @return array + */ + protected function pendingMigrations($files, $ran) + { + return Collection::make($files) + ->reject(function ($file) use ($ran) { + return in_array($this->getMigrationName($file), $ran); + })->values()->all(); + } + + /** + * Run an array of migrations. + * + * @param array $migrations + * @param array $options + * @return void + */ + public function runPending(array $migrations, array $options = []) + { + // First we will just make sure that there are any migrations to run. If there + // aren't, we will just make a note of it to the developer so they're aware + // that all of the migrations have been run against this database system. + if (count($migrations) === 0) { + $this->fireMigrationEvent(new NoPendingMigrations('up')); + + $this->note('Nothing to migrate.'); + + return; + } + + // Next, we will get the next batch number for the migrations so we can insert + // correct batch number in the database migrations repository when we store + // each migration's execution. We will also extract a few of the options. + $batch = $this->repository->getNextBatchNumber(); + + $pretend = $options['pretend'] ?? false; + + $step = $options['step'] ?? false; + + $this->fireMigrationEvent(new MigrationsStarted('up')); + + // Once we have the array of migrations, we will spin through them and run the + // migrations "up" so the changes are made to the databases. We'll then log + // that the migration was run so we don't repeat it next time we execute. + foreach ($migrations as $file) { + $this->runUp($file, $batch, $pretend); + + if ($step) { + $batch++; + } + } + + $this->fireMigrationEvent(new MigrationsEnded('up')); + } + + /** + * Run "up" a migration instance. + * + * @param string $file + * @param int $batch + * @param bool $pretend + * @return void + */ + protected function runUp($file, $batch, $pretend) + { + // First we will resolve a "real" instance of the migration class from this + // migration file name. Once we have the instances we can run the actual + // command such as "up" or "down", or we can just simulate the action. + $migration = $this->resolvePath($file); + + $name = $this->getMigrationName($file); + + if ($pretend) { + return $this->pretendToRun($migration, 'up'); + } + + $this->note("Migrating: {$name}"); + + $startTime = microtime(true); + + $this->runMigration($migration, 'up'); + + $runTime = number_format((microtime(true) - $startTime) * 1000, 2); + + // Once we have run a migrations class, we will log that it was run in this + // repository so that we don't try to run it next time we do a migration + // in the application. A migration repository keeps the migrate order. + $this->repository->log($name, $batch); + + $this->note("Migrated: {$name} ({$runTime}ms)"); + } + + /** + * Rollback the last migration operation. + * + * @param array|string $paths + * @param array $options + * @return array + */ + public function rollback($paths = [], array $options = []) + { + // We want to pull in the last batch of migrations that ran on the previous + // migration operation. We'll then reverse those migrations and run each + // of them "down" to reverse the last migration "operation" which ran. + $migrations = $this->getMigrationsForRollback($options); + + if (count($migrations) === 0) { + $this->fireMigrationEvent(new NoPendingMigrations('down')); + + $this->note('Nothing to rollback.'); + + return []; + } + + return $this->rollbackMigrations($migrations, $paths, $options); + } + + /** + * Get the migrations for a rollback operation. + * + * @param array $options + * @return array + */ + protected function getMigrationsForRollback(array $options) + { + if (($steps = $options['step'] ?? 0) > 0) { + return $this->repository->getMigrations($steps); + } + + return $this->repository->getLast(); + } + + /** + * Rollback the given migrations. + * + * @param array $migrations + * @param array|string $paths + * @param array $options + * @return array + */ + protected function rollbackMigrations(array $migrations, $paths, array $options) + { + $rolledBack = []; + + $this->requireFiles($files = $this->getMigrationFiles($paths)); + + $this->fireMigrationEvent(new MigrationsStarted('down')); + + // Next we will run through all of the migrations and call the "down" method + // which will reverse each migration in order. This getLast method on the + // repository already returns these migration's names in reverse order. + foreach ($migrations as $migration) { + $migration = (object) $migration; + + if (! $file = Arr::get($files, $migration->migration)) { + $this->note("Migration not found: {$migration->migration}"); + + continue; + } + + $rolledBack[] = $file; + + $this->runDown( + $file, $migration, + $options['pretend'] ?? false + ); + } + + $this->fireMigrationEvent(new MigrationsEnded('down')); + + return $rolledBack; + } + + /** + * Rolls all of the currently applied migrations back. + * + * @param array|string $paths + * @param bool $pretend + * @return array + */ + public function reset($paths = [], $pretend = false) + { + // Next, we will reverse the migration list so we can run them back in the + // correct order for resetting this database. This will allow us to get + // the database back into its "empty" state ready for the migrations. + $migrations = array_reverse($this->repository->getRan()); + + if (count($migrations) === 0) { + $this->note('Nothing to rollback.'); + + return []; + } + + return $this->resetMigrations($migrations, $paths, $pretend); + } + + /** + * Reset the given migrations. + * + * @param array $migrations + * @param array $paths + * @param bool $pretend + * @return array + */ + protected function resetMigrations(array $migrations, array $paths, $pretend = false) + { + // Since the getRan method that retrieves the migration name just gives us the + // migration name, we will format the names into objects with the name as a + // property on the objects so that we can pass it to the rollback method. + $migrations = collect($migrations)->map(function ($m) { + return (object) ['migration' => $m]; + })->all(); + + return $this->rollbackMigrations( + $migrations, $paths, compact('pretend') + ); + } + + /** + * Run "down" a migration instance. + * + * @param string $file + * @param object $migration + * @param bool $pretend + * @return void + */ + protected function runDown($file, $migration, $pretend) + { + // First we will get the file name of the migration so we can resolve out an + // instance of the migration. Once we get an instance we can either run a + // pretend execution of the migration or we can run the real migration. + $instance = $this->resolvePath($file); + + $name = $this->getMigrationName($file); + + $this->note("Rolling back: {$name}"); + + if ($pretend) { + return $this->pretendToRun($instance, 'down'); + } + + $startTime = microtime(true); + + $this->runMigration($instance, 'down'); + + $runTime = number_format((microtime(true) - $startTime) * 1000, 2); + + // Once we have successfully run the migration "down" we will remove it from + // the migration repository so it will be considered to have not been run + // by the application then will be able to fire by any later operation. + $this->repository->delete($migration); + + $this->note("Rolled back: {$name} ({$runTime}ms)"); + } + + /** + * Run a migration inside a transaction if the database supports it. + * + * @param object $migration + * @param string $method + * @return void + */ + protected function runMigration($migration, $method) + { + $connection = $this->resolveConnection( + $migration->getConnection() + ); + + $callback = function () use ($connection, $migration, $method) { + if (method_exists($migration, $method)) { + $this->fireMigrationEvent(new MigrationStarted($migration, $method)); + + $this->runMethod($connection, $migration, $method); + + $this->fireMigrationEvent(new MigrationEnded($migration, $method)); + } + }; + + $this->getSchemaGrammar($connection)->supportsSchemaTransactions() + && $migration->withinTransaction + ? $connection->transaction($callback) + : $callback(); + } + + /** + * Pretend to run the migrations. + * + * @param object $migration + * @param string $method + * @return void + */ + protected function pretendToRun($migration, $method) + { + try { + foreach ($this->getQueries($migration, $method) as $query) { + $name = get_class($migration); + + $reflectionClass = new ReflectionClass($migration); + + if ($reflectionClass->isAnonymous()) { + $name = $this->getMigrationName($reflectionClass->getFileName()); + } + + $this->note("{$name}: {$query['query']}"); + } + } catch (SchemaException $e) { + $name = get_class($migration); + + $this->note("{$name}: failed to dump queries. This may be due to changing database columns using Doctrine, which is not supported while pretending to run migrations."); + } + } + + /** + * Get all of the queries that would be run for a migration. + * + * @param object $migration + * @param string $method + * @return array + */ + protected function getQueries($migration, $method) + { + // Now that we have the connections we can resolve it and pretend to run the + // queries against the database returning the array of raw SQL statements + // that would get fired against the database system for this migration. + $db = $this->resolveConnection( + $migration->getConnection() + ); + + return $db->pretend(function () use ($db, $migration, $method) { + if (method_exists($migration, $method)) { + $this->runMethod($db, $migration, $method); + } + }); + } + + /** + * Run a migration method on the given connection. + * + * @param \Illuminate\Database\Connection $connection + * @param object $migration + * @param string $method + * @return void + */ + protected function runMethod($connection, $migration, $method) + { + $previousConnection = $this->resolver->getDefaultConnection(); + + try { + $this->resolver->setDefaultConnection($connection->getName()); + + $migration->{$method}(); + } finally { + $this->resolver->setDefaultConnection($previousConnection); + } + } + + /** + * Resolve a migration instance from a file. + * + * @param string $file + * @return object + */ + public function resolve($file) + { + $class = $this->getMigrationClass($file); + + return new $class; + } + + /** + * Resolve a migration instance from a migration path. + * + * @param string $path + * @return object + */ + protected function resolvePath(string $path) + { + $class = $this->getMigrationClass($this->getMigrationName($path)); + + if (class_exists($class) && realpath($path) == (new ReflectionClass($class))->getFileName()) { + return new $class; + } + + $migration = $this->files->getRequire($path); + + return is_object($migration) ? $migration : new $class; + } + + /** + * Generate a migration class name based on the migration file name. + * + * @param string $migrationName + * @return string + */ + protected function getMigrationClass(string $migrationName): string + { + return Str::studly(implode('_', array_slice(explode('_', $migrationName), 4))); + } + + /** + * Get all of the migration files in a given path. + * + * @param string|array $paths + * @return array + */ + public function getMigrationFiles($paths) + { + return Collection::make($paths)->flatMap(function ($path) { + return Str::endsWith($path, '.php') ? [$path] : $this->files->glob($path.'/*_*.php'); + })->filter()->values()->keyBy(function ($file) { + return $this->getMigrationName($file); + })->sortBy(function ($file, $key) { + return $key; + })->all(); + } + + /** + * Require in all the migration files in a given path. + * + * @param array $files + * @return void + */ + public function requireFiles(array $files) + { + foreach ($files as $file) { + $this->files->requireOnce($file); + } + } + + /** + * Get the name of the migration. + * + * @param string $path + * @return string + */ + public function getMigrationName($path) + { + return str_replace('.php', '', basename($path)); + } + + /** + * Register a custom migration path. + * + * @param string $path + * @return void + */ + public function path($path) + { + $this->paths = array_unique(array_merge($this->paths, [$path])); + } + + /** + * Get all of the custom migration paths. + * + * @return array + */ + public function paths() + { + return $this->paths; + } + + /** + * Get the default connection name. + * + * @return string + */ + public function getConnection() + { + return $this->connection; + } + + /** + * Execute the given callback using the given connection as the default connection. + * + * @param string $name + * @param callable $callback + * @return mixed + */ + public function usingConnection($name, callable $callback) + { + $previousConnection = $this->resolver->getDefaultConnection(); + + $this->setConnection($name); + + return tap($callback(), function () use ($previousConnection) { + $this->setConnection($previousConnection); + }); + } + + /** + * Set the default connection name. + * + * @param string $name + * @return void + */ + public function setConnection($name) + { + if (! is_null($name)) { + $this->resolver->setDefaultConnection($name); + } + + $this->repository->setSource($name); + + $this->connection = $name; + } + + /** + * Resolve the database connection instance. + * + * @param string $connection + * @return \Illuminate\Database\Connection + */ + public function resolveConnection($connection) + { + return $this->resolver->connection($connection ?: $this->connection); + } + + /** + * Get the schema grammar out of a migration connection. + * + * @param \Illuminate\Database\Connection $connection + * @return \Illuminate\Database\Schema\Grammars\Grammar + */ + protected function getSchemaGrammar($connection) + { + if (is_null($grammar = $connection->getSchemaGrammar())) { + $connection->useDefaultSchemaGrammar(); + + $grammar = $connection->getSchemaGrammar(); + } + + return $grammar; + } + + /** + * Get the migration repository instance. + * + * @return \Illuminate\Database\Migrations\MigrationRepositoryInterface + */ + public function getRepository() + { + return $this->repository; + } + + /** + * Determine if the migration repository exists. + * + * @return bool + */ + public function repositoryExists() + { + return $this->repository->repositoryExists(); + } + + /** + * Determine if any migrations have been run. + * + * @return bool + */ + public function hasRunAnyMigrations() + { + return $this->repositoryExists() && count($this->repository->getRan()) > 0; + } + + /** + * Delete the migration repository data store. + * + * @return void + */ + public function deleteRepository() + { + return $this->repository->deleteRepository(); + } + + /** + * Get the file system instance. + * + * @return \Illuminate\Filesystem\Filesystem + */ + public function getFilesystem() + { + return $this->files; + } + + /** + * Set the output implementation that should be used by the console. + * + * @param \Symfony\Component\Console\Output\OutputInterface $output + * @return $this + */ + public function setOutput(OutputInterface $output) + { + $this->output = $output; + + return $this; + } + + /** + * Write a note to the console's output. + * + * @param string $message + * @return void + */ + protected function note($message) + { + if ($this->output) { + $this->output->writeln($message); + } + } + + /** + * Fire the given event for the migration. + * + * @param \Illuminate\Contracts\Database\Events\MigrationEvent $event + * @return void + */ + public function fireMigrationEvent($event) + { + if ($this->events) { + $this->events->dispatch($event); + } + } +} diff --git a/vendor/illuminate/database/Migrations/stubs/migration.create.stub b/vendor/illuminate/database/Migrations/stubs/migration.create.stub new file mode 100644 index 0000000..f4a56a0 --- /dev/null +++ b/vendor/illuminate/database/Migrations/stubs/migration.create.stub @@ -0,0 +1,31 @@ +id(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('{{ table }}'); + } +} diff --git a/vendor/illuminate/database/Migrations/stubs/migration.stub b/vendor/illuminate/database/Migrations/stubs/migration.stub new file mode 100644 index 0000000..fd0e437 --- /dev/null +++ b/vendor/illuminate/database/Migrations/stubs/migration.stub @@ -0,0 +1,28 @@ +getPdo()->getAttribute(PDO::ATTR_SERVER_VERSION), 'MariaDB') !== false; + } + + /** + * Get the default query grammar instance. + * + * @return \Illuminate\Database\Query\Grammars\MySqlGrammar + */ + protected function getDefaultQueryGrammar() + { + return $this->withTablePrefix(new QueryGrammar); + } + + /** + * Get a schema builder instance for the connection. + * + * @return \Illuminate\Database\Schema\MySqlBuilder + */ + public function getSchemaBuilder() + { + if (is_null($this->schemaGrammar)) { + $this->useDefaultSchemaGrammar(); + } + + return new MySqlBuilder($this); + } + + /** + * Get the default schema grammar instance. + * + * @return \Illuminate\Database\Schema\Grammars\MySqlGrammar + */ + protected function getDefaultSchemaGrammar() + { + return $this->withTablePrefix(new SchemaGrammar); + } + + /** + * Get the schema state for the connection. + * + * @param \Illuminate\Filesystem\Filesystem|null $files + * @param callable|null $processFactory + * @return \Illuminate\Database\Schema\MySqlSchemaState + */ + public function getSchemaState(Filesystem $files = null, callable $processFactory = null) + { + return new MySqlSchemaState($this, $files, $processFactory); + } + + /** + * Get the default post processor instance. + * + * @return \Illuminate\Database\Query\Processors\MySqlProcessor + */ + protected function getDefaultPostProcessor() + { + return new MySqlProcessor; + } + + /** + * Get the Doctrine DBAL driver. + * + * @return \Doctrine\DBAL\Driver\PDOMySql\Driver|\Illuminate\Database\PDO\MySqlDriver + */ + protected function getDoctrineDriver() + { + return class_exists(Version::class) ? new DoctrineDriver : new MySqlDriver; + } +} diff --git a/vendor/illuminate/database/PDO/Concerns/ConnectsToDatabase.php b/vendor/illuminate/database/PDO/Concerns/ConnectsToDatabase.php new file mode 100644 index 0000000..84c3338 --- /dev/null +++ b/vendor/illuminate/database/PDO/Concerns/ConnectsToDatabase.php @@ -0,0 +1,27 @@ +connection = $connection; + } + + /** + * Execute an SQL statement. + * + * @param string $statement + * @return int + */ + public function exec(string $statement): int + { + try { + $result = $this->connection->exec($statement); + + \assert($result !== false); + + return $result; + } catch (PDOException $exception) { + throw Exception::new($exception); + } + } + + /** + * Prepare a new SQL statement. + * + * @param string $sql + * @return \Doctrine\DBAL\Driver\Statement + */ + public function prepare(string $sql): StatementInterface + { + try { + return $this->createStatement( + $this->connection->prepare($sql) + ); + } catch (PDOException $exception) { + throw Exception::new($exception); + } + } + + /** + * Execute a new query against the connection. + * + * @param string $sql + * @return \Doctrine\DBAL\Driver\Result + */ + public function query(string $sql): ResultInterface + { + try { + $stmt = $this->connection->query($sql); + + \assert($stmt instanceof PDOStatement); + + return new Result($stmt); + } catch (PDOException $exception) { + throw Exception::new($exception); + } + } + + /** + * Get the last insert ID. + * + * @param string|null $name + * @return mixed + */ + public function lastInsertId($name = null) + { + try { + if ($name === null) { + return $this->connection->lastInsertId(); + } + + return $this->connection->lastInsertId($name); + } catch (PDOException $exception) { + throw Exception::new($exception); + } + } + + /** + * Create a new statement instance. + * + * @param \PDOStatement $stmt + * @return \Doctrine\DBAL\Driver\PDO\Statement + */ + protected function createStatement(PDOStatement $stmt): Statement + { + return new Statement($stmt); + } + + /** + * Begin a new database transaction. + * + * @return void + */ + public function beginTransaction() + { + return $this->connection->beginTransaction(); + } + + /** + * Commit a database transaction. + * + * @return void + */ + public function commit() + { + return $this->connection->commit(); + } + + /** + * Rollback a database transaction. + * + * @return void + */ + public function rollBack() + { + return $this->connection->rollBack(); + } + + /** + * Wrap quotes around the given input. + * + * @param string $input + * @param string $type + * @return string + */ + public function quote($input, $type = ParameterType::STRING) + { + return $this->connection->quote($input, $type); + } + + /** + * Get the server version for the connection. + * + * @return string + */ + public function getServerVersion() + { + return $this->connection->getAttribute(PDO::ATTR_SERVER_VERSION); + } + + /** + * Get the wrapped PDO connection. + * + * @return \PDO + */ + public function getWrappedConnection(): PDO + { + return $this->connection; + } +} diff --git a/vendor/illuminate/database/PDO/MySqlDriver.php b/vendor/illuminate/database/PDO/MySqlDriver.php new file mode 100644 index 0000000..5f68c6f --- /dev/null +++ b/vendor/illuminate/database/PDO/MySqlDriver.php @@ -0,0 +1,11 @@ +connection = $connection; + } + + /** + * Prepare a new SQL statement. + * + * @param string $sql + * @return \Doctrine\DBAL\Driver\Statement + */ + public function prepare(string $sql): StatementInterface + { + return new Statement( + $this->connection->prepare($sql) + ); + } + + /** + * Execute a new query against the connection. + * + * @param string $sql + * @return \Doctrine\DBAL\Driver\Result + */ + public function query(string $sql): Result + { + return $this->connection->query($sql); + } + + /** + * Execute an SQL statement. + * + * @param string $statement + * @return int + */ + public function exec(string $statement): int + { + return $this->connection->exec($statement); + } + + /** + * Get the last insert ID. + * + * @param string|null $name + * @return mixed + */ + public function lastInsertId($name = null) + { + if ($name === null) { + return $this->connection->lastInsertId($name); + } + + return $this->prepare('SELECT CONVERT(VARCHAR(MAX), current_value) FROM sys.sequences WHERE name = ?') + ->execute([$name]) + ->fetchOne(); + } + + /** + * Begin a new database transaction. + * + * @return void + */ + public function beginTransaction() + { + return $this->connection->beginTransaction(); + } + + /** + * Commit a database transaction. + * + * @return void + */ + public function commit() + { + return $this->connection->commit(); + } + + /** + * Rollback a database transaction. + * + * @return void + */ + public function rollBack() + { + return $this->connection->rollBack(); + } + + /** + * Wrap quotes around the given input. + * + * @param string $value + * @param int $type + * @return string + */ + public function quote($value, $type = ParameterType::STRING) + { + $val = $this->connection->quote($value, $type); + + // Fix for a driver version terminating all values with null byte... + if (\is_string($val) && \strpos($val, "\0") !== false) { + $val = \substr($val, 0, -1); + } + + return $val; + } + + /** + * Get the server version for the connection. + * + * @return string + */ + public function getServerVersion() + { + return $this->connection->getServerVersion(); + } + + /** + * Get the wrapped PDO connection. + * + * @return \PDO + */ + public function getWrappedConnection(): PDO + { + return $this->connection->getWrappedConnection(); + } +} diff --git a/vendor/illuminate/database/PDO/SqlServerDriver.php b/vendor/illuminate/database/PDO/SqlServerDriver.php new file mode 100644 index 0000000..1373fc4 --- /dev/null +++ b/vendor/illuminate/database/PDO/SqlServerDriver.php @@ -0,0 +1,18 @@ + $value) { + if (is_int($value)) { + $pdoParam = PDO::PARAM_INT; + } elseif (is_resource($value)) { + $pdoParam = PDO::PARAM_LOB; + } else { + $pdoParam = PDO::PARAM_STR; + } + + $statement->bindValue( + is_string($key) ? $key : $key + 1, + $value, + $pdoParam + ); + } + } + + /** + * Get the default query grammar instance. + * + * @return \Illuminate\Database\Query\Grammars\PostgresGrammar + */ + protected function getDefaultQueryGrammar() + { + return $this->withTablePrefix(new QueryGrammar); + } + + /** + * Get a schema builder instance for the connection. + * + * @return \Illuminate\Database\Schema\PostgresBuilder + */ + public function getSchemaBuilder() + { + if (is_null($this->schemaGrammar)) { + $this->useDefaultSchemaGrammar(); + } + + return new PostgresBuilder($this); + } + + /** + * Get the default schema grammar instance. + * + * @return \Illuminate\Database\Schema\Grammars\PostgresGrammar + */ + protected function getDefaultSchemaGrammar() + { + return $this->withTablePrefix(new SchemaGrammar); + } + + /** + * Get the schema state for the connection. + * + * @param \Illuminate\Filesystem\Filesystem|null $files + * @param callable|null $processFactory + * @return \Illuminate\Database\Schema\PostgresSchemaState + */ + public function getSchemaState(Filesystem $files = null, callable $processFactory = null) + { + return new PostgresSchemaState($this, $files, $processFactory); + } + + /** + * Get the default post processor instance. + * + * @return \Illuminate\Database\Query\Processors\PostgresProcessor + */ + protected function getDefaultPostProcessor() + { + return new PostgresProcessor; + } + + /** + * Get the Doctrine DBAL driver. + * + * @return \Doctrine\DBAL\Driver\PDOPgSql\Driver|\Illuminate\Database\PDO\PostgresDriver + */ + protected function getDoctrineDriver() + { + return class_exists(Version::class) ? new DoctrineDriver : new PostgresDriver; + } +} diff --git a/vendor/illuminate/database/Query/Builder.php b/vendor/illuminate/database/Query/Builder.php new file mode 100644 index 0000000..40bd0b9 --- /dev/null +++ b/vendor/illuminate/database/Query/Builder.php @@ -0,0 +1,3540 @@ + [], + 'from' => [], + 'join' => [], + 'where' => [], + 'groupBy' => [], + 'having' => [], + 'order' => [], + 'union' => [], + 'unionOrder' => [], + ]; + + /** + * An aggregate function and column to be run. + * + * @var array + */ + public $aggregate; + + /** + * The columns that should be returned. + * + * @var array + */ + public $columns; + + /** + * Indicates if the query returns distinct results. + * + * Occasionally contains the columns that should be distinct. + * + * @var bool|array + */ + public $distinct = false; + + /** + * The table which the query is targeting. + * + * @var string + */ + public $from; + + /** + * The table joins for the query. + * + * @var array + */ + public $joins; + + /** + * The where constraints for the query. + * + * @var array + */ + public $wheres = []; + + /** + * The groupings for the query. + * + * @var array + */ + public $groups; + + /** + * The having constraints for the query. + * + * @var array + */ + public $havings; + + /** + * The orderings for the query. + * + * @var array + */ + public $orders; + + /** + * The maximum number of records to return. + * + * @var int + */ + public $limit; + + /** + * The number of records to skip. + * + * @var int + */ + public $offset; + + /** + * The query union statements. + * + * @var array + */ + public $unions; + + /** + * The maximum number of union records to return. + * + * @var int + */ + public $unionLimit; + + /** + * The number of union records to skip. + * + * @var int + */ + public $unionOffset; + + /** + * The orderings for the union query. + * + * @var array + */ + public $unionOrders; + + /** + * Indicates whether row locking is being used. + * + * @var string|bool + */ + public $lock; + + /** + * The callbacks that should be invoked before the query is executed. + * + * @var array + */ + public $beforeQueryCallbacks = []; + + /** + * All of the available clause operators. + * + * @var string[] + */ + public $operators = [ + '=', '<', '>', '<=', '>=', '<>', '!=', '<=>', + 'like', 'like binary', 'not like', 'ilike', + '&', '|', '^', '<<', '>>', '&~', + 'rlike', 'not rlike', 'regexp', 'not regexp', + '~', '~*', '!~', '!~*', 'similar to', + 'not similar to', 'not ilike', '~~*', '!~~*', + ]; + + /** + * All of the available bitwise operators. + * + * @var string[] + */ + public $bitwiseOperators = [ + '&', '|', '^', '<<', '>>', '&~', + ]; + + /** + * Whether to use write pdo for the select. + * + * @var bool + */ + public $useWritePdo = false; + + /** + * Create a new query builder instance. + * + * @param \Illuminate\Database\ConnectionInterface $connection + * @param \Illuminate\Database\Query\Grammars\Grammar|null $grammar + * @param \Illuminate\Database\Query\Processors\Processor|null $processor + * @return void + */ + public function __construct(ConnectionInterface $connection, + Grammar $grammar = null, + Processor $processor = null) + { + $this->connection = $connection; + $this->grammar = $grammar ?: $connection->getQueryGrammar(); + $this->processor = $processor ?: $connection->getPostProcessor(); + } + + /** + * Set the columns to be selected. + * + * @param array|mixed $columns + * @return $this + */ + public function select($columns = ['*']) + { + $this->columns = []; + $this->bindings['select'] = []; + $columns = is_array($columns) ? $columns : func_get_args(); + + foreach ($columns as $as => $column) { + if (is_string($as) && $this->isQueryable($column)) { + $this->selectSub($column, $as); + } else { + $this->columns[] = $column; + } + } + + return $this; + } + + /** + * Add a subselect expression to the query. + * + * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder|string $query + * @param string $as + * @return $this + * + * @throws \InvalidArgumentException + */ + public function selectSub($query, $as) + { + [$query, $bindings] = $this->createSub($query); + + return $this->selectRaw( + '('.$query.') as '.$this->grammar->wrap($as), $bindings + ); + } + + /** + * Add a new "raw" select expression to the query. + * + * @param string $expression + * @param array $bindings + * @return $this + */ + public function selectRaw($expression, array $bindings = []) + { + $this->addSelect(new Expression($expression)); + + if ($bindings) { + $this->addBinding($bindings, 'select'); + } + + return $this; + } + + /** + * Makes "from" fetch from a subquery. + * + * @param \Closure|\Illuminate\Database\Query\Builder|string $query + * @param string $as + * @return $this + * + * @throws \InvalidArgumentException + */ + public function fromSub($query, $as) + { + [$query, $bindings] = $this->createSub($query); + + return $this->fromRaw('('.$query.') as '.$this->grammar->wrapTable($as), $bindings); + } + + /** + * Add a raw from clause to the query. + * + * @param string $expression + * @param mixed $bindings + * @return $this + */ + public function fromRaw($expression, $bindings = []) + { + $this->from = new Expression($expression); + + $this->addBinding($bindings, 'from'); + + return $this; + } + + /** + * Creates a subquery and parse it. + * + * @param \Closure|\Illuminate\Database\Query\Builder|string $query + * @return array + */ + protected function createSub($query) + { + // If the given query is a Closure, we will execute it while passing in a new + // query instance to the Closure. This will give the developer a chance to + // format and work with the query before we cast it to a raw SQL string. + if ($query instanceof Closure) { + $callback = $query; + + $callback($query = $this->forSubQuery()); + } + + return $this->parseSub($query); + } + + /** + * Parse the subquery into SQL and bindings. + * + * @param mixed $query + * @return array + * + * @throws \InvalidArgumentException + */ + protected function parseSub($query) + { + if ($query instanceof self || $query instanceof EloquentBuilder || $query instanceof Relation) { + $query = $this->prependDatabaseNameIfCrossDatabaseQuery($query); + + return [$query->toSql(), $query->getBindings()]; + } elseif (is_string($query)) { + return [$query, []]; + } else { + throw new InvalidArgumentException( + 'A subquery must be a query builder instance, a Closure, or a string.' + ); + } + } + + /** + * Prepend the database name if the given query is on another database. + * + * @param mixed $query + * @return mixed + */ + protected function prependDatabaseNameIfCrossDatabaseQuery($query) + { + if ($query->getConnection()->getDatabaseName() !== + $this->getConnection()->getDatabaseName()) { + $databaseName = $query->getConnection()->getDatabaseName(); + + if (strpos($query->from, $databaseName) !== 0 && strpos($query->from, '.') === false) { + $query->from($databaseName.'.'.$query->from); + } + } + + return $query; + } + + /** + * Add a new select column to the query. + * + * @param array|mixed $column + * @return $this + */ + public function addSelect($column) + { + $columns = is_array($column) ? $column : func_get_args(); + + foreach ($columns as $as => $column) { + if (is_string($as) && $this->isQueryable($column)) { + if (is_null($this->columns)) { + $this->select($this->from.'.*'); + } + + $this->selectSub($column, $as); + } else { + $this->columns[] = $column; + } + } + + return $this; + } + + /** + * Force the query to only return distinct results. + * + * @param mixed ...$distinct + * @return $this + */ + public function distinct() + { + $columns = func_get_args(); + + if (count($columns) > 0) { + $this->distinct = is_array($columns[0]) || is_bool($columns[0]) ? $columns[0] : $columns; + } else { + $this->distinct = true; + } + + return $this; + } + + /** + * Set the table which the query is targeting. + * + * @param \Closure|\Illuminate\Database\Query\Builder|string $table + * @param string|null $as + * @return $this + */ + public function from($table, $as = null) + { + if ($this->isQueryable($table)) { + return $this->fromSub($table, $as); + } + + $this->from = $as ? "{$table} as {$as}" : $table; + + return $this; + } + + /** + * Add a join clause to the query. + * + * @param string $table + * @param \Closure|string $first + * @param string|null $operator + * @param string|null $second + * @param string $type + * @param bool $where + * @return $this + */ + public function join($table, $first, $operator = null, $second = null, $type = 'inner', $where = false) + { + $join = $this->newJoinClause($this, $type, $table); + + // If the first "column" of the join is really a Closure instance the developer + // is trying to build a join with a complex "on" clause containing more than + // one condition, so we'll add the join and call a Closure with the query. + if ($first instanceof Closure) { + $first($join); + + $this->joins[] = $join; + + $this->addBinding($join->getBindings(), 'join'); + } + + // If the column is simply a string, we can assume the join simply has a basic + // "on" clause with a single condition. So we will just build the join with + // this simple join clauses attached to it. There is not a join callback. + else { + $method = $where ? 'where' : 'on'; + + $this->joins[] = $join->$method($first, $operator, $second); + + $this->addBinding($join->getBindings(), 'join'); + } + + return $this; + } + + /** + * Add a "join where" clause to the query. + * + * @param string $table + * @param \Closure|string $first + * @param string $operator + * @param string $second + * @param string $type + * @return $this + */ + public function joinWhere($table, $first, $operator, $second, $type = 'inner') + { + return $this->join($table, $first, $operator, $second, $type, true); + } + + /** + * Add a subquery join clause to the query. + * + * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder|string $query + * @param string $as + * @param \Closure|string $first + * @param string|null $operator + * @param string|null $second + * @param string $type + * @param bool $where + * @return $this + * + * @throws \InvalidArgumentException + */ + public function joinSub($query, $as, $first, $operator = null, $second = null, $type = 'inner', $where = false) + { + [$query, $bindings] = $this->createSub($query); + + $expression = '('.$query.') as '.$this->grammar->wrapTable($as); + + $this->addBinding($bindings, 'join'); + + return $this->join(new Expression($expression), $first, $operator, $second, $type, $where); + } + + /** + * Add a left join to the query. + * + * @param string $table + * @param \Closure|string $first + * @param string|null $operator + * @param string|null $second + * @return $this + */ + public function leftJoin($table, $first, $operator = null, $second = null) + { + return $this->join($table, $first, $operator, $second, 'left'); + } + + /** + * Add a "join where" clause to the query. + * + * @param string $table + * @param \Closure|string $first + * @param string $operator + * @param string $second + * @return $this + */ + public function leftJoinWhere($table, $first, $operator, $second) + { + return $this->joinWhere($table, $first, $operator, $second, 'left'); + } + + /** + * Add a subquery left join to the query. + * + * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder|string $query + * @param string $as + * @param \Closure|string $first + * @param string|null $operator + * @param string|null $second + * @return $this + */ + public function leftJoinSub($query, $as, $first, $operator = null, $second = null) + { + return $this->joinSub($query, $as, $first, $operator, $second, 'left'); + } + + /** + * Add a right join to the query. + * + * @param string $table + * @param \Closure|string $first + * @param string|null $operator + * @param string|null $second + * @return $this + */ + public function rightJoin($table, $first, $operator = null, $second = null) + { + return $this->join($table, $first, $operator, $second, 'right'); + } + + /** + * Add a "right join where" clause to the query. + * + * @param string $table + * @param \Closure|string $first + * @param string $operator + * @param string $second + * @return $this + */ + public function rightJoinWhere($table, $first, $operator, $second) + { + return $this->joinWhere($table, $first, $operator, $second, 'right'); + } + + /** + * Add a subquery right join to the query. + * + * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder|string $query + * @param string $as + * @param \Closure|string $first + * @param string|null $operator + * @param string|null $second + * @return $this + */ + public function rightJoinSub($query, $as, $first, $operator = null, $second = null) + { + return $this->joinSub($query, $as, $first, $operator, $second, 'right'); + } + + /** + * Add a "cross join" clause to the query. + * + * @param string $table + * @param \Closure|string|null $first + * @param string|null $operator + * @param string|null $second + * @return $this + */ + public function crossJoin($table, $first = null, $operator = null, $second = null) + { + if ($first) { + return $this->join($table, $first, $operator, $second, 'cross'); + } + + $this->joins[] = $this->newJoinClause($this, 'cross', $table); + + return $this; + } + + /** + * Add a subquery cross join to the query. + * + * @param \Closure|\Illuminate\Database\Query\Builder|string $query + * @param string $as + * @return $this + */ + public function crossJoinSub($query, $as) + { + [$query, $bindings] = $this->createSub($query); + + $expression = '('.$query.') as '.$this->grammar->wrapTable($as); + + $this->addBinding($bindings, 'join'); + + $this->joins[] = $this->newJoinClause($this, 'cross', new Expression($expression)); + + return $this; + } + + /** + * Get a new join clause. + * + * @param \Illuminate\Database\Query\Builder $parentQuery + * @param string $type + * @param string $table + * @return \Illuminate\Database\Query\JoinClause + */ + protected function newJoinClause(self $parentQuery, $type, $table) + { + return new JoinClause($parentQuery, $type, $table); + } + + /** + * Merge an array of where clauses and bindings. + * + * @param array $wheres + * @param array $bindings + * @return void + */ + public function mergeWheres($wheres, $bindings) + { + $this->wheres = array_merge($this->wheres, (array) $wheres); + + $this->bindings['where'] = array_values( + array_merge($this->bindings['where'], (array) $bindings) + ); + } + + /** + * Add a basic where clause to the query. + * + * @param \Closure|string|array $column + * @param mixed $operator + * @param mixed $value + * @param string $boolean + * @return $this + */ + public function where($column, $operator = null, $value = null, $boolean = 'and') + { + // If the column is an array, we will assume it is an array of key-value pairs + // and can add them each as a where clause. We will maintain the boolean we + // received when the method was called and pass it into the nested where. + if (is_array($column)) { + return $this->addArrayOfWheres($column, $boolean); + } + + // Here we will make some assumptions about the operator. If only 2 values are + // passed to the method, we will assume that the operator is an equals sign + // and keep going. Otherwise, we'll require the operator to be passed in. + [$value, $operator] = $this->prepareValueAndOperator( + $value, $operator, func_num_args() === 2 + ); + + // If the columns is actually a Closure instance, we will assume the developer + // wants to begin a nested where statement which is wrapped in parenthesis. + // We'll add that Closure to the query then return back out immediately. + if ($column instanceof Closure && is_null($operator)) { + return $this->whereNested($column, $boolean); + } + + // If the column is a Closure instance and there is an operator value, we will + // assume the developer wants to run a subquery and then compare the result + // of that subquery with the given value that was provided to the method. + if ($this->isQueryable($column) && ! is_null($operator)) { + [$sub, $bindings] = $this->createSub($column); + + return $this->addBinding($bindings, 'where') + ->where(new Expression('('.$sub.')'), $operator, $value, $boolean); + } + + // If the given operator is not found in the list of valid operators we will + // assume that the developer is just short-cutting the '=' operators and + // we will set the operators to '=' and set the values appropriately. + if ($this->invalidOperator($operator)) { + [$value, $operator] = [$operator, '=']; + } + + // If the value is a Closure, it means the developer is performing an entire + // sub-select within the query and we will need to compile the sub-select + // within the where clause to get the appropriate query record results. + if ($value instanceof Closure) { + return $this->whereSub($column, $operator, $value, $boolean); + } + + // If the value is "null", we will just assume the developer wants to add a + // where null clause to the query. So, we will allow a short-cut here to + // that method for convenience so the developer doesn't have to check. + if (is_null($value)) { + return $this->whereNull($column, $boolean, $operator !== '='); + } + + $type = 'Basic'; + + // If the column is making a JSON reference we'll check to see if the value + // is a boolean. If it is, we'll add the raw boolean string as an actual + // value to the query to ensure this is properly handled by the query. + if (Str::contains($column, '->') && is_bool($value)) { + $value = new Expression($value ? 'true' : 'false'); + + if (is_string($column)) { + $type = 'JsonBoolean'; + } + } + + if ($this->isBitwiseOperator($operator)) { + $type = 'Bitwise'; + } + + // Now that we are working with just a simple query we can put the elements + // in our array and add the query binding to our array of bindings that + // will be bound to each SQL statements when it is finally executed. + $this->wheres[] = compact( + 'type', 'column', 'operator', 'value', 'boolean' + ); + + if (! $value instanceof Expression) { + $this->addBinding($this->flattenValue($value), 'where'); + } + + return $this; + } + + /** + * Add an array of where clauses to the query. + * + * @param array $column + * @param string $boolean + * @param string $method + * @return $this + */ + protected function addArrayOfWheres($column, $boolean, $method = 'where') + { + return $this->whereNested(function ($query) use ($column, $method, $boolean) { + foreach ($column as $key => $value) { + if (is_numeric($key) && is_array($value)) { + $query->{$method}(...array_values($value)); + } else { + $query->$method($key, '=', $value, $boolean); + } + } + }, $boolean); + } + + /** + * Prepare the value and operator for a where clause. + * + * @param string $value + * @param string $operator + * @param bool $useDefault + * @return array + * + * @throws \InvalidArgumentException + */ + public function prepareValueAndOperator($value, $operator, $useDefault = false) + { + if ($useDefault) { + return [$operator, '=']; + } elseif ($this->invalidOperatorAndValue($operator, $value)) { + throw new InvalidArgumentException('Illegal operator and value combination.'); + } + + return [$value, $operator]; + } + + /** + * Determine if the given operator and value combination is legal. + * + * Prevents using Null values with invalid operators. + * + * @param string $operator + * @param mixed $value + * @return bool + */ + protected function invalidOperatorAndValue($operator, $value) + { + return is_null($value) && in_array($operator, $this->operators) && + ! in_array($operator, ['=', '<>', '!=']); + } + + /** + * Determine if the given operator is supported. + * + * @param string $operator + * @return bool + */ + protected function invalidOperator($operator) + { + return ! is_string($operator) || (! in_array(strtolower($operator), $this->operators, true) && + ! in_array(strtolower($operator), $this->grammar->getOperators(), true)); + } + + /** + * Determine if the operator is a bitwise operator. + * + * @param string $operator + * @return bool + */ + protected function isBitwiseOperator($operator) + { + return in_array(strtolower($operator), $this->bitwiseOperators, true) || + in_array(strtolower($operator), $this->grammar->getBitwiseOperators(), true); + } + + /** + * Add an "or where" clause to the query. + * + * @param \Closure|string|array $column + * @param mixed $operator + * @param mixed $value + * @return $this + */ + public function orWhere($column, $operator = null, $value = null) + { + [$value, $operator] = $this->prepareValueAndOperator( + $value, $operator, func_num_args() === 2 + ); + + return $this->where($column, $operator, $value, 'or'); + } + + /** + * Add a "where" clause comparing two columns to the query. + * + * @param string|array $first + * @param string|null $operator + * @param string|null $second + * @param string|null $boolean + * @return $this + */ + public function whereColumn($first, $operator = null, $second = null, $boolean = 'and') + { + // If the column is an array, we will assume it is an array of key-value pairs + // and can add them each as a where clause. We will maintain the boolean we + // received when the method was called and pass it into the nested where. + if (is_array($first)) { + return $this->addArrayOfWheres($first, $boolean, 'whereColumn'); + } + + // If the given operator is not found in the list of valid operators we will + // assume that the developer is just short-cutting the '=' operators and + // we will set the operators to '=' and set the values appropriately. + if ($this->invalidOperator($operator)) { + [$second, $operator] = [$operator, '=']; + } + + // Finally, we will add this where clause into this array of clauses that we + // are building for the query. All of them will be compiled via a grammar + // once the query is about to be executed and run against the database. + $type = 'Column'; + + $this->wheres[] = compact( + 'type', 'first', 'operator', 'second', 'boolean' + ); + + return $this; + } + + /** + * Add an "or where" clause comparing two columns to the query. + * + * @param string|array $first + * @param string|null $operator + * @param string|null $second + * @return $this + */ + public function orWhereColumn($first, $operator = null, $second = null) + { + return $this->whereColumn($first, $operator, $second, 'or'); + } + + /** + * Add a raw where clause to the query. + * + * @param string $sql + * @param mixed $bindings + * @param string $boolean + * @return $this + */ + public function whereRaw($sql, $bindings = [], $boolean = 'and') + { + $this->wheres[] = ['type' => 'raw', 'sql' => $sql, 'boolean' => $boolean]; + + $this->addBinding((array) $bindings, 'where'); + + return $this; + } + + /** + * Add a raw or where clause to the query. + * + * @param string $sql + * @param mixed $bindings + * @return $this + */ + public function orWhereRaw($sql, $bindings = []) + { + return $this->whereRaw($sql, $bindings, 'or'); + } + + /** + * Add a "where in" clause to the query. + * + * @param string $column + * @param mixed $values + * @param string $boolean + * @param bool $not + * @return $this + */ + public function whereIn($column, $values, $boolean = 'and', $not = false) + { + $type = $not ? 'NotIn' : 'In'; + + // If the value is a query builder instance we will assume the developer wants to + // look for any values that exists within this given query. So we will add the + // query accordingly so that this query is properly executed when it is run. + if ($this->isQueryable($values)) { + [$query, $bindings] = $this->createSub($values); + + $values = [new Expression($query)]; + + $this->addBinding($bindings, 'where'); + } + + // Next, if the value is Arrayable we need to cast it to its raw array form so we + // have the underlying array value instead of an Arrayable object which is not + // able to be added as a binding, etc. We will then add to the wheres array. + if ($values instanceof Arrayable) { + $values = $values->toArray(); + } + + $this->wheres[] = compact('type', 'column', 'values', 'boolean'); + + // Finally we'll add a binding for each values unless that value is an expression + // in which case we will just skip over it since it will be the query as a raw + // string and not as a parameterized place-holder to be replaced by the PDO. + $this->addBinding($this->cleanBindings($values), 'where'); + + return $this; + } + + /** + * Add an "or where in" clause to the query. + * + * @param string $column + * @param mixed $values + * @return $this + */ + public function orWhereIn($column, $values) + { + return $this->whereIn($column, $values, 'or'); + } + + /** + * Add a "where not in" clause to the query. + * + * @param string $column + * @param mixed $values + * @param string $boolean + * @return $this + */ + public function whereNotIn($column, $values, $boolean = 'and') + { + return $this->whereIn($column, $values, $boolean, true); + } + + /** + * Add an "or where not in" clause to the query. + * + * @param string $column + * @param mixed $values + * @return $this + */ + public function orWhereNotIn($column, $values) + { + return $this->whereNotIn($column, $values, 'or'); + } + + /** + * Add a "where in raw" clause for integer values to the query. + * + * @param string $column + * @param \Illuminate\Contracts\Support\Arrayable|array $values + * @param string $boolean + * @param bool $not + * @return $this + */ + public function whereIntegerInRaw($column, $values, $boolean = 'and', $not = false) + { + $type = $not ? 'NotInRaw' : 'InRaw'; + + if ($values instanceof Arrayable) { + $values = $values->toArray(); + } + + foreach ($values as &$value) { + $value = (int) $value; + } + + $this->wheres[] = compact('type', 'column', 'values', 'boolean'); + + return $this; + } + + /** + * Add an "or where in raw" clause for integer values to the query. + * + * @param string $column + * @param \Illuminate\Contracts\Support\Arrayable|array $values + * @return $this + */ + public function orWhereIntegerInRaw($column, $values) + { + return $this->whereIntegerInRaw($column, $values, 'or'); + } + + /** + * Add a "where not in raw" clause for integer values to the query. + * + * @param string $column + * @param \Illuminate\Contracts\Support\Arrayable|array $values + * @param string $boolean + * @return $this + */ + public function whereIntegerNotInRaw($column, $values, $boolean = 'and') + { + return $this->whereIntegerInRaw($column, $values, $boolean, true); + } + + /** + * Add an "or where not in raw" clause for integer values to the query. + * + * @param string $column + * @param \Illuminate\Contracts\Support\Arrayable|array $values + * @return $this + */ + public function orWhereIntegerNotInRaw($column, $values) + { + return $this->whereIntegerNotInRaw($column, $values, 'or'); + } + + /** + * Add a "where null" clause to the query. + * + * @param string|array $columns + * @param string $boolean + * @param bool $not + * @return $this + */ + public function whereNull($columns, $boolean = 'and', $not = false) + { + $type = $not ? 'NotNull' : 'Null'; + + foreach (Arr::wrap($columns) as $column) { + $this->wheres[] = compact('type', 'column', 'boolean'); + } + + return $this; + } + + /** + * Add an "or where null" clause to the query. + * + * @param string|array $column + * @return $this + */ + public function orWhereNull($column) + { + return $this->whereNull($column, 'or'); + } + + /** + * Add a "where not null" clause to the query. + * + * @param string|array $columns + * @param string $boolean + * @return $this + */ + public function whereNotNull($columns, $boolean = 'and') + { + return $this->whereNull($columns, $boolean, true); + } + + /** + * Add a where between statement to the query. + * + * @param string|\Illuminate\Database\Query\Expression $column + * @param array $values + * @param string $boolean + * @param bool $not + * @return $this + */ + public function whereBetween($column, array $values, $boolean = 'and', $not = false) + { + $type = 'between'; + + $this->wheres[] = compact('type', 'column', 'values', 'boolean', 'not'); + + $this->addBinding(array_slice($this->cleanBindings(Arr::flatten($values)), 0, 2), 'where'); + + return $this; + } + + /** + * Add a where between statement using columns to the query. + * + * @param string $column + * @param array $values + * @param string $boolean + * @param bool $not + * @return $this + */ + public function whereBetweenColumns($column, array $values, $boolean = 'and', $not = false) + { + $type = 'betweenColumns'; + + $this->wheres[] = compact('type', 'column', 'values', 'boolean', 'not'); + + return $this; + } + + /** + * Add an or where between statement to the query. + * + * @param string $column + * @param array $values + * @return $this + */ + public function orWhereBetween($column, array $values) + { + return $this->whereBetween($column, $values, 'or'); + } + + /** + * Add an or where between statement using columns to the query. + * + * @param string $column + * @param array $values + * @return $this + */ + public function orWhereBetweenColumns($column, array $values) + { + return $this->whereBetweenColumns($column, $values, 'or'); + } + + /** + * Add a where not between statement to the query. + * + * @param string $column + * @param array $values + * @param string $boolean + * @return $this + */ + public function whereNotBetween($column, array $values, $boolean = 'and') + { + return $this->whereBetween($column, $values, $boolean, true); + } + + /** + * Add a where not between statement using columns to the query. + * + * @param string $column + * @param array $values + * @param string $boolean + * @return $this + */ + public function whereNotBetweenColumns($column, array $values, $boolean = 'and') + { + return $this->whereBetweenColumns($column, $values, $boolean, true); + } + + /** + * Add an or where not between statement to the query. + * + * @param string $column + * @param array $values + * @return $this + */ + public function orWhereNotBetween($column, array $values) + { + return $this->whereNotBetween($column, $values, 'or'); + } + + /** + * Add an or where not between statement using columns to the query. + * + * @param string $column + * @param array $values + * @return $this + */ + public function orWhereNotBetweenColumns($column, array $values) + { + return $this->whereNotBetweenColumns($column, $values, 'or'); + } + + /** + * Add an "or where not null" clause to the query. + * + * @param string $column + * @return $this + */ + public function orWhereNotNull($column) + { + return $this->whereNotNull($column, 'or'); + } + + /** + * Add a "where date" statement to the query. + * + * @param \Illuminate\Database\Query\Expression|string $column + * @param string $operator + * @param \DateTimeInterface|string|null $value + * @param string $boolean + * @return $this + */ + public function whereDate($column, $operator, $value = null, $boolean = 'and') + { + [$value, $operator] = $this->prepareValueAndOperator( + $value, $operator, func_num_args() === 2 + ); + + $value = $this->flattenValue($value); + + if ($value instanceof DateTimeInterface) { + $value = $value->format('Y-m-d'); + } + + return $this->addDateBasedWhere('Date', $column, $operator, $value, $boolean); + } + + /** + * Add an "or where date" statement to the query. + * + * @param string $column + * @param string $operator + * @param \DateTimeInterface|string|null $value + * @return $this + */ + public function orWhereDate($column, $operator, $value = null) + { + [$value, $operator] = $this->prepareValueAndOperator( + $value, $operator, func_num_args() === 2 + ); + + return $this->whereDate($column, $operator, $value, 'or'); + } + + /** + * Add a "where time" statement to the query. + * + * @param string $column + * @param string $operator + * @param \DateTimeInterface|string|null $value + * @param string $boolean + * @return $this + */ + public function whereTime($column, $operator, $value = null, $boolean = 'and') + { + [$value, $operator] = $this->prepareValueAndOperator( + $value, $operator, func_num_args() === 2 + ); + + $value = $this->flattenValue($value); + + if ($value instanceof DateTimeInterface) { + $value = $value->format('H:i:s'); + } + + return $this->addDateBasedWhere('Time', $column, $operator, $value, $boolean); + } + + /** + * Add an "or where time" statement to the query. + * + * @param string $column + * @param string $operator + * @param \DateTimeInterface|string|null $value + * @return $this + */ + public function orWhereTime($column, $operator, $value = null) + { + [$value, $operator] = $this->prepareValueAndOperator( + $value, $operator, func_num_args() === 2 + ); + + return $this->whereTime($column, $operator, $value, 'or'); + } + + /** + * Add a "where day" statement to the query. + * + * @param string $column + * @param string $operator + * @param \DateTimeInterface|string|null $value + * @param string $boolean + * @return $this + */ + public function whereDay($column, $operator, $value = null, $boolean = 'and') + { + [$value, $operator] = $this->prepareValueAndOperator( + $value, $operator, func_num_args() === 2 + ); + + $value = $this->flattenValue($value); + + if ($value instanceof DateTimeInterface) { + $value = $value->format('d'); + } + + if (! $value instanceof Expression) { + $value = str_pad($value, 2, '0', STR_PAD_LEFT); + } + + return $this->addDateBasedWhere('Day', $column, $operator, $value, $boolean); + } + + /** + * Add an "or where day" statement to the query. + * + * @param string $column + * @param string $operator + * @param \DateTimeInterface|string|null $value + * @return $this + */ + public function orWhereDay($column, $operator, $value = null) + { + [$value, $operator] = $this->prepareValueAndOperator( + $value, $operator, func_num_args() === 2 + ); + + return $this->whereDay($column, $operator, $value, 'or'); + } + + /** + * Add a "where month" statement to the query. + * + * @param string $column + * @param string $operator + * @param \DateTimeInterface|string|null $value + * @param string $boolean + * @return $this + */ + public function whereMonth($column, $operator, $value = null, $boolean = 'and') + { + [$value, $operator] = $this->prepareValueAndOperator( + $value, $operator, func_num_args() === 2 + ); + + $value = $this->flattenValue($value); + + if ($value instanceof DateTimeInterface) { + $value = $value->format('m'); + } + + if (! $value instanceof Expression) { + $value = str_pad($value, 2, '0', STR_PAD_LEFT); + } + + return $this->addDateBasedWhere('Month', $column, $operator, $value, $boolean); + } + + /** + * Add an "or where month" statement to the query. + * + * @param string $column + * @param string $operator + * @param \DateTimeInterface|string|null $value + * @return $this + */ + public function orWhereMonth($column, $operator, $value = null) + { + [$value, $operator] = $this->prepareValueAndOperator( + $value, $operator, func_num_args() === 2 + ); + + return $this->whereMonth($column, $operator, $value, 'or'); + } + + /** + * Add a "where year" statement to the query. + * + * @param string $column + * @param string $operator + * @param \DateTimeInterface|string|int|null $value + * @param string $boolean + * @return $this + */ + public function whereYear($column, $operator, $value = null, $boolean = 'and') + { + [$value, $operator] = $this->prepareValueAndOperator( + $value, $operator, func_num_args() === 2 + ); + + $value = $this->flattenValue($value); + + if ($value instanceof DateTimeInterface) { + $value = $value->format('Y'); + } + + return $this->addDateBasedWhere('Year', $column, $operator, $value, $boolean); + } + + /** + * Add an "or where year" statement to the query. + * + * @param string $column + * @param string $operator + * @param \DateTimeInterface|string|int|null $value + * @return $this + */ + public function orWhereYear($column, $operator, $value = null) + { + [$value, $operator] = $this->prepareValueAndOperator( + $value, $operator, func_num_args() === 2 + ); + + return $this->whereYear($column, $operator, $value, 'or'); + } + + /** + * Add a date based (year, month, day, time) statement to the query. + * + * @param string $type + * @param string $column + * @param string $operator + * @param mixed $value + * @param string $boolean + * @return $this + */ + protected function addDateBasedWhere($type, $column, $operator, $value, $boolean = 'and') + { + $this->wheres[] = compact('column', 'type', 'boolean', 'operator', 'value'); + + if (! $value instanceof Expression) { + $this->addBinding($value, 'where'); + } + + return $this; + } + + /** + * Add a nested where statement to the query. + * + * @param \Closure $callback + * @param string $boolean + * @return $this + */ + public function whereNested(Closure $callback, $boolean = 'and') + { + call_user_func($callback, $query = $this->forNestedWhere()); + + return $this->addNestedWhereQuery($query, $boolean); + } + + /** + * Create a new query instance for nested where condition. + * + * @return \Illuminate\Database\Query\Builder + */ + public function forNestedWhere() + { + return $this->newQuery()->from($this->from); + } + + /** + * Add another query builder as a nested where to the query builder. + * + * @param \Illuminate\Database\Query\Builder $query + * @param string $boolean + * @return $this + */ + public function addNestedWhereQuery($query, $boolean = 'and') + { + if (count($query->wheres)) { + $type = 'Nested'; + + $this->wheres[] = compact('type', 'query', 'boolean'); + + $this->addBinding($query->getRawBindings()['where'], 'where'); + } + + return $this; + } + + /** + * Add a full sub-select to the query. + * + * @param string $column + * @param string $operator + * @param \Closure $callback + * @param string $boolean + * @return $this + */ + protected function whereSub($column, $operator, Closure $callback, $boolean) + { + $type = 'Sub'; + + // Once we have the query instance we can simply execute it so it can add all + // of the sub-select's conditions to itself, and then we can cache it off + // in the array of where clauses for the "main" parent query instance. + call_user_func($callback, $query = $this->forSubQuery()); + + $this->wheres[] = compact( + 'type', 'column', 'operator', 'query', 'boolean' + ); + + $this->addBinding($query->getBindings(), 'where'); + + return $this; + } + + /** + * Add an exists clause to the query. + * + * @param \Closure $callback + * @param string $boolean + * @param bool $not + * @return $this + */ + public function whereExists(Closure $callback, $boolean = 'and', $not = false) + { + $query = $this->forSubQuery(); + + // Similar to the sub-select clause, we will create a new query instance so + // the developer may cleanly specify the entire exists query and we will + // compile the whole thing in the grammar and insert it into the SQL. + call_user_func($callback, $query); + + return $this->addWhereExistsQuery($query, $boolean, $not); + } + + /** + * Add an or exists clause to the query. + * + * @param \Closure $callback + * @param bool $not + * @return $this + */ + public function orWhereExists(Closure $callback, $not = false) + { + return $this->whereExists($callback, 'or', $not); + } + + /** + * Add a where not exists clause to the query. + * + * @param \Closure $callback + * @param string $boolean + * @return $this + */ + public function whereNotExists(Closure $callback, $boolean = 'and') + { + return $this->whereExists($callback, $boolean, true); + } + + /** + * Add a where not exists clause to the query. + * + * @param \Closure $callback + * @return $this + */ + public function orWhereNotExists(Closure $callback) + { + return $this->orWhereExists($callback, true); + } + + /** + * Add an exists clause to the query. + * + * @param \Illuminate\Database\Query\Builder $query + * @param string $boolean + * @param bool $not + * @return $this + */ + public function addWhereExistsQuery(self $query, $boolean = 'and', $not = false) + { + $type = $not ? 'NotExists' : 'Exists'; + + $this->wheres[] = compact('type', 'query', 'boolean'); + + $this->addBinding($query->getBindings(), 'where'); + + return $this; + } + + /** + * Adds a where condition using row values. + * + * @param array $columns + * @param string $operator + * @param array $values + * @param string $boolean + * @return $this + * + * @throws \InvalidArgumentException + */ + public function whereRowValues($columns, $operator, $values, $boolean = 'and') + { + if (count($columns) !== count($values)) { + throw new InvalidArgumentException('The number of columns must match the number of values'); + } + + $type = 'RowValues'; + + $this->wheres[] = compact('type', 'columns', 'operator', 'values', 'boolean'); + + $this->addBinding($this->cleanBindings($values)); + + return $this; + } + + /** + * Adds an or where condition using row values. + * + * @param array $columns + * @param string $operator + * @param array $values + * @return $this + */ + public function orWhereRowValues($columns, $operator, $values) + { + return $this->whereRowValues($columns, $operator, $values, 'or'); + } + + /** + * Add a "where JSON contains" clause to the query. + * + * @param string $column + * @param mixed $value + * @param string $boolean + * @param bool $not + * @return $this + */ + public function whereJsonContains($column, $value, $boolean = 'and', $not = false) + { + $type = 'JsonContains'; + + $this->wheres[] = compact('type', 'column', 'value', 'boolean', 'not'); + + if (! $value instanceof Expression) { + $this->addBinding($this->grammar->prepareBindingForJsonContains($value)); + } + + return $this; + } + + /** + * Add an "or where JSON contains" clause to the query. + * + * @param string $column + * @param mixed $value + * @return $this + */ + public function orWhereJsonContains($column, $value) + { + return $this->whereJsonContains($column, $value, 'or'); + } + + /** + * Add a "where JSON not contains" clause to the query. + * + * @param string $column + * @param mixed $value + * @param string $boolean + * @return $this + */ + public function whereJsonDoesntContain($column, $value, $boolean = 'and') + { + return $this->whereJsonContains($column, $value, $boolean, true); + } + + /** + * Add an "or where JSON not contains" clause to the query. + * + * @param string $column + * @param mixed $value + * @return $this + */ + public function orWhereJsonDoesntContain($column, $value) + { + return $this->whereJsonDoesntContain($column, $value, 'or'); + } + + /** + * Add a "where JSON length" clause to the query. + * + * @param string $column + * @param mixed $operator + * @param mixed $value + * @param string $boolean + * @return $this + */ + public function whereJsonLength($column, $operator, $value = null, $boolean = 'and') + { + $type = 'JsonLength'; + + [$value, $operator] = $this->prepareValueAndOperator( + $value, $operator, func_num_args() === 2 + ); + + $this->wheres[] = compact('type', 'column', 'operator', 'value', 'boolean'); + + if (! $value instanceof Expression) { + $this->addBinding((int) $this->flattenValue($value)); + } + + return $this; + } + + /** + * Add an "or where JSON length" clause to the query. + * + * @param string $column + * @param mixed $operator + * @param mixed $value + * @return $this + */ + public function orWhereJsonLength($column, $operator, $value = null) + { + [$value, $operator] = $this->prepareValueAndOperator( + $value, $operator, func_num_args() === 2 + ); + + return $this->whereJsonLength($column, $operator, $value, 'or'); + } + + /** + * Handles dynamic "where" clauses to the query. + * + * @param string $method + * @param array $parameters + * @return $this + */ + public function dynamicWhere($method, $parameters) + { + $finder = substr($method, 5); + + $segments = preg_split( + '/(And|Or)(?=[A-Z])/', $finder, -1, PREG_SPLIT_DELIM_CAPTURE + ); + + // The connector variable will determine which connector will be used for the + // query condition. We will change it as we come across new boolean values + // in the dynamic method strings, which could contain a number of these. + $connector = 'and'; + + $index = 0; + + foreach ($segments as $segment) { + // If the segment is not a boolean connector, we can assume it is a column's name + // and we will add it to the query as a new constraint as a where clause, then + // we can keep iterating through the dynamic method string's segments again. + if ($segment !== 'And' && $segment !== 'Or') { + $this->addDynamic($segment, $connector, $parameters, $index); + + $index++; + } + + // Otherwise, we will store the connector so we know how the next where clause we + // find in the query should be connected to the previous ones, meaning we will + // have the proper boolean connector to connect the next where clause found. + else { + $connector = $segment; + } + } + + return $this; + } + + /** + * Add a single dynamic where clause statement to the query. + * + * @param string $segment + * @param string $connector + * @param array $parameters + * @param int $index + * @return void + */ + protected function addDynamic($segment, $connector, $parameters, $index) + { + // Once we have parsed out the columns and formatted the boolean operators we + // are ready to add it to this query as a where clause just like any other + // clause on the query. Then we'll increment the parameter index values. + $bool = strtolower($connector); + + $this->where(Str::snake($segment), '=', $parameters[$index], $bool); + } + + /** + * Add a "where fulltext" clause to the query. + * + * @param string|string[] $columns + * @param string $value + * @param string $boolean + * @return $this + */ + public function whereFullText($columns, $value, array $options = [], $boolean = 'and') + { + $type = 'Fulltext'; + + $columns = (array) $columns; + + $this->wheres[] = compact('type', 'columns', 'value', 'options', 'boolean'); + + $this->addBinding($value); + + return $this; + } + + /** + * Add a "or where fulltext" clause to the query. + * + * @param string|string[] $columns + * @param string $value + * @return $this + */ + public function orWhereFullText($columns, $value, array $options = []) + { + return $this->whereFulltext($columns, $value, $options, 'or'); + } + + /** + * Add a "group by" clause to the query. + * + * @param array|string ...$groups + * @return $this + */ + public function groupBy(...$groups) + { + foreach ($groups as $group) { + $this->groups = array_merge( + (array) $this->groups, + Arr::wrap($group) + ); + } + + return $this; + } + + /** + * Add a raw groupBy clause to the query. + * + * @param string $sql + * @param array $bindings + * @return $this + */ + public function groupByRaw($sql, array $bindings = []) + { + $this->groups[] = new Expression($sql); + + $this->addBinding($bindings, 'groupBy'); + + return $this; + } + + /** + * Add a "having" clause to the query. + * + * @param string $column + * @param string|null $operator + * @param string|null $value + * @param string $boolean + * @return $this + */ + public function having($column, $operator = null, $value = null, $boolean = 'and') + { + $type = 'Basic'; + + // Here we will make some assumptions about the operator. If only 2 values are + // passed to the method, we will assume that the operator is an equals sign + // and keep going. Otherwise, we'll require the operator to be passed in. + [$value, $operator] = $this->prepareValueAndOperator( + $value, $operator, func_num_args() === 2 + ); + + // If the given operator is not found in the list of valid operators we will + // assume that the developer is just short-cutting the '=' operators and + // we will set the operators to '=' and set the values appropriately. + if ($this->invalidOperator($operator)) { + [$value, $operator] = [$operator, '=']; + } + + if ($this->isBitwiseOperator($operator)) { + $type = 'Bitwise'; + } + + $this->havings[] = compact('type', 'column', 'operator', 'value', 'boolean'); + + if (! $value instanceof Expression) { + $this->addBinding($this->flattenValue($value), 'having'); + } + + return $this; + } + + /** + * Add an "or having" clause to the query. + * + * @param string $column + * @param string|null $operator + * @param string|null $value + * @return $this + */ + public function orHaving($column, $operator = null, $value = null) + { + [$value, $operator] = $this->prepareValueAndOperator( + $value, $operator, func_num_args() === 2 + ); + + return $this->having($column, $operator, $value, 'or'); + } + + /** + * Add a "having between " clause to the query. + * + * @param string $column + * @param array $values + * @param string $boolean + * @param bool $not + * @return $this + */ + public function havingBetween($column, array $values, $boolean = 'and', $not = false) + { + $type = 'between'; + + $this->havings[] = compact('type', 'column', 'values', 'boolean', 'not'); + + $this->addBinding(array_slice($this->cleanBindings(Arr::flatten($values)), 0, 2), 'having'); + + return $this; + } + + /** + * Add a raw having clause to the query. + * + * @param string $sql + * @param array $bindings + * @param string $boolean + * @return $this + */ + public function havingRaw($sql, array $bindings = [], $boolean = 'and') + { + $type = 'Raw'; + + $this->havings[] = compact('type', 'sql', 'boolean'); + + $this->addBinding($bindings, 'having'); + + return $this; + } + + /** + * Add a raw or having clause to the query. + * + * @param string $sql + * @param array $bindings + * @return $this + */ + public function orHavingRaw($sql, array $bindings = []) + { + return $this->havingRaw($sql, $bindings, 'or'); + } + + /** + * Add an "order by" clause to the query. + * + * @param \Closure|\Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder|\Illuminate\Database\Query\Expression|string $column + * @param string $direction + * @return $this + * + * @throws \InvalidArgumentException + */ + public function orderBy($column, $direction = 'asc') + { + if ($this->isQueryable($column)) { + [$query, $bindings] = $this->createSub($column); + + $column = new Expression('('.$query.')'); + + $this->addBinding($bindings, $this->unions ? 'unionOrder' : 'order'); + } + + $direction = strtolower($direction); + + if (! in_array($direction, ['asc', 'desc'], true)) { + throw new InvalidArgumentException('Order direction must be "asc" or "desc".'); + } + + $this->{$this->unions ? 'unionOrders' : 'orders'}[] = [ + 'column' => $column, + 'direction' => $direction, + ]; + + return $this; + } + + /** + * Add a descending "order by" clause to the query. + * + * @param \Closure|\Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder|\Illuminate\Database\Query\Expression|string $column + * @return $this + */ + public function orderByDesc($column) + { + return $this->orderBy($column, 'desc'); + } + + /** + * Add an "order by" clause for a timestamp to the query. + * + * @param \Closure|\Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder|\Illuminate\Database\Query\Expression|string $column + * @return $this + */ + public function latest($column = 'created_at') + { + return $this->orderBy($column, 'desc'); + } + + /** + * Add an "order by" clause for a timestamp to the query. + * + * @param \Closure|\Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder|\Illuminate\Database\Query\Expression|string $column + * @return $this + */ + public function oldest($column = 'created_at') + { + return $this->orderBy($column, 'asc'); + } + + /** + * Put the query's results in random order. + * + * @param string $seed + * @return $this + */ + public function inRandomOrder($seed = '') + { + return $this->orderByRaw($this->grammar->compileRandom($seed)); + } + + /** + * Add a raw "order by" clause to the query. + * + * @param string $sql + * @param array $bindings + * @return $this + */ + public function orderByRaw($sql, $bindings = []) + { + $type = 'Raw'; + + $this->{$this->unions ? 'unionOrders' : 'orders'}[] = compact('type', 'sql'); + + $this->addBinding($bindings, $this->unions ? 'unionOrder' : 'order'); + + return $this; + } + + /** + * Alias to set the "offset" value of the query. + * + * @param int $value + * @return $this + */ + public function skip($value) + { + return $this->offset($value); + } + + /** + * Set the "offset" value of the query. + * + * @param int $value + * @return $this + */ + public function offset($value) + { + $property = $this->unions ? 'unionOffset' : 'offset'; + + $this->$property = max(0, (int) $value); + + return $this; + } + + /** + * Alias to set the "limit" value of the query. + * + * @param int $value + * @return $this + */ + public function take($value) + { + return $this->limit($value); + } + + /** + * Set the "limit" value of the query. + * + * @param int $value + * @return $this + */ + public function limit($value) + { + $property = $this->unions ? 'unionLimit' : 'limit'; + + if ($value >= 0) { + $this->$property = ! is_null($value) ? (int) $value : null; + } + + return $this; + } + + /** + * Set the limit and offset for a given page. + * + * @param int $page + * @param int $perPage + * @return $this + */ + public function forPage($page, $perPage = 15) + { + return $this->offset(($page - 1) * $perPage)->limit($perPage); + } + + /** + * Constrain the query to the previous "page" of results before a given ID. + * + * @param int $perPage + * @param int|null $lastId + * @param string $column + * @return $this + */ + public function forPageBeforeId($perPage = 15, $lastId = 0, $column = 'id') + { + $this->orders = $this->removeExistingOrdersFor($column); + + if (! is_null($lastId)) { + $this->where($column, '<', $lastId); + } + + return $this->orderBy($column, 'desc') + ->limit($perPage); + } + + /** + * Constrain the query to the next "page" of results after a given ID. + * + * @param int $perPage + * @param int|null $lastId + * @param string $column + * @return $this + */ + public function forPageAfterId($perPage = 15, $lastId = 0, $column = 'id') + { + $this->orders = $this->removeExistingOrdersFor($column); + + if (! is_null($lastId)) { + $this->where($column, '>', $lastId); + } + + return $this->orderBy($column, 'asc') + ->limit($perPage); + } + + /** + * Remove all existing orders and optionally add a new order. + * + * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Query\Expression|string|null $column + * @param string $direction + * @return $this + */ + public function reorder($column = null, $direction = 'asc') + { + $this->orders = null; + $this->unionOrders = null; + $this->bindings['order'] = []; + $this->bindings['unionOrder'] = []; + + if ($column) { + return $this->orderBy($column, $direction); + } + + return $this; + } + + /** + * Get an array with all orders with a given column removed. + * + * @param string $column + * @return array + */ + protected function removeExistingOrdersFor($column) + { + return Collection::make($this->orders) + ->reject(function ($order) use ($column) { + return isset($order['column']) + ? $order['column'] === $column : false; + })->values()->all(); + } + + /** + * Add a union statement to the query. + * + * @param \Illuminate\Database\Query\Builder|\Closure $query + * @param bool $all + * @return $this + */ + public function union($query, $all = false) + { + if ($query instanceof Closure) { + call_user_func($query, $query = $this->newQuery()); + } + + $this->unions[] = compact('query', 'all'); + + $this->addBinding($query->getBindings(), 'union'); + + return $this; + } + + /** + * Add a union all statement to the query. + * + * @param \Illuminate\Database\Query\Builder|\Closure $query + * @return $this + */ + public function unionAll($query) + { + return $this->union($query, true); + } + + /** + * Lock the selected rows in the table. + * + * @param string|bool $value + * @return $this + */ + public function lock($value = true) + { + $this->lock = $value; + + if (! is_null($this->lock)) { + $this->useWritePdo(); + } + + return $this; + } + + /** + * Lock the selected rows in the table for updating. + * + * @return \Illuminate\Database\Query\Builder + */ + public function lockForUpdate() + { + return $this->lock(true); + } + + /** + * Share lock the selected rows in the table. + * + * @return \Illuminate\Database\Query\Builder + */ + public function sharedLock() + { + return $this->lock(false); + } + + /** + * Register a closure to be invoked before the query is executed. + * + * @param callable $callback + * @return $this + */ + public function beforeQuery(callable $callback) + { + $this->beforeQueryCallbacks[] = $callback; + + return $this; + } + + /** + * Invoke the "before query" modification callbacks. + * + * @return void + */ + public function applyBeforeQueryCallbacks() + { + foreach ($this->beforeQueryCallbacks as $callback) { + $callback($this); + } + + $this->beforeQueryCallbacks = []; + } + + /** + * Get the SQL representation of the query. + * + * @return string + */ + public function toSql() + { + $this->applyBeforeQueryCallbacks(); + + return $this->grammar->compileSelect($this); + } + + /** + * Execute a query for a single record by ID. + * + * @param int|string $id + * @param array $columns + * @return mixed|static + */ + public function find($id, $columns = ['*']) + { + return $this->where('id', '=', $id)->first($columns); + } + + /** + * Get a single column's value from the first result of a query. + * + * @param string $column + * @return mixed + */ + public function value($column) + { + $result = (array) $this->first([$column]); + + return count($result) > 0 ? reset($result) : null; + } + + /** + * Execute the query as a "select" statement. + * + * @param array|string $columns + * @return \Illuminate\Support\Collection + */ + public function get($columns = ['*']) + { + return collect($this->onceWithColumns(Arr::wrap($columns), function () { + return $this->processor->processSelect($this, $this->runSelect()); + })); + } + + /** + * Run the query as a "select" statement against the connection. + * + * @return array + */ + protected function runSelect() + { + return $this->connection->select( + $this->toSql(), $this->getBindings(), ! $this->useWritePdo + ); + } + + /** + * Paginate the given query into a simple paginator. + * + * @param int $perPage + * @param array $columns + * @param string $pageName + * @param int|null $page + * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator + */ + public function paginate($perPage = 15, $columns = ['*'], $pageName = 'page', $page = null) + { + $page = $page ?: Paginator::resolveCurrentPage($pageName); + + $total = $this->getCountForPagination(); + + $results = $total ? $this->forPage($page, $perPage)->get($columns) : collect(); + + return $this->paginator($results, $total, $perPage, $page, [ + 'path' => Paginator::resolveCurrentPath(), + 'pageName' => $pageName, + ]); + } + + /** + * Get a paginator only supporting simple next and previous links. + * + * This is more efficient on larger data-sets, etc. + * + * @param int $perPage + * @param array $columns + * @param string $pageName + * @param int|null $page + * @return \Illuminate\Contracts\Pagination\Paginator + */ + public function simplePaginate($perPage = 15, $columns = ['*'], $pageName = 'page', $page = null) + { + $page = $page ?: Paginator::resolveCurrentPage($pageName); + + $this->offset(($page - 1) * $perPage)->limit($perPage + 1); + + return $this->simplePaginator($this->get($columns), $perPage, $page, [ + 'path' => Paginator::resolveCurrentPath(), + 'pageName' => $pageName, + ]); + } + + /** + * Get a paginator only supporting simple next and previous links. + * + * This is more efficient on larger data-sets, etc. + * + * @param int|null $perPage + * @param array $columns + * @param string $cursorName + * @param \Illuminate\Pagination\Cursor|string|null $cursor + * @return \Illuminate\Contracts\Pagination\CursorPaginator + */ + public function cursorPaginate($perPage = 15, $columns = ['*'], $cursorName = 'cursor', $cursor = null) + { + return $this->paginateUsingCursor($perPage, $columns, $cursorName, $cursor); + } + + /** + * Ensure the proper order by required for cursor pagination. + * + * @param bool $shouldReverse + * @return \Illuminate\Support\Collection + */ + protected function ensureOrderForCursorPagination($shouldReverse = false) + { + $this->enforceOrderBy(); + + return collect($this->orders ?? $this->unionOrders ?? [])->filter(function ($order) { + return Arr::has($order, 'direction'); + })->when($shouldReverse, function (Collection $orders) { + return $orders->map(function ($order) { + $order['direction'] = $order['direction'] === 'asc' ? 'desc' : 'asc'; + + return $order; + }); + })->values(); + } + + /** + * Get the count of the total records for the paginator. + * + * @param array $columns + * @return int + */ + public function getCountForPagination($columns = ['*']) + { + $results = $this->runPaginationCountQuery($columns); + + // Once we have run the pagination count query, we will get the resulting count and + // take into account what type of query it was. When there is a group by we will + // just return the count of the entire results set since that will be correct. + if (! isset($results[0])) { + return 0; + } elseif (is_object($results[0])) { + return (int) $results[0]->aggregate; + } + + return (int) array_change_key_case((array) $results[0])['aggregate']; + } + + /** + * Run a pagination count query. + * + * @param array $columns + * @return array + */ + protected function runPaginationCountQuery($columns = ['*']) + { + if ($this->groups || $this->havings) { + $clone = $this->cloneForPaginationCount(); + + if (is_null($clone->columns) && ! empty($this->joins)) { + $clone->select($this->from.'.*'); + } + + return $this->newQuery() + ->from(new Expression('('.$clone->toSql().') as '.$this->grammar->wrap('aggregate_table'))) + ->mergeBindings($clone) + ->setAggregate('count', $this->withoutSelectAliases($columns)) + ->get()->all(); + } + + $without = $this->unions ? ['orders', 'limit', 'offset'] : ['columns', 'orders', 'limit', 'offset']; + + return $this->cloneWithout($without) + ->cloneWithoutBindings($this->unions ? ['order'] : ['select', 'order']) + ->setAggregate('count', $this->withoutSelectAliases($columns)) + ->get()->all(); + } + + /** + * Clone the existing query instance for usage in a pagination subquery. + * + * @return self + */ + protected function cloneForPaginationCount() + { + return $this->cloneWithout(['orders', 'limit', 'offset']) + ->cloneWithoutBindings(['order']); + } + + /** + * Remove the column aliases since they will break count queries. + * + * @param array $columns + * @return array + */ + protected function withoutSelectAliases(array $columns) + { + return array_map(function ($column) { + return is_string($column) && ($aliasPosition = stripos($column, ' as ')) !== false + ? substr($column, 0, $aliasPosition) : $column; + }, $columns); + } + + /** + * Get a lazy collection for the given query. + * + * @return \Illuminate\Support\LazyCollection + */ + public function cursor() + { + if (is_null($this->columns)) { + $this->columns = ['*']; + } + + return new LazyCollection(function () { + yield from $this->connection->cursor( + $this->toSql(), $this->getBindings(), ! $this->useWritePdo + ); + }); + } + + /** + * Throw an exception if the query doesn't have an orderBy clause. + * + * @return void + * + * @throws \RuntimeException + */ + protected function enforceOrderBy() + { + if (empty($this->orders) && empty($this->unionOrders)) { + throw new RuntimeException('You must specify an orderBy clause when using this function.'); + } + } + + /** + * Get a collection instance containing the values of a given column. + * + * @param string $column + * @param string|null $key + * @return \Illuminate\Support\Collection + */ + public function pluck($column, $key = null) + { + // First, we will need to select the results of the query accounting for the + // given columns / key. Once we have the results, we will be able to take + // the results and get the exact data that was requested for the query. + $queryResult = $this->onceWithColumns( + is_null($key) ? [$column] : [$column, $key], + function () { + return $this->processor->processSelect( + $this, $this->runSelect() + ); + } + ); + + if (empty($queryResult)) { + return collect(); + } + + // If the columns are qualified with a table or have an alias, we cannot use + // those directly in the "pluck" operations since the results from the DB + // are only keyed by the column itself. We'll strip the table out here. + $column = $this->stripTableForPluck($column); + + $key = $this->stripTableForPluck($key); + + return is_array($queryResult[0]) + ? $this->pluckFromArrayColumn($queryResult, $column, $key) + : $this->pluckFromObjectColumn($queryResult, $column, $key); + } + + /** + * Strip off the table name or alias from a column identifier. + * + * @param string $column + * @return string|null + */ + protected function stripTableForPluck($column) + { + if (is_null($column)) { + return $column; + } + + $separator = strpos(strtolower($column), ' as ') !== false ? ' as ' : '\.'; + + return last(preg_split('~'.$separator.'~i', $column)); + } + + /** + * Retrieve column values from rows represented as objects. + * + * @param array $queryResult + * @param string $column + * @param string $key + * @return \Illuminate\Support\Collection + */ + protected function pluckFromObjectColumn($queryResult, $column, $key) + { + $results = []; + + if (is_null($key)) { + foreach ($queryResult as $row) { + $results[] = $row->$column; + } + } else { + foreach ($queryResult as $row) { + $results[$row->$key] = $row->$column; + } + } + + return collect($results); + } + + /** + * Retrieve column values from rows represented as arrays. + * + * @param array $queryResult + * @param string $column + * @param string $key + * @return \Illuminate\Support\Collection + */ + protected function pluckFromArrayColumn($queryResult, $column, $key) + { + $results = []; + + if (is_null($key)) { + foreach ($queryResult as $row) { + $results[] = $row[$column]; + } + } else { + foreach ($queryResult as $row) { + $results[$row[$key]] = $row[$column]; + } + } + + return collect($results); + } + + /** + * Concatenate values of a given column as a string. + * + * @param string $column + * @param string $glue + * @return string + */ + public function implode($column, $glue = '') + { + return $this->pluck($column)->implode($glue); + } + + /** + * Determine if any rows exist for the current query. + * + * @return bool + */ + public function exists() + { + $this->applyBeforeQueryCallbacks(); + + $results = $this->connection->select( + $this->grammar->compileExists($this), $this->getBindings(), ! $this->useWritePdo + ); + + // If the results has rows, we will get the row and see if the exists column is a + // boolean true. If there is no results for this query we will return false as + // there are no rows for this query at all and we can return that info here. + if (isset($results[0])) { + $results = (array) $results[0]; + + return (bool) $results['exists']; + } + + return false; + } + + /** + * Determine if no rows exist for the current query. + * + * @return bool + */ + public function doesntExist() + { + return ! $this->exists(); + } + + /** + * Execute the given callback if no rows exist for the current query. + * + * @param \Closure $callback + * @return mixed + */ + public function existsOr(Closure $callback) + { + return $this->exists() ? true : $callback(); + } + + /** + * Execute the given callback if rows exist for the current query. + * + * @param \Closure $callback + * @return mixed + */ + public function doesntExistOr(Closure $callback) + { + return $this->doesntExist() ? true : $callback(); + } + + /** + * Retrieve the "count" result of the query. + * + * @param string $columns + * @return int + */ + public function count($columns = '*') + { + return (int) $this->aggregate(__FUNCTION__, Arr::wrap($columns)); + } + + /** + * Retrieve the minimum value of a given column. + * + * @param string $column + * @return mixed + */ + public function min($column) + { + return $this->aggregate(__FUNCTION__, [$column]); + } + + /** + * Retrieve the maximum value of a given column. + * + * @param string $column + * @return mixed + */ + public function max($column) + { + return $this->aggregate(__FUNCTION__, [$column]); + } + + /** + * Retrieve the sum of the values of a given column. + * + * @param string $column + * @return mixed + */ + public function sum($column) + { + $result = $this->aggregate(__FUNCTION__, [$column]); + + return $result ?: 0; + } + + /** + * Retrieve the average of the values of a given column. + * + * @param string $column + * @return mixed + */ + public function avg($column) + { + return $this->aggregate(__FUNCTION__, [$column]); + } + + /** + * Alias for the "avg" method. + * + * @param string $column + * @return mixed + */ + public function average($column) + { + return $this->avg($column); + } + + /** + * Execute an aggregate function on the database. + * + * @param string $function + * @param array $columns + * @return mixed + */ + public function aggregate($function, $columns = ['*']) + { + $results = $this->cloneWithout($this->unions || $this->havings ? [] : ['columns']) + ->cloneWithoutBindings($this->unions || $this->havings ? [] : ['select']) + ->setAggregate($function, $columns) + ->get($columns); + + if (! $results->isEmpty()) { + return array_change_key_case((array) $results[0])['aggregate']; + } + } + + /** + * Execute a numeric aggregate function on the database. + * + * @param string $function + * @param array $columns + * @return float|int + */ + public function numericAggregate($function, $columns = ['*']) + { + $result = $this->aggregate($function, $columns); + + // If there is no result, we can obviously just return 0 here. Next, we will check + // if the result is an integer or float. If it is already one of these two data + // types we can just return the result as-is, otherwise we will convert this. + if (! $result) { + return 0; + } + + if (is_int($result) || is_float($result)) { + return $result; + } + + // If the result doesn't contain a decimal place, we will assume it is an int then + // cast it to one. When it does we will cast it to a float since it needs to be + // cast to the expected data type for the developers out of pure convenience. + return strpos((string) $result, '.') === false + ? (int) $result : (float) $result; + } + + /** + * Set the aggregate property without running the query. + * + * @param string $function + * @param array $columns + * @return $this + */ + protected function setAggregate($function, $columns) + { + $this->aggregate = compact('function', 'columns'); + + if (empty($this->groups)) { + $this->orders = null; + + $this->bindings['order'] = []; + } + + return $this; + } + + /** + * Execute the given callback while selecting the given columns. + * + * After running the callback, the columns are reset to the original value. + * + * @param array $columns + * @param callable $callback + * @return mixed + */ + protected function onceWithColumns($columns, $callback) + { + $original = $this->columns; + + if (is_null($original)) { + $this->columns = $columns; + } + + $result = $callback(); + + $this->columns = $original; + + return $result; + } + + /** + * Insert new records into the database. + * + * @param array $values + * @return bool + */ + public function insert(array $values) + { + // Since every insert gets treated like a batch insert, we will make sure the + // bindings are structured in a way that is convenient when building these + // inserts statements by verifying these elements are actually an array. + if (empty($values)) { + return true; + } + + if (! is_array(reset($values))) { + $values = [$values]; + } + + // Here, we will sort the insert keys for every record so that each insert is + // in the same order for the record. We need to make sure this is the case + // so there are not any errors or problems when inserting these records. + else { + foreach ($values as $key => $value) { + ksort($value); + + $values[$key] = $value; + } + } + + $this->applyBeforeQueryCallbacks(); + + // Finally, we will run this query against the database connection and return + // the results. We will need to also flatten these bindings before running + // the query so they are all in one huge, flattened array for execution. + return $this->connection->insert( + $this->grammar->compileInsert($this, $values), + $this->cleanBindings(Arr::flatten($values, 1)) + ); + } + + /** + * Insert new records into the database while ignoring errors. + * + * @param array $values + * @return int + */ + public function insertOrIgnore(array $values) + { + if (empty($values)) { + return 0; + } + + if (! is_array(reset($values))) { + $values = [$values]; + } else { + foreach ($values as $key => $value) { + ksort($value); + $values[$key] = $value; + } + } + + $this->applyBeforeQueryCallbacks(); + + return $this->connection->affectingStatement( + $this->grammar->compileInsertOrIgnore($this, $values), + $this->cleanBindings(Arr::flatten($values, 1)) + ); + } + + /** + * Insert a new record and get the value of the primary key. + * + * @param array $values + * @param string|null $sequence + * @return int + */ + public function insertGetId(array $values, $sequence = null) + { + $this->applyBeforeQueryCallbacks(); + + $sql = $this->grammar->compileInsertGetId($this, $values, $sequence); + + $values = $this->cleanBindings($values); + + return $this->processor->processInsertGetId($this, $sql, $values, $sequence); + } + + /** + * Insert new records into the table using a subquery. + * + * @param array $columns + * @param \Closure|\Illuminate\Database\Query\Builder|string $query + * @return int + */ + public function insertUsing(array $columns, $query) + { + $this->applyBeforeQueryCallbacks(); + + [$sql, $bindings] = $this->createSub($query); + + return $this->connection->affectingStatement( + $this->grammar->compileInsertUsing($this, $columns, $sql), + $this->cleanBindings($bindings) + ); + } + + /** + * Update records in the database. + * + * @param array $values + * @return int + */ + public function update(array $values) + { + $this->applyBeforeQueryCallbacks(); + + $sql = $this->grammar->compileUpdate($this, $values); + + return $this->connection->update($sql, $this->cleanBindings( + $this->grammar->prepareBindingsForUpdate($this->bindings, $values) + )); + } + + /** + * Update records in a PostgreSQL database using the update from syntax. + * + * @param array $values + * @return int + */ + public function updateFrom(array $values) + { + if (! method_exists($this->grammar, 'compileUpdateFrom')) { + throw new LogicException('This database engine does not support the updateFrom method.'); + } + + $this->applyBeforeQueryCallbacks(); + + $sql = $this->grammar->compileUpdateFrom($this, $values); + + return $this->connection->update($sql, $this->cleanBindings( + $this->grammar->prepareBindingsForUpdateFrom($this->bindings, $values) + )); + } + + /** + * Insert or update a record matching the attributes, and fill it with values. + * + * @param array $attributes + * @param array $values + * @return bool + */ + public function updateOrInsert(array $attributes, array $values = []) + { + if (! $this->where($attributes)->exists()) { + return $this->insert(array_merge($attributes, $values)); + } + + if (empty($values)) { + return true; + } + + return (bool) $this->limit(1)->update($values); + } + + /** + * Insert new records or update the existing ones. + * + * @param array $values + * @param array|string $uniqueBy + * @param array|null $update + * @return int + */ + public function upsert(array $values, $uniqueBy, $update = null) + { + if (empty($values)) { + return 0; + } elseif ($update === []) { + return (int) $this->insert($values); + } + + if (! is_array(reset($values))) { + $values = [$values]; + } else { + foreach ($values as $key => $value) { + ksort($value); + + $values[$key] = $value; + } + } + + if (is_null($update)) { + $update = array_keys(reset($values)); + } + + $this->applyBeforeQueryCallbacks(); + + $bindings = $this->cleanBindings(array_merge( + Arr::flatten($values, 1), + collect($update)->reject(function ($value, $key) { + return is_int($key); + })->all() + )); + + return $this->connection->affectingStatement( + $this->grammar->compileUpsert($this, $values, (array) $uniqueBy, $update), + $bindings + ); + } + + /** + * Increment a column's value by a given amount. + * + * @param string $column + * @param float|int $amount + * @param array $extra + * @return int + * + * @throws \InvalidArgumentException + */ + public function increment($column, $amount = 1, array $extra = []) + { + if (! is_numeric($amount)) { + throw new InvalidArgumentException('Non-numeric value passed to increment method.'); + } + + $wrapped = $this->grammar->wrap($column); + + $columns = array_merge([$column => $this->raw("$wrapped + $amount")], $extra); + + return $this->update($columns); + } + + /** + * Decrement a column's value by a given amount. + * + * @param string $column + * @param float|int $amount + * @param array $extra + * @return int + * + * @throws \InvalidArgumentException + */ + public function decrement($column, $amount = 1, array $extra = []) + { + if (! is_numeric($amount)) { + throw new InvalidArgumentException('Non-numeric value passed to decrement method.'); + } + + $wrapped = $this->grammar->wrap($column); + + $columns = array_merge([$column => $this->raw("$wrapped - $amount")], $extra); + + return $this->update($columns); + } + + /** + * Delete records from the database. + * + * @param mixed $id + * @return int + */ + public function delete($id = null) + { + // If an ID is passed to the method, we will set the where clause to check the + // ID to let developers to simply and quickly remove a single row from this + // database without manually specifying the "where" clauses on the query. + if (! is_null($id)) { + $this->where($this->from.'.id', '=', $id); + } + + $this->applyBeforeQueryCallbacks(); + + return $this->connection->delete( + $this->grammar->compileDelete($this), $this->cleanBindings( + $this->grammar->prepareBindingsForDelete($this->bindings) + ) + ); + } + + /** + * Run a truncate statement on the table. + * + * @return void + */ + public function truncate() + { + $this->applyBeforeQueryCallbacks(); + + foreach ($this->grammar->compileTruncate($this) as $sql => $bindings) { + $this->connection->statement($sql, $bindings); + } + } + + /** + * Get a new instance of the query builder. + * + * @return \Illuminate\Database\Query\Builder + */ + public function newQuery() + { + return new static($this->connection, $this->grammar, $this->processor); + } + + /** + * Create a new query instance for a sub-query. + * + * @return \Illuminate\Database\Query\Builder + */ + protected function forSubQuery() + { + return $this->newQuery(); + } + + /** + * Create a raw database expression. + * + * @param mixed $value + * @return \Illuminate\Database\Query\Expression + */ + public function raw($value) + { + return $this->connection->raw($value); + } + + /** + * Get the current query value bindings in a flattened array. + * + * @return array + */ + public function getBindings() + { + return Arr::flatten($this->bindings); + } + + /** + * Get the raw array of bindings. + * + * @return array + */ + public function getRawBindings() + { + return $this->bindings; + } + + /** + * Set the bindings on the query builder. + * + * @param array $bindings + * @param string $type + * @return $this + * + * @throws \InvalidArgumentException + */ + public function setBindings(array $bindings, $type = 'where') + { + if (! array_key_exists($type, $this->bindings)) { + throw new InvalidArgumentException("Invalid binding type: {$type}."); + } + + $this->bindings[$type] = $bindings; + + return $this; + } + + /** + * Add a binding to the query. + * + * @param mixed $value + * @param string $type + * @return $this + * + * @throws \InvalidArgumentException + */ + public function addBinding($value, $type = 'where') + { + if (! array_key_exists($type, $this->bindings)) { + throw new InvalidArgumentException("Invalid binding type: {$type}."); + } + + if (is_array($value)) { + $this->bindings[$type] = array_values(array_map( + [$this, 'castBinding'], + array_merge($this->bindings[$type], $value), + )); + } else { + $this->bindings[$type][] = $this->castBinding($value); + } + + return $this; + } + + /** + * Cast the given binding value. + * + * @param mixed $value + * @return mixed + */ + public function castBinding($value) + { + if (function_exists('enum_exists') && $value instanceof BackedEnum) { + return $value->value; + } + + return $value; + } + + /** + * Merge an array of bindings into our bindings. + * + * @param \Illuminate\Database\Query\Builder $query + * @return $this + */ + public function mergeBindings(self $query) + { + $this->bindings = array_merge_recursive($this->bindings, $query->bindings); + + return $this; + } + + /** + * Remove all of the expressions from a list of bindings. + * + * @param array $bindings + * @return array + */ + public function cleanBindings(array $bindings) + { + return collect($bindings) + ->reject(function ($binding) { + return $binding instanceof Expression; + }) + ->map([$this, 'castBinding']) + ->values() + ->all(); + } + + /** + * Get a scalar type value from an unknown type of input. + * + * @param mixed $value + * @return mixed + */ + protected function flattenValue($value) + { + return is_array($value) ? head(Arr::flatten($value)) : $value; + } + + /** + * Get the default key name of the table. + * + * @return string + */ + protected function defaultKeyName() + { + return 'id'; + } + + /** + * Get the database connection instance. + * + * @return \Illuminate\Database\ConnectionInterface + */ + public function getConnection() + { + return $this->connection; + } + + /** + * Get the database query processor instance. + * + * @return \Illuminate\Database\Query\Processors\Processor + */ + public function getProcessor() + { + return $this->processor; + } + + /** + * Get the query grammar instance. + * + * @return \Illuminate\Database\Query\Grammars\Grammar + */ + public function getGrammar() + { + return $this->grammar; + } + + /** + * Use the write pdo for query. + * + * @return $this + */ + public function useWritePdo() + { + $this->useWritePdo = true; + + return $this; + } + + /** + * Determine if the value is a query builder instance or a Closure. + * + * @param mixed $value + * @return bool + */ + protected function isQueryable($value) + { + return $value instanceof self || + $value instanceof EloquentBuilder || + $value instanceof Relation || + $value instanceof Closure; + } + + /** + * Clone the query. + * + * @return static + */ + public function clone() + { + return clone $this; + } + + /** + * Clone the query without the given properties. + * + * @param array $properties + * @return static + */ + public function cloneWithout(array $properties) + { + return tap($this->clone(), function ($clone) use ($properties) { + foreach ($properties as $property) { + $clone->{$property} = null; + } + }); + } + + /** + * Clone the query without the given bindings. + * + * @param array $except + * @return static + */ + public function cloneWithoutBindings(array $except) + { + return tap($this->clone(), function ($clone) use ($except) { + foreach ($except as $type) { + $clone->bindings[$type] = []; + } + }); + } + + /** + * Dump the current SQL and bindings. + * + * @return $this + */ + public function dump() + { + dump($this->toSql(), $this->getBindings()); + + return $this; + } + + /** + * Die and dump the current SQL and bindings. + * + * @return never + */ + public function dd() + { + dd($this->toSql(), $this->getBindings()); + } + + /** + * Handle dynamic method calls into the method. + * + * @param string $method + * @param array $parameters + * @return mixed + * + * @throws \BadMethodCallException + */ + public function __call($method, $parameters) + { + if (static::hasMacro($method)) { + return $this->macroCall($method, $parameters); + } + + if (Str::startsWith($method, 'where')) { + return $this->dynamicWhere($method, $parameters); + } + + static::throwBadMethodCallException($method); + } +} diff --git a/vendor/illuminate/database/Query/Expression.php b/vendor/illuminate/database/Query/Expression.php new file mode 100644 index 0000000..de69029 --- /dev/null +++ b/vendor/illuminate/database/Query/Expression.php @@ -0,0 +1,44 @@ +value = $value; + } + + /** + * Get the value of the expression. + * + * @return mixed + */ + public function getValue() + { + return $this->value; + } + + /** + * Get the value of the expression. + * + * @return string + */ + public function __toString() + { + return (string) $this->getValue(); + } +} diff --git a/vendor/illuminate/database/Query/Grammars/Grammar.php b/vendor/illuminate/database/Query/Grammars/Grammar.php new file mode 100644 index 0000000..0dbdb1e --- /dev/null +++ b/vendor/illuminate/database/Query/Grammars/Grammar.php @@ -0,0 +1,1331 @@ +unions || $query->havings) && $query->aggregate) { + return $this->compileUnionAggregate($query); + } + + // If the query does not have any columns set, we'll set the columns to the + // * character to just get all of the columns from the database. Then we + // can build the query and concatenate all the pieces together as one. + $original = $query->columns; + + if (is_null($query->columns)) { + $query->columns = ['*']; + } + + // To compile the query, we'll spin through each component of the query and + // see if that component exists. If it does we'll just call the compiler + // function for the component which is responsible for making the SQL. + $sql = trim($this->concatenate( + $this->compileComponents($query)) + ); + + if ($query->unions) { + $sql = $this->wrapUnion($sql).' '.$this->compileUnions($query); + } + + $query->columns = $original; + + return $sql; + } + + /** + * Compile the components necessary for a select clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @return array + */ + protected function compileComponents(Builder $query) + { + $sql = []; + + foreach ($this->selectComponents as $component) { + if (isset($query->$component)) { + $method = 'compile'.ucfirst($component); + + $sql[$component] = $this->$method($query, $query->$component); + } + } + + return $sql; + } + + /** + * Compile an aggregated select clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $aggregate + * @return string + */ + protected function compileAggregate(Builder $query, $aggregate) + { + $column = $this->columnize($aggregate['columns']); + + // If the query has a "distinct" constraint and we're not asking for all columns + // we need to prepend "distinct" onto the column name so that the query takes + // it into account when it performs the aggregating operations on the data. + if (is_array($query->distinct)) { + $column = 'distinct '.$this->columnize($query->distinct); + } elseif ($query->distinct && $column !== '*') { + $column = 'distinct '.$column; + } + + return 'select '.$aggregate['function'].'('.$column.') as aggregate'; + } + + /** + * Compile the "select *" portion of the query. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $columns + * @return string|null + */ + protected function compileColumns(Builder $query, $columns) + { + // If the query is actually performing an aggregating select, we will let that + // compiler handle the building of the select clauses, as it will need some + // more syntax that is best handled by that function to keep things neat. + if (! is_null($query->aggregate)) { + return; + } + + if ($query->distinct) { + $select = 'select distinct '; + } else { + $select = 'select '; + } + + return $select.$this->columnize($columns); + } + + /** + * Compile the "from" portion of the query. + * + * @param \Illuminate\Database\Query\Builder $query + * @param string $table + * @return string + */ + protected function compileFrom(Builder $query, $table) + { + return 'from '.$this->wrapTable($table); + } + + /** + * Compile the "join" portions of the query. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $joins + * @return string + */ + protected function compileJoins(Builder $query, $joins) + { + return collect($joins)->map(function ($join) use ($query) { + $table = $this->wrapTable($join->table); + + $nestedJoins = is_null($join->joins) ? '' : ' '.$this->compileJoins($query, $join->joins); + + $tableAndNestedJoins = is_null($join->joins) ? $table : '('.$table.$nestedJoins.')'; + + return trim("{$join->type} join {$tableAndNestedJoins} {$this->compileWheres($join)}"); + })->implode(' '); + } + + /** + * Compile the "where" portions of the query. + * + * @param \Illuminate\Database\Query\Builder $query + * @return string + */ + public function compileWheres(Builder $query) + { + // Each type of where clauses has its own compiler function which is responsible + // for actually creating the where clauses SQL. This helps keep the code nice + // and maintainable since each clause has a very small method that it uses. + if (is_null($query->wheres)) { + return ''; + } + + // If we actually have some where clauses, we will strip off the first boolean + // operator, which is added by the query builders for convenience so we can + // avoid checking for the first clauses in each of the compilers methods. + if (count($sql = $this->compileWheresToArray($query)) > 0) { + return $this->concatenateWhereClauses($query, $sql); + } + + return ''; + } + + /** + * Get an array of all the where clauses for the query. + * + * @param \Illuminate\Database\Query\Builder $query + * @return array + */ + protected function compileWheresToArray($query) + { + return collect($query->wheres)->map(function ($where) use ($query) { + return $where['boolean'].' '.$this->{"where{$where['type']}"}($query, $where); + })->all(); + } + + /** + * Format the where clause statements into one string. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $sql + * @return string + */ + protected function concatenateWhereClauses($query, $sql) + { + $conjunction = $query instanceof JoinClause ? 'on' : 'where'; + + return $conjunction.' '.$this->removeLeadingBoolean(implode(' ', $sql)); + } + + /** + * Compile a raw where clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereRaw(Builder $query, $where) + { + return $where['sql']; + } + + /** + * Compile a basic where clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereBasic(Builder $query, $where) + { + $value = $this->parameter($where['value']); + + $operator = str_replace('?', '??', $where['operator']); + + return $this->wrap($where['column']).' '.$operator.' '.$value; + } + + /** + * Compile a bitwise operator where clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereBitwise(Builder $query, $where) + { + return $this->whereBasic($query, $where); + } + + /** + * Compile a "where in" clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereIn(Builder $query, $where) + { + if (! empty($where['values'])) { + return $this->wrap($where['column']).' in ('.$this->parameterize($where['values']).')'; + } + + return '0 = 1'; + } + + /** + * Compile a "where not in" clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereNotIn(Builder $query, $where) + { + if (! empty($where['values'])) { + return $this->wrap($where['column']).' not in ('.$this->parameterize($where['values']).')'; + } + + return '1 = 1'; + } + + /** + * Compile a "where not in raw" clause. + * + * For safety, whereIntegerInRaw ensures this method is only used with integer values. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereNotInRaw(Builder $query, $where) + { + if (! empty($where['values'])) { + return $this->wrap($where['column']).' not in ('.implode(', ', $where['values']).')'; + } + + return '1 = 1'; + } + + /** + * Compile a "where in raw" clause. + * + * For safety, whereIntegerInRaw ensures this method is only used with integer values. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereInRaw(Builder $query, $where) + { + if (! empty($where['values'])) { + return $this->wrap($where['column']).' in ('.implode(', ', $where['values']).')'; + } + + return '0 = 1'; + } + + /** + * Compile a "where null" clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereNull(Builder $query, $where) + { + return $this->wrap($where['column']).' is null'; + } + + /** + * Compile a "where not null" clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereNotNull(Builder $query, $where) + { + return $this->wrap($where['column']).' is not null'; + } + + /** + * Compile a "between" where clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereBetween(Builder $query, $where) + { + $between = $where['not'] ? 'not between' : 'between'; + + $min = $this->parameter(reset($where['values'])); + + $max = $this->parameter(end($where['values'])); + + return $this->wrap($where['column']).' '.$between.' '.$min.' and '.$max; + } + + /** + * Compile a "between" where clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereBetweenColumns(Builder $query, $where) + { + $between = $where['not'] ? 'not between' : 'between'; + + $min = $this->wrap(reset($where['values'])); + + $max = $this->wrap(end($where['values'])); + + return $this->wrap($where['column']).' '.$between.' '.$min.' and '.$max; + } + + /** + * Compile a "where date" clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereDate(Builder $query, $where) + { + return $this->dateBasedWhere('date', $query, $where); + } + + /** + * Compile a "where time" clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereTime(Builder $query, $where) + { + return $this->dateBasedWhere('time', $query, $where); + } + + /** + * Compile a "where day" clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereDay(Builder $query, $where) + { + return $this->dateBasedWhere('day', $query, $where); + } + + /** + * Compile a "where month" clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereMonth(Builder $query, $where) + { + return $this->dateBasedWhere('month', $query, $where); + } + + /** + * Compile a "where year" clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereYear(Builder $query, $where) + { + return $this->dateBasedWhere('year', $query, $where); + } + + /** + * Compile a date based where clause. + * + * @param string $type + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function dateBasedWhere($type, Builder $query, $where) + { + $value = $this->parameter($where['value']); + + return $type.'('.$this->wrap($where['column']).') '.$where['operator'].' '.$value; + } + + /** + * Compile a where clause comparing two columns. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereColumn(Builder $query, $where) + { + return $this->wrap($where['first']).' '.$where['operator'].' '.$this->wrap($where['second']); + } + + /** + * Compile a nested where clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereNested(Builder $query, $where) + { + // Here we will calculate what portion of the string we need to remove. If this + // is a join clause query, we need to remove the "on" portion of the SQL and + // if it is a normal query we need to take the leading "where" of queries. + $offset = $query instanceof JoinClause ? 3 : 6; + + return '('.substr($this->compileWheres($where['query']), $offset).')'; + } + + /** + * Compile a where condition with a sub-select. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereSub(Builder $query, $where) + { + $select = $this->compileSelect($where['query']); + + return $this->wrap($where['column']).' '.$where['operator']." ($select)"; + } + + /** + * Compile a where exists clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereExists(Builder $query, $where) + { + return 'exists ('.$this->compileSelect($where['query']).')'; + } + + /** + * Compile a where exists clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereNotExists(Builder $query, $where) + { + return 'not exists ('.$this->compileSelect($where['query']).')'; + } + + /** + * Compile a where row values condition. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereRowValues(Builder $query, $where) + { + $columns = $this->columnize($where['columns']); + + $values = $this->parameterize($where['values']); + + return '('.$columns.') '.$where['operator'].' ('.$values.')'; + } + + /** + * Compile a "where JSON boolean" clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereJsonBoolean(Builder $query, $where) + { + $column = $this->wrapJsonBooleanSelector($where['column']); + + $value = $this->wrapJsonBooleanValue( + $this->parameter($where['value']) + ); + + return $column.' '.$where['operator'].' '.$value; + } + + /** + * Compile a "where JSON contains" clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereJsonContains(Builder $query, $where) + { + $not = $where['not'] ? 'not ' : ''; + + return $not.$this->compileJsonContains( + $where['column'], + $this->parameter($where['value']) + ); + } + + /** + * Compile a "JSON contains" statement into SQL. + * + * @param string $column + * @param string $value + * @return string + * + * @throws \RuntimeException + */ + protected function compileJsonContains($column, $value) + { + throw new RuntimeException('This database engine does not support JSON contains operations.'); + } + + /** + * Prepare the binding for a "JSON contains" statement. + * + * @param mixed $binding + * @return string + */ + public function prepareBindingForJsonContains($binding) + { + return json_encode($binding); + } + + /** + * Compile a "where JSON length" clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereJsonLength(Builder $query, $where) + { + return $this->compileJsonLength( + $where['column'], + $where['operator'], + $this->parameter($where['value']) + ); + } + + /** + * Compile a "JSON length" statement into SQL. + * + * @param string $column + * @param string $operator + * @param string $value + * @return string + * + * @throws \RuntimeException + */ + protected function compileJsonLength($column, $operator, $value) + { + throw new RuntimeException('This database engine does not support JSON length operations.'); + } + + /** + * Compile a "where fulltext" clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + public function whereFullText(Builder $query, $where) + { + throw new RuntimeException('This database engine does not support fulltext search operations.'); + } + + /** + * Compile the "group by" portions of the query. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $groups + * @return string + */ + protected function compileGroups(Builder $query, $groups) + { + return 'group by '.$this->columnize($groups); + } + + /** + * Compile the "having" portions of the query. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $havings + * @return string + */ + protected function compileHavings(Builder $query, $havings) + { + $sql = implode(' ', array_map([$this, 'compileHaving'], $havings)); + + return 'having '.$this->removeLeadingBoolean($sql); + } + + /** + * Compile a single having clause. + * + * @param array $having + * @return string + */ + protected function compileHaving(array $having) + { + // If the having clause is "raw", we can just return the clause straight away + // without doing any more processing on it. Otherwise, we will compile the + // clause into SQL based on the components that make it up from builder. + if ($having['type'] === 'Raw') { + return $having['boolean'].' '.$having['sql']; + } elseif ($having['type'] === 'between') { + return $this->compileHavingBetween($having); + } + + return $this->compileBasicHaving($having); + } + + /** + * Compile a basic having clause. + * + * @param array $having + * @return string + */ + protected function compileBasicHaving($having) + { + $column = $this->wrap($having['column']); + + $parameter = $this->parameter($having['value']); + + return $having['boolean'].' '.$column.' '.$having['operator'].' '.$parameter; + } + + /** + * Compile a "between" having clause. + * + * @param array $having + * @return string + */ + protected function compileHavingBetween($having) + { + $between = $having['not'] ? 'not between' : 'between'; + + $column = $this->wrap($having['column']); + + $min = $this->parameter(head($having['values'])); + + $max = $this->parameter(last($having['values'])); + + return $having['boolean'].' '.$column.' '.$between.' '.$min.' and '.$max; + } + + /** + * Compile the "order by" portions of the query. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $orders + * @return string + */ + protected function compileOrders(Builder $query, $orders) + { + if (! empty($orders)) { + return 'order by '.implode(', ', $this->compileOrdersToArray($query, $orders)); + } + + return ''; + } + + /** + * Compile the query orders to an array. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $orders + * @return array + */ + protected function compileOrdersToArray(Builder $query, $orders) + { + return array_map(function ($order) { + return $order['sql'] ?? $this->wrap($order['column']).' '.$order['direction']; + }, $orders); + } + + /** + * Compile the random statement into SQL. + * + * @param string $seed + * @return string + */ + public function compileRandom($seed) + { + return 'RANDOM()'; + } + + /** + * Compile the "limit" portions of the query. + * + * @param \Illuminate\Database\Query\Builder $query + * @param int $limit + * @return string + */ + protected function compileLimit(Builder $query, $limit) + { + return 'limit '.(int) $limit; + } + + /** + * Compile the "offset" portions of the query. + * + * @param \Illuminate\Database\Query\Builder $query + * @param int $offset + * @return string + */ + protected function compileOffset(Builder $query, $offset) + { + return 'offset '.(int) $offset; + } + + /** + * Compile the "union" queries attached to the main query. + * + * @param \Illuminate\Database\Query\Builder $query + * @return string + */ + protected function compileUnions(Builder $query) + { + $sql = ''; + + foreach ($query->unions as $union) { + $sql .= $this->compileUnion($union); + } + + if (! empty($query->unionOrders)) { + $sql .= ' '.$this->compileOrders($query, $query->unionOrders); + } + + if (isset($query->unionLimit)) { + $sql .= ' '.$this->compileLimit($query, $query->unionLimit); + } + + if (isset($query->unionOffset)) { + $sql .= ' '.$this->compileOffset($query, $query->unionOffset); + } + + return ltrim($sql); + } + + /** + * Compile a single union statement. + * + * @param array $union + * @return string + */ + protected function compileUnion(array $union) + { + $conjunction = $union['all'] ? ' union all ' : ' union '; + + return $conjunction.$this->wrapUnion($union['query']->toSql()); + } + + /** + * Wrap a union subquery in parentheses. + * + * @param string $sql + * @return string + */ + protected function wrapUnion($sql) + { + return '('.$sql.')'; + } + + /** + * Compile a union aggregate query into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @return string + */ + protected function compileUnionAggregate(Builder $query) + { + $sql = $this->compileAggregate($query, $query->aggregate); + + $query->aggregate = null; + + return $sql.' from ('.$this->compileSelect($query).') as '.$this->wrapTable('temp_table'); + } + + /** + * Compile an exists statement into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @return string + */ + public function compileExists(Builder $query) + { + $select = $this->compileSelect($query); + + return "select exists({$select}) as {$this->wrap('exists')}"; + } + + /** + * Compile an insert statement into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $values + * @return string + */ + public function compileInsert(Builder $query, array $values) + { + // Essentially we will force every insert to be treated as a batch insert which + // simply makes creating the SQL easier for us since we can utilize the same + // basic routine regardless of an amount of records given to us to insert. + $table = $this->wrapTable($query->from); + + if (empty($values)) { + return "insert into {$table} default values"; + } + + if (! is_array(reset($values))) { + $values = [$values]; + } + + $columns = $this->columnize(array_keys(reset($values))); + + // We need to build a list of parameter place-holders of values that are bound + // to the query. Each insert should have the exact same amount of parameter + // bindings so we will loop through the record and parameterize them all. + $parameters = collect($values)->map(function ($record) { + return '('.$this->parameterize($record).')'; + })->implode(', '); + + return "insert into $table ($columns) values $parameters"; + } + + /** + * Compile an insert ignore statement into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $values + * @return string + * + * @throws \RuntimeException + */ + public function compileInsertOrIgnore(Builder $query, array $values) + { + throw new RuntimeException('This database engine does not support inserting while ignoring errors.'); + } + + /** + * Compile an insert and get ID statement into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $values + * @param string $sequence + * @return string + */ + public function compileInsertGetId(Builder $query, $values, $sequence) + { + return $this->compileInsert($query, $values); + } + + /** + * Compile an insert statement using a subquery into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $columns + * @param string $sql + * @return string + */ + public function compileInsertUsing(Builder $query, array $columns, string $sql) + { + return "insert into {$this->wrapTable($query->from)} ({$this->columnize($columns)}) $sql"; + } + + /** + * Compile an update statement into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $values + * @return string + */ + public function compileUpdate(Builder $query, array $values) + { + $table = $this->wrapTable($query->from); + + $columns = $this->compileUpdateColumns($query, $values); + + $where = $this->compileWheres($query); + + return trim( + isset($query->joins) + ? $this->compileUpdateWithJoins($query, $table, $columns, $where) + : $this->compileUpdateWithoutJoins($query, $table, $columns, $where) + ); + } + + /** + * Compile the columns for an update statement. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $values + * @return string + */ + protected function compileUpdateColumns(Builder $query, array $values) + { + return collect($values)->map(function ($value, $key) { + return $this->wrap($key).' = '.$this->parameter($value); + })->implode(', '); + } + + /** + * Compile an update statement without joins into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param string $table + * @param string $columns + * @param string $where + * @return string + */ + protected function compileUpdateWithoutJoins(Builder $query, $table, $columns, $where) + { + return "update {$table} set {$columns} {$where}"; + } + + /** + * Compile an update statement with joins into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param string $table + * @param string $columns + * @param string $where + * @return string + */ + protected function compileUpdateWithJoins(Builder $query, $table, $columns, $where) + { + $joins = $this->compileJoins($query, $query->joins); + + return "update {$table} {$joins} set {$columns} {$where}"; + } + + /** + * Compile an "upsert" statement into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $values + * @param array $uniqueBy + * @param array $update + * @return string + * + * @throws \RuntimeException + */ + public function compileUpsert(Builder $query, array $values, array $uniqueBy, array $update) + { + throw new RuntimeException('This database engine does not support upserts.'); + } + + /** + * Prepare the bindings for an update statement. + * + * @param array $bindings + * @param array $values + * @return array + */ + public function prepareBindingsForUpdate(array $bindings, array $values) + { + $cleanBindings = Arr::except($bindings, ['select', 'join']); + + return array_values( + array_merge($bindings['join'], $values, Arr::flatten($cleanBindings)) + ); + } + + /** + * Compile a delete statement into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @return string + */ + public function compileDelete(Builder $query) + { + $table = $this->wrapTable($query->from); + + $where = $this->compileWheres($query); + + return trim( + isset($query->joins) + ? $this->compileDeleteWithJoins($query, $table, $where) + : $this->compileDeleteWithoutJoins($query, $table, $where) + ); + } + + /** + * Compile a delete statement without joins into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param string $table + * @param string $where + * @return string + */ + protected function compileDeleteWithoutJoins(Builder $query, $table, $where) + { + return "delete from {$table} {$where}"; + } + + /** + * Compile a delete statement with joins into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param string $table + * @param string $where + * @return string + */ + protected function compileDeleteWithJoins(Builder $query, $table, $where) + { + $alias = last(explode(' as ', $table)); + + $joins = $this->compileJoins($query, $query->joins); + + return "delete {$alias} from {$table} {$joins} {$where}"; + } + + /** + * Prepare the bindings for a delete statement. + * + * @param array $bindings + * @return array + */ + public function prepareBindingsForDelete(array $bindings) + { + return Arr::flatten( + Arr::except($bindings, 'select') + ); + } + + /** + * Compile a truncate table statement into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @return array + */ + public function compileTruncate(Builder $query) + { + return ['truncate table '.$this->wrapTable($query->from) => []]; + } + + /** + * Compile the lock into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param bool|string $value + * @return string + */ + protected function compileLock(Builder $query, $value) + { + return is_string($value) ? $value : ''; + } + + /** + * Determine if the grammar supports savepoints. + * + * @return bool + */ + public function supportsSavepoints() + { + return true; + } + + /** + * Compile the SQL statement to define a savepoint. + * + * @param string $name + * @return string + */ + public function compileSavepoint($name) + { + return 'SAVEPOINT '.$name; + } + + /** + * Compile the SQL statement to execute a savepoint rollback. + * + * @param string $name + * @return string + */ + public function compileSavepointRollBack($name) + { + return 'ROLLBACK TO SAVEPOINT '.$name; + } + + /** + * Wrap a value in keyword identifiers. + * + * @param \Illuminate\Database\Query\Expression|string $value + * @param bool $prefixAlias + * @return string + */ + public function wrap($value, $prefixAlias = false) + { + if ($this->isExpression($value)) { + return $this->getValue($value); + } + + // If the value being wrapped has a column alias we will need to separate out + // the pieces so we can wrap each of the segments of the expression on its + // own, and then join these both back together using the "as" connector. + if (stripos($value, ' as ') !== false) { + return $this->wrapAliasedValue($value, $prefixAlias); + } + + // If the given value is a JSON selector we will wrap it differently than a + // traditional value. We will need to split this path and wrap each part + // wrapped, etc. Otherwise, we will simply wrap the value as a string. + if ($this->isJsonSelector($value)) { + return $this->wrapJsonSelector($value); + } + + return $this->wrapSegments(explode('.', $value)); + } + + /** + * Wrap the given JSON selector. + * + * @param string $value + * @return string + * + * @throws \RuntimeException + */ + protected function wrapJsonSelector($value) + { + throw new RuntimeException('This database engine does not support JSON operations.'); + } + + /** + * Wrap the given JSON selector for boolean values. + * + * @param string $value + * @return string + */ + protected function wrapJsonBooleanSelector($value) + { + return $this->wrapJsonSelector($value); + } + + /** + * Wrap the given JSON boolean value. + * + * @param string $value + * @return string + */ + protected function wrapJsonBooleanValue($value) + { + return $value; + } + + /** + * Split the given JSON selector into the field and the optional path and wrap them separately. + * + * @param string $column + * @return array + */ + protected function wrapJsonFieldAndPath($column) + { + $parts = explode('->', $column, 2); + + $field = $this->wrap($parts[0]); + + $path = count($parts) > 1 ? ', '.$this->wrapJsonPath($parts[1], '->') : ''; + + return [$field, $path]; + } + + /** + * Wrap the given JSON path. + * + * @param string $value + * @param string $delimiter + * @return string + */ + protected function wrapJsonPath($value, $delimiter = '->') + { + $value = preg_replace("/([\\\\]+)?\\'/", "''", $value); + + return '\'$."'.str_replace($delimiter, '"."', $value).'"\''; + } + + /** + * Determine if the given string is a JSON selector. + * + * @param string $value + * @return bool + */ + protected function isJsonSelector($value) + { + return Str::contains($value, '->'); + } + + /** + * Concatenate an array of segments, removing empties. + * + * @param array $segments + * @return string + */ + protected function concatenate($segments) + { + return implode(' ', array_filter($segments, function ($value) { + return (string) $value !== ''; + })); + } + + /** + * Remove the leading boolean from a statement. + * + * @param string $value + * @return string + */ + protected function removeLeadingBoolean($value) + { + return preg_replace('/and |or /i', '', $value, 1); + } + + /** + * Get the grammar specific operators. + * + * @return array + */ + public function getOperators() + { + return $this->operators; + } + + /** + * Get the grammar specific bitwise operators. + * + * @return array + */ + public function getBitwiseOperators() + { + return $this->bitwiseOperators; + } +} diff --git a/vendor/illuminate/database/Query/Grammars/MySqlGrammar.php b/vendor/illuminate/database/Query/Grammars/MySqlGrammar.php new file mode 100644 index 0000000..404b3d5 --- /dev/null +++ b/vendor/illuminate/database/Query/Grammars/MySqlGrammar.php @@ -0,0 +1,329 @@ +isJsonSelector($where['column'])) { + [$field, $path] = $this->wrapJsonFieldAndPath($where['column']); + + return '(json_extract('.$field.$path.') is null OR json_type(json_extract('.$field.$path.')) = \'NULL\')'; + } + + return parent::whereNull($query, $where); + } + + /** + * Add a "where not null" clause to the query. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereNotNull(Builder $query, $where) + { + if ($this->isJsonSelector($where['column'])) { + [$field, $path] = $this->wrapJsonFieldAndPath($where['column']); + + return '(json_extract('.$field.$path.') is not null AND json_type(json_extract('.$field.$path.')) != \'NULL\')'; + } + + return parent::whereNotNull($query, $where); + } + + /** + * Compile a "where fulltext" clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + public function whereFullText(Builder $query, $where) + { + $columns = $this->columnize($where['columns']); + + $value = $this->parameter($where['value']); + + $mode = ($where['options']['mode'] ?? []) === 'boolean' + ? ' in boolean mode' + : ' in natural language mode'; + + $expanded = ($where['options']['expanded'] ?? []) && ($where['options']['mode'] ?? []) !== 'boolean' + ? ' with query expansion' + : ''; + + return "match ({$columns}) against (".$value."{$mode}{$expanded})"; + } + + /** + * Compile an insert ignore statement into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $values + * @return string + */ + public function compileInsertOrIgnore(Builder $query, array $values) + { + return Str::replaceFirst('insert', 'insert ignore', $this->compileInsert($query, $values)); + } + + /** + * Compile a "JSON contains" statement into SQL. + * + * @param string $column + * @param string $value + * @return string + */ + protected function compileJsonContains($column, $value) + { + [$field, $path] = $this->wrapJsonFieldAndPath($column); + + return 'json_contains('.$field.', '.$value.$path.')'; + } + + /** + * Compile a "JSON length" statement into SQL. + * + * @param string $column + * @param string $operator + * @param string $value + * @return string + */ + protected function compileJsonLength($column, $operator, $value) + { + [$field, $path] = $this->wrapJsonFieldAndPath($column); + + return 'json_length('.$field.$path.') '.$operator.' '.$value; + } + + /** + * Compile the random statement into SQL. + * + * @param string $seed + * @return string + */ + public function compileRandom($seed) + { + return 'RAND('.$seed.')'; + } + + /** + * Compile the lock into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param bool|string $value + * @return string + */ + protected function compileLock(Builder $query, $value) + { + if (! is_string($value)) { + return $value ? 'for update' : 'lock in share mode'; + } + + return $value; + } + + /** + * Compile an insert statement into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $values + * @return string + */ + public function compileInsert(Builder $query, array $values) + { + if (empty($values)) { + $values = [[]]; + } + + return parent::compileInsert($query, $values); + } + + /** + * Compile the columns for an update statement. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $values + * @return string + */ + protected function compileUpdateColumns(Builder $query, array $values) + { + return collect($values)->map(function ($value, $key) { + if ($this->isJsonSelector($key)) { + return $this->compileJsonUpdateColumn($key, $value); + } + + return $this->wrap($key).' = '.$this->parameter($value); + })->implode(', '); + } + + /** + * Compile an "upsert" statement into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $values + * @param array $uniqueBy + * @param array $update + * @return string + */ + public function compileUpsert(Builder $query, array $values, array $uniqueBy, array $update) + { + $sql = $this->compileInsert($query, $values).' on duplicate key update '; + + $columns = collect($update)->map(function ($value, $key) { + return is_numeric($key) + ? $this->wrap($value).' = values('.$this->wrap($value).')' + : $this->wrap($key).' = '.$this->parameter($value); + })->implode(', '); + + return $sql.$columns; + } + + /** + * Prepare a JSON column being updated using the JSON_SET function. + * + * @param string $key + * @param mixed $value + * @return string + */ + protected function compileJsonUpdateColumn($key, $value) + { + if (is_bool($value)) { + $value = $value ? 'true' : 'false'; + } elseif (is_array($value)) { + $value = 'cast(? as json)'; + } else { + $value = $this->parameter($value); + } + + [$field, $path] = $this->wrapJsonFieldAndPath($key); + + return "{$field} = json_set({$field}{$path}, {$value})"; + } + + /** + * Compile an update statement without joins into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param string $table + * @param string $columns + * @param string $where + * @return string + */ + protected function compileUpdateWithoutJoins(Builder $query, $table, $columns, $where) + { + $sql = parent::compileUpdateWithoutJoins($query, $table, $columns, $where); + + if (! empty($query->orders)) { + $sql .= ' '.$this->compileOrders($query, $query->orders); + } + + if (isset($query->limit)) { + $sql .= ' '.$this->compileLimit($query, $query->limit); + } + + return $sql; + } + + /** + * Prepare the bindings for an update statement. + * + * Booleans, integers, and doubles are inserted into JSON updates as raw values. + * + * @param array $bindings + * @param array $values + * @return array + */ + public function prepareBindingsForUpdate(array $bindings, array $values) + { + $values = collect($values)->reject(function ($value, $column) { + return $this->isJsonSelector($column) && is_bool($value); + })->map(function ($value) { + return is_array($value) ? json_encode($value) : $value; + })->all(); + + return parent::prepareBindingsForUpdate($bindings, $values); + } + + /** + * Compile a delete query that does not use joins. + * + * @param \Illuminate\Database\Query\Builder $query + * @param string $table + * @param string $where + * @return string + */ + protected function compileDeleteWithoutJoins(Builder $query, $table, $where) + { + $sql = parent::compileDeleteWithoutJoins($query, $table, $where); + + // When using MySQL, delete statements may contain order by statements and limits + // so we will compile both of those here. Once we have finished compiling this + // we will return the completed SQL statement so it will be executed for us. + if (! empty($query->orders)) { + $sql .= ' '.$this->compileOrders($query, $query->orders); + } + + if (isset($query->limit)) { + $sql .= ' '.$this->compileLimit($query, $query->limit); + } + + return $sql; + } + + /** + * Wrap a single string in keyword identifiers. + * + * @param string $value + * @return string + */ + protected function wrapValue($value) + { + return $value === '*' ? $value : '`'.str_replace('`', '``', $value).'`'; + } + + /** + * Wrap the given JSON selector. + * + * @param string $value + * @return string + */ + protected function wrapJsonSelector($value) + { + [$field, $path] = $this->wrapJsonFieldAndPath($value); + + return 'json_unquote(json_extract('.$field.$path.'))'; + } + + /** + * Wrap the given JSON selector for boolean values. + * + * @param string $value + * @return string + */ + protected function wrapJsonBooleanSelector($value) + { + [$field, $path] = $this->wrapJsonFieldAndPath($value); + + return 'json_extract('.$field.$path.')'; + } +} diff --git a/vendor/illuminate/database/Query/Grammars/PostgresGrammar.php b/vendor/illuminate/database/Query/Grammars/PostgresGrammar.php new file mode 100644 index 0000000..1b49bf1 --- /dev/null +++ b/vendor/illuminate/database/Query/Grammars/PostgresGrammar.php @@ -0,0 +1,640 @@ +', '<=', '>=', '<>', '!=', + 'like', 'not like', 'between', 'ilike', 'not ilike', + '~', '&', '|', '#', '<<', '>>', '<<=', '>>=', + '&&', '@>', '<@', '?', '?|', '?&', '||', '-', '@?', '@@', '#-', + 'is distinct from', 'is not distinct from', + ]; + + /** + * The grammar specific bitwise operators. + * + * @var array + */ + protected $bitwiseOperators = [ + '~', '&', '|', '#', '<<', '>>', '<<=', '>>=', + ]; + + /** + * {@inheritdoc} + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereBasic(Builder $query, $where) + { + if (Str::contains(strtolower($where['operator']), 'like')) { + return sprintf( + '%s::text %s %s', + $this->wrap($where['column']), + $where['operator'], + $this->parameter($where['value']) + ); + } + + return parent::whereBasic($query, $where); + } + + /** + * {@inheritdoc} + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereBitwise(Builder $query, $where) + { + $value = $this->parameter($where['value']); + + $operator = str_replace('?', '??', $where['operator']); + + return '('.$this->wrap($where['column']).' '.$operator.' '.$value.')::bool'; + } + + /** + * Compile a "where date" clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereDate(Builder $query, $where) + { + $value = $this->parameter($where['value']); + + return $this->wrap($where['column']).'::date '.$where['operator'].' '.$value; + } + + /** + * Compile a "where time" clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereTime(Builder $query, $where) + { + $value = $this->parameter($where['value']); + + return $this->wrap($where['column']).'::time '.$where['operator'].' '.$value; + } + + /** + * Compile a date based where clause. + * + * @param string $type + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function dateBasedWhere($type, Builder $query, $where) + { + $value = $this->parameter($where['value']); + + return 'extract('.$type.' from '.$this->wrap($where['column']).') '.$where['operator'].' '.$value; + } + + /** + * Compile a "where fulltext" clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + public function whereFullText(Builder $query, $where) + { + $language = $where['options']['language'] ?? 'english'; + + if (! in_array($language, $this->validFullTextLanguages())) { + $language = 'english'; + } + + $columns = collect($where['columns'])->map(function ($column) use ($language) { + return "to_tsvector('{$language}', {$this->wrap($column)})"; + })->implode(' || '); + + $mode = 'plainto_tsquery'; + + if (($where['options']['mode'] ?? []) === 'phrase') { + $mode = 'phraseto_tsquery'; + } + + if (($where['options']['mode'] ?? []) === 'websearch') { + $mode = 'websearch_to_tsquery'; + } + + return "({$columns}) @@ {$mode}('{$language}', {$this->parameter($where['value'])})"; + } + + /** + * Get an array of valid full text languages. + * + * @return array + */ + protected function validFullTextLanguages() + { + return [ + 'simple', + 'arabic', + 'danish', + 'dutch', + 'english', + 'finnish', + 'french', + 'german', + 'hungarian', + 'indonesian', + 'irish', + 'italian', + 'lithuanian', + 'nepali', + 'norwegian', + 'portuguese', + 'romanian', + 'russian', + 'spanish', + 'swedish', + 'tamil', + 'turkish', + ]; + } + + /** + * Compile the "select *" portion of the query. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $columns + * @return string|null + */ + protected function compileColumns(Builder $query, $columns) + { + // If the query is actually performing an aggregating select, we will let that + // compiler handle the building of the select clauses, as it will need some + // more syntax that is best handled by that function to keep things neat. + if (! is_null($query->aggregate)) { + return; + } + + if (is_array($query->distinct)) { + $select = 'select distinct on ('.$this->columnize($query->distinct).') '; + } elseif ($query->distinct) { + $select = 'select distinct '; + } else { + $select = 'select '; + } + + return $select.$this->columnize($columns); + } + + /** + * Compile a "JSON contains" statement into SQL. + * + * @param string $column + * @param string $value + * @return string + */ + protected function compileJsonContains($column, $value) + { + $column = str_replace('->>', '->', $this->wrap($column)); + + return '('.$column.')::jsonb @> '.$value; + } + + /** + * Compile a "JSON length" statement into SQL. + * + * @param string $column + * @param string $operator + * @param string $value + * @return string + */ + protected function compileJsonLength($column, $operator, $value) + { + $column = str_replace('->>', '->', $this->wrap($column)); + + return 'json_array_length(('.$column.')::json) '.$operator.' '.$value; + } + + /** + * {@inheritdoc} + * + * @param array $having + * @return string + */ + protected function compileHaving(array $having) + { + if ($having['type'] === 'Bitwise') { + return $this->compileHavingBitwise($having); + } + + return parent::compileHaving($having); + } + + /** + * Compile a having clause involving a bitwise operator. + * + * @param array $having + * @return string + */ + protected function compileHavingBitwise($having) + { + $column = $this->wrap($having['column']); + + $parameter = $this->parameter($having['value']); + + return $having['boolean'].' ('.$column.' '.$having['operator'].' '.$parameter.')::bool'; + } + + /** + * Compile the lock into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param bool|string $value + * @return string + */ + protected function compileLock(Builder $query, $value) + { + if (! is_string($value)) { + return $value ? 'for update' : 'for share'; + } + + return $value; + } + + /** + * Compile an insert ignore statement into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $values + * @return string + */ + public function compileInsertOrIgnore(Builder $query, array $values) + { + return $this->compileInsert($query, $values).' on conflict do nothing'; + } + + /** + * Compile an insert and get ID statement into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $values + * @param string $sequence + * @return string + */ + public function compileInsertGetId(Builder $query, $values, $sequence) + { + return $this->compileInsert($query, $values).' returning '.$this->wrap($sequence ?: 'id'); + } + + /** + * Compile an update statement into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $values + * @return string + */ + public function compileUpdate(Builder $query, array $values) + { + if (isset($query->joins) || isset($query->limit)) { + return $this->compileUpdateWithJoinsOrLimit($query, $values); + } + + return parent::compileUpdate($query, $values); + } + + /** + * Compile the columns for an update statement. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $values + * @return string + */ + protected function compileUpdateColumns(Builder $query, array $values) + { + return collect($values)->map(function ($value, $key) { + $column = last(explode('.', $key)); + + if ($this->isJsonSelector($key)) { + return $this->compileJsonUpdateColumn($column, $value); + } + + return $this->wrap($column).' = '.$this->parameter($value); + })->implode(', '); + } + + /** + * Compile an "upsert" statement into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $values + * @param array $uniqueBy + * @param array $update + * @return string + */ + public function compileUpsert(Builder $query, array $values, array $uniqueBy, array $update) + { + $sql = $this->compileInsert($query, $values); + + $sql .= ' on conflict ('.$this->columnize($uniqueBy).') do update set '; + + $columns = collect($update)->map(function ($value, $key) { + return is_numeric($key) + ? $this->wrap($value).' = '.$this->wrapValue('excluded').'.'.$this->wrap($value) + : $this->wrap($key).' = '.$this->parameter($value); + })->implode(', '); + + return $sql.$columns; + } + + /** + * Prepares a JSON column being updated using the JSONB_SET function. + * + * @param string $key + * @param mixed $value + * @return string + */ + protected function compileJsonUpdateColumn($key, $value) + { + $segments = explode('->', $key); + + $field = $this->wrap(array_shift($segments)); + + $path = '\'{"'.implode('","', $segments).'"}\''; + + return "{$field} = jsonb_set({$field}::jsonb, {$path}, {$this->parameter($value)})"; + } + + /** + * Compile an update from statement into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $values + * @return string + */ + public function compileUpdateFrom(Builder $query, $values) + { + $table = $this->wrapTable($query->from); + + // Each one of the columns in the update statements needs to be wrapped in the + // keyword identifiers, also a place-holder needs to be created for each of + // the values in the list of bindings so we can make the sets statements. + $columns = $this->compileUpdateColumns($query, $values); + + $from = ''; + + if (isset($query->joins)) { + // When using Postgres, updates with joins list the joined tables in the from + // clause, which is different than other systems like MySQL. Here, we will + // compile out the tables that are joined and add them to a from clause. + $froms = collect($query->joins)->map(function ($join) { + return $this->wrapTable($join->table); + })->all(); + + if (count($froms) > 0) { + $from = ' from '.implode(', ', $froms); + } + } + + $where = $this->compileUpdateWheres($query); + + return trim("update {$table} set {$columns}{$from} {$where}"); + } + + /** + * Compile the additional where clauses for updates with joins. + * + * @param \Illuminate\Database\Query\Builder $query + * @return string + */ + protected function compileUpdateWheres(Builder $query) + { + $baseWheres = $this->compileWheres($query); + + if (! isset($query->joins)) { + return $baseWheres; + } + + // Once we compile the join constraints, we will either use them as the where + // clause or append them to the existing base where clauses. If we need to + // strip the leading boolean we will do so when using as the only where. + $joinWheres = $this->compileUpdateJoinWheres($query); + + if (trim($baseWheres) == '') { + return 'where '.$this->removeLeadingBoolean($joinWheres); + } + + return $baseWheres.' '.$joinWheres; + } + + /** + * Compile the "join" clause where clauses for an update. + * + * @param \Illuminate\Database\Query\Builder $query + * @return string + */ + protected function compileUpdateJoinWheres(Builder $query) + { + $joinWheres = []; + + // Here we will just loop through all of the join constraints and compile them + // all out then implode them. This should give us "where" like syntax after + // everything has been built and then we will join it to the real wheres. + foreach ($query->joins as $join) { + foreach ($join->wheres as $where) { + $method = "where{$where['type']}"; + + $joinWheres[] = $where['boolean'].' '.$this->$method($query, $where); + } + } + + return implode(' ', $joinWheres); + } + + /** + * Prepare the bindings for an update statement. + * + * @param array $bindings + * @param array $values + * @return array + */ + public function prepareBindingsForUpdateFrom(array $bindings, array $values) + { + $values = collect($values)->map(function ($value, $column) { + return is_array($value) || ($this->isJsonSelector($column) && ! $this->isExpression($value)) + ? json_encode($value) + : $value; + })->all(); + + $bindingsWithoutWhere = Arr::except($bindings, ['select', 'where']); + + return array_values( + array_merge($values, $bindings['where'], Arr::flatten($bindingsWithoutWhere)) + ); + } + + /** + * Compile an update statement with joins or limit into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $values + * @return string + */ + protected function compileUpdateWithJoinsOrLimit(Builder $query, array $values) + { + $table = $this->wrapTable($query->from); + + $columns = $this->compileUpdateColumns($query, $values); + + $alias = last(preg_split('/\s+as\s+/i', $query->from)); + + $selectSql = $this->compileSelect($query->select($alias.'.ctid')); + + return "update {$table} set {$columns} where {$this->wrap('ctid')} in ({$selectSql})"; + } + + /** + * Prepare the bindings for an update statement. + * + * @param array $bindings + * @param array $values + * @return array + */ + public function prepareBindingsForUpdate(array $bindings, array $values) + { + $values = collect($values)->map(function ($value, $column) { + return is_array($value) || ($this->isJsonSelector($column) && ! $this->isExpression($value)) + ? json_encode($value) + : $value; + })->all(); + + $cleanBindings = Arr::except($bindings, 'select'); + + return array_values( + array_merge($values, Arr::flatten($cleanBindings)) + ); + } + + /** + * Compile a delete statement into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @return string + */ + public function compileDelete(Builder $query) + { + if (isset($query->joins) || isset($query->limit)) { + return $this->compileDeleteWithJoinsOrLimit($query); + } + + return parent::compileDelete($query); + } + + /** + * Compile a delete statement with joins or limit into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @return string + */ + protected function compileDeleteWithJoinsOrLimit(Builder $query) + { + $table = $this->wrapTable($query->from); + + $alias = last(preg_split('/\s+as\s+/i', $query->from)); + + $selectSql = $this->compileSelect($query->select($alias.'.ctid')); + + return "delete from {$table} where {$this->wrap('ctid')} in ({$selectSql})"; + } + + /** + * Compile a truncate table statement into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @return array + */ + public function compileTruncate(Builder $query) + { + return ['truncate '.$this->wrapTable($query->from).' restart identity cascade' => []]; + } + + /** + * Wrap the given JSON selector. + * + * @param string $value + * @return string + */ + protected function wrapJsonSelector($value) + { + $path = explode('->', $value); + + $field = $this->wrapSegments(explode('.', array_shift($path))); + + $wrappedPath = $this->wrapJsonPathAttributes($path); + + $attribute = array_pop($wrappedPath); + + if (! empty($wrappedPath)) { + return $field.'->'.implode('->', $wrappedPath).'->>'.$attribute; + } + + return $field.'->>'.$attribute; + } + + /** + * Wrap the given JSON selector for boolean values. + * + * @param string $value + * @return string + */ + protected function wrapJsonBooleanSelector($value) + { + $selector = str_replace( + '->>', '->', + $this->wrapJsonSelector($value) + ); + + return '('.$selector.')::jsonb'; + } + + /** + * Wrap the given JSON boolean value. + * + * @param string $value + * @return string + */ + protected function wrapJsonBooleanValue($value) + { + return "'".$value."'::jsonb"; + } + + /** + * Wrap the attributes of the give JSON path. + * + * @param array $path + * @return array + */ + protected function wrapJsonPathAttributes($path) + { + return array_map(function ($attribute) { + return filter_var($attribute, FILTER_VALIDATE_INT) !== false + ? $attribute + : "'$attribute'"; + }, $path); + } +} diff --git a/vendor/illuminate/database/Query/Grammars/SQLiteGrammar.php b/vendor/illuminate/database/Query/Grammars/SQLiteGrammar.php new file mode 100644 index 0000000..29a3796 --- /dev/null +++ b/vendor/illuminate/database/Query/Grammars/SQLiteGrammar.php @@ -0,0 +1,342 @@ +', '<=', '>=', '<>', '!=', + 'like', 'not like', 'ilike', + '&', '|', '<<', '>>', + ]; + + /** + * Compile the lock into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param bool|string $value + * @return string + */ + protected function compileLock(Builder $query, $value) + { + return ''; + } + + /** + * Wrap a union subquery in parentheses. + * + * @param string $sql + * @return string + */ + protected function wrapUnion($sql) + { + return 'select * from ('.$sql.')'; + } + + /** + * Compile a "where date" clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereDate(Builder $query, $where) + { + return $this->dateBasedWhere('%Y-%m-%d', $query, $where); + } + + /** + * Compile a "where day" clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereDay(Builder $query, $where) + { + return $this->dateBasedWhere('%d', $query, $where); + } + + /** + * Compile a "where month" clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereMonth(Builder $query, $where) + { + return $this->dateBasedWhere('%m', $query, $where); + } + + /** + * Compile a "where year" clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereYear(Builder $query, $where) + { + return $this->dateBasedWhere('%Y', $query, $where); + } + + /** + * Compile a "where time" clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereTime(Builder $query, $where) + { + return $this->dateBasedWhere('%H:%M:%S', $query, $where); + } + + /** + * Compile a date based where clause. + * + * @param string $type + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function dateBasedWhere($type, Builder $query, $where) + { + $value = $this->parameter($where['value']); + + return "strftime('{$type}', {$this->wrap($where['column'])}) {$where['operator']} cast({$value} as text)"; + } + + /** + * Compile a "JSON length" statement into SQL. + * + * @param string $column + * @param string $operator + * @param string $value + * @return string + */ + protected function compileJsonLength($column, $operator, $value) + { + [$field, $path] = $this->wrapJsonFieldAndPath($column); + + return 'json_array_length('.$field.$path.') '.$operator.' '.$value; + } + + /** + * Compile an update statement into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $values + * @return string + */ + public function compileUpdate(Builder $query, array $values) + { + if (isset($query->joins) || isset($query->limit)) { + return $this->compileUpdateWithJoinsOrLimit($query, $values); + } + + return parent::compileUpdate($query, $values); + } + + /** + * Compile an insert ignore statement into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $values + * @return string + */ + public function compileInsertOrIgnore(Builder $query, array $values) + { + return Str::replaceFirst('insert', 'insert or ignore', $this->compileInsert($query, $values)); + } + + /** + * Compile the columns for an update statement. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $values + * @return string + */ + protected function compileUpdateColumns(Builder $query, array $values) + { + $jsonGroups = $this->groupJsonColumnsForUpdate($values); + + return collect($values)->reject(function ($value, $key) { + return $this->isJsonSelector($key); + })->merge($jsonGroups)->map(function ($value, $key) use ($jsonGroups) { + $column = last(explode('.', $key)); + + $value = isset($jsonGroups[$key]) ? $this->compileJsonPatch($column, $value) : $this->parameter($value); + + return $this->wrap($column).' = '.$value; + })->implode(', '); + } + + /** + * Compile an "upsert" statement into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $values + * @param array $uniqueBy + * @param array $update + * @return string + */ + public function compileUpsert(Builder $query, array $values, array $uniqueBy, array $update) + { + $sql = $this->compileInsert($query, $values); + + $sql .= ' on conflict ('.$this->columnize($uniqueBy).') do update set '; + + $columns = collect($update)->map(function ($value, $key) { + return is_numeric($key) + ? $this->wrap($value).' = '.$this->wrapValue('excluded').'.'.$this->wrap($value) + : $this->wrap($key).' = '.$this->parameter($value); + })->implode(', '); + + return $sql.$columns; + } + + /** + * Group the nested JSON columns. + * + * @param array $values + * @return array + */ + protected function groupJsonColumnsForUpdate(array $values) + { + $groups = []; + + foreach ($values as $key => $value) { + if ($this->isJsonSelector($key)) { + Arr::set($groups, str_replace('->', '.', Str::after($key, '.')), $value); + } + } + + return $groups; + } + + /** + * Compile a "JSON" patch statement into SQL. + * + * @param string $column + * @param mixed $value + * @return string + */ + protected function compileJsonPatch($column, $value) + { + return "json_patch(ifnull({$this->wrap($column)}, json('{}')), json({$this->parameter($value)}))"; + } + + /** + * Compile an update statement with joins or limit into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $values + * @return string + */ + protected function compileUpdateWithJoinsOrLimit(Builder $query, array $values) + { + $table = $this->wrapTable($query->from); + + $columns = $this->compileUpdateColumns($query, $values); + + $alias = last(preg_split('/\s+as\s+/i', $query->from)); + + $selectSql = $this->compileSelect($query->select($alias.'.rowid')); + + return "update {$table} set {$columns} where {$this->wrap('rowid')} in ({$selectSql})"; + } + + /** + * Prepare the bindings for an update statement. + * + * @param array $bindings + * @param array $values + * @return array + */ + public function prepareBindingsForUpdate(array $bindings, array $values) + { + $groups = $this->groupJsonColumnsForUpdate($values); + + $values = collect($values)->reject(function ($value, $key) { + return $this->isJsonSelector($key); + })->merge($groups)->map(function ($value) { + return is_array($value) ? json_encode($value) : $value; + })->all(); + + $cleanBindings = Arr::except($bindings, 'select'); + + return array_values( + array_merge($values, Arr::flatten($cleanBindings)) + ); + } + + /** + * Compile a delete statement into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @return string + */ + public function compileDelete(Builder $query) + { + if (isset($query->joins) || isset($query->limit)) { + return $this->compileDeleteWithJoinsOrLimit($query); + } + + return parent::compileDelete($query); + } + + /** + * Compile a delete statement with joins or limit into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @return string + */ + protected function compileDeleteWithJoinsOrLimit(Builder $query) + { + $table = $this->wrapTable($query->from); + + $alias = last(preg_split('/\s+as\s+/i', $query->from)); + + $selectSql = $this->compileSelect($query->select($alias.'.rowid')); + + return "delete from {$table} where {$this->wrap('rowid')} in ({$selectSql})"; + } + + /** + * Compile a truncate table statement into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @return array + */ + public function compileTruncate(Builder $query) + { + return [ + 'delete from sqlite_sequence where name = ?' => [$query->from], + 'delete from '.$this->wrapTable($query->from) => [], + ]; + } + + /** + * Wrap the given JSON selector. + * + * @param string $value + * @return string + */ + protected function wrapJsonSelector($value) + { + [$field, $path] = $this->wrapJsonFieldAndPath($value); + + return 'json_extract('.$field.$path.')'; + } +} diff --git a/vendor/illuminate/database/Query/Grammars/SqlServerGrammar.php b/vendor/illuminate/database/Query/Grammars/SqlServerGrammar.php new file mode 100644 index 0000000..417b63e --- /dev/null +++ b/vendor/illuminate/database/Query/Grammars/SqlServerGrammar.php @@ -0,0 +1,584 @@ +', '<=', '>=', '!<', '!>', '<>', '!=', + 'like', 'not like', 'ilike', + '&', '&=', '|', '|=', '^', '^=', + ]; + + /** + * Compile a select query into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @return string + */ + public function compileSelect(Builder $query) + { + if (! $query->offset) { + return parent::compileSelect($query); + } + + if (is_null($query->columns)) { + $query->columns = ['*']; + } + + $components = $this->compileComponents($query); + + if (! empty($components['orders'])) { + return parent::compileSelect($query)." offset {$query->offset} rows fetch next {$query->limit} rows only"; + } + + // If an offset is present on the query, we will need to wrap the query in + // a big "ANSI" offset syntax block. This is very nasty compared to the + // other database systems but is necessary for implementing features. + return $this->compileAnsiOffset( + $query, $components + ); + } + + /** + * Compile the "select *" portion of the query. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $columns + * @return string|null + */ + protected function compileColumns(Builder $query, $columns) + { + if (! is_null($query->aggregate)) { + return; + } + + $select = $query->distinct ? 'select distinct ' : 'select '; + + // If there is a limit on the query, but not an offset, we will add the top + // clause to the query, which serves as a "limit" type clause within the + // SQL Server system similar to the limit keywords available in MySQL. + if (is_numeric($query->limit) && $query->limit > 0 && $query->offset <= 0) { + $select .= 'top '.((int) $query->limit).' '; + } + + return $select.$this->columnize($columns); + } + + /** + * Compile the "from" portion of the query. + * + * @param \Illuminate\Database\Query\Builder $query + * @param string $table + * @return string + */ + protected function compileFrom(Builder $query, $table) + { + $from = parent::compileFrom($query, $table); + + if (is_string($query->lock)) { + return $from.' '.$query->lock; + } + + if (! is_null($query->lock)) { + return $from.' with(rowlock,'.($query->lock ? 'updlock,' : '').'holdlock)'; + } + + return $from; + } + + /** + * {@inheritdoc} + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereBitwise(Builder $query, $where) + { + $value = $this->parameter($where['value']); + + $operator = str_replace('?', '??', $where['operator']); + + return '('.$this->wrap($where['column']).' '.$operator.' '.$value.') != 0'; + } + + /** + * Compile a "where date" clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereDate(Builder $query, $where) + { + $value = $this->parameter($where['value']); + + return 'cast('.$this->wrap($where['column']).' as date) '.$where['operator'].' '.$value; + } + + /** + * Compile a "where time" clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereTime(Builder $query, $where) + { + $value = $this->parameter($where['value']); + + return 'cast('.$this->wrap($where['column']).' as time) '.$where['operator'].' '.$value; + } + + /** + * Compile a "JSON contains" statement into SQL. + * + * @param string $column + * @param string $value + * @return string + */ + protected function compileJsonContains($column, $value) + { + [$field, $path] = $this->wrapJsonFieldAndPath($column); + + return $value.' in (select [value] from openjson('.$field.$path.'))'; + } + + /** + * Prepare the binding for a "JSON contains" statement. + * + * @param mixed $binding + * @return string + */ + public function prepareBindingForJsonContains($binding) + { + return is_bool($binding) ? json_encode($binding) : $binding; + } + + /** + * Compile a "JSON length" statement into SQL. + * + * @param string $column + * @param string $operator + * @param string $value + * @return string + */ + protected function compileJsonLength($column, $operator, $value) + { + [$field, $path] = $this->wrapJsonFieldAndPath($column); + + return '(select count(*) from openjson('.$field.$path.')) '.$operator.' '.$value; + } + + /** + * {@inheritdoc} + * + * @param array $having + * @return string + */ + protected function compileHaving(array $having) + { + if ($having['type'] === 'Bitwise') { + return $this->compileHavingBitwise($having); + } + + return parent::compileHaving($having); + } + + /** + * Compile a having clause involving a bitwise operator. + * + * @param array $having + * @return string + */ + protected function compileHavingBitwise($having) + { + $column = $this->wrap($having['column']); + + $parameter = $this->parameter($having['value']); + + return $having['boolean'].' ('.$column.' '.$having['operator'].' '.$parameter.') != 0'; + } + + /** + * Create a full ANSI offset clause for the query. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $components + * @return string + */ + protected function compileAnsiOffset(Builder $query, $components) + { + // An ORDER BY clause is required to make this offset query work, so if one does + // not exist we'll just create a dummy clause to trick the database and so it + // does not complain about the queries for not having an "order by" clause. + if (empty($components['orders'])) { + $components['orders'] = 'order by (select 0)'; + } + + // We need to add the row number to the query so we can compare it to the offset + // and limit values given for the statements. So we will add an expression to + // the "select" that will give back the row numbers on each of the records. + $components['columns'] .= $this->compileOver($components['orders']); + + unset($components['orders']); + + if ($this->queryOrderContainsSubquery($query)) { + $query->bindings = $this->sortBindingsForSubqueryOrderBy($query); + } + + // Next we need to calculate the constraints that should be placed on the query + // to get the right offset and limit from our query but if there is no limit + // set we will just handle the offset only since that is all that matters. + $sql = $this->concatenate($components); + + return $this->compileTableExpression($sql, $query); + } + + /** + * Compile the over statement for a table expression. + * + * @param string $orderings + * @return string + */ + protected function compileOver($orderings) + { + return ", row_number() over ({$orderings}) as row_num"; + } + + /** + * Determine if the query's order by clauses contain a subquery. + * + * @param \Illuminate\Database\Query\Builder $query + * @return bool + */ + protected function queryOrderContainsSubquery($query) + { + if (! is_array($query->orders)) { + return false; + } + + return Arr::first($query->orders, function ($value) { + return $this->isExpression($value['column'] ?? null); + }, false) !== false; + } + + /** + * Move the order bindings to be after the "select" statement to account for an order by subquery. + * + * @param \Illuminate\Database\Query\Builder $query + * @return array + */ + protected function sortBindingsForSubqueryOrderBy($query) + { + return Arr::sort($query->bindings, function ($bindings, $key) { + return array_search($key, ['select', 'order', 'from', 'join', 'where', 'groupBy', 'having', 'union', 'unionOrder']); + }); + } + + /** + * Compile a common table expression for a query. + * + * @param string $sql + * @param \Illuminate\Database\Query\Builder $query + * @return string + */ + protected function compileTableExpression($sql, $query) + { + $constraint = $this->compileRowConstraint($query); + + return "select * from ({$sql}) as temp_table where row_num {$constraint} order by row_num"; + } + + /** + * Compile the limit / offset row constraint for a query. + * + * @param \Illuminate\Database\Query\Builder $query + * @return string + */ + protected function compileRowConstraint($query) + { + $start = (int) $query->offset + 1; + + if ($query->limit > 0) { + $finish = (int) $query->offset + (int) $query->limit; + + return "between {$start} and {$finish}"; + } + + return ">= {$start}"; + } + + /** + * Compile a delete statement without joins into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param string $table + * @param string $where + * @return string + */ + protected function compileDeleteWithoutJoins(Builder $query, $table, $where) + { + $sql = parent::compileDeleteWithoutJoins($query, $table, $where); + + return ! is_null($query->limit) && $query->limit > 0 && $query->offset <= 0 + ? Str::replaceFirst('delete', 'delete top ('.$query->limit.')', $sql) + : $sql; + } + + /** + * Compile the random statement into SQL. + * + * @param string $seed + * @return string + */ + public function compileRandom($seed) + { + return 'NEWID()'; + } + + /** + * Compile the "limit" portions of the query. + * + * @param \Illuminate\Database\Query\Builder $query + * @param int $limit + * @return string + */ + protected function compileLimit(Builder $query, $limit) + { + return ''; + } + + /** + * Compile the "offset" portions of the query. + * + * @param \Illuminate\Database\Query\Builder $query + * @param int $offset + * @return string + */ + protected function compileOffset(Builder $query, $offset) + { + return ''; + } + + /** + * Compile the lock into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param bool|string $value + * @return string + */ + protected function compileLock(Builder $query, $value) + { + return ''; + } + + /** + * Wrap a union subquery in parentheses. + * + * @param string $sql + * @return string + */ + protected function wrapUnion($sql) + { + return 'select * from ('.$sql.') as '.$this->wrapTable('temp_table'); + } + + /** + * Compile an exists statement into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @return string + */ + public function compileExists(Builder $query) + { + $existsQuery = clone $query; + + $existsQuery->columns = []; + + return $this->compileSelect($existsQuery->selectRaw('1 [exists]')->limit(1)); + } + + /** + * Compile an update statement with joins into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param string $table + * @param string $columns + * @param string $where + * @return string + */ + protected function compileUpdateWithJoins(Builder $query, $table, $columns, $where) + { + $alias = last(explode(' as ', $table)); + + $joins = $this->compileJoins($query, $query->joins); + + return "update {$alias} set {$columns} from {$table} {$joins} {$where}"; + } + + /** + * Compile an "upsert" statement into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $values + * @param array $uniqueBy + * @param array $update + * @return string + */ + public function compileUpsert(Builder $query, array $values, array $uniqueBy, array $update) + { + $columns = $this->columnize(array_keys(reset($values))); + + $sql = 'merge '.$this->wrapTable($query->from).' '; + + $parameters = collect($values)->map(function ($record) { + return '('.$this->parameterize($record).')'; + })->implode(', '); + + $sql .= 'using (values '.$parameters.') '.$this->wrapTable('laravel_source').' ('.$columns.') '; + + $on = collect($uniqueBy)->map(function ($column) use ($query) { + return $this->wrap('laravel_source.'.$column).' = '.$this->wrap($query->from.'.'.$column); + })->implode(' and '); + + $sql .= 'on '.$on.' '; + + if ($update) { + $update = collect($update)->map(function ($value, $key) { + return is_numeric($key) + ? $this->wrap($value).' = '.$this->wrap('laravel_source.'.$value) + : $this->wrap($key).' = '.$this->parameter($value); + })->implode(', '); + + $sql .= 'when matched then update set '.$update.' '; + } + + $sql .= 'when not matched then insert ('.$columns.') values ('.$columns.');'; + + return $sql; + } + + /** + * Prepare the bindings for an update statement. + * + * @param array $bindings + * @param array $values + * @return array + */ + public function prepareBindingsForUpdate(array $bindings, array $values) + { + $cleanBindings = Arr::except($bindings, 'select'); + + return array_values( + array_merge($values, Arr::flatten($cleanBindings)) + ); + } + + /** + * Compile the SQL statement to define a savepoint. + * + * @param string $name + * @return string + */ + public function compileSavepoint($name) + { + return 'SAVE TRANSACTION '.$name; + } + + /** + * Compile the SQL statement to execute a savepoint rollback. + * + * @param string $name + * @return string + */ + public function compileSavepointRollBack($name) + { + return 'ROLLBACK TRANSACTION '.$name; + } + + /** + * Get the format for database stored dates. + * + * @return string + */ + public function getDateFormat() + { + return 'Y-m-d H:i:s.v'; + } + + /** + * Wrap a single string in keyword identifiers. + * + * @param string $value + * @return string + */ + protected function wrapValue($value) + { + return $value === '*' ? $value : '['.str_replace(']', ']]', $value).']'; + } + + /** + * Wrap the given JSON selector. + * + * @param string $value + * @return string + */ + protected function wrapJsonSelector($value) + { + [$field, $path] = $this->wrapJsonFieldAndPath($value); + + return 'json_value('.$field.$path.')'; + } + + /** + * Wrap the given JSON boolean value. + * + * @param string $value + * @return string + */ + protected function wrapJsonBooleanValue($value) + { + return "'".$value."'"; + } + + /** + * Wrap a table in keyword identifiers. + * + * @param \Illuminate\Database\Query\Expression|string $table + * @return string + */ + public function wrapTable($table) + { + if (! $this->isExpression($table)) { + return $this->wrapTableValuedFunction(parent::wrapTable($table)); + } + + return $this->getValue($table); + } + + /** + * Wrap a table in keyword identifiers. + * + * @param string $table + * @return string + */ + protected function wrapTableValuedFunction($table) + { + if (preg_match('/^(.+?)(\(.*?\))]$/', $table, $matches) === 1) { + $table = $matches[1].']'.$matches[2]; + } + + return $table; + } +} diff --git a/vendor/illuminate/database/Query/JoinClause.php b/vendor/illuminate/database/Query/JoinClause.php new file mode 100644 index 0000000..57d650a --- /dev/null +++ b/vendor/illuminate/database/Query/JoinClause.php @@ -0,0 +1,146 @@ +type = $type; + $this->table = $table; + $this->parentClass = get_class($parentQuery); + $this->parentGrammar = $parentQuery->getGrammar(); + $this->parentProcessor = $parentQuery->getProcessor(); + $this->parentConnection = $parentQuery->getConnection(); + + parent::__construct( + $this->parentConnection, $this->parentGrammar, $this->parentProcessor + ); + } + + /** + * Add an "on" clause to the join. + * + * On clauses can be chained, e.g. + * + * $join->on('contacts.user_id', '=', 'users.id') + * ->on('contacts.info_id', '=', 'info.id') + * + * will produce the following SQL: + * + * on `contacts`.`user_id` = `users`.`id` and `contacts`.`info_id` = `info`.`id` + * + * @param \Closure|string $first + * @param string|null $operator + * @param \Illuminate\Database\Query\Expression|string|null $second + * @param string $boolean + * @return $this + * + * @throws \InvalidArgumentException + */ + public function on($first, $operator = null, $second = null, $boolean = 'and') + { + if ($first instanceof Closure) { + return $this->whereNested($first, $boolean); + } + + return $this->whereColumn($first, $operator, $second, $boolean); + } + + /** + * Add an "or on" clause to the join. + * + * @param \Closure|string $first + * @param string|null $operator + * @param \Illuminate\Database\Query\Expression|string|null $second + * @return \Illuminate\Database\Query\JoinClause + */ + public function orOn($first, $operator = null, $second = null) + { + return $this->on($first, $operator, $second, 'or'); + } + + /** + * Get a new instance of the join clause builder. + * + * @return \Illuminate\Database\Query\JoinClause + */ + public function newQuery() + { + return new static($this->newParentQuery(), $this->type, $this->table); + } + + /** + * Create a new query instance for sub-query. + * + * @return \Illuminate\Database\Query\Builder + */ + protected function forSubQuery() + { + return $this->newParentQuery()->newQuery(); + } + + /** + * Create a new parent query instance. + * + * @return \Illuminate\Database\Query\Builder + */ + protected function newParentQuery() + { + $class = $this->parentClass; + + return new $class($this->parentConnection, $this->parentGrammar, $this->parentProcessor); + } +} diff --git a/vendor/illuminate/database/Query/Processors/MySqlProcessor.php b/vendor/illuminate/database/Query/Processors/MySqlProcessor.php new file mode 100644 index 0000000..ce91838 --- /dev/null +++ b/vendor/illuminate/database/Query/Processors/MySqlProcessor.php @@ -0,0 +1,19 @@ +column_name; + }, $results); + } +} diff --git a/vendor/illuminate/database/Query/Processors/PostgresProcessor.php b/vendor/illuminate/database/Query/Processors/PostgresProcessor.php new file mode 100644 index 0000000..5956a8f --- /dev/null +++ b/vendor/illuminate/database/Query/Processors/PostgresProcessor.php @@ -0,0 +1,45 @@ +getConnection(); + + $connection->recordsHaveBeenModified(); + + $result = $connection->selectFromWriteConnection($sql, $values)[0]; + + $sequence = $sequence ?: 'id'; + + $id = is_object($result) ? $result->{$sequence} : $result[$sequence]; + + return is_numeric($id) ? (int) $id : $id; + } + + /** + * Process the results of a column listing query. + * + * @param array $results + * @return array + */ + public function processColumnListing($results) + { + return array_map(function ($result) { + return ((object) $result)->column_name; + }, $results); + } +} diff --git a/vendor/illuminate/database/Query/Processors/Processor.php b/vendor/illuminate/database/Query/Processors/Processor.php new file mode 100644 index 0000000..0069b43 --- /dev/null +++ b/vendor/illuminate/database/Query/Processors/Processor.php @@ -0,0 +1,49 @@ +getConnection()->insert($sql, $values); + + $id = $query->getConnection()->getPdo()->lastInsertId($sequence); + + return is_numeric($id) ? (int) $id : $id; + } + + /** + * Process the results of a column listing query. + * + * @param array $results + * @return array + */ + public function processColumnListing($results) + { + return $results; + } +} diff --git a/vendor/illuminate/database/Query/Processors/SQLiteProcessor.php b/vendor/illuminate/database/Query/Processors/SQLiteProcessor.php new file mode 100644 index 0000000..65da1df --- /dev/null +++ b/vendor/illuminate/database/Query/Processors/SQLiteProcessor.php @@ -0,0 +1,19 @@ +name; + }, $results); + } +} diff --git a/vendor/illuminate/database/Query/Processors/SqlServerProcessor.php b/vendor/illuminate/database/Query/Processors/SqlServerProcessor.php new file mode 100644 index 0000000..49476f0 --- /dev/null +++ b/vendor/illuminate/database/Query/Processors/SqlServerProcessor.php @@ -0,0 +1,70 @@ +getConnection(); + + $connection->insert($sql, $values); + + if ($connection->getConfig('odbc') === true) { + $id = $this->processInsertGetIdForOdbc($connection); + } else { + $id = $connection->getPdo()->lastInsertId(); + } + + return is_numeric($id) ? (int) $id : $id; + } + + /** + * Process an "insert get ID" query for ODBC. + * + * @param \Illuminate\Database\Connection $connection + * @return int + * + * @throws \Exception + */ + protected function processInsertGetIdForOdbc(Connection $connection) + { + $result = $connection->selectFromWriteConnection( + 'SELECT CAST(COALESCE(SCOPE_IDENTITY(), @@IDENTITY) AS int) AS insertid' + ); + + if (! $result) { + throw new Exception('Unable to retrieve lastInsertID for ODBC.'); + } + + $row = $result[0]; + + return is_object($row) ? $row->insertid : $row['insertid']; + } + + /** + * Process the results of a column listing query. + * + * @param array $results + * @return array + */ + public function processColumnListing($results) + { + return array_map(function ($result) { + return ((object) $result)->name; + }, $results); + } +} diff --git a/vendor/illuminate/database/QueryException.php b/vendor/illuminate/database/QueryException.php new file mode 100644 index 0000000..74e5a31 --- /dev/null +++ b/vendor/illuminate/database/QueryException.php @@ -0,0 +1,79 @@ +sql = $sql; + $this->bindings = $bindings; + $this->code = $previous->getCode(); + $this->message = $this->formatMessage($sql, $bindings, $previous); + + if ($previous instanceof PDOException) { + $this->errorInfo = $previous->errorInfo; + } + } + + /** + * Format the SQL error message. + * + * @param string $sql + * @param array $bindings + * @param \Throwable $previous + * @return string + */ + protected function formatMessage($sql, $bindings, Throwable $previous) + { + return $previous->getMessage().' (SQL: '.Str::replaceArray('?', $bindings, $sql).')'; + } + + /** + * Get the SQL for the query. + * + * @return string + */ + public function getSql() + { + return $this->sql; + } + + /** + * Get the bindings for the query. + * + * @return array + */ + public function getBindings() + { + return $this->bindings; + } +} diff --git a/vendor/illuminate/database/README.md b/vendor/illuminate/database/README.md new file mode 100644 index 0000000..9019936 --- /dev/null +++ b/vendor/illuminate/database/README.md @@ -0,0 +1,69 @@ +## Illuminate Database + +The Illuminate Database component is a full database toolkit for PHP, providing an expressive query builder, ActiveRecord style ORM, and schema builder. It currently supports MySQL, Postgres, SQL Server, and SQLite. It also serves as the database layer of the Laravel PHP framework. + +### Usage Instructions + +First, create a new "Capsule" manager instance. Capsule aims to make configuring the library for usage outside of the Laravel framework as easy as possible. + +```PHP +use Illuminate\Database\Capsule\Manager as Capsule; + +$capsule = new Capsule; + +$capsule->addConnection([ + 'driver' => 'mysql', + 'host' => 'localhost', + 'database' => 'database', + 'username' => 'root', + 'password' => 'password', + 'charset' => 'utf8', + 'collation' => 'utf8_unicode_ci', + 'prefix' => '', +]); + +// Set the event dispatcher used by Eloquent models... (optional) +use Illuminate\Events\Dispatcher; +use Illuminate\Container\Container; +$capsule->setEventDispatcher(new Dispatcher(new Container)); + +// Make this Capsule instance available globally via static methods... (optional) +$capsule->setAsGlobal(); + +// Setup the Eloquent ORM... (optional; unless you've used setEventDispatcher()) +$capsule->bootEloquent(); +``` + +> `composer require "illuminate/events"` required when you need to use observers with Eloquent. + +Once the Capsule instance has been registered. You may use it like so: + +**Using The Query Builder** + +```PHP +$users = Capsule::table('users')->where('votes', '>', 100)->get(); +``` +Other core methods may be accessed directly from the Capsule in the same manner as from the DB facade: +```PHP +$results = Capsule::select('select * from users where id = ?', [1]); +``` + +**Using The Schema Builder** + +```PHP +Capsule::schema()->create('users', function ($table) { + $table->increments('id'); + $table->string('email')->unique(); + $table->timestamps(); +}); +``` + +**Using The Eloquent ORM** + +```PHP +class User extends Illuminate\Database\Eloquent\Model {} + +$users = User::where('votes', '>', 1)->get(); +``` + +For further documentation on using the various database facilities this library provides, consult the [Laravel framework documentation](https://laravel.com/docs). diff --git a/vendor/illuminate/database/RecordsNotFoundException.php b/vendor/illuminate/database/RecordsNotFoundException.php new file mode 100644 index 0000000..3e0d955 --- /dev/null +++ b/vendor/illuminate/database/RecordsNotFoundException.php @@ -0,0 +1,10 @@ +getForeignKeyConstraintsConfigurationValue(); + + if ($enableForeignKeyConstraints === null) { + return; + } + + $enableForeignKeyConstraints + ? $this->getSchemaBuilder()->enableForeignKeyConstraints() + : $this->getSchemaBuilder()->disableForeignKeyConstraints(); + } + + /** + * Get the default query grammar instance. + * + * @return \Illuminate\Database\Query\Grammars\SQLiteGrammar + */ + protected function getDefaultQueryGrammar() + { + return $this->withTablePrefix(new QueryGrammar); + } + + /** + * Get a schema builder instance for the connection. + * + * @return \Illuminate\Database\Schema\SQLiteBuilder + */ + public function getSchemaBuilder() + { + if (is_null($this->schemaGrammar)) { + $this->useDefaultSchemaGrammar(); + } + + return new SQLiteBuilder($this); + } + + /** + * Get the default schema grammar instance. + * + * @return \Illuminate\Database\Schema\Grammars\SQLiteGrammar + */ + protected function getDefaultSchemaGrammar() + { + return $this->withTablePrefix(new SchemaGrammar); + } + + /** + * Get the schema state for the connection. + * + * @param \Illuminate\Filesystem\Filesystem|null $files + * @param callable|null $processFactory + * + * @throws \RuntimeException + */ + public function getSchemaState(Filesystem $files = null, callable $processFactory = null) + { + return new SqliteSchemaState($this, $files, $processFactory); + } + + /** + * Get the default post processor instance. + * + * @return \Illuminate\Database\Query\Processors\SQLiteProcessor + */ + protected function getDefaultPostProcessor() + { + return new SQLiteProcessor; + } + + /** + * Get the Doctrine DBAL driver. + * + * @return \Doctrine\DBAL\Driver\PDOSqlite\Driver|\Illuminate\Database\PDO\SQLiteDriver + */ + protected function getDoctrineDriver() + { + return class_exists(Version::class) ? new DoctrineDriver : new SQLiteDriver; + } + + /** + * Get the database connection foreign key constraints configuration option. + * + * @return bool|null + */ + protected function getForeignKeyConstraintsConfigurationValue() + { + return $this->getConfig('foreign_key_constraints'); + } +} diff --git a/vendor/illuminate/database/Schema/Blueprint.php b/vendor/illuminate/database/Schema/Blueprint.php new file mode 100644 index 0000000..dfe53ee --- /dev/null +++ b/vendor/illuminate/database/Schema/Blueprint.php @@ -0,0 +1,1711 @@ +table = $table; + $this->prefix = $prefix; + + if (! is_null($callback)) { + $callback($this); + } + } + + /** + * Execute the blueprint against the database. + * + * @param \Illuminate\Database\Connection $connection + * @param \Illuminate\Database\Schema\Grammars\Grammar $grammar + * @return void + */ + public function build(Connection $connection, Grammar $grammar) + { + foreach ($this->toSql($connection, $grammar) as $statement) { + $connection->statement($statement); + } + } + + /** + * Get the raw SQL statements for the blueprint. + * + * @param \Illuminate\Database\Connection $connection + * @param \Illuminate\Database\Schema\Grammars\Grammar $grammar + * @return array + */ + public function toSql(Connection $connection, Grammar $grammar) + { + $this->addImpliedCommands($grammar); + + $statements = []; + + // Each type of command has a corresponding compiler function on the schema + // grammar which is used to build the necessary SQL statements to build + // the blueprint element, so we'll just call that compilers function. + $this->ensureCommandsAreValid($connection); + + foreach ($this->commands as $command) { + $method = 'compile'.ucfirst($command->name); + + if (method_exists($grammar, $method) || $grammar::hasMacro($method)) { + if (! is_null($sql = $grammar->$method($this, $command, $connection))) { + $statements = array_merge($statements, (array) $sql); + } + } + } + + return $statements; + } + + /** + * Ensure the commands on the blueprint are valid for the connection type. + * + * @param \Illuminate\Database\Connection $connection + * @return void + * + * @throws \BadMethodCallException + */ + protected function ensureCommandsAreValid(Connection $connection) + { + if ($connection instanceof SQLiteConnection) { + if ($this->commandsNamed(['dropColumn', 'renameColumn'])->count() > 1) { + throw new BadMethodCallException( + "SQLite doesn't support multiple calls to dropColumn / renameColumn in a single modification." + ); + } + + if ($this->commandsNamed(['dropForeign'])->count() > 0) { + throw new BadMethodCallException( + "SQLite doesn't support dropping foreign keys (you would need to re-create the table)." + ); + } + } + } + + /** + * Get all of the commands matching the given names. + * + * @param array $names + * @return \Illuminate\Support\Collection + */ + protected function commandsNamed(array $names) + { + return collect($this->commands)->filter(function ($command) use ($names) { + return in_array($command->name, $names); + }); + } + + /** + * Add the commands that are implied by the blueprint's state. + * + * @param \Illuminate\Database\Schema\Grammars\Grammar $grammar + * @return void + */ + protected function addImpliedCommands(Grammar $grammar) + { + if (count($this->getAddedColumns()) > 0 && ! $this->creating()) { + array_unshift($this->commands, $this->createCommand('add')); + } + + if (count($this->getChangedColumns()) > 0 && ! $this->creating()) { + array_unshift($this->commands, $this->createCommand('change')); + } + + $this->addFluentIndexes(); + + $this->addFluentCommands($grammar); + } + + /** + * Add the index commands fluently specified on columns. + * + * @return void + */ + protected function addFluentIndexes() + { + foreach ($this->columns as $column) { + foreach (['primary', 'unique', 'index', 'fulltext', 'fullText', 'spatialIndex'] as $index) { + // If the index has been specified on the given column, but is simply equal + // to "true" (boolean), no name has been specified for this index so the + // index method can be called without a name and it will generate one. + if ($column->{$index} === true) { + $this->{$index}($column->name); + $column->{$index} = false; + + continue 2; + } + + // If the index has been specified on the given column, and it has a string + // value, we'll go ahead and call the index method and pass the name for + // the index since the developer specified the explicit name for this. + elseif (isset($column->{$index})) { + $this->{$index}($column->name, $column->{$index}); + $column->{$index} = false; + + continue 2; + } + } + } + } + + /** + * Add the fluent commands specified on any columns. + * + * @param \Illuminate\Database\Schema\Grammars\Grammar $grammar + * @return void + */ + public function addFluentCommands(Grammar $grammar) + { + foreach ($this->columns as $column) { + foreach ($grammar->getFluentCommands() as $commandName) { + $attributeName = lcfirst($commandName); + + if (! isset($column->{$attributeName})) { + continue; + } + + $value = $column->{$attributeName}; + + $this->addCommand( + $commandName, compact('value', 'column') + ); + } + } + } + + /** + * Determine if the blueprint has a create command. + * + * @return bool + */ + public function creating() + { + return collect($this->commands)->contains(function ($command) { + return $command->name === 'create'; + }); + } + + /** + * Indicate that the table needs to be created. + * + * @return \Illuminate\Support\Fluent + */ + public function create() + { + return $this->addCommand('create'); + } + + /** + * Indicate that the table needs to be temporary. + * + * @return void + */ + public function temporary() + { + $this->temporary = true; + } + + /** + * Indicate that the table should be dropped. + * + * @return \Illuminate\Support\Fluent + */ + public function drop() + { + return $this->addCommand('drop'); + } + + /** + * Indicate that the table should be dropped if it exists. + * + * @return \Illuminate\Support\Fluent + */ + public function dropIfExists() + { + return $this->addCommand('dropIfExists'); + } + + /** + * Indicate that the given columns should be dropped. + * + * @param array|mixed $columns + * @return \Illuminate\Support\Fluent + */ + public function dropColumn($columns) + { + $columns = is_array($columns) ? $columns : func_get_args(); + + return $this->addCommand('dropColumn', compact('columns')); + } + + /** + * Indicate that the given columns should be renamed. + * + * @param string $from + * @param string $to + * @return \Illuminate\Support\Fluent + */ + public function renameColumn($from, $to) + { + return $this->addCommand('renameColumn', compact('from', 'to')); + } + + /** + * Indicate that the given primary key should be dropped. + * + * @param string|array|null $index + * @return \Illuminate\Support\Fluent + */ + public function dropPrimary($index = null) + { + return $this->dropIndexCommand('dropPrimary', 'primary', $index); + } + + /** + * Indicate that the given unique key should be dropped. + * + * @param string|array $index + * @return \Illuminate\Support\Fluent + */ + public function dropUnique($index) + { + return $this->dropIndexCommand('dropUnique', 'unique', $index); + } + + /** + * Indicate that the given index should be dropped. + * + * @param string|array $index + * @return \Illuminate\Support\Fluent + */ + public function dropIndex($index) + { + return $this->dropIndexCommand('dropIndex', 'index', $index); + } + + /** + * Indicate that the given fulltext index should be dropped. + * + * @param string|array $index + * @return \Illuminate\Support\Fluent + */ + public function dropFullText($index) + { + return $this->dropIndexCommand('dropFullText', 'fulltext', $index); + } + + /** + * Indicate that the given spatial index should be dropped. + * + * @param string|array $index + * @return \Illuminate\Support\Fluent + */ + public function dropSpatialIndex($index) + { + return $this->dropIndexCommand('dropSpatialIndex', 'spatialIndex', $index); + } + + /** + * Indicate that the given foreign key should be dropped. + * + * @param string|array $index + * @return \Illuminate\Support\Fluent + */ + public function dropForeign($index) + { + return $this->dropIndexCommand('dropForeign', 'foreign', $index); + } + + /** + * Indicate that the given column and foreign key should be dropped. + * + * @param string $column + * @return \Illuminate\Support\Fluent + */ + public function dropConstrainedForeignId($column) + { + $this->dropForeign([$column]); + + return $this->dropColumn($column); + } + + /** + * Indicate that the given indexes should be renamed. + * + * @param string $from + * @param string $to + * @return \Illuminate\Support\Fluent + */ + public function renameIndex($from, $to) + { + return $this->addCommand('renameIndex', compact('from', 'to')); + } + + /** + * Indicate that the timestamp columns should be dropped. + * + * @return void + */ + public function dropTimestamps() + { + $this->dropColumn('created_at', 'updated_at'); + } + + /** + * Indicate that the timestamp columns should be dropped. + * + * @return void + */ + public function dropTimestampsTz() + { + $this->dropTimestamps(); + } + + /** + * Indicate that the soft delete column should be dropped. + * + * @param string $column + * @return void + */ + public function dropSoftDeletes($column = 'deleted_at') + { + $this->dropColumn($column); + } + + /** + * Indicate that the soft delete column should be dropped. + * + * @param string $column + * @return void + */ + public function dropSoftDeletesTz($column = 'deleted_at') + { + $this->dropSoftDeletes($column); + } + + /** + * Indicate that the remember token column should be dropped. + * + * @return void + */ + public function dropRememberToken() + { + $this->dropColumn('remember_token'); + } + + /** + * Indicate that the polymorphic columns should be dropped. + * + * @param string $name + * @param string|null $indexName + * @return void + */ + public function dropMorphs($name, $indexName = null) + { + $this->dropIndex($indexName ?: $this->createIndexName('index', ["{$name}_type", "{$name}_id"])); + + $this->dropColumn("{$name}_type", "{$name}_id"); + } + + /** + * Rename the table to a given name. + * + * @param string $to + * @return \Illuminate\Support\Fluent + */ + public function rename($to) + { + return $this->addCommand('rename', compact('to')); + } + + /** + * Specify the primary key(s) for the table. + * + * @param string|array $columns + * @param string|null $name + * @param string|null $algorithm + * @return \Illuminate\Support\Fluent + */ + public function primary($columns, $name = null, $algorithm = null) + { + return $this->indexCommand('primary', $columns, $name, $algorithm); + } + + /** + * Specify a unique index for the table. + * + * @param string|array $columns + * @param string|null $name + * @param string|null $algorithm + * @return \Illuminate\Support\Fluent + */ + public function unique($columns, $name = null, $algorithm = null) + { + return $this->indexCommand('unique', $columns, $name, $algorithm); + } + + /** + * Specify an index for the table. + * + * @param string|array $columns + * @param string|null $name + * @param string|null $algorithm + * @return \Illuminate\Support\Fluent + */ + public function index($columns, $name = null, $algorithm = null) + { + return $this->indexCommand('index', $columns, $name, $algorithm); + } + + /** + * Specify an fulltext for the table. + * + * @param string|array $columns + * @param string|null $name + * @param string|null $algorithm + * @return \Illuminate\Support\Fluent + */ + public function fullText($columns, $name = null, $algorithm = null) + { + return $this->indexCommand('fulltext', $columns, $name, $algorithm); + } + + /** + * Specify a spatial index for the table. + * + * @param string|array $columns + * @param string|null $name + * @return \Illuminate\Support\Fluent + */ + public function spatialIndex($columns, $name = null) + { + return $this->indexCommand('spatialIndex', $columns, $name); + } + + /** + * Specify a raw index for the table. + * + * @param string $expression + * @param string $name + * @return \Illuminate\Support\Fluent + */ + public function rawIndex($expression, $name) + { + return $this->index([new Expression($expression)], $name); + } + + /** + * Specify a foreign key for the table. + * + * @param string|array $columns + * @param string|null $name + * @return \Illuminate\Database\Schema\ForeignKeyDefinition + */ + public function foreign($columns, $name = null) + { + $command = new ForeignKeyDefinition( + $this->indexCommand('foreign', $columns, $name)->getAttributes() + ); + + $this->commands[count($this->commands) - 1] = $command; + + return $command; + } + + /** + * Create a new auto-incrementing big integer (8-byte) column on the table. + * + * @param string $column + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function id($column = 'id') + { + return $this->bigIncrements($column); + } + + /** + * Create a new auto-incrementing integer (4-byte) column on the table. + * + * @param string $column + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function increments($column) + { + return $this->unsignedInteger($column, true); + } + + /** + * Create a new auto-incrementing integer (4-byte) column on the table. + * + * @param string $column + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function integerIncrements($column) + { + return $this->unsignedInteger($column, true); + } + + /** + * Create a new auto-incrementing tiny integer (1-byte) column on the table. + * + * @param string $column + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function tinyIncrements($column) + { + return $this->unsignedTinyInteger($column, true); + } + + /** + * Create a new auto-incrementing small integer (2-byte) column on the table. + * + * @param string $column + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function smallIncrements($column) + { + return $this->unsignedSmallInteger($column, true); + } + + /** + * Create a new auto-incrementing medium integer (3-byte) column on the table. + * + * @param string $column + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function mediumIncrements($column) + { + return $this->unsignedMediumInteger($column, true); + } + + /** + * Create a new auto-incrementing big integer (8-byte) column on the table. + * + * @param string $column + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function bigIncrements($column) + { + return $this->unsignedBigInteger($column, true); + } + + /** + * Create a new char column on the table. + * + * @param string $column + * @param int|null $length + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function char($column, $length = null) + { + $length = $length ?: Builder::$defaultStringLength; + + return $this->addColumn('char', $column, compact('length')); + } + + /** + * Create a new string column on the table. + * + * @param string $column + * @param int|null $length + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function string($column, $length = null) + { + $length = $length ?: Builder::$defaultStringLength; + + return $this->addColumn('string', $column, compact('length')); + } + + /** + * Create a new tiny text column on the table. + * + * @param string $column + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function tinyText($column) + { + return $this->addColumn('tinyText', $column); + } + + /** + * Create a new text column on the table. + * + * @param string $column + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function text($column) + { + return $this->addColumn('text', $column); + } + + /** + * Create a new medium text column on the table. + * + * @param string $column + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function mediumText($column) + { + return $this->addColumn('mediumText', $column); + } + + /** + * Create a new long text column on the table. + * + * @param string $column + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function longText($column) + { + return $this->addColumn('longText', $column); + } + + /** + * Create a new integer (4-byte) column on the table. + * + * @param string $column + * @param bool $autoIncrement + * @param bool $unsigned + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function integer($column, $autoIncrement = false, $unsigned = false) + { + return $this->addColumn('integer', $column, compact('autoIncrement', 'unsigned')); + } + + /** + * Create a new tiny integer (1-byte) column on the table. + * + * @param string $column + * @param bool $autoIncrement + * @param bool $unsigned + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function tinyInteger($column, $autoIncrement = false, $unsigned = false) + { + return $this->addColumn('tinyInteger', $column, compact('autoIncrement', 'unsigned')); + } + + /** + * Create a new small integer (2-byte) column on the table. + * + * @param string $column + * @param bool $autoIncrement + * @param bool $unsigned + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function smallInteger($column, $autoIncrement = false, $unsigned = false) + { + return $this->addColumn('smallInteger', $column, compact('autoIncrement', 'unsigned')); + } + + /** + * Create a new medium integer (3-byte) column on the table. + * + * @param string $column + * @param bool $autoIncrement + * @param bool $unsigned + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function mediumInteger($column, $autoIncrement = false, $unsigned = false) + { + return $this->addColumn('mediumInteger', $column, compact('autoIncrement', 'unsigned')); + } + + /** + * Create a new big integer (8-byte) column on the table. + * + * @param string $column + * @param bool $autoIncrement + * @param bool $unsigned + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function bigInteger($column, $autoIncrement = false, $unsigned = false) + { + return $this->addColumn('bigInteger', $column, compact('autoIncrement', 'unsigned')); + } + + /** + * Create a new unsigned integer (4-byte) column on the table. + * + * @param string $column + * @param bool $autoIncrement + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function unsignedInteger($column, $autoIncrement = false) + { + return $this->integer($column, $autoIncrement, true); + } + + /** + * Create a new unsigned tiny integer (1-byte) column on the table. + * + * @param string $column + * @param bool $autoIncrement + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function unsignedTinyInteger($column, $autoIncrement = false) + { + return $this->tinyInteger($column, $autoIncrement, true); + } + + /** + * Create a new unsigned small integer (2-byte) column on the table. + * + * @param string $column + * @param bool $autoIncrement + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function unsignedSmallInteger($column, $autoIncrement = false) + { + return $this->smallInteger($column, $autoIncrement, true); + } + + /** + * Create a new unsigned medium integer (3-byte) column on the table. + * + * @param string $column + * @param bool $autoIncrement + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function unsignedMediumInteger($column, $autoIncrement = false) + { + return $this->mediumInteger($column, $autoIncrement, true); + } + + /** + * Create a new unsigned big integer (8-byte) column on the table. + * + * @param string $column + * @param bool $autoIncrement + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function unsignedBigInteger($column, $autoIncrement = false) + { + return $this->bigInteger($column, $autoIncrement, true); + } + + /** + * Create a new unsigned big integer (8-byte) column on the table. + * + * @param string $column + * @return \Illuminate\Database\Schema\ForeignIdColumnDefinition + */ + public function foreignId($column) + { + return $this->addColumnDefinition(new ForeignIdColumnDefinition($this, [ + 'type' => 'bigInteger', + 'name' => $column, + 'autoIncrement' => false, + 'unsigned' => true, + ])); + } + + /** + * Create a foreign ID column for the given model. + * + * @param \Illuminate\Database\Eloquent\Model|string $model + * @param string|null $column + * @return \Illuminate\Database\Schema\ForeignIdColumnDefinition + */ + public function foreignIdFor($model, $column = null) + { + if (is_string($model)) { + $model = new $model; + } + + return $model->getKeyType() === 'int' && $model->getIncrementing() + ? $this->foreignId($column ?: $model->getForeignKey()) + : $this->foreignUuid($column ?: $model->getForeignKey()); + } + + /** + * Create a new float column on the table. + * + * @param string $column + * @param int $total + * @param int $places + * @param bool $unsigned + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function float($column, $total = 8, $places = 2, $unsigned = false) + { + return $this->addColumn('float', $column, compact('total', 'places', 'unsigned')); + } + + /** + * Create a new double column on the table. + * + * @param string $column + * @param int|null $total + * @param int|null $places + * @param bool $unsigned + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function double($column, $total = null, $places = null, $unsigned = false) + { + return $this->addColumn('double', $column, compact('total', 'places', 'unsigned')); + } + + /** + * Create a new decimal column on the table. + * + * @param string $column + * @param int $total + * @param int $places + * @param bool $unsigned + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function decimal($column, $total = 8, $places = 2, $unsigned = false) + { + return $this->addColumn('decimal', $column, compact('total', 'places', 'unsigned')); + } + + /** + * Create a new unsigned float column on the table. + * + * @param string $column + * @param int $total + * @param int $places + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function unsignedFloat($column, $total = 8, $places = 2) + { + return $this->float($column, $total, $places, true); + } + + /** + * Create a new unsigned double column on the table. + * + * @param string $column + * @param int $total + * @param int $places + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function unsignedDouble($column, $total = null, $places = null) + { + return $this->double($column, $total, $places, true); + } + + /** + * Create a new unsigned decimal column on the table. + * + * @param string $column + * @param int $total + * @param int $places + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function unsignedDecimal($column, $total = 8, $places = 2) + { + return $this->decimal($column, $total, $places, true); + } + + /** + * Create a new boolean column on the table. + * + * @param string $column + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function boolean($column) + { + return $this->addColumn('boolean', $column); + } + + /** + * Create a new enum column on the table. + * + * @param string $column + * @param array $allowed + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function enum($column, array $allowed) + { + return $this->addColumn('enum', $column, compact('allowed')); + } + + /** + * Create a new set column on the table. + * + * @param string $column + * @param array $allowed + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function set($column, array $allowed) + { + return $this->addColumn('set', $column, compact('allowed')); + } + + /** + * Create a new json column on the table. + * + * @param string $column + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function json($column) + { + return $this->addColumn('json', $column); + } + + /** + * Create a new jsonb column on the table. + * + * @param string $column + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function jsonb($column) + { + return $this->addColumn('jsonb', $column); + } + + /** + * Create a new date column on the table. + * + * @param string $column + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function date($column) + { + return $this->addColumn('date', $column); + } + + /** + * Create a new date-time column on the table. + * + * @param string $column + * @param int $precision + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function dateTime($column, $precision = 0) + { + return $this->addColumn('dateTime', $column, compact('precision')); + } + + /** + * Create a new date-time column (with time zone) on the table. + * + * @param string $column + * @param int $precision + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function dateTimeTz($column, $precision = 0) + { + return $this->addColumn('dateTimeTz', $column, compact('precision')); + } + + /** + * Create a new time column on the table. + * + * @param string $column + * @param int $precision + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function time($column, $precision = 0) + { + return $this->addColumn('time', $column, compact('precision')); + } + + /** + * Create a new time column (with time zone) on the table. + * + * @param string $column + * @param int $precision + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function timeTz($column, $precision = 0) + { + return $this->addColumn('timeTz', $column, compact('precision')); + } + + /** + * Create a new timestamp column on the table. + * + * @param string $column + * @param int $precision + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function timestamp($column, $precision = 0) + { + return $this->addColumn('timestamp', $column, compact('precision')); + } + + /** + * Create a new timestamp (with time zone) column on the table. + * + * @param string $column + * @param int $precision + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function timestampTz($column, $precision = 0) + { + return $this->addColumn('timestampTz', $column, compact('precision')); + } + + /** + * Add nullable creation and update timestamps to the table. + * + * @param int $precision + * @return void + */ + public function timestamps($precision = 0) + { + $this->timestamp('created_at', $precision)->nullable(); + + $this->timestamp('updated_at', $precision)->nullable(); + } + + /** + * Add nullable creation and update timestamps to the table. + * + * Alias for self::timestamps(). + * + * @param int $precision + * @return void + */ + public function nullableTimestamps($precision = 0) + { + $this->timestamps($precision); + } + + /** + * Add creation and update timestampTz columns to the table. + * + * @param int $precision + * @return void + */ + public function timestampsTz($precision = 0) + { + $this->timestampTz('created_at', $precision)->nullable(); + + $this->timestampTz('updated_at', $precision)->nullable(); + } + + /** + * Add a "deleted at" timestamp for the table. + * + * @param string $column + * @param int $precision + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function softDeletes($column = 'deleted_at', $precision = 0) + { + return $this->timestamp($column, $precision)->nullable(); + } + + /** + * Add a "deleted at" timestampTz for the table. + * + * @param string $column + * @param int $precision + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function softDeletesTz($column = 'deleted_at', $precision = 0) + { + return $this->timestampTz($column, $precision)->nullable(); + } + + /** + * Create a new year column on the table. + * + * @param string $column + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function year($column) + { + return $this->addColumn('year', $column); + } + + /** + * Create a new binary column on the table. + * + * @param string $column + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function binary($column) + { + return $this->addColumn('binary', $column); + } + + /** + * Create a new uuid column on the table. + * + * @param string $column + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function uuid($column) + { + return $this->addColumn('uuid', $column); + } + + /** + * Create a new UUID column on the table with a foreign key constraint. + * + * @param string $column + * @return \Illuminate\Database\Schema\ForeignIdColumnDefinition + */ + public function foreignUuid($column) + { + return $this->addColumnDefinition(new ForeignIdColumnDefinition($this, [ + 'type' => 'uuid', + 'name' => $column, + ])); + } + + /** + * Create a new IP address column on the table. + * + * @param string $column + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function ipAddress($column) + { + return $this->addColumn('ipAddress', $column); + } + + /** + * Create a new MAC address column on the table. + * + * @param string $column + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function macAddress($column) + { + return $this->addColumn('macAddress', $column); + } + + /** + * Create a new geometry column on the table. + * + * @param string $column + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function geometry($column) + { + return $this->addColumn('geometry', $column); + } + + /** + * Create a new point column on the table. + * + * @param string $column + * @param int|null $srid + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function point($column, $srid = null) + { + return $this->addColumn('point', $column, compact('srid')); + } + + /** + * Create a new linestring column on the table. + * + * @param string $column + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function lineString($column) + { + return $this->addColumn('linestring', $column); + } + + /** + * Create a new polygon column on the table. + * + * @param string $column + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function polygon($column) + { + return $this->addColumn('polygon', $column); + } + + /** + * Create a new geometrycollection column on the table. + * + * @param string $column + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function geometryCollection($column) + { + return $this->addColumn('geometrycollection', $column); + } + + /** + * Create a new multipoint column on the table. + * + * @param string $column + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function multiPoint($column) + { + return $this->addColumn('multipoint', $column); + } + + /** + * Create a new multilinestring column on the table. + * + * @param string $column + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function multiLineString($column) + { + return $this->addColumn('multilinestring', $column); + } + + /** + * Create a new multipolygon column on the table. + * + * @param string $column + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function multiPolygon($column) + { + return $this->addColumn('multipolygon', $column); + } + + /** + * Create a new multipolygon column on the table. + * + * @param string $column + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function multiPolygonZ($column) + { + return $this->addColumn('multipolygonz', $column); + } + + /** + * Create a new generated, computed column on the table. + * + * @param string $column + * @param string $expression + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function computed($column, $expression) + { + return $this->addColumn('computed', $column, compact('expression')); + } + + /** + * Add the proper columns for a polymorphic table. + * + * @param string $name + * @param string|null $indexName + * @return void + */ + public function morphs($name, $indexName = null) + { + if (Builder::$defaultMorphKeyType === 'uuid') { + $this->uuidMorphs($name, $indexName); + } else { + $this->numericMorphs($name, $indexName); + } + } + + /** + * Add nullable columns for a polymorphic table. + * + * @param string $name + * @param string|null $indexName + * @return void + */ + public function nullableMorphs($name, $indexName = null) + { + if (Builder::$defaultMorphKeyType === 'uuid') { + $this->nullableUuidMorphs($name, $indexName); + } else { + $this->nullableNumericMorphs($name, $indexName); + } + } + + /** + * Add the proper columns for a polymorphic table using numeric IDs (incremental). + * + * @param string $name + * @param string|null $indexName + * @return void + */ + public function numericMorphs($name, $indexName = null) + { + $this->string("{$name}_type"); + + $this->unsignedBigInteger("{$name}_id"); + + $this->index(["{$name}_type", "{$name}_id"], $indexName); + } + + /** + * Add nullable columns for a polymorphic table using numeric IDs (incremental). + * + * @param string $name + * @param string|null $indexName + * @return void + */ + public function nullableNumericMorphs($name, $indexName = null) + { + $this->string("{$name}_type")->nullable(); + + $this->unsignedBigInteger("{$name}_id")->nullable(); + + $this->index(["{$name}_type", "{$name}_id"], $indexName); + } + + /** + * Add the proper columns for a polymorphic table using UUIDs. + * + * @param string $name + * @param string|null $indexName + * @return void + */ + public function uuidMorphs($name, $indexName = null) + { + $this->string("{$name}_type"); + + $this->uuid("{$name}_id"); + + $this->index(["{$name}_type", "{$name}_id"], $indexName); + } + + /** + * Add nullable columns for a polymorphic table using UUIDs. + * + * @param string $name + * @param string|null $indexName + * @return void + */ + public function nullableUuidMorphs($name, $indexName = null) + { + $this->string("{$name}_type")->nullable(); + + $this->uuid("{$name}_id")->nullable(); + + $this->index(["{$name}_type", "{$name}_id"], $indexName); + } + + /** + * Adds the `remember_token` column to the table. + * + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function rememberToken() + { + return $this->string('remember_token', 100)->nullable(); + } + + /** + * Add a new index command to the blueprint. + * + * @param string $type + * @param string|array $columns + * @param string $index + * @param string|null $algorithm + * @return \Illuminate\Support\Fluent + */ + protected function indexCommand($type, $columns, $index, $algorithm = null) + { + $columns = (array) $columns; + + // If no name was specified for this index, we will create one using a basic + // convention of the table name, followed by the columns, followed by an + // index type, such as primary or index, which makes the index unique. + $index = $index ?: $this->createIndexName($type, $columns); + + return $this->addCommand( + $type, compact('index', 'columns', 'algorithm') + ); + } + + /** + * Create a new drop index command on the blueprint. + * + * @param string $command + * @param string $type + * @param string|array $index + * @return \Illuminate\Support\Fluent + */ + protected function dropIndexCommand($command, $type, $index) + { + $columns = []; + + // If the given "index" is actually an array of columns, the developer means + // to drop an index merely by specifying the columns involved without the + // conventional name, so we will build the index name from the columns. + if (is_array($index)) { + $index = $this->createIndexName($type, $columns = $index); + } + + return $this->indexCommand($command, $columns, $index); + } + + /** + * Create a default index name for the table. + * + * @param string $type + * @param array $columns + * @return string + */ + protected function createIndexName($type, array $columns) + { + $index = strtolower($this->prefix.$this->table.'_'.implode('_', $columns).'_'.$type); + + return str_replace(['-', '.'], '_', $index); + } + + /** + * Add a new column to the blueprint. + * + * @param string $type + * @param string $name + * @param array $parameters + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function addColumn($type, $name, array $parameters = []) + { + return $this->addColumnDefinition(new ColumnDefinition( + array_merge(compact('type', 'name'), $parameters) + )); + } + + /** + * Add a new column definition to the blueprint. + * + * @param \Illuminate\Database\Schema\ColumnDefinition $definition + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + protected function addColumnDefinition($definition) + { + $this->columns[] = $definition; + + if ($this->after) { + $definition->after($this->after); + + $this->after = $definition->name; + } + + return $definition; + } + + /** + * Add the columns from the callback after the given column. + * + * @param string $column + * @param \Closure $callback + * @return void + */ + public function after($column, Closure $callback) + { + $this->after = $column; + + $callback($this); + + $this->after = null; + } + + /** + * Remove a column from the schema blueprint. + * + * @param string $name + * @return $this + */ + public function removeColumn($name) + { + $this->columns = array_values(array_filter($this->columns, function ($c) use ($name) { + return $c['name'] != $name; + })); + + return $this; + } + + /** + * Add a new command to the blueprint. + * + * @param string $name + * @param array $parameters + * @return \Illuminate\Support\Fluent + */ + protected function addCommand($name, array $parameters = []) + { + $this->commands[] = $command = $this->createCommand($name, $parameters); + + return $command; + } + + /** + * Create a new Fluent command. + * + * @param string $name + * @param array $parameters + * @return \Illuminate\Support\Fluent + */ + protected function createCommand($name, array $parameters = []) + { + return new Fluent(array_merge(compact('name'), $parameters)); + } + + /** + * Get the table the blueprint describes. + * + * @return string + */ + public function getTable() + { + return $this->table; + } + + /** + * Get the columns on the blueprint. + * + * @return \Illuminate\Database\Schema\ColumnDefinition[] + */ + public function getColumns() + { + return $this->columns; + } + + /** + * Get the commands on the blueprint. + * + * @return \Illuminate\Support\Fluent[] + */ + public function getCommands() + { + return $this->commands; + } + + /** + * Get the columns on the blueprint that should be added. + * + * @return \Illuminate\Database\Schema\ColumnDefinition[] + */ + public function getAddedColumns() + { + return array_filter($this->columns, function ($column) { + return ! $column->change; + }); + } + + /** + * Get the columns on the blueprint that should be changed. + * + * @return \Illuminate\Database\Schema\ColumnDefinition[] + */ + public function getChangedColumns() + { + return array_filter($this->columns, function ($column) { + return (bool) $column->change; + }); + } + + /** + * Determine if the blueprint has auto-increment columns. + * + * @return bool + */ + public function hasAutoIncrementColumn() + { + return ! is_null(collect($this->getAddedColumns())->first(function ($column) { + return $column->autoIncrement === true; + })); + } + + /** + * Get the auto-increment column starting values. + * + * @return array + */ + public function autoIncrementingStartingValues() + { + if (! $this->hasAutoIncrementColumn()) { + return []; + } + + return collect($this->getAddedColumns())->mapWithKeys(function ($column) { + return $column->autoIncrement === true + ? [$column->name => $column->get('startingValue', $column->get('from'))] + : [$column->name => null]; + })->filter()->all(); + } +} diff --git a/vendor/illuminate/database/Schema/Builder.php b/vendor/illuminate/database/Schema/Builder.php new file mode 100644 index 0000000..40f7888 --- /dev/null +++ b/vendor/illuminate/database/Schema/Builder.php @@ -0,0 +1,433 @@ +connection = $connection; + $this->grammar = $connection->getSchemaGrammar(); + } + + /** + * Set the default string length for migrations. + * + * @param int $length + * @return void + */ + public static function defaultStringLength($length) + { + static::$defaultStringLength = $length; + } + + /** + * Set the default morph key type for migrations. + * + * @param string $type + * @return void + * + * @throws \InvalidArgumentException + */ + public static function defaultMorphKeyType(string $type) + { + if (! in_array($type, ['int', 'uuid'])) { + throw new InvalidArgumentException("Morph key type must be 'int' or 'uuid'."); + } + + static::$defaultMorphKeyType = $type; + } + + /** + * Set the default morph key type for migrations to UUIDs. + * + * @return void + */ + public static function morphUsingUuids() + { + return static::defaultMorphKeyType('uuid'); + } + + /** + * Create a database in the schema. + * + * @param string $name + * @return bool + * + * @throws \LogicException + */ + public function createDatabase($name) + { + throw new LogicException('This database driver does not support creating databases.'); + } + + /** + * Drop a database from the schema if the database exists. + * + * @param string $name + * @return bool + * + * @throws \LogicException + */ + public function dropDatabaseIfExists($name) + { + throw new LogicException('This database driver does not support dropping databases.'); + } + + /** + * Determine if the given table exists. + * + * @param string $table + * @return bool + */ + public function hasTable($table) + { + $table = $this->connection->getTablePrefix().$table; + + return count($this->connection->selectFromWriteConnection( + $this->grammar->compileTableExists(), [$table] + )) > 0; + } + + /** + * Determine if the given table has a given column. + * + * @param string $table + * @param string $column + * @return bool + */ + public function hasColumn($table, $column) + { + return in_array( + strtolower($column), array_map('strtolower', $this->getColumnListing($table)) + ); + } + + /** + * Determine if the given table has given columns. + * + * @param string $table + * @param array $columns + * @return bool + */ + public function hasColumns($table, array $columns) + { + $tableColumns = array_map('strtolower', $this->getColumnListing($table)); + + foreach ($columns as $column) { + if (! in_array(strtolower($column), $tableColumns)) { + return false; + } + } + + return true; + } + + /** + * Get the data type for the given column name. + * + * @param string $table + * @param string $column + * @return string + */ + public function getColumnType($table, $column) + { + $table = $this->connection->getTablePrefix().$table; + + return $this->connection->getDoctrineColumn($table, $column)->getType()->getName(); + } + + /** + * Get the column listing for a given table. + * + * @param string $table + * @return array + */ + public function getColumnListing($table) + { + $results = $this->connection->selectFromWriteConnection($this->grammar->compileColumnListing( + $this->connection->getTablePrefix().$table + )); + + return $this->connection->getPostProcessor()->processColumnListing($results); + } + + /** + * Modify a table on the schema. + * + * @param string $table + * @param \Closure $callback + * @return void + */ + public function table($table, Closure $callback) + { + $this->build($this->createBlueprint($table, $callback)); + } + + /** + * Create a new table on the schema. + * + * @param string $table + * @param \Closure $callback + * @return void + */ + public function create($table, Closure $callback) + { + $this->build(tap($this->createBlueprint($table), function ($blueprint) use ($callback) { + $blueprint->create(); + + $callback($blueprint); + })); + } + + /** + * Drop a table from the schema. + * + * @param string $table + * @return void + */ + public function drop($table) + { + $this->build(tap($this->createBlueprint($table), function ($blueprint) { + $blueprint->drop(); + })); + } + + /** + * Drop a table from the schema if it exists. + * + * @param string $table + * @return void + */ + public function dropIfExists($table) + { + $this->build(tap($this->createBlueprint($table), function ($blueprint) { + $blueprint->dropIfExists(); + })); + } + + /** + * Drop columns from a table schema. + * + * @param string $table + * @param string|array $columns + * @return void + */ + public function dropColumns($table, $columns) + { + $this->table($table, function (Blueprint $blueprint) use ($columns) { + $blueprint->dropColumn($columns); + }); + } + + /** + * Drop all tables from the database. + * + * @return void + * + * @throws \LogicException + */ + public function dropAllTables() + { + throw new LogicException('This database driver does not support dropping all tables.'); + } + + /** + * Drop all views from the database. + * + * @return void + * + * @throws \LogicException + */ + public function dropAllViews() + { + throw new LogicException('This database driver does not support dropping all views.'); + } + + /** + * Drop all types from the database. + * + * @return void + * + * @throws \LogicException + */ + public function dropAllTypes() + { + throw new LogicException('This database driver does not support dropping all types.'); + } + + /** + * Get all of the table names for the database. + * + * @return void + * + * @throws \LogicException + */ + public function getAllTables() + { + throw new LogicException('This database driver does not support getting all tables.'); + } + + /** + * Rename a table on the schema. + * + * @param string $from + * @param string $to + * @return void + */ + public function rename($from, $to) + { + $this->build(tap($this->createBlueprint($from), function ($blueprint) use ($to) { + $blueprint->rename($to); + })); + } + + /** + * Enable foreign key constraints. + * + * @return bool + */ + public function enableForeignKeyConstraints() + { + return $this->connection->statement( + $this->grammar->compileEnableForeignKeyConstraints() + ); + } + + /** + * Disable foreign key constraints. + * + * @return bool + */ + public function disableForeignKeyConstraints() + { + return $this->connection->statement( + $this->grammar->compileDisableForeignKeyConstraints() + ); + } + + /** + * Execute the blueprint to build / modify the table. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @return void + */ + protected function build(Blueprint $blueprint) + { + $blueprint->build($this->connection, $this->grammar); + } + + /** + * Create a new command set with a Closure. + * + * @param string $table + * @param \Closure|null $callback + * @return \Illuminate\Database\Schema\Blueprint + */ + protected function createBlueprint($table, Closure $callback = null) + { + $prefix = $this->connection->getConfig('prefix_indexes') + ? $this->connection->getConfig('prefix') + : ''; + + if (isset($this->resolver)) { + return call_user_func($this->resolver, $table, $callback, $prefix); + } + + return Container::getInstance()->make(Blueprint::class, compact('table', 'callback', 'prefix')); + } + + /** + * Register a custom Doctrine mapping type. + * + * @param string $class + * @param string $name + * @param string $type + * @return void + */ + public function registerCustomDoctrineType($class, $name, $type) + { + $this->connection->registerDoctrineType($class, $name, $type); + } + + /** + * Get the database connection instance. + * + * @return \Illuminate\Database\Connection + */ + public function getConnection() + { + return $this->connection; + } + + /** + * Set the database connection instance. + * + * @param \Illuminate\Database\Connection $connection + * @return $this + */ + public function setConnection(Connection $connection) + { + $this->connection = $connection; + + return $this; + } + + /** + * Set the Schema Blueprint resolver callback. + * + * @param \Closure $resolver + * @return void + */ + public function blueprintResolver(Closure $resolver) + { + $this->resolver = $resolver; + } +} diff --git a/vendor/illuminate/database/Schema/ColumnDefinition.php b/vendor/illuminate/database/Schema/ColumnDefinition.php new file mode 100644 index 0000000..85f8ba3 --- /dev/null +++ b/vendor/illuminate/database/Schema/ColumnDefinition.php @@ -0,0 +1,38 @@ +blueprint = $blueprint; + } + + /** + * Create a foreign key constraint on this column referencing the "id" column of the conventionally related table. + * + * @param string|null $table + * @param string $column + * @return \Illuminate\Database\Schema\ForeignKeyDefinition + */ + public function constrained($table = null, $column = 'id') + { + return $this->references($column)->on($table ?? Str::plural(Str::beforeLast($this->name, '_'.$column))); + } + + /** + * Specify which column this foreign ID references on another table. + * + * @param string $column + * @return \Illuminate\Database\Schema\ForeignKeyDefinition + */ + public function references($column) + { + return $this->blueprint->foreign($this->name)->references($column); + } +} diff --git a/vendor/illuminate/database/Schema/ForeignKeyDefinition.php b/vendor/illuminate/database/Schema/ForeignKeyDefinition.php new file mode 100644 index 0000000..a03fcff --- /dev/null +++ b/vendor/illuminate/database/Schema/ForeignKeyDefinition.php @@ -0,0 +1,66 @@ +onUpdate('cascade'); + } + + /** + * Indicate that updates should be restricted. + * + * @return $this + */ + public function restrictOnUpdate() + { + return $this->onUpdate('restrict'); + } + + /** + * Indicate that deletes should cascade. + * + * @return $this + */ + public function cascadeOnDelete() + { + return $this->onDelete('cascade'); + } + + /** + * Indicate that deletes should be restricted. + * + * @return $this + */ + public function restrictOnDelete() + { + return $this->onDelete('restrict'); + } + + /** + * Indicate that deletes should set the foreign key value to null. + * + * @return $this + */ + public function nullOnDelete() + { + return $this->onDelete('set null'); + } +} diff --git a/vendor/illuminate/database/Schema/Grammars/ChangeColumn.php b/vendor/illuminate/database/Schema/Grammars/ChangeColumn.php new file mode 100644 index 0000000..260935f --- /dev/null +++ b/vendor/illuminate/database/Schema/Grammars/ChangeColumn.php @@ -0,0 +1,248 @@ +isDoctrineAvailable()) { + throw new RuntimeException(sprintf( + 'Changing columns for table "%s" requires Doctrine DBAL. Please install the doctrine/dbal package.', + $blueprint->getTable() + )); + } + + $schema = $connection->getDoctrineSchemaManager(); + $databasePlatform = $schema->getDatabasePlatform(); + $databasePlatform->registerDoctrineTypeMapping('enum', 'string'); + + $tableDiff = static::getChangedDiff( + $grammar, $blueprint, $schema + ); + + if ($tableDiff !== false) { + return (array) $databasePlatform->getAlterTableSQL($tableDiff); + } + + return []; + } + + /** + * Get the Doctrine table difference for the given changes. + * + * @param \Illuminate\Database\Schema\Grammars\Grammar $grammar + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Doctrine\DBAL\Schema\AbstractSchemaManager $schema + * @return \Doctrine\DBAL\Schema\TableDiff|bool + */ + protected static function getChangedDiff($grammar, Blueprint $blueprint, SchemaManager $schema) + { + $current = $schema->listTableDetails($grammar->getTablePrefix().$blueprint->getTable()); + + return (new Comparator)->diffTable( + $current, static::getTableWithColumnChanges($blueprint, $current) + ); + } + + /** + * Get a copy of the given Doctrine table after making the column changes. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Doctrine\DBAL\Schema\Table $table + * @return \Doctrine\DBAL\Schema\Table + */ + protected static function getTableWithColumnChanges(Blueprint $blueprint, Table $table) + { + $table = clone $table; + + foreach ($blueprint->getChangedColumns() as $fluent) { + $column = static::getDoctrineColumn($table, $fluent); + + // Here we will spin through each fluent column definition and map it to the proper + // Doctrine column definitions - which is necessary because Laravel and Doctrine + // use some different terminology for various column attributes on the tables. + foreach ($fluent->getAttributes() as $key => $value) { + if (! is_null($option = static::mapFluentOptionToDoctrine($key))) { + if (method_exists($column, $method = 'set'.ucfirst($option))) { + $column->{$method}(static::mapFluentValueToDoctrine($option, $value)); + continue; + } + + $column->setCustomSchemaOption($option, static::mapFluentValueToDoctrine($option, $value)); + } + } + } + + return $table; + } + + /** + * Get the Doctrine column instance for a column change. + * + * @param \Doctrine\DBAL\Schema\Table $table + * @param \Illuminate\Support\Fluent $fluent + * @return \Doctrine\DBAL\Schema\Column + */ + protected static function getDoctrineColumn(Table $table, Fluent $fluent) + { + return $table->changeColumn( + $fluent['name'], static::getDoctrineColumnChangeOptions($fluent) + )->getColumn($fluent['name']); + } + + /** + * Get the Doctrine column change options. + * + * @param \Illuminate\Support\Fluent $fluent + * @return array + */ + protected static function getDoctrineColumnChangeOptions(Fluent $fluent) + { + $options = ['type' => static::getDoctrineColumnType($fluent['type'])]; + + if (in_array($fluent['type'], ['text', 'mediumText', 'longText'])) { + $options['length'] = static::calculateDoctrineTextLength($fluent['type']); + } + + if (static::doesntNeedCharacterOptions($fluent['type'])) { + $options['customSchemaOptions'] = [ + 'collation' => '', + 'charset' => '', + ]; + } + + return $options; + } + + /** + * Get the doctrine column type. + * + * @param string $type + * @return \Doctrine\DBAL\Types\Type + */ + protected static function getDoctrineColumnType($type) + { + $type = strtolower($type); + + switch ($type) { + case 'biginteger': + $type = 'bigint'; + break; + case 'smallinteger': + $type = 'smallint'; + break; + case 'mediumtext': + case 'longtext': + $type = 'text'; + break; + case 'binary': + $type = 'blob'; + break; + case 'uuid': + $type = 'guid'; + break; + } + + return Type::getType($type); + } + + /** + * Calculate the proper column length to force the Doctrine text type. + * + * @param string $type + * @return int + */ + protected static function calculateDoctrineTextLength($type) + { + switch ($type) { + case 'mediumText': + return 65535 + 1; + case 'longText': + return 16777215 + 1; + default: + return 255 + 1; + } + } + + /** + * Determine if the given type does not need character / collation options. + * + * @param string $type + * @return bool + */ + protected static function doesntNeedCharacterOptions($type) + { + return in_array($type, [ + 'bigInteger', + 'binary', + 'boolean', + 'date', + 'dateTime', + 'decimal', + 'double', + 'float', + 'integer', + 'json', + 'mediumInteger', + 'smallInteger', + 'time', + 'tinyInteger', + ]); + } + + /** + * Get the matching Doctrine option for a given Fluent attribute name. + * + * @param string $attribute + * @return string|null + */ + protected static function mapFluentOptionToDoctrine($attribute) + { + switch ($attribute) { + case 'type': + case 'name': + return; + case 'nullable': + return 'notnull'; + case 'total': + return 'precision'; + case 'places': + return 'scale'; + default: + return $attribute; + } + } + + /** + * Get the matching Doctrine value for a given Fluent attribute. + * + * @param string $option + * @param mixed $value + * @return mixed + */ + protected static function mapFluentValueToDoctrine($option, $value) + { + return $option === 'notnull' ? ! $value : $value; + } +} diff --git a/vendor/illuminate/database/Schema/Grammars/Grammar.php b/vendor/illuminate/database/Schema/Grammars/Grammar.php new file mode 100644 index 0000000..7313576 --- /dev/null +++ b/vendor/illuminate/database/Schema/Grammars/Grammar.php @@ -0,0 +1,340 @@ +wrapTable($blueprint), + $this->wrap($command->index) + ); + + // Once we have the initial portion of the SQL statement we will add on the + // key name, table name, and referenced columns. These will complete the + // main portion of the SQL statement and this SQL will almost be done. + $sql .= sprintf('foreign key (%s) references %s (%s)', + $this->columnize($command->columns), + $this->wrapTable($command->on), + $this->columnize((array) $command->references) + ); + + // Once we have the basic foreign key creation statement constructed we can + // build out the syntax for what should happen on an update or delete of + // the affected columns, which will get something like "cascade", etc. + if (! is_null($command->onDelete)) { + $sql .= " on delete {$command->onDelete}"; + } + + if (! is_null($command->onUpdate)) { + $sql .= " on update {$command->onUpdate}"; + } + + return $sql; + } + + /** + * Compile the blueprint's column definitions. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @return array + */ + protected function getColumns(Blueprint $blueprint) + { + $columns = []; + + foreach ($blueprint->getAddedColumns() as $column) { + // Each of the column types have their own compiler functions which are tasked + // with turning the column definition into its SQL format for this platform + // used by the connection. The column's modifiers are compiled and added. + $sql = $this->wrap($column).' '.$this->getType($column); + + $columns[] = $this->addModifiers($sql, $blueprint, $column); + } + + return $columns; + } + + /** + * Get the SQL for the column data type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function getType(Fluent $column) + { + return $this->{'type'.ucfirst($column->type)}($column); + } + + /** + * Create the column definition for a generated, computed column type. + * + * @param \Illuminate\Support\Fluent $column + * @return void + * + * @throws \RuntimeException + */ + protected function typeComputed(Fluent $column) + { + throw new RuntimeException('This database driver does not support the computed type.'); + } + + /** + * Add the column modifiers to the definition. + * + * @param string $sql + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function addModifiers($sql, Blueprint $blueprint, Fluent $column) + { + foreach ($this->modifiers as $modifier) { + if (method_exists($this, $method = "modify{$modifier}")) { + $sql .= $this->{$method}($blueprint, $column); + } + } + + return $sql; + } + + /** + * Get the primary key command if it exists on the blueprint. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param string $name + * @return \Illuminate\Support\Fluent|null + */ + protected function getCommandByName(Blueprint $blueprint, $name) + { + $commands = $this->getCommandsByName($blueprint, $name); + + if (count($commands) > 0) { + return reset($commands); + } + } + + /** + * Get all of the commands with a given name. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param string $name + * @return array + */ + protected function getCommandsByName(Blueprint $blueprint, $name) + { + return array_filter($blueprint->getCommands(), function ($value) use ($name) { + return $value->name == $name; + }); + } + + /** + * Add a prefix to an array of values. + * + * @param string $prefix + * @param array $values + * @return array + */ + public function prefixArray($prefix, array $values) + { + return array_map(function ($value) use ($prefix) { + return $prefix.' '.$value; + }, $values); + } + + /** + * Wrap a table in keyword identifiers. + * + * @param mixed $table + * @return string + */ + public function wrapTable($table) + { + return parent::wrapTable( + $table instanceof Blueprint ? $table->getTable() : $table + ); + } + + /** + * Wrap a value in keyword identifiers. + * + * @param \Illuminate\Database\Query\Expression|string $value + * @param bool $prefixAlias + * @return string + */ + public function wrap($value, $prefixAlias = false) + { + return parent::wrap( + $value instanceof Fluent ? $value->name : $value, $prefixAlias + ); + } + + /** + * Format a value so that it can be used in "default" clauses. + * + * @param mixed $value + * @return string + */ + protected function getDefaultValue($value) + { + if ($value instanceof Expression) { + return $value; + } + + return is_bool($value) + ? "'".(int) $value."'" + : "'".(string) $value."'"; + } + + /** + * Create an empty Doctrine DBAL TableDiff from the Blueprint. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Doctrine\DBAL\Schema\AbstractSchemaManager $schema + * @return \Doctrine\DBAL\Schema\TableDiff + */ + public function getDoctrineTableDiff(Blueprint $blueprint, SchemaManager $schema) + { + $table = $this->getTablePrefix().$blueprint->getTable(); + + return tap(new TableDiff($table), function ($tableDiff) use ($schema, $table) { + $tableDiff->fromTable = $schema->listTableDetails($table); + }); + } + + /** + * Get the fluent commands for the grammar. + * + * @return array + */ + public function getFluentCommands() + { + return $this->fluentCommands; + } + + /** + * Check if this Grammar supports schema changes wrapped in a transaction. + * + * @return bool + */ + public function supportsSchemaTransactions() + { + return $this->transactions; + } +} diff --git a/vendor/illuminate/database/Schema/Grammars/MySqlGrammar.php b/vendor/illuminate/database/Schema/Grammars/MySqlGrammar.php new file mode 100644 index 0000000..1f64cf3 --- /dev/null +++ b/vendor/illuminate/database/Schema/Grammars/MySqlGrammar.php @@ -0,0 +1,1153 @@ +wrapValue($name), + $this->wrapValue($connection->getConfig('charset')), + $this->wrapValue($connection->getConfig('collation')), + ); + } + + /** + * Compile a drop database if exists command. + * + * @param string $name + * @return string + */ + public function compileDropDatabaseIfExists($name) + { + return sprintf( + 'drop database if exists %s', + $this->wrapValue($name) + ); + } + + /** + * Compile the query to determine the list of tables. + * + * @return string + */ + public function compileTableExists() + { + return "select * from information_schema.tables where table_schema = ? and table_name = ? and table_type = 'BASE TABLE'"; + } + + /** + * Compile the query to determine the list of columns. + * + * @return string + */ + public function compileColumnListing() + { + return 'select column_name as `column_name` from information_schema.columns where table_schema = ? and table_name = ?'; + } + + /** + * Compile a create table command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @param \Illuminate\Database\Connection $connection + * @return array + */ + public function compileCreate(Blueprint $blueprint, Fluent $command, Connection $connection) + { + $sql = $this->compileCreateTable( + $blueprint, $command, $connection + ); + + // Once we have the primary SQL, we can add the encoding option to the SQL for + // the table. Then, we can check if a storage engine has been supplied for + // the table. If so, we will add the engine declaration to the SQL query. + $sql = $this->compileCreateEncoding( + $sql, $connection, $blueprint + ); + + // Finally, we will append the engine configuration onto this SQL statement as + // the final thing we do before returning this finished SQL. Once this gets + // added the query will be ready to execute against the real connections. + return array_values(array_filter(array_merge([$this->compileCreateEngine( + $sql, $connection, $blueprint + )], $this->compileAutoIncrementStartingValues($blueprint)))); + } + + /** + * Create the main create table clause. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @param \Illuminate\Database\Connection $connection + * @return array + */ + protected function compileCreateTable($blueprint, $command, $connection) + { + return trim(sprintf('%s table %s (%s)', + $blueprint->temporary ? 'create temporary' : 'create', + $this->wrapTable($blueprint), + implode(', ', $this->getColumns($blueprint)) + )); + } + + /** + * Append the character set specifications to a command. + * + * @param string $sql + * @param \Illuminate\Database\Connection $connection + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @return string + */ + protected function compileCreateEncoding($sql, Connection $connection, Blueprint $blueprint) + { + // First we will set the character set if one has been set on either the create + // blueprint itself or on the root configuration for the connection that the + // table is being created on. We will add these to the create table query. + if (isset($blueprint->charset)) { + $sql .= ' default character set '.$blueprint->charset; + } elseif (! is_null($charset = $connection->getConfig('charset'))) { + $sql .= ' default character set '.$charset; + } + + // Next we will add the collation to the create table statement if one has been + // added to either this create table blueprint or the configuration for this + // connection that the query is targeting. We'll add it to this SQL query. + if (isset($blueprint->collation)) { + $sql .= " collate '{$blueprint->collation}'"; + } elseif (! is_null($collation = $connection->getConfig('collation'))) { + $sql .= " collate '{$collation}'"; + } + + return $sql; + } + + /** + * Append the engine specifications to a command. + * + * @param string $sql + * @param \Illuminate\Database\Connection $connection + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @return string + */ + protected function compileCreateEngine($sql, Connection $connection, Blueprint $blueprint) + { + if (isset($blueprint->engine)) { + return $sql.' engine = '.$blueprint->engine; + } elseif (! is_null($engine = $connection->getConfig('engine'))) { + return $sql.' engine = '.$engine; + } + + return $sql; + } + + /** + * Compile an add column command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return array + */ + public function compileAdd(Blueprint $blueprint, Fluent $command) + { + $columns = $this->prefixArray('add', $this->getColumns($blueprint)); + + return array_values(array_merge( + ['alter table '.$this->wrapTable($blueprint).' '.implode(', ', $columns)], + $this->compileAutoIncrementStartingValues($blueprint) + )); + } + + /** + * Compile the auto-incrementing column starting values. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @return array + */ + public function compileAutoIncrementStartingValues(Blueprint $blueprint) + { + return collect($blueprint->autoIncrementingStartingValues())->map(function ($value, $column) use ($blueprint) { + return 'alter table '.$this->wrapTable($blueprint->getTable()).' auto_increment = '.$value; + })->all(); + } + + /** + * Compile a primary key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compilePrimary(Blueprint $blueprint, Fluent $command) + { + $command->name(null); + + return $this->compileKey($blueprint, $command, 'primary key'); + } + + /** + * Compile a unique key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileUnique(Blueprint $blueprint, Fluent $command) + { + return $this->compileKey($blueprint, $command, 'unique'); + } + + /** + * Compile a plain index key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileIndex(Blueprint $blueprint, Fluent $command) + { + return $this->compileKey($blueprint, $command, 'index'); + } + + /** + * Compile a fulltext index key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileFullText(Blueprint $blueprint, Fluent $command) + { + return $this->compileKey($blueprint, $command, 'fulltext'); + } + + /** + * Compile a spatial index key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileSpatialIndex(Blueprint $blueprint, Fluent $command) + { + return $this->compileKey($blueprint, $command, 'spatial index'); + } + + /** + * Compile an index creation command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @param string $type + * @return string + */ + protected function compileKey(Blueprint $blueprint, Fluent $command, $type) + { + return sprintf('alter table %s add %s %s%s(%s)', + $this->wrapTable($blueprint), + $type, + $this->wrap($command->index), + $command->algorithm ? ' using '.$command->algorithm : '', + $this->columnize($command->columns) + ); + } + + /** + * Compile a drop table command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDrop(Blueprint $blueprint, Fluent $command) + { + return 'drop table '.$this->wrapTable($blueprint); + } + + /** + * Compile a drop table (if exists) command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropIfExists(Blueprint $blueprint, Fluent $command) + { + return 'drop table if exists '.$this->wrapTable($blueprint); + } + + /** + * Compile a drop column command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropColumn(Blueprint $blueprint, Fluent $command) + { + $columns = $this->prefixArray('drop', $this->wrapArray($command->columns)); + + return 'alter table '.$this->wrapTable($blueprint).' '.implode(', ', $columns); + } + + /** + * Compile a drop primary key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropPrimary(Blueprint $blueprint, Fluent $command) + { + return 'alter table '.$this->wrapTable($blueprint).' drop primary key'; + } + + /** + * Compile a drop unique key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropUnique(Blueprint $blueprint, Fluent $command) + { + $index = $this->wrap($command->index); + + return "alter table {$this->wrapTable($blueprint)} drop index {$index}"; + } + + /** + * Compile a drop index command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropIndex(Blueprint $blueprint, Fluent $command) + { + $index = $this->wrap($command->index); + + return "alter table {$this->wrapTable($blueprint)} drop index {$index}"; + } + + /** + * Compile a drop fulltext index command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropFullText(Blueprint $blueprint, Fluent $command) + { + return $this->compileDropIndex($blueprint, $command); + } + + /** + * Compile a drop spatial index command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropSpatialIndex(Blueprint $blueprint, Fluent $command) + { + return $this->compileDropIndex($blueprint, $command); + } + + /** + * Compile a drop foreign key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropForeign(Blueprint $blueprint, Fluent $command) + { + $index = $this->wrap($command->index); + + return "alter table {$this->wrapTable($blueprint)} drop foreign key {$index}"; + } + + /** + * Compile a rename table command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileRename(Blueprint $blueprint, Fluent $command) + { + $from = $this->wrapTable($blueprint); + + return "rename table {$from} to ".$this->wrapTable($command->to); + } + + /** + * Compile a rename index command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileRenameIndex(Blueprint $blueprint, Fluent $command) + { + return sprintf('alter table %s rename index %s to %s', + $this->wrapTable($blueprint), + $this->wrap($command->from), + $this->wrap($command->to) + ); + } + + /** + * Compile the SQL needed to drop all tables. + * + * @param array $tables + * @return string + */ + public function compileDropAllTables($tables) + { + return 'drop table '.implode(',', $this->wrapArray($tables)); + } + + /** + * Compile the SQL needed to drop all views. + * + * @param array $views + * @return string + */ + public function compileDropAllViews($views) + { + return 'drop view '.implode(',', $this->wrapArray($views)); + } + + /** + * Compile the SQL needed to retrieve all table names. + * + * @return string + */ + public function compileGetAllTables() + { + return 'SHOW FULL TABLES WHERE table_type = \'BASE TABLE\''; + } + + /** + * Compile the SQL needed to retrieve all view names. + * + * @return string + */ + public function compileGetAllViews() + { + return 'SHOW FULL TABLES WHERE table_type = \'VIEW\''; + } + + /** + * Compile the command to enable foreign key constraints. + * + * @return string + */ + public function compileEnableForeignKeyConstraints() + { + return 'SET FOREIGN_KEY_CHECKS=1;'; + } + + /** + * Compile the command to disable foreign key constraints. + * + * @return string + */ + public function compileDisableForeignKeyConstraints() + { + return 'SET FOREIGN_KEY_CHECKS=0;'; + } + + /** + * Create the column definition for a char type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeChar(Fluent $column) + { + return "char({$column->length})"; + } + + /** + * Create the column definition for a string type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeString(Fluent $column) + { + return "varchar({$column->length})"; + } + + /** + * Create the column definition for a tiny text type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTinyText(Fluent $column) + { + return 'tinytext'; + } + + /** + * Create the column definition for a text type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeText(Fluent $column) + { + return 'text'; + } + + /** + * Create the column definition for a medium text type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeMediumText(Fluent $column) + { + return 'mediumtext'; + } + + /** + * Create the column definition for a long text type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeLongText(Fluent $column) + { + return 'longtext'; + } + + /** + * Create the column definition for a big integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeBigInteger(Fluent $column) + { + return 'bigint'; + } + + /** + * Create the column definition for an integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeInteger(Fluent $column) + { + return 'int'; + } + + /** + * Create the column definition for a medium integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeMediumInteger(Fluent $column) + { + return 'mediumint'; + } + + /** + * Create the column definition for a tiny integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTinyInteger(Fluent $column) + { + return 'tinyint'; + } + + /** + * Create the column definition for a small integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeSmallInteger(Fluent $column) + { + return 'smallint'; + } + + /** + * Create the column definition for a float type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeFloat(Fluent $column) + { + return $this->typeDouble($column); + } + + /** + * Create the column definition for a double type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeDouble(Fluent $column) + { + if ($column->total && $column->places) { + return "double({$column->total}, {$column->places})"; + } + + return 'double'; + } + + /** + * Create the column definition for a decimal type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeDecimal(Fluent $column) + { + return "decimal({$column->total}, {$column->places})"; + } + + /** + * Create the column definition for a boolean type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeBoolean(Fluent $column) + { + return 'tinyint(1)'; + } + + /** + * Create the column definition for an enumeration type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeEnum(Fluent $column) + { + return sprintf('enum(%s)', $this->quoteString($column->allowed)); + } + + /** + * Create the column definition for a set enumeration type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeSet(Fluent $column) + { + return sprintf('set(%s)', $this->quoteString($column->allowed)); + } + + /** + * Create the column definition for a json type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeJson(Fluent $column) + { + return 'json'; + } + + /** + * Create the column definition for a jsonb type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeJsonb(Fluent $column) + { + return 'json'; + } + + /** + * Create the column definition for a date type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeDate(Fluent $column) + { + return 'date'; + } + + /** + * Create the column definition for a date-time type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeDateTime(Fluent $column) + { + $columnType = $column->precision ? "datetime($column->precision)" : 'datetime'; + + $current = $column->precision ? "CURRENT_TIMESTAMP($column->precision)" : 'CURRENT_TIMESTAMP'; + + $columnType = $column->useCurrent ? "$columnType default $current" : $columnType; + + return $column->useCurrentOnUpdate ? "$columnType on update $current" : $columnType; + } + + /** + * Create the column definition for a date-time (with time zone) type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeDateTimeTz(Fluent $column) + { + return $this->typeDateTime($column); + } + + /** + * Create the column definition for a time type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTime(Fluent $column) + { + return $column->precision ? "time($column->precision)" : 'time'; + } + + /** + * Create the column definition for a time (with time zone) type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTimeTz(Fluent $column) + { + return $this->typeTime($column); + } + + /** + * Create the column definition for a timestamp type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTimestamp(Fluent $column) + { + $columnType = $column->precision ? "timestamp($column->precision)" : 'timestamp'; + + $current = $column->precision ? "CURRENT_TIMESTAMP($column->precision)" : 'CURRENT_TIMESTAMP'; + + $columnType = $column->useCurrent ? "$columnType default $current" : $columnType; + + return $column->useCurrentOnUpdate ? "$columnType on update $current" : $columnType; + } + + /** + * Create the column definition for a timestamp (with time zone) type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTimestampTz(Fluent $column) + { + return $this->typeTimestamp($column); + } + + /** + * Create the column definition for a year type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeYear(Fluent $column) + { + return 'year'; + } + + /** + * Create the column definition for a binary type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeBinary(Fluent $column) + { + return 'blob'; + } + + /** + * Create the column definition for a uuid type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeUuid(Fluent $column) + { + return 'char(36)'; + } + + /** + * Create the column definition for an IP address type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeIpAddress(Fluent $column) + { + return 'varchar(45)'; + } + + /** + * Create the column definition for a MAC address type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeMacAddress(Fluent $column) + { + return 'varchar(17)'; + } + + /** + * Create the column definition for a spatial Geometry type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + public function typeGeometry(Fluent $column) + { + return 'geometry'; + } + + /** + * Create the column definition for a spatial Point type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + public function typePoint(Fluent $column) + { + return 'point'; + } + + /** + * Create the column definition for a spatial LineString type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + public function typeLineString(Fluent $column) + { + return 'linestring'; + } + + /** + * Create the column definition for a spatial Polygon type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + public function typePolygon(Fluent $column) + { + return 'polygon'; + } + + /** + * Create the column definition for a spatial GeometryCollection type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + public function typeGeometryCollection(Fluent $column) + { + return 'geometrycollection'; + } + + /** + * Create the column definition for a spatial MultiPoint type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + public function typeMultiPoint(Fluent $column) + { + return 'multipoint'; + } + + /** + * Create the column definition for a spatial MultiLineString type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + public function typeMultiLineString(Fluent $column) + { + return 'multilinestring'; + } + + /** + * Create the column definition for a spatial MultiPolygon type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + public function typeMultiPolygon(Fluent $column) + { + return 'multipolygon'; + } + + /** + * Create the column definition for a generated, computed column type. + * + * @param \Illuminate\Support\Fluent $column + * @return void + * + * @throws \RuntimeException + */ + protected function typeComputed(Fluent $column) + { + throw new RuntimeException('This database driver requires a type, see the virtualAs / storedAs modifiers.'); + } + + /** + * Get the SQL for a generated virtual column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyVirtualAs(Blueprint $blueprint, Fluent $column) + { + if (! is_null($column->virtualAs)) { + return " as ({$column->virtualAs})"; + } + } + + /** + * Get the SQL for a generated stored column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyStoredAs(Blueprint $blueprint, Fluent $column) + { + if (! is_null($column->storedAs)) { + return " as ({$column->storedAs}) stored"; + } + } + + /** + * Get the SQL for an unsigned column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyUnsigned(Blueprint $blueprint, Fluent $column) + { + if ($column->unsigned) { + return ' unsigned'; + } + } + + /** + * Get the SQL for a character set column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyCharset(Blueprint $blueprint, Fluent $column) + { + if (! is_null($column->charset)) { + return ' character set '.$column->charset; + } + } + + /** + * Get the SQL for a collation column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyCollate(Blueprint $blueprint, Fluent $column) + { + if (! is_null($column->collation)) { + return " collate '{$column->collation}'"; + } + } + + /** + * Get the SQL for a nullable column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyNullable(Blueprint $blueprint, Fluent $column) + { + if (is_null($column->virtualAs) && is_null($column->storedAs)) { + return $column->nullable ? ' null' : ' not null'; + } + + if ($column->nullable === false) { + return ' not null'; + } + } + + /** + * Get the SQL for an invisible column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyInvisible(Blueprint $blueprint, Fluent $column) + { + if (! is_null($column->invisible)) { + return ' invisible'; + } + } + + /** + * Get the SQL for a default column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyDefault(Blueprint $blueprint, Fluent $column) + { + if (! is_null($column->default)) { + return ' default '.$this->getDefaultValue($column->default); + } + } + + /** + * Get the SQL for an auto-increment column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyIncrement(Blueprint $blueprint, Fluent $column) + { + if (in_array($column->type, $this->serials) && $column->autoIncrement) { + return ' auto_increment primary key'; + } + } + + /** + * Get the SQL for a "first" column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyFirst(Blueprint $blueprint, Fluent $column) + { + if (! is_null($column->first)) { + return ' first'; + } + } + + /** + * Get the SQL for an "after" column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyAfter(Blueprint $blueprint, Fluent $column) + { + if (! is_null($column->after)) { + return ' after '.$this->wrap($column->after); + } + } + + /** + * Get the SQL for a "comment" column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyComment(Blueprint $blueprint, Fluent $column) + { + if (! is_null($column->comment)) { + return " comment '".addslashes($column->comment)."'"; + } + } + + /** + * Get the SQL for a SRID column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifySrid(Blueprint $blueprint, Fluent $column) + { + if (! is_null($column->srid) && is_int($column->srid) && $column->srid > 0) { + return ' srid '.$column->srid; + } + } + + /** + * Wrap a single string in keyword identifiers. + * + * @param string $value + * @return string + */ + protected function wrapValue($value) + { + if ($value !== '*') { + return '`'.str_replace('`', '``', $value).'`'; + } + + return $value; + } +} diff --git a/vendor/illuminate/database/Schema/Grammars/PostgresGrammar.php b/vendor/illuminate/database/Schema/Grammars/PostgresGrammar.php new file mode 100644 index 0000000..27490bc --- /dev/null +++ b/vendor/illuminate/database/Schema/Grammars/PostgresGrammar.php @@ -0,0 +1,1073 @@ +wrapValue($name), + $this->wrapValue($connection->getConfig('charset')), + ); + } + + /** + * Compile a drop database if exists command. + * + * @param string $name + * @return string + */ + public function compileDropDatabaseIfExists($name) + { + return sprintf( + 'drop database if exists %s', + $this->wrapValue($name) + ); + } + + /** + * Compile the query to determine if a table exists. + * + * @return string + */ + public function compileTableExists() + { + return "select * from information_schema.tables where table_schema = ? and table_name = ? and table_type = 'BASE TABLE'"; + } + + /** + * Compile the query to determine the list of columns. + * + * @return string + */ + public function compileColumnListing() + { + return 'select column_name from information_schema.columns where table_schema = ? and table_name = ?'; + } + + /** + * Compile a create table command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return array + */ + public function compileCreate(Blueprint $blueprint, Fluent $command) + { + return array_values(array_filter(array_merge([sprintf('%s table %s (%s)', + $blueprint->temporary ? 'create temporary' : 'create', + $this->wrapTable($blueprint), + implode(', ', $this->getColumns($blueprint)) + )], $this->compileAutoIncrementStartingValues($blueprint)))); + } + + /** + * Compile a column addition command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileAdd(Blueprint $blueprint, Fluent $command) + { + return array_values(array_filter(array_merge([sprintf('alter table %s %s', + $this->wrapTable($blueprint), + implode(', ', $this->prefixArray('add column', $this->getColumns($blueprint))) + )], $this->compileAutoIncrementStartingValues($blueprint)))); + } + + /** + * Compile the auto-incrementing column starting values. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @return array + */ + public function compileAutoIncrementStartingValues(Blueprint $blueprint) + { + return collect($blueprint->autoIncrementingStartingValues())->map(function ($value, $column) use ($blueprint) { + return 'alter sequence '.$blueprint->getTable().'_'.$column.'_seq restart with '.$value; + })->all(); + } + + /** + * Compile a primary key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compilePrimary(Blueprint $blueprint, Fluent $command) + { + $columns = $this->columnize($command->columns); + + return 'alter table '.$this->wrapTable($blueprint)." add primary key ({$columns})"; + } + + /** + * Compile a unique key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileUnique(Blueprint $blueprint, Fluent $command) + { + return sprintf('alter table %s add constraint %s unique (%s)', + $this->wrapTable($blueprint), + $this->wrap($command->index), + $this->columnize($command->columns) + ); + } + + /** + * Compile a plain index key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileIndex(Blueprint $blueprint, Fluent $command) + { + return sprintf('create index %s on %s%s (%s)', + $this->wrap($command->index), + $this->wrapTable($blueprint), + $command->algorithm ? ' using '.$command->algorithm : '', + $this->columnize($command->columns) + ); + } + + /** + * Compile a fulltext index key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + * + * @throws \RuntimeException + */ + public function compileFulltext(Blueprint $blueprint, Fluent $command) + { + $language = $command->language ?: 'english'; + + $columns = array_map(function ($column) use ($language) { + return "to_tsvector({$this->quoteString($language)}, {$this->wrap($column)})"; + }, $command->columns); + + return sprintf('create index %s on %s using gin ((%s))', + $this->wrap($command->index), + $this->wrapTable($blueprint), + implode(' || ', $columns) + ); + } + + /** + * Compile a spatial index key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileSpatialIndex(Blueprint $blueprint, Fluent $command) + { + $command->algorithm = 'gist'; + + return $this->compileIndex($blueprint, $command); + } + + /** + * Compile a foreign key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileForeign(Blueprint $blueprint, Fluent $command) + { + $sql = parent::compileForeign($blueprint, $command); + + if (! is_null($command->deferrable)) { + $sql .= $command->deferrable ? ' deferrable' : ' not deferrable'; + } + + if ($command->deferrable && ! is_null($command->initiallyImmediate)) { + $sql .= $command->initiallyImmediate ? ' initially immediate' : ' initially deferred'; + } + + if (! is_null($command->notValid)) { + $sql .= ' not valid'; + } + + return $sql; + } + + /** + * Compile a drop table command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDrop(Blueprint $blueprint, Fluent $command) + { + return 'drop table '.$this->wrapTable($blueprint); + } + + /** + * Compile a drop table (if exists) command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropIfExists(Blueprint $blueprint, Fluent $command) + { + return 'drop table if exists '.$this->wrapTable($blueprint); + } + + /** + * Compile the SQL needed to drop all tables. + * + * @param array $tables + * @return string + */ + public function compileDropAllTables($tables) + { + return 'drop table "'.implode('","', $tables).'" cascade'; + } + + /** + * Compile the SQL needed to drop all views. + * + * @param array $views + * @return string + */ + public function compileDropAllViews($views) + { + return 'drop view "'.implode('","', $views).'" cascade'; + } + + /** + * Compile the SQL needed to drop all types. + * + * @param array $types + * @return string + */ + public function compileDropAllTypes($types) + { + return 'drop type "'.implode('","', $types).'" cascade'; + } + + /** + * Compile the SQL needed to retrieve all table names. + * + * @param string|array $schema + * @return string + */ + public function compileGetAllTables($schema) + { + return "select tablename from pg_catalog.pg_tables where schemaname in ('".implode("','", (array) $schema)."')"; + } + + /** + * Compile the SQL needed to retrieve all view names. + * + * @param string|array $schema + * @return string + */ + public function compileGetAllViews($schema) + { + return "select viewname from pg_catalog.pg_views where schemaname in ('".implode("','", (array) $schema)."')"; + } + + /** + * Compile the SQL needed to retrieve all type names. + * + * @return string + */ + public function compileGetAllTypes() + { + return 'select distinct pg_type.typname from pg_type inner join pg_enum on pg_enum.enumtypid = pg_type.oid'; + } + + /** + * Compile a drop column command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropColumn(Blueprint $blueprint, Fluent $command) + { + $columns = $this->prefixArray('drop column', $this->wrapArray($command->columns)); + + return 'alter table '.$this->wrapTable($blueprint).' '.implode(', ', $columns); + } + + /** + * Compile a drop primary key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropPrimary(Blueprint $blueprint, Fluent $command) + { + $index = $this->wrap("{$blueprint->getTable()}_pkey"); + + return 'alter table '.$this->wrapTable($blueprint)." drop constraint {$index}"; + } + + /** + * Compile a drop unique key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropUnique(Blueprint $blueprint, Fluent $command) + { + $index = $this->wrap($command->index); + + return "alter table {$this->wrapTable($blueprint)} drop constraint {$index}"; + } + + /** + * Compile a drop index command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropIndex(Blueprint $blueprint, Fluent $command) + { + return "drop index {$this->wrap($command->index)}"; + } + + /** + * Compile a drop fulltext index command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropFullText(Blueprint $blueprint, Fluent $command) + { + return $this->compileDropIndex($blueprint, $command); + } + + /** + * Compile a drop spatial index command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropSpatialIndex(Blueprint $blueprint, Fluent $command) + { + return $this->compileDropIndex($blueprint, $command); + } + + /** + * Compile a drop foreign key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropForeign(Blueprint $blueprint, Fluent $command) + { + $index = $this->wrap($command->index); + + return "alter table {$this->wrapTable($blueprint)} drop constraint {$index}"; + } + + /** + * Compile a rename table command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileRename(Blueprint $blueprint, Fluent $command) + { + $from = $this->wrapTable($blueprint); + + return "alter table {$from} rename to ".$this->wrapTable($command->to); + } + + /** + * Compile a rename index command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileRenameIndex(Blueprint $blueprint, Fluent $command) + { + return sprintf('alter index %s rename to %s', + $this->wrap($command->from), + $this->wrap($command->to) + ); + } + + /** + * Compile the command to enable foreign key constraints. + * + * @return string + */ + public function compileEnableForeignKeyConstraints() + { + return 'SET CONSTRAINTS ALL IMMEDIATE;'; + } + + /** + * Compile the command to disable foreign key constraints. + * + * @return string + */ + public function compileDisableForeignKeyConstraints() + { + return 'SET CONSTRAINTS ALL DEFERRED;'; + } + + /** + * Compile a comment command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileComment(Blueprint $blueprint, Fluent $command) + { + return sprintf('comment on column %s.%s is %s', + $this->wrapTable($blueprint), + $this->wrap($command->column->name), + "'".str_replace("'", "''", $command->value)."'" + ); + } + + /** + * Create the column definition for a char type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeChar(Fluent $column) + { + return "char({$column->length})"; + } + + /** + * Create the column definition for a string type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeString(Fluent $column) + { + return "varchar({$column->length})"; + } + + /** + * Create the column definition for a tiny text type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTinyText(Fluent $column) + { + return 'varchar(255)'; + } + + /** + * Create the column definition for a text type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeText(Fluent $column) + { + return 'text'; + } + + /** + * Create the column definition for a medium text type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeMediumText(Fluent $column) + { + return 'text'; + } + + /** + * Create the column definition for a long text type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeLongText(Fluent $column) + { + return 'text'; + } + + /** + * Create the column definition for an integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeInteger(Fluent $column) + { + return $this->generatableColumn('integer', $column); + } + + /** + * Create the column definition for a big integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeBigInteger(Fluent $column) + { + return $this->generatableColumn('bigint', $column); + } + + /** + * Create the column definition for a medium integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeMediumInteger(Fluent $column) + { + return $this->generatableColumn('integer', $column); + } + + /** + * Create the column definition for a tiny integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTinyInteger(Fluent $column) + { + return $this->generatableColumn('smallint', $column); + } + + /** + * Create the column definition for a small integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeSmallInteger(Fluent $column) + { + return $this->generatableColumn('smallint', $column); + } + + /** + * Create the column definition for a generatable column. + * + * @param string $type + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function generatableColumn($type, Fluent $column) + { + if (! $column->autoIncrement && is_null($column->generatedAs)) { + return $type; + } + + if ($column->autoIncrement && is_null($column->generatedAs)) { + return with([ + 'integer' => 'serial', + 'bigint' => 'bigserial', + 'smallint' => 'smallserial', + ])[$type]; + } + + $options = ''; + + if (! is_bool($column->generatedAs) && ! empty($column->generatedAs)) { + $options = sprintf(' (%s)', $column->generatedAs); + } + + return sprintf( + '%s generated %s as identity%s', + $type, + $column->always ? 'always' : 'by default', + $options + ); + } + + /** + * Create the column definition for a float type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeFloat(Fluent $column) + { + return $this->typeDouble($column); + } + + /** + * Create the column definition for a double type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeDouble(Fluent $column) + { + return 'double precision'; + } + + /** + * Create the column definition for a real type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeReal(Fluent $column) + { + return 'real'; + } + + /** + * Create the column definition for a decimal type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeDecimal(Fluent $column) + { + return "decimal({$column->total}, {$column->places})"; + } + + /** + * Create the column definition for a boolean type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeBoolean(Fluent $column) + { + return 'boolean'; + } + + /** + * Create the column definition for an enumeration type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeEnum(Fluent $column) + { + return sprintf( + 'varchar(255) check ("%s" in (%s))', + $column->name, + $this->quoteString($column->allowed) + ); + } + + /** + * Create the column definition for a json type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeJson(Fluent $column) + { + return 'json'; + } + + /** + * Create the column definition for a jsonb type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeJsonb(Fluent $column) + { + return 'jsonb'; + } + + /** + * Create the column definition for a date type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeDate(Fluent $column) + { + return 'date'; + } + + /** + * Create the column definition for a date-time type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeDateTime(Fluent $column) + { + return $this->typeTimestamp($column); + } + + /** + * Create the column definition for a date-time (with time zone) type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeDateTimeTz(Fluent $column) + { + return $this->typeTimestampTz($column); + } + + /** + * Create the column definition for a time type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTime(Fluent $column) + { + return 'time'.(is_null($column->precision) ? '' : "($column->precision)").' without time zone'; + } + + /** + * Create the column definition for a time (with time zone) type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTimeTz(Fluent $column) + { + return 'time'.(is_null($column->precision) ? '' : "($column->precision)").' with time zone'; + } + + /** + * Create the column definition for a timestamp type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTimestamp(Fluent $column) + { + $columnType = 'timestamp'.(is_null($column->precision) ? '' : "($column->precision)").' without time zone'; + + return $column->useCurrent ? "$columnType default CURRENT_TIMESTAMP" : $columnType; + } + + /** + * Create the column definition for a timestamp (with time zone) type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTimestampTz(Fluent $column) + { + $columnType = 'timestamp'.(is_null($column->precision) ? '' : "($column->precision)").' with time zone'; + + return $column->useCurrent ? "$columnType default CURRENT_TIMESTAMP" : $columnType; + } + + /** + * Create the column definition for a year type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeYear(Fluent $column) + { + return $this->typeInteger($column); + } + + /** + * Create the column definition for a binary type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeBinary(Fluent $column) + { + return 'bytea'; + } + + /** + * Create the column definition for a uuid type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeUuid(Fluent $column) + { + return 'uuid'; + } + + /** + * Create the column definition for an IP address type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeIpAddress(Fluent $column) + { + return 'inet'; + } + + /** + * Create the column definition for a MAC address type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeMacAddress(Fluent $column) + { + return 'macaddr'; + } + + /** + * Create the column definition for a spatial Geometry type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeGeometry(Fluent $column) + { + return $this->formatPostGisType('geometry', $column); + } + + /** + * Create the column definition for a spatial Point type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typePoint(Fluent $column) + { + return $this->formatPostGisType('point', $column); + } + + /** + * Create the column definition for a spatial LineString type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeLineString(Fluent $column) + { + return $this->formatPostGisType('linestring', $column); + } + + /** + * Create the column definition for a spatial Polygon type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typePolygon(Fluent $column) + { + return $this->formatPostGisType('polygon', $column); + } + + /** + * Create the column definition for a spatial GeometryCollection type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeGeometryCollection(Fluent $column) + { + return $this->formatPostGisType('geometrycollection', $column); + } + + /** + * Create the column definition for a spatial MultiPoint type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeMultiPoint(Fluent $column) + { + return $this->formatPostGisType('multipoint', $column); + } + + /** + * Create the column definition for a spatial MultiLineString type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + public function typeMultiLineString(Fluent $column) + { + return $this->formatPostGisType('multilinestring', $column); + } + + /** + * Create the column definition for a spatial MultiPolygon type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeMultiPolygon(Fluent $column) + { + return $this->formatPostGisType('multipolygon', $column); + } + + /** + * Create the column definition for a spatial MultiPolygonZ type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeMultiPolygonZ(Fluent $column) + { + return $this->formatPostGisType('multipolygonz', $column); + } + + /** + * Format the column definition for a PostGIS spatial type. + * + * @param string $type + * @param \Illuminate\Support\Fluent $column + * @return string + */ + private function formatPostGisType($type, Fluent $column) + { + if ($column->isGeometry === null) { + return sprintf('geography(%s, %s)', $type, $column->projection ?? '4326'); + } + + if ($column->projection !== null) { + return sprintf('geometry(%s, %s)', $type, $column->projection); + } + + return "geometry({$type})"; + } + + /** + * Get the SQL for a collation column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyCollate(Blueprint $blueprint, Fluent $column) + { + if (! is_null($column->collation)) { + return ' collate '.$this->wrapValue($column->collation); + } + } + + /** + * Get the SQL for a nullable column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyNullable(Blueprint $blueprint, Fluent $column) + { + return $column->nullable ? ' null' : ' not null'; + } + + /** + * Get the SQL for a default column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyDefault(Blueprint $blueprint, Fluent $column) + { + if (! is_null($column->default)) { + return ' default '.$this->getDefaultValue($column->default); + } + } + + /** + * Get the SQL for an auto-increment column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyIncrement(Blueprint $blueprint, Fluent $column) + { + if ((in_array($column->type, $this->serials) || ($column->generatedAs !== null)) && $column->autoIncrement) { + return ' primary key'; + } + } + + /** + * Get the SQL for a generated virtual column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyVirtualAs(Blueprint $blueprint, Fluent $column) + { + if ($column->virtualAs !== null) { + return " generated always as ({$column->virtualAs})"; + } + } + + /** + * Get the SQL for a generated stored column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyStoredAs(Blueprint $blueprint, Fluent $column) + { + if ($column->storedAs !== null) { + return " generated always as ({$column->storedAs}) stored"; + } + } +} diff --git a/vendor/illuminate/database/Schema/Grammars/RenameColumn.php b/vendor/illuminate/database/Schema/Grammars/RenameColumn.php new file mode 100644 index 0000000..0db0c50 --- /dev/null +++ b/vendor/illuminate/database/Schema/Grammars/RenameColumn.php @@ -0,0 +1,84 @@ +getDoctrineSchemaManager(); + $databasePlatform = $schema->getDatabasePlatform(); + $databasePlatform->registerDoctrineTypeMapping('enum', 'string'); + + $column = $connection->getDoctrineColumn( + $grammar->getTablePrefix().$blueprint->getTable(), $command->from + ); + + return (array) $databasePlatform->getAlterTableSQL(static::getRenamedDiff( + $grammar, $blueprint, $command, $column, $schema + )); + } + + /** + * Get a new column instance with the new column name. + * + * @param \Illuminate\Database\Schema\Grammars\Grammar $grammar + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @param \Doctrine\DBAL\Schema\Column $column + * @param \Doctrine\DBAL\Schema\AbstractSchemaManager $schema + * @return \Doctrine\DBAL\Schema\TableDiff + */ + protected static function getRenamedDiff(Grammar $grammar, Blueprint $blueprint, Fluent $command, Column $column, SchemaManager $schema) + { + return static::setRenamedColumns( + $grammar->getDoctrineTableDiff($blueprint, $schema), $command, $column + ); + } + + /** + * Set the renamed columns on the table diff. + * + * @param \Doctrine\DBAL\Schema\TableDiff $tableDiff + * @param \Illuminate\Support\Fluent $command + * @param \Doctrine\DBAL\Schema\Column $column + * @return \Doctrine\DBAL\Schema\TableDiff + */ + protected static function setRenamedColumns(TableDiff $tableDiff, Fluent $command, Column $column) + { + $tableDiff->renamedColumns = [ + $command->from => new Column($command->to, $column->getType(), self::getWritableColumnOptions($column)), + ]; + + return $tableDiff; + } + + /** + * Get the writable column options. + * + * @param \Doctrine\DBAL\Schema\Column $column + * @return array + */ + private static function getWritableColumnOptions(Column $column) + { + return array_filter($column->toArray(), function (string $name) use ($column) { + return method_exists($column, 'set'.$name); + }, ARRAY_FILTER_USE_KEY); + } +} diff --git a/vendor/illuminate/database/Schema/Grammars/SQLiteGrammar.php b/vendor/illuminate/database/Schema/Grammars/SQLiteGrammar.php new file mode 100644 index 0000000..e699ee6 --- /dev/null +++ b/vendor/illuminate/database/Schema/Grammars/SQLiteGrammar.php @@ -0,0 +1,925 @@ +wrap(str_replace('.', '__', $table)).')'; + } + + /** + * Compile a create table command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileCreate(Blueprint $blueprint, Fluent $command) + { + return sprintf('%s table %s (%s%s%s)', + $blueprint->temporary ? 'create temporary' : 'create', + $this->wrapTable($blueprint), + implode(', ', $this->getColumns($blueprint)), + (string) $this->addForeignKeys($blueprint), + (string) $this->addPrimaryKeys($blueprint) + ); + } + + /** + * Get the foreign key syntax for a table creation statement. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @return string|null + */ + protected function addForeignKeys(Blueprint $blueprint) + { + $foreigns = $this->getCommandsByName($blueprint, 'foreign'); + + return collect($foreigns)->reduce(function ($sql, $foreign) { + // Once we have all the foreign key commands for the table creation statement + // we'll loop through each of them and add them to the create table SQL we + // are building, since SQLite needs foreign keys on the tables creation. + $sql .= $this->getForeignKey($foreign); + + if (! is_null($foreign->onDelete)) { + $sql .= " on delete {$foreign->onDelete}"; + } + + // If this foreign key specifies the action to be taken on update we will add + // that to the statement here. We'll append it to this SQL and then return + // the SQL so we can keep adding any other foreign constraints onto this. + if (! is_null($foreign->onUpdate)) { + $sql .= " on update {$foreign->onUpdate}"; + } + + return $sql; + }, ''); + } + + /** + * Get the SQL for the foreign key. + * + * @param \Illuminate\Support\Fluent $foreign + * @return string + */ + protected function getForeignKey($foreign) + { + // We need to columnize the columns that the foreign key is being defined for + // so that it is a properly formatted list. Once we have done this, we can + // return the foreign key SQL declaration to the calling method for use. + return sprintf(', foreign key(%s) references %s(%s)', + $this->columnize($foreign->columns), + $this->wrapTable($foreign->on), + $this->columnize((array) $foreign->references) + ); + } + + /** + * Get the primary key syntax for a table creation statement. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @return string|null + */ + protected function addPrimaryKeys(Blueprint $blueprint) + { + if (! is_null($primary = $this->getCommandByName($blueprint, 'primary'))) { + return ", primary key ({$this->columnize($primary->columns)})"; + } + } + + /** + * Compile alter table commands for adding columns. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return array + */ + public function compileAdd(Blueprint $blueprint, Fluent $command) + { + $columns = $this->prefixArray('add column', $this->getColumns($blueprint)); + + return collect($columns)->reject(function ($column) { + return preg_match('/as \(.*\) stored/', $column) > 0; + })->map(function ($column) use ($blueprint) { + return 'alter table '.$this->wrapTable($blueprint).' '.$column; + })->all(); + } + + /** + * Compile a unique key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileUnique(Blueprint $blueprint, Fluent $command) + { + return sprintf('create unique index %s on %s (%s)', + $this->wrap($command->index), + $this->wrapTable($blueprint), + $this->columnize($command->columns) + ); + } + + /** + * Compile a plain index key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileIndex(Blueprint $blueprint, Fluent $command) + { + return sprintf('create index %s on %s (%s)', + $this->wrap($command->index), + $this->wrapTable($blueprint), + $this->columnize($command->columns) + ); + } + + /** + * Compile a spatial index key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return void + * + * @throws \RuntimeException + */ + public function compileSpatialIndex(Blueprint $blueprint, Fluent $command) + { + throw new RuntimeException('The database driver in use does not support spatial indexes.'); + } + + /** + * Compile a foreign key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileForeign(Blueprint $blueprint, Fluent $command) + { + // Handled on table creation... + } + + /** + * Compile a drop table command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDrop(Blueprint $blueprint, Fluent $command) + { + return 'drop table '.$this->wrapTable($blueprint); + } + + /** + * Compile a drop table (if exists) command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropIfExists(Blueprint $blueprint, Fluent $command) + { + return 'drop table if exists '.$this->wrapTable($blueprint); + } + + /** + * Compile the SQL needed to drop all tables. + * + * @return string + */ + public function compileDropAllTables() + { + return "delete from sqlite_master where type in ('table', 'index', 'trigger')"; + } + + /** + * Compile the SQL needed to drop all views. + * + * @return string + */ + public function compileDropAllViews() + { + return "delete from sqlite_master where type in ('view')"; + } + + /** + * Compile the SQL needed to rebuild the database. + * + * @return string + */ + public function compileRebuild() + { + return 'vacuum'; + } + + /** + * Compile a drop column command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @param \Illuminate\Database\Connection $connection + * @return array + */ + public function compileDropColumn(Blueprint $blueprint, Fluent $command, Connection $connection) + { + $tableDiff = $this->getDoctrineTableDiff( + $blueprint, $schema = $connection->getDoctrineSchemaManager() + ); + + foreach ($command->columns as $name) { + $tableDiff->removedColumns[$name] = $connection->getDoctrineColumn( + $this->getTablePrefix().$blueprint->getTable(), $name + ); + } + + return (array) $schema->getDatabasePlatform()->getAlterTableSQL($tableDiff); + } + + /** + * Compile a drop unique key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropUnique(Blueprint $blueprint, Fluent $command) + { + $index = $this->wrap($command->index); + + return "drop index {$index}"; + } + + /** + * Compile a drop index command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropIndex(Blueprint $blueprint, Fluent $command) + { + $index = $this->wrap($command->index); + + return "drop index {$index}"; + } + + /** + * Compile a drop spatial index command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return void + * + * @throws \RuntimeException + */ + public function compileDropSpatialIndex(Blueprint $blueprint, Fluent $command) + { + throw new RuntimeException('The database driver in use does not support spatial indexes.'); + } + + /** + * Compile a rename table command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileRename(Blueprint $blueprint, Fluent $command) + { + $from = $this->wrapTable($blueprint); + + return "alter table {$from} rename to ".$this->wrapTable($command->to); + } + + /** + * Compile a rename index command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @param \Illuminate\Database\Connection $connection + * @return array + * + * @throws \RuntimeException + */ + public function compileRenameIndex(Blueprint $blueprint, Fluent $command, Connection $connection) + { + $schemaManager = $connection->getDoctrineSchemaManager(); + + $indexes = $schemaManager->listTableIndexes($this->getTablePrefix().$blueprint->getTable()); + + $index = Arr::get($indexes, $command->from); + + if (! $index) { + throw new RuntimeException("Index [{$command->from}] does not exist."); + } + + $newIndex = new Index( + $command->to, $index->getColumns(), $index->isUnique(), + $index->isPrimary(), $index->getFlags(), $index->getOptions() + ); + + $platform = $schemaManager->getDatabasePlatform(); + + return [ + $platform->getDropIndexSQL($command->from, $this->getTablePrefix().$blueprint->getTable()), + $platform->getCreateIndexSQL($newIndex, $this->getTablePrefix().$blueprint->getTable()), + ]; + } + + /** + * Compile the command to enable foreign key constraints. + * + * @return string + */ + public function compileEnableForeignKeyConstraints() + { + return 'PRAGMA foreign_keys = ON;'; + } + + /** + * Compile the command to disable foreign key constraints. + * + * @return string + */ + public function compileDisableForeignKeyConstraints() + { + return 'PRAGMA foreign_keys = OFF;'; + } + + /** + * Compile the SQL needed to enable a writable schema. + * + * @return string + */ + public function compileEnableWriteableSchema() + { + return 'PRAGMA writable_schema = 1;'; + } + + /** + * Compile the SQL needed to disable a writable schema. + * + * @return string + */ + public function compileDisableWriteableSchema() + { + return 'PRAGMA writable_schema = 0;'; + } + + /** + * Create the column definition for a char type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeChar(Fluent $column) + { + return 'varchar'; + } + + /** + * Create the column definition for a string type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeString(Fluent $column) + { + return 'varchar'; + } + + /** + * Create the column definition for a tiny text type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTinyText(Fluent $column) + { + return 'text'; + } + + /** + * Create the column definition for a text type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeText(Fluent $column) + { + return 'text'; + } + + /** + * Create the column definition for a medium text type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeMediumText(Fluent $column) + { + return 'text'; + } + + /** + * Create the column definition for a long text type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeLongText(Fluent $column) + { + return 'text'; + } + + /** + * Create the column definition for an integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeInteger(Fluent $column) + { + return 'integer'; + } + + /** + * Create the column definition for a big integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeBigInteger(Fluent $column) + { + return 'integer'; + } + + /** + * Create the column definition for a medium integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeMediumInteger(Fluent $column) + { + return 'integer'; + } + + /** + * Create the column definition for a tiny integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTinyInteger(Fluent $column) + { + return 'integer'; + } + + /** + * Create the column definition for a small integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeSmallInteger(Fluent $column) + { + return 'integer'; + } + + /** + * Create the column definition for a float type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeFloat(Fluent $column) + { + return 'float'; + } + + /** + * Create the column definition for a double type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeDouble(Fluent $column) + { + return 'float'; + } + + /** + * Create the column definition for a decimal type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeDecimal(Fluent $column) + { + return 'numeric'; + } + + /** + * Create the column definition for a boolean type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeBoolean(Fluent $column) + { + return 'tinyint(1)'; + } + + /** + * Create the column definition for an enumeration type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeEnum(Fluent $column) + { + return sprintf( + 'varchar check ("%s" in (%s))', + $column->name, + $this->quoteString($column->allowed) + ); + } + + /** + * Create the column definition for a json type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeJson(Fluent $column) + { + return 'text'; + } + + /** + * Create the column definition for a jsonb type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeJsonb(Fluent $column) + { + return 'text'; + } + + /** + * Create the column definition for a date type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeDate(Fluent $column) + { + return 'date'; + } + + /** + * Create the column definition for a date-time type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeDateTime(Fluent $column) + { + return $this->typeTimestamp($column); + } + + /** + * Create the column definition for a date-time (with time zone) type. + * + * Note: "SQLite does not have a storage class set aside for storing dates and/or times." + * + * @link https://www.sqlite.org/datatype3.html + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeDateTimeTz(Fluent $column) + { + return $this->typeDateTime($column); + } + + /** + * Create the column definition for a time type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTime(Fluent $column) + { + return 'time'; + } + + /** + * Create the column definition for a time (with time zone) type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTimeTz(Fluent $column) + { + return $this->typeTime($column); + } + + /** + * Create the column definition for a timestamp type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTimestamp(Fluent $column) + { + return $column->useCurrent ? 'datetime default CURRENT_TIMESTAMP' : 'datetime'; + } + + /** + * Create the column definition for a timestamp (with time zone) type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTimestampTz(Fluent $column) + { + return $this->typeTimestamp($column); + } + + /** + * Create the column definition for a year type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeYear(Fluent $column) + { + return $this->typeInteger($column); + } + + /** + * Create the column definition for a binary type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeBinary(Fluent $column) + { + return 'blob'; + } + + /** + * Create the column definition for a uuid type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeUuid(Fluent $column) + { + return 'varchar'; + } + + /** + * Create the column definition for an IP address type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeIpAddress(Fluent $column) + { + return 'varchar'; + } + + /** + * Create the column definition for a MAC address type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeMacAddress(Fluent $column) + { + return 'varchar'; + } + + /** + * Create the column definition for a spatial Geometry type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + public function typeGeometry(Fluent $column) + { + return 'geometry'; + } + + /** + * Create the column definition for a spatial Point type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + public function typePoint(Fluent $column) + { + return 'point'; + } + + /** + * Create the column definition for a spatial LineString type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + public function typeLineString(Fluent $column) + { + return 'linestring'; + } + + /** + * Create the column definition for a spatial Polygon type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + public function typePolygon(Fluent $column) + { + return 'polygon'; + } + + /** + * Create the column definition for a spatial GeometryCollection type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + public function typeGeometryCollection(Fluent $column) + { + return 'geometrycollection'; + } + + /** + * Create the column definition for a spatial MultiPoint type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + public function typeMultiPoint(Fluent $column) + { + return 'multipoint'; + } + + /** + * Create the column definition for a spatial MultiLineString type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + public function typeMultiLineString(Fluent $column) + { + return 'multilinestring'; + } + + /** + * Create the column definition for a spatial MultiPolygon type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + public function typeMultiPolygon(Fluent $column) + { + return 'multipolygon'; + } + + /** + * Create the column definition for a generated, computed column type. + * + * @param \Illuminate\Support\Fluent $column + * @return void + * + * @throws \RuntimeException + */ + protected function typeComputed(Fluent $column) + { + throw new RuntimeException('This database driver requires a type, see the virtualAs / storedAs modifiers.'); + } + + /** + * Get the SQL for a generated virtual column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyVirtualAs(Blueprint $blueprint, Fluent $column) + { + if (! is_null($column->virtualAs)) { + return " as ({$column->virtualAs})"; + } + } + + /** + * Get the SQL for a generated stored column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyStoredAs(Blueprint $blueprint, Fluent $column) + { + if (! is_null($column->storedAs)) { + return " as ({$column->storedAs}) stored"; + } + } + + /** + * Get the SQL for a nullable column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyNullable(Blueprint $blueprint, Fluent $column) + { + if (is_null($column->virtualAs) && is_null($column->storedAs)) { + return $column->nullable ? '' : ' not null'; + } + + if ($column->nullable === false) { + return ' not null'; + } + } + + /** + * Get the SQL for a default column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyDefault(Blueprint $blueprint, Fluent $column) + { + if (! is_null($column->default) && is_null($column->virtualAs) && is_null($column->storedAs)) { + return ' default '.$this->getDefaultValue($column->default); + } + } + + /** + * Get the SQL for an auto-increment column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyIncrement(Blueprint $blueprint, Fluent $column) + { + if (in_array($column->type, $this->serials) && $column->autoIncrement) { + return ' primary key autoincrement'; + } + } +} diff --git a/vendor/illuminate/database/Schema/Grammars/SqlServerGrammar.php b/vendor/illuminate/database/Schema/Grammars/SqlServerGrammar.php new file mode 100644 index 0000000..e594cfd --- /dev/null +++ b/vendor/illuminate/database/Schema/Grammars/SqlServerGrammar.php @@ -0,0 +1,934 @@ +wrapValue($name), + ); + } + + /** + * Compile a drop database if exists command. + * + * @param string $name + * @return string + */ + public function compileDropDatabaseIfExists($name) + { + return sprintf( + 'drop database if exists %s', + $this->wrapValue($name) + ); + } + + /** + * Compile the query to determine if a table exists. + * + * @return string + */ + public function compileTableExists() + { + return "select * from sys.sysobjects where id = object_id(?) and xtype in ('U', 'V')"; + } + + /** + * Compile the query to determine the list of columns. + * + * @param string $table + * @return string + */ + public function compileColumnListing($table) + { + return "select name from sys.columns where object_id = object_id('$table')"; + } + + /** + * Compile a create table command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileCreate(Blueprint $blueprint, Fluent $command) + { + $columns = implode(', ', $this->getColumns($blueprint)); + + return 'create table '.$this->wrapTable($blueprint)." ($columns)"; + } + + /** + * Compile a column addition table command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileAdd(Blueprint $blueprint, Fluent $command) + { + return sprintf('alter table %s add %s', + $this->wrapTable($blueprint), + implode(', ', $this->getColumns($blueprint)) + ); + } + + /** + * Compile a primary key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compilePrimary(Blueprint $blueprint, Fluent $command) + { + return sprintf('alter table %s add constraint %s primary key (%s)', + $this->wrapTable($blueprint), + $this->wrap($command->index), + $this->columnize($command->columns) + ); + } + + /** + * Compile a unique key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileUnique(Blueprint $blueprint, Fluent $command) + { + return sprintf('create unique index %s on %s (%s)', + $this->wrap($command->index), + $this->wrapTable($blueprint), + $this->columnize($command->columns) + ); + } + + /** + * Compile a plain index key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileIndex(Blueprint $blueprint, Fluent $command) + { + return sprintf('create index %s on %s (%s)', + $this->wrap($command->index), + $this->wrapTable($blueprint), + $this->columnize($command->columns) + ); + } + + /** + * Compile a spatial index key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileSpatialIndex(Blueprint $blueprint, Fluent $command) + { + return sprintf('create spatial index %s on %s (%s)', + $this->wrap($command->index), + $this->wrapTable($blueprint), + $this->columnize($command->columns) + ); + } + + /** + * Compile a drop table command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDrop(Blueprint $blueprint, Fluent $command) + { + return 'drop table '.$this->wrapTable($blueprint); + } + + /** + * Compile a drop table (if exists) command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropIfExists(Blueprint $blueprint, Fluent $command) + { + return sprintf('if exists (select * from sys.sysobjects where id = object_id(%s, \'U\')) drop table %s', + "'".str_replace("'", "''", $this->getTablePrefix().$blueprint->getTable())."'", + $this->wrapTable($blueprint) + ); + } + + /** + * Compile the SQL needed to drop all tables. + * + * @return string + */ + public function compileDropAllTables() + { + return "EXEC sp_msforeachtable 'DROP TABLE ?'"; + } + + /** + * Compile a drop column command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropColumn(Blueprint $blueprint, Fluent $command) + { + $columns = $this->wrapArray($command->columns); + + $dropExistingConstraintsSql = $this->compileDropDefaultConstraint($blueprint, $command).';'; + + return $dropExistingConstraintsSql.'alter table '.$this->wrapTable($blueprint).' drop column '.implode(', ', $columns); + } + + /** + * Compile a drop default constraint command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropDefaultConstraint(Blueprint $blueprint, Fluent $command) + { + $columns = "'".implode("','", $command->columns)."'"; + + $tableName = $this->getTablePrefix().$blueprint->getTable(); + + $sql = "DECLARE @sql NVARCHAR(MAX) = '';"; + $sql .= "SELECT @sql += 'ALTER TABLE [dbo].[{$tableName}] DROP CONSTRAINT ' + OBJECT_NAME([default_object_id]) + ';' "; + $sql .= 'FROM sys.columns '; + $sql .= "WHERE [object_id] = OBJECT_ID('[dbo].[{$tableName}]') AND [name] in ({$columns}) AND [default_object_id] <> 0;"; + $sql .= 'EXEC(@sql)'; + + return $sql; + } + + /** + * Compile a drop primary key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropPrimary(Blueprint $blueprint, Fluent $command) + { + $index = $this->wrap($command->index); + + return "alter table {$this->wrapTable($blueprint)} drop constraint {$index}"; + } + + /** + * Compile a drop unique key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropUnique(Blueprint $blueprint, Fluent $command) + { + $index = $this->wrap($command->index); + + return "drop index {$index} on {$this->wrapTable($blueprint)}"; + } + + /** + * Compile a drop index command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropIndex(Blueprint $blueprint, Fluent $command) + { + $index = $this->wrap($command->index); + + return "drop index {$index} on {$this->wrapTable($blueprint)}"; + } + + /** + * Compile a drop spatial index command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropSpatialIndex(Blueprint $blueprint, Fluent $command) + { + return $this->compileDropIndex($blueprint, $command); + } + + /** + * Compile a drop foreign key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropForeign(Blueprint $blueprint, Fluent $command) + { + $index = $this->wrap($command->index); + + return "alter table {$this->wrapTable($blueprint)} drop constraint {$index}"; + } + + /** + * Compile a rename table command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileRename(Blueprint $blueprint, Fluent $command) + { + $from = $this->wrapTable($blueprint); + + return "sp_rename {$from}, ".$this->wrapTable($command->to); + } + + /** + * Compile a rename index command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileRenameIndex(Blueprint $blueprint, Fluent $command) + { + return sprintf("sp_rename N'%s', %s, N'INDEX'", + $this->wrap($blueprint->getTable().'.'.$command->from), + $this->wrap($command->to) + ); + } + + /** + * Compile the command to enable foreign key constraints. + * + * @return string + */ + public function compileEnableForeignKeyConstraints() + { + return 'EXEC sp_msforeachtable @command1="print \'?\'", @command2="ALTER TABLE ? WITH CHECK CHECK CONSTRAINT all";'; + } + + /** + * Compile the command to disable foreign key constraints. + * + * @return string + */ + public function compileDisableForeignKeyConstraints() + { + return 'EXEC sp_msforeachtable "ALTER TABLE ? NOCHECK CONSTRAINT all";'; + } + + /** + * Compile the command to drop all foreign keys. + * + * @return string + */ + public function compileDropAllForeignKeys() + { + return "DECLARE @sql NVARCHAR(MAX) = N''; + SELECT @sql += 'ALTER TABLE ' + + QUOTENAME(OBJECT_SCHEMA_NAME(parent_object_id)) + '.' + + QUOTENAME(OBJECT_NAME(parent_object_id)) + + ' DROP CONSTRAINT ' + QUOTENAME(name) + ';' + FROM sys.foreign_keys; + + EXEC sp_executesql @sql;"; + } + + /** + * Compile the command to drop all views. + * + * @return string + */ + public function compileDropAllViews() + { + return "DECLARE @sql NVARCHAR(MAX) = N''; + SELECT @sql += 'DROP VIEW ' + QUOTENAME(OBJECT_SCHEMA_NAME(object_id)) + '.' + QUOTENAME(name) + ';' + FROM sys.views; + + EXEC sp_executesql @sql;"; + } + + /** + * Create the column definition for a char type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeChar(Fluent $column) + { + return "nchar({$column->length})"; + } + + /** + * Create the column definition for a string type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeString(Fluent $column) + { + return "nvarchar({$column->length})"; + } + + /** + * Create the column definition for a tiny text type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTinyText(Fluent $column) + { + return 'nvarchar(255)'; + } + + /** + * Create the column definition for a text type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeText(Fluent $column) + { + return 'nvarchar(max)'; + } + + /** + * Create the column definition for a medium text type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeMediumText(Fluent $column) + { + return 'nvarchar(max)'; + } + + /** + * Create the column definition for a long text type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeLongText(Fluent $column) + { + return 'nvarchar(max)'; + } + + /** + * Create the column definition for an integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeInteger(Fluent $column) + { + return 'int'; + } + + /** + * Create the column definition for a big integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeBigInteger(Fluent $column) + { + return 'bigint'; + } + + /** + * Create the column definition for a medium integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeMediumInteger(Fluent $column) + { + return 'int'; + } + + /** + * Create the column definition for a tiny integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTinyInteger(Fluent $column) + { + return 'tinyint'; + } + + /** + * Create the column definition for a small integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeSmallInteger(Fluent $column) + { + return 'smallint'; + } + + /** + * Create the column definition for a float type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeFloat(Fluent $column) + { + return 'float'; + } + + /** + * Create the column definition for a double type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeDouble(Fluent $column) + { + return 'float'; + } + + /** + * Create the column definition for a decimal type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeDecimal(Fluent $column) + { + return "decimal({$column->total}, {$column->places})"; + } + + /** + * Create the column definition for a boolean type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeBoolean(Fluent $column) + { + return 'bit'; + } + + /** + * Create the column definition for an enumeration type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeEnum(Fluent $column) + { + return sprintf( + 'nvarchar(255) check ("%s" in (%s))', + $column->name, + $this->quoteString($column->allowed) + ); + } + + /** + * Create the column definition for a json type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeJson(Fluent $column) + { + return 'nvarchar(max)'; + } + + /** + * Create the column definition for a jsonb type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeJsonb(Fluent $column) + { + return 'nvarchar(max)'; + } + + /** + * Create the column definition for a date type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeDate(Fluent $column) + { + return 'date'; + } + + /** + * Create the column definition for a date-time type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeDateTime(Fluent $column) + { + return $this->typeTimestamp($column); + } + + /** + * Create the column definition for a date-time (with time zone) type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeDateTimeTz(Fluent $column) + { + return $this->typeTimestampTz($column); + } + + /** + * Create the column definition for a time type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTime(Fluent $column) + { + return $column->precision ? "time($column->precision)" : 'time'; + } + + /** + * Create the column definition for a time (with time zone) type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTimeTz(Fluent $column) + { + return $this->typeTime($column); + } + + /** + * Create the column definition for a timestamp type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTimestamp(Fluent $column) + { + $columnType = $column->precision ? "datetime2($column->precision)" : 'datetime'; + + return $column->useCurrent ? "$columnType default CURRENT_TIMESTAMP" : $columnType; + } + + /** + * Create the column definition for a timestamp (with time zone) type. + * + * @link https://docs.microsoft.com/en-us/sql/t-sql/data-types/datetimeoffset-transact-sql?view=sql-server-ver15 + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTimestampTz(Fluent $column) + { + $columnType = $column->precision ? "datetimeoffset($column->precision)" : 'datetimeoffset'; + + return $column->useCurrent ? "$columnType default CURRENT_TIMESTAMP" : $columnType; + } + + /** + * Create the column definition for a year type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeYear(Fluent $column) + { + return $this->typeInteger($column); + } + + /** + * Create the column definition for a binary type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeBinary(Fluent $column) + { + return 'varbinary(max)'; + } + + /** + * Create the column definition for a uuid type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeUuid(Fluent $column) + { + return 'uniqueidentifier'; + } + + /** + * Create the column definition for an IP address type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeIpAddress(Fluent $column) + { + return 'nvarchar(45)'; + } + + /** + * Create the column definition for a MAC address type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeMacAddress(Fluent $column) + { + return 'nvarchar(17)'; + } + + /** + * Create the column definition for a spatial Geometry type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + public function typeGeometry(Fluent $column) + { + return 'geography'; + } + + /** + * Create the column definition for a spatial Point type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + public function typePoint(Fluent $column) + { + return 'geography'; + } + + /** + * Create the column definition for a spatial LineString type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + public function typeLineString(Fluent $column) + { + return 'geography'; + } + + /** + * Create the column definition for a spatial Polygon type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + public function typePolygon(Fluent $column) + { + return 'geography'; + } + + /** + * Create the column definition for a spatial GeometryCollection type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + public function typeGeometryCollection(Fluent $column) + { + return 'geography'; + } + + /** + * Create the column definition for a spatial MultiPoint type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + public function typeMultiPoint(Fluent $column) + { + return 'geography'; + } + + /** + * Create the column definition for a spatial MultiLineString type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + public function typeMultiLineString(Fluent $column) + { + return 'geography'; + } + + /** + * Create the column definition for a spatial MultiPolygon type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + public function typeMultiPolygon(Fluent $column) + { + return 'geography'; + } + + /** + * Create the column definition for a generated, computed column type. + * + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function typeComputed(Fluent $column) + { + return "as ({$column->expression})"; + } + + /** + * Get the SQL for a collation column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyCollate(Blueprint $blueprint, Fluent $column) + { + if (! is_null($column->collation)) { + return ' collate '.$column->collation; + } + } + + /** + * Get the SQL for a nullable column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyNullable(Blueprint $blueprint, Fluent $column) + { + if ($column->type !== 'computed') { + return $column->nullable ? ' null' : ' not null'; + } + } + + /** + * Get the SQL for a default column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyDefault(Blueprint $blueprint, Fluent $column) + { + if (! is_null($column->default)) { + return ' default '.$this->getDefaultValue($column->default); + } + } + + /** + * Get the SQL for an auto-increment column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyIncrement(Blueprint $blueprint, Fluent $column) + { + if (in_array($column->type, $this->serials) && $column->autoIncrement) { + return ' identity primary key'; + } + } + + /** + * Get the SQL for a generated stored column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyPersisted(Blueprint $blueprint, Fluent $column) + { + if ($column->persisted) { + return ' persisted'; + } + } + + /** + * Wrap a table in keyword identifiers. + * + * @param \Illuminate\Database\Query\Expression|string $table + * @return string + */ + public function wrapTable($table) + { + if ($table instanceof Blueprint && $table->temporary) { + $this->setTablePrefix('#'); + } + + return parent::wrapTable($table); + } + + /** + * Quote the given string literal. + * + * @param string|array $value + * @return string + */ + public function quoteString($value) + { + if (is_array($value)) { + return implode(', ', array_map([$this, __FUNCTION__], $value)); + } + + return "N'$value'"; + } +} diff --git a/vendor/illuminate/database/Schema/MySqlBuilder.php b/vendor/illuminate/database/Schema/MySqlBuilder.php new file mode 100644 index 0000000..699b41d --- /dev/null +++ b/vendor/illuminate/database/Schema/MySqlBuilder.php @@ -0,0 +1,140 @@ +connection->statement( + $this->grammar->compileCreateDatabase($name, $this->connection) + ); + } + + /** + * Drop a database from the schema if the database exists. + * + * @param string $name + * @return bool + */ + public function dropDatabaseIfExists($name) + { + return $this->connection->statement( + $this->grammar->compileDropDatabaseIfExists($name) + ); + } + + /** + * Determine if the given table exists. + * + * @param string $table + * @return bool + */ + public function hasTable($table) + { + $table = $this->connection->getTablePrefix().$table; + + return count($this->connection->select( + $this->grammar->compileTableExists(), [$this->connection->getDatabaseName(), $table] + )) > 0; + } + + /** + * Get the column listing for a given table. + * + * @param string $table + * @return array + */ + public function getColumnListing($table) + { + $table = $this->connection->getTablePrefix().$table; + + $results = $this->connection->select( + $this->grammar->compileColumnListing(), [$this->connection->getDatabaseName(), $table] + ); + + return $this->connection->getPostProcessor()->processColumnListing($results); + } + + /** + * Drop all tables from the database. + * + * @return void + */ + public function dropAllTables() + { + $tables = []; + + foreach ($this->getAllTables() as $row) { + $row = (array) $row; + + $tables[] = reset($row); + } + + if (empty($tables)) { + return; + } + + $this->disableForeignKeyConstraints(); + + $this->connection->statement( + $this->grammar->compileDropAllTables($tables) + ); + + $this->enableForeignKeyConstraints(); + } + + /** + * Drop all views from the database. + * + * @return void + */ + public function dropAllViews() + { + $views = []; + + foreach ($this->getAllViews() as $row) { + $row = (array) $row; + + $views[] = reset($row); + } + + if (empty($views)) { + return; + } + + $this->connection->statement( + $this->grammar->compileDropAllViews($views) + ); + } + + /** + * Get all of the table names for the database. + * + * @return array + */ + public function getAllTables() + { + return $this->connection->select( + $this->grammar->compileGetAllTables() + ); + } + + /** + * Get all of the view names for the database. + * + * @return array + */ + public function getAllViews() + { + return $this->connection->select( + $this->grammar->compileGetAllViews() + ); + } +} diff --git a/vendor/illuminate/database/Schema/MySqlSchemaState.php b/vendor/illuminate/database/Schema/MySqlSchemaState.php new file mode 100644 index 0000000..e0b0103 --- /dev/null +++ b/vendor/illuminate/database/Schema/MySqlSchemaState.php @@ -0,0 +1,163 @@ +executeDumpProcess($this->makeProcess( + $this->baseDumpCommand().' --routines --result-file="${:LARAVEL_LOAD_PATH}" --no-data' + ), $this->output, array_merge($this->baseVariables($this->connection->getConfig()), [ + 'LARAVEL_LOAD_PATH' => $path, + ])); + + $this->removeAutoIncrementingState($path); + + $this->appendMigrationData($path); + } + + /** + * Remove the auto-incrementing state from the given schema dump. + * + * @param string $path + * @return void + */ + protected function removeAutoIncrementingState(string $path) + { + $this->files->put($path, preg_replace( + '/\s+AUTO_INCREMENT=[0-9]+/iu', + '', + $this->files->get($path) + )); + } + + /** + * Append the migration data to the schema dump. + * + * @param string $path + * @return void + */ + protected function appendMigrationData(string $path) + { + $process = $this->executeDumpProcess($this->makeProcess( + $this->baseDumpCommand().' '.$this->migrationTable.' --no-create-info --skip-extended-insert --skip-routines --compact' + ), null, array_merge($this->baseVariables($this->connection->getConfig()), [ + // + ])); + + $this->files->append($path, $process->getOutput()); + } + + /** + * Load the given schema file into the database. + * + * @param string $path + * @return void + */ + public function load($path) + { + $command = 'mysql '.$this->connectionString().' --database="${:LARAVEL_LOAD_DATABASE}" < "${:LARAVEL_LOAD_PATH}"'; + + $process = $this->makeProcess($command)->setTimeout(null); + + $process->mustRun(null, array_merge($this->baseVariables($this->connection->getConfig()), [ + 'LARAVEL_LOAD_PATH' => $path, + ])); + } + + /** + * Get the base dump command arguments for MySQL as a string. + * + * @return string + */ + protected function baseDumpCommand() + { + $command = 'mysqldump '.$this->connectionString().' --no-tablespaces --skip-add-locks --skip-comments --skip-set-charset --tz-utc --column-statistics=0'; + + if (! $this->connection->isMaria()) { + $command .= ' --set-gtid-purged=OFF'; + } + + return $command.' "${:LARAVEL_LOAD_DATABASE}"'; + } + + /** + * Generate a basic connection string (--socket, --host, --port, --user, --password) for the database. + * + * @return string + */ + protected function connectionString() + { + $value = ' --user="${:LARAVEL_LOAD_USER}" --password="${:LARAVEL_LOAD_PASSWORD}"'; + + $value .= $this->connection->getConfig()['unix_socket'] ?? false + ? ' --socket="${:LARAVEL_LOAD_SOCKET}"' + : ' --host="${:LARAVEL_LOAD_HOST}" --port="${:LARAVEL_LOAD_PORT}"'; + + return $value; + } + + /** + * Get the base variables for a dump / load command. + * + * @param array $config + * @return array + */ + protected function baseVariables(array $config) + { + $config['host'] = $config['host'] ?? ''; + + return [ + 'LARAVEL_LOAD_SOCKET' => $config['unix_socket'] ?? '', + 'LARAVEL_LOAD_HOST' => is_array($config['host']) ? $config['host'][0] : $config['host'], + 'LARAVEL_LOAD_PORT' => $config['port'] ?? '', + 'LARAVEL_LOAD_USER' => $config['username'], + 'LARAVEL_LOAD_PASSWORD' => $config['password'] ?? '', + 'LARAVEL_LOAD_DATABASE' => $config['database'], + ]; + } + + /** + * Execute the given dump process. + * + * @param \Symfony\Component\Process\Process $process + * @param callable $output + * @param array $variables + * @return \Symfony\Component\Process\Process + */ + protected function executeDumpProcess(Process $process, $output, array $variables) + { + try { + $process->setTimeout(null)->mustRun($output, $variables); + } catch (Exception $e) { + if (Str::contains($e->getMessage(), ['column-statistics', 'column_statistics'])) { + return $this->executeDumpProcess(Process::fromShellCommandLine( + str_replace(' --column-statistics=0', '', $process->getCommandLine()) + ), $output, $variables); + } + + if (Str::contains($e->getMessage(), ['set-gtid-purged'])) { + return $this->executeDumpProcess(Process::fromShellCommandLine( + str_replace(' --set-gtid-purged=OFF', '', $process->getCommandLine()) + ), $output, $variables); + } + + throw $e; + } + + return $process; + } +} diff --git a/vendor/illuminate/database/Schema/PostgresBuilder.php b/vendor/illuminate/database/Schema/PostgresBuilder.php new file mode 100644 index 0000000..ce1b577 --- /dev/null +++ b/vendor/illuminate/database/Schema/PostgresBuilder.php @@ -0,0 +1,203 @@ +connection->statement( + $this->grammar->compileCreateDatabase($name, $this->connection) + ); + } + + /** + * Drop a database from the schema if the database exists. + * + * @param string $name + * @return bool + */ + public function dropDatabaseIfExists($name) + { + return $this->connection->statement( + $this->grammar->compileDropDatabaseIfExists($name) + ); + } + + /** + * Determine if the given table exists. + * + * @param string $table + * @return bool + */ + public function hasTable($table) + { + [$schema, $table] = $this->parseSchemaAndTable($table); + + $table = $this->connection->getTablePrefix().$table; + + return count($this->connection->select( + $this->grammar->compileTableExists(), [$schema, $table] + )) > 0; + } + + /** + * Drop all tables from the database. + * + * @return void + */ + public function dropAllTables() + { + $tables = []; + + $excludedTables = $this->connection->getConfig('dont_drop') ?? ['spatial_ref_sys']; + + foreach ($this->getAllTables() as $row) { + $row = (array) $row; + + $table = reset($row); + + if (! in_array($table, $excludedTables)) { + $tables[] = $table; + } + } + + if (empty($tables)) { + return; + } + + $this->connection->statement( + $this->grammar->compileDropAllTables($tables) + ); + } + + /** + * Drop all views from the database. + * + * @return void + */ + public function dropAllViews() + { + $views = []; + + foreach ($this->getAllViews() as $row) { + $row = (array) $row; + + $views[] = reset($row); + } + + if (empty($views)) { + return; + } + + $this->connection->statement( + $this->grammar->compileDropAllViews($views) + ); + } + + /** + * Drop all types from the database. + * + * @return void + */ + public function dropAllTypes() + { + $types = []; + + foreach ($this->getAllTypes() as $row) { + $row = (array) $row; + + $types[] = reset($row); + } + + if (empty($types)) { + return; + } + + $this->connection->statement( + $this->grammar->compileDropAllTypes($types) + ); + } + + /** + * Get all of the table names for the database. + * + * @return array + */ + public function getAllTables() + { + return $this->connection->select( + $this->grammar->compileGetAllTables((array) $this->connection->getConfig('schema')) + ); + } + + /** + * Get all of the view names for the database. + * + * @return array + */ + public function getAllViews() + { + return $this->connection->select( + $this->grammar->compileGetAllViews((array) $this->connection->getConfig('schema')) + ); + } + + /** + * Get all of the type names for the database. + * + * @return array + */ + public function getAllTypes() + { + return $this->connection->select( + $this->grammar->compileGetAllTypes() + ); + } + + /** + * Get the column listing for a given table. + * + * @param string $table + * @return array + */ + public function getColumnListing($table) + { + [$schema, $table] = $this->parseSchemaAndTable($table); + + $table = $this->connection->getTablePrefix().$table; + + $results = $this->connection->select( + $this->grammar->compileColumnListing(), [$schema, $table] + ); + + return $this->connection->getPostProcessor()->processColumnListing($results); + } + + /** + * Parse the table name and extract the schema and table. + * + * @param string $table + * @return array + */ + protected function parseSchemaAndTable($table) + { + $table = explode('.', $table); + + if (is_array($schema = $this->connection->getConfig('schema'))) { + if (in_array($table[0], $schema)) { + return [array_shift($table), implode('.', $table)]; + } + + $schema = head($schema); + } + + return [$schema ?: 'public', implode('.', $table)]; + } +} diff --git a/vendor/illuminate/database/Schema/PostgresSchemaState.php b/vendor/illuminate/database/Schema/PostgresSchemaState.php new file mode 100644 index 0000000..3e2f566 --- /dev/null +++ b/vendor/illuminate/database/Schema/PostgresSchemaState.php @@ -0,0 +1,83 @@ +getSchemaBuilder()->getAllTables()) + ->map->tablename + ->reject(function ($table) { + return $table === $this->migrationTable; + })->map(function ($table) { + return '--exclude-table-data="*.'.$table.'"'; + })->implode(' '); + + $this->makeProcess( + $this->baseDumpCommand().' --file="${:LARAVEL_LOAD_PATH}" '.$excludedTables + )->mustRun($this->output, array_merge($this->baseVariables($this->connection->getConfig()), [ + 'LARAVEL_LOAD_PATH' => $path, + ])); + } + + /** + * Load the given schema file into the database. + * + * @param string $path + * @return void + */ + public function load($path) + { + $command = 'pg_restore --no-owner --no-acl --clean --if-exists --host="${:LARAVEL_LOAD_HOST}" --port="${:LARAVEL_LOAD_PORT}" --username="${:LARAVEL_LOAD_USER}" --dbname="${:LARAVEL_LOAD_DATABASE}" "${:LARAVEL_LOAD_PATH}"'; + + if (Str::endsWith($path, '.sql')) { + $command = 'psql --file="${:LARAVEL_LOAD_PATH}" --host="${:LARAVEL_LOAD_HOST}" --port="${:LARAVEL_LOAD_PORT}" --username="${:LARAVEL_LOAD_USER}" --dbname="${:LARAVEL_LOAD_DATABASE}"'; + } + + $process = $this->makeProcess($command); + + $process->mustRun(null, array_merge($this->baseVariables($this->connection->getConfig()), [ + 'LARAVEL_LOAD_PATH' => $path, + ])); + } + + /** + * Get the base dump command arguments for PostgreSQL as a string. + * + * @return string + */ + protected function baseDumpCommand() + { + return 'pg_dump --no-owner --no-acl -Fc --host="${:LARAVEL_LOAD_HOST}" --port="${:LARAVEL_LOAD_PORT}" --username="${:LARAVEL_LOAD_USER}" --dbname="${:LARAVEL_LOAD_DATABASE}"'; + } + + /** + * Get the base variables for a dump / load command. + * + * @param array $config + * @return array + */ + protected function baseVariables(array $config) + { + $config['host'] = $config['host'] ?? ''; + + return [ + 'LARAVEL_LOAD_HOST' => is_array($config['host']) ? $config['host'][0] : $config['host'], + 'LARAVEL_LOAD_PORT' => $config['port'], + 'LARAVEL_LOAD_USER' => $config['username'], + 'PGPASSWORD' => $config['password'], + 'LARAVEL_LOAD_DATABASE' => $config['database'], + ]; + } +} diff --git a/vendor/illuminate/database/Schema/SQLiteBuilder.php b/vendor/illuminate/database/Schema/SQLiteBuilder.php new file mode 100644 index 0000000..3bc1275 --- /dev/null +++ b/vendor/illuminate/database/Schema/SQLiteBuilder.php @@ -0,0 +1,78 @@ +connection->getDatabaseName() !== ':memory:') { + return $this->refreshDatabaseFile(); + } + + $this->connection->select($this->grammar->compileEnableWriteableSchema()); + + $this->connection->select($this->grammar->compileDropAllTables()); + + $this->connection->select($this->grammar->compileDisableWriteableSchema()); + + $this->connection->select($this->grammar->compileRebuild()); + } + + /** + * Drop all views from the database. + * + * @return void + */ + public function dropAllViews() + { + $this->connection->select($this->grammar->compileEnableWriteableSchema()); + + $this->connection->select($this->grammar->compileDropAllViews()); + + $this->connection->select($this->grammar->compileDisableWriteableSchema()); + + $this->connection->select($this->grammar->compileRebuild()); + } + + /** + * Empty the database file. + * + * @return void + */ + public function refreshDatabaseFile() + { + file_put_contents($this->connection->getDatabaseName(), ''); + } +} diff --git a/vendor/illuminate/database/Schema/SchemaState.php b/vendor/illuminate/database/Schema/SchemaState.php new file mode 100644 index 0000000..e6f35ab --- /dev/null +++ b/vendor/illuminate/database/Schema/SchemaState.php @@ -0,0 +1,122 @@ +connection = $connection; + + $this->files = $files ?: new Filesystem; + + $this->processFactory = $processFactory ?: function (...$arguments) { + return Process::fromShellCommandline(...$arguments)->setTimeout(null); + }; + + $this->handleOutputUsing(function () { + // + }); + } + + /** + * Dump the database's schema into a file. + * + * @param \Illuminate\Database\Connection $connection + * @param string $path + * @return void + */ + abstract public function dump(Connection $connection, $path); + + /** + * Load the given schema file into the database. + * + * @param string $path + * @return void + */ + abstract public function load($path); + + /** + * Create a new process instance. + * + * @param array $arguments + * @return \Symfony\Component\Process\Process + */ + public function makeProcess(...$arguments) + { + return call_user_func($this->processFactory, ...$arguments); + } + + /** + * Specify the name of the application's migration table. + * + * @param string $table + * @return $this + */ + public function withMigrationTable(string $table) + { + $this->migrationTable = $table; + + return $this; + } + + /** + * Specify the callback that should be used to handle process output. + * + * @param callable $output + * @return $this + */ + public function handleOutputUsing(callable $output) + { + $this->output = $output; + + return $this; + } +} diff --git a/vendor/illuminate/database/Schema/SqlServerBuilder.php b/vendor/illuminate/database/Schema/SqlServerBuilder.php new file mode 100644 index 0000000..93da1cb --- /dev/null +++ b/vendor/illuminate/database/Schema/SqlServerBuilder.php @@ -0,0 +1,54 @@ +connection->statement( + $this->grammar->compileCreateDatabase($name, $this->connection) + ); + } + + /** + * Drop a database from the schema if the database exists. + * + * @param string $name + * @return bool + */ + public function dropDatabaseIfExists($name) + { + return $this->connection->statement( + $this->grammar->compileDropDatabaseIfExists($name) + ); + } + + /** + * Drop all tables from the database. + * + * @return void + */ + public function dropAllTables() + { + $this->connection->statement($this->grammar->compileDropAllForeignKeys()); + + $this->connection->statement($this->grammar->compileDropAllTables()); + } + + /** + * Drop all views from the database. + * + * @return void + */ + public function dropAllViews() + { + $this->connection->statement($this->grammar->compileDropAllViews()); + } +} diff --git a/vendor/illuminate/database/Schema/SqliteSchemaState.php b/vendor/illuminate/database/Schema/SqliteSchemaState.php new file mode 100644 index 0000000..9a98b63 --- /dev/null +++ b/vendor/illuminate/database/Schema/SqliteSchemaState.php @@ -0,0 +1,93 @@ +makeProcess( + $this->baseCommand().' .schema' + ))->setTimeout(null)->mustRun(null, array_merge($this->baseVariables($this->connection->getConfig()), [ + // + ])); + + $migrations = collect(preg_split("/\r\n|\n|\r/", $process->getOutput()))->filter(function ($line) { + return stripos($line, 'sqlite_sequence') === false && + strlen($line) > 0; + })->all(); + + $this->files->put($path, implode(PHP_EOL, $migrations).PHP_EOL); + + $this->appendMigrationData($path); + } + + /** + * Append the migration data to the schema dump. + * + * @param string $path + * @return void + */ + protected function appendMigrationData(string $path) + { + with($process = $this->makeProcess( + $this->baseCommand().' ".dump \''.$this->migrationTable.'\'"' + ))->mustRun(null, array_merge($this->baseVariables($this->connection->getConfig()), [ + // + ])); + + $migrations = collect(preg_split("/\r\n|\n|\r/", $process->getOutput()))->filter(function ($line) { + return preg_match('/^\s*(--|INSERT\s)/iu', $line) === 1 && + strlen($line) > 0; + })->all(); + + $this->files->append($path, implode(PHP_EOL, $migrations).PHP_EOL); + } + + /** + * Load the given schema file into the database. + * + * @param string $path + * @return void + */ + public function load($path) + { + $process = $this->makeProcess($this->baseCommand().' < "${:LARAVEL_LOAD_PATH}"'); + + $process->mustRun(null, array_merge($this->baseVariables($this->connection->getConfig()), [ + 'LARAVEL_LOAD_PATH' => $path, + ])); + } + + /** + * Get the base sqlite command arguments as a string. + * + * @return string + */ + protected function baseCommand() + { + return 'sqlite3 "${:LARAVEL_LOAD_DATABASE}"'; + } + + /** + * Get the base variables for a dump / load command. + * + * @param array $config + * @return array + */ + protected function baseVariables(array $config) + { + return [ + 'LARAVEL_LOAD_DATABASE' => $config['database'], + ]; + } +} diff --git a/vendor/illuminate/database/Seeder.php b/vendor/illuminate/database/Seeder.php new file mode 100644 index 0000000..441fa27 --- /dev/null +++ b/vendor/illuminate/database/Seeder.php @@ -0,0 +1,152 @@ +resolve($class); + + $name = get_class($seeder); + + if ($silent === false && isset($this->command)) { + $this->command->getOutput()->writeln("Seeding: {$name}"); + } + + $startTime = microtime(true); + + $seeder->__invoke($parameters); + + $runTime = number_format((microtime(true) - $startTime) * 1000, 2); + + if ($silent === false && isset($this->command)) { + $this->command->getOutput()->writeln("Seeded: {$name} ({$runTime}ms)"); + } + } + + return $this; + } + + /** + * Run the given seeder class. + * + * @param array|string $class + * @param array $parameters + * @return void + */ + public function callWith($class, array $parameters = []) + { + $this->call($class, false, $parameters); + } + + /** + * Silently run the given seeder class. + * + * @param array|string $class + * @param array $parameters + * @return void + */ + public function callSilent($class, array $parameters = []) + { + $this->call($class, true, $parameters); + } + + /** + * Resolve an instance of the given seeder class. + * + * @param string $class + * @return \Illuminate\Database\Seeder + */ + protected function resolve($class) + { + if (isset($this->container)) { + $instance = $this->container->make($class); + + $instance->setContainer($this->container); + } else { + $instance = new $class; + } + + if (isset($this->command)) { + $instance->setCommand($this->command); + } + + return $instance; + } + + /** + * Set the IoC container instance. + * + * @param \Illuminate\Container\Container $container + * @return $this + */ + public function setContainer(Container $container) + { + $this->container = $container; + + return $this; + } + + /** + * Set the console command instance. + * + * @param \Illuminate\Console\Command $command + * @return $this + */ + public function setCommand(Command $command) + { + $this->command = $command; + + return $this; + } + + /** + * Run the database seeds. + * + * @param array $parameters + * @return mixed + * + * @throws \InvalidArgumentException + */ + public function __invoke(array $parameters = []) + { + if (! method_exists($this, 'run')) { + throw new InvalidArgumentException('Method [run] missing from '.get_class($this)); + } + + return isset($this->container) + ? $this->container->call([$this, 'run'], $parameters) + : $this->run(...$parameters); + } +} diff --git a/vendor/illuminate/database/SqlServerConnection.php b/vendor/illuminate/database/SqlServerConnection.php new file mode 100644 index 0000000..d6a2b7a --- /dev/null +++ b/vendor/illuminate/database/SqlServerConnection.php @@ -0,0 +1,125 @@ +getDriverName() === 'sqlsrv') { + return parent::transaction($callback, $attempts); + } + + $this->getPdo()->exec('BEGIN TRAN'); + + // We'll simply execute the given callback within a try / catch block + // and if we catch any exception we can rollback the transaction + // so that none of the changes are persisted to the database. + try { + $result = $callback($this); + + $this->getPdo()->exec('COMMIT TRAN'); + } + + // If we catch an exception, we will rollback so nothing gets messed + // up in the database. Then we'll re-throw the exception so it can + // be handled how the developer sees fit for their applications. + catch (Throwable $e) { + $this->getPdo()->exec('ROLLBACK TRAN'); + + throw $e; + } + + return $result; + } + } + + /** + * Get the default query grammar instance. + * + * @return \Illuminate\Database\Query\Grammars\SqlServerGrammar + */ + protected function getDefaultQueryGrammar() + { + return $this->withTablePrefix(new QueryGrammar); + } + + /** + * Get a schema builder instance for the connection. + * + * @return \Illuminate\Database\Schema\SqlServerBuilder + */ + public function getSchemaBuilder() + { + if (is_null($this->schemaGrammar)) { + $this->useDefaultSchemaGrammar(); + } + + return new SqlServerBuilder($this); + } + + /** + * Get the default schema grammar instance. + * + * @return \Illuminate\Database\Schema\Grammars\SqlServerGrammar + */ + protected function getDefaultSchemaGrammar() + { + return $this->withTablePrefix(new SchemaGrammar); + } + + /** + * Get the schema state for the connection. + * + * @param \Illuminate\Filesystem\Filesystem|null $files + * @param callable|null $processFactory + * + * @throws \RuntimeException + */ + public function getSchemaState(Filesystem $files = null, callable $processFactory = null) + { + throw new RuntimeException('Schema dumping is not supported when using SQL Server.'); + } + + /** + * Get the default post processor instance. + * + * @return \Illuminate\Database\Query\Processors\SqlServerProcessor + */ + protected function getDefaultPostProcessor() + { + return new SqlServerProcessor; + } + + /** + * Get the Doctrine DBAL driver. + * + * @return \Doctrine\DBAL\Driver\PDOSqlsrv\Driver|\Illuminate\Database\PDO\SqlServerDriver + */ + protected function getDoctrineDriver() + { + return class_exists(Version::class) ? new DoctrineDriver : new SqlServerDriver; + } +} diff --git a/vendor/illuminate/events/CallQueuedListener.php b/vendor/illuminate/events/CallQueuedListener.php new file mode 100644 index 0000000..6a39008 --- /dev/null +++ b/vendor/illuminate/events/CallQueuedListener.php @@ -0,0 +1,180 @@ +data = $data; + $this->class = $class; + $this->method = $method; + } + + /** + * Handle the queued job. + * + * @param \Illuminate\Container\Container $container + * @return void + */ + public function handle(Container $container) + { + $this->prepareData(); + + $handler = $this->setJobInstanceIfNecessary( + $this->job, $container->make($this->class) + ); + + $handler->{$this->method}(...array_values($this->data)); + } + + /** + * Set the job instance of the given class if necessary. + * + * @param \Illuminate\Contracts\Queue\Job $job + * @param object $instance + * @return object + */ + protected function setJobInstanceIfNecessary(Job $job, $instance) + { + if (in_array(InteractsWithQueue::class, class_uses_recursive($instance))) { + $instance->setJob($job); + } + + return $instance; + } + + /** + * Call the failed method on the job instance. + * + * The event instance and the exception will be passed. + * + * @param \Throwable $e + * @return void + */ + public function failed($e) + { + $this->prepareData(); + + $handler = Container::getInstance()->make($this->class); + + $parameters = array_merge(array_values($this->data), [$e]); + + if (method_exists($handler, 'failed')) { + $handler->failed(...$parameters); + } + } + + /** + * Unserialize the data if needed. + * + * @return void + */ + protected function prepareData() + { + if (is_string($this->data)) { + $this->data = unserialize($this->data); + } + } + + /** + * Get the display name for the queued job. + * + * @return string + */ + public function displayName() + { + return $this->class; + } + + /** + * Prepare the instance for cloning. + * + * @return void + */ + public function __clone() + { + $this->data = array_map(function ($data) { + return is_object($data) ? clone $data : $data; + }, $this->data); + } +} diff --git a/vendor/illuminate/events/Dispatcher.php b/vendor/illuminate/events/Dispatcher.php new file mode 100644 index 0000000..5972a83 --- /dev/null +++ b/vendor/illuminate/events/Dispatcher.php @@ -0,0 +1,676 @@ +container = $container ?: new Container; + } + + /** + * Register an event listener with the dispatcher. + * + * @param \Closure|string|array $events + * @param \Closure|string|array|null $listener + * @return void + */ + public function listen($events, $listener = null) + { + if ($events instanceof Closure) { + return collect($this->firstClosureParameterTypes($events)) + ->each(function ($event) use ($events) { + $this->listen($event, $events); + }); + } elseif ($events instanceof QueuedClosure) { + return collect($this->firstClosureParameterTypes($events->closure)) + ->each(function ($event) use ($events) { + $this->listen($event, $events->resolve()); + }); + } elseif ($listener instanceof QueuedClosure) { + $listener = $listener->resolve(); + } + + foreach ((array) $events as $event) { + if (Str::contains($event, '*')) { + $this->setupWildcardListen($event, $listener); + } else { + $this->listeners[$event][] = $this->makeListener($listener); + } + } + } + + /** + * Setup a wildcard listener callback. + * + * @param string $event + * @param \Closure|string $listener + * @return void + */ + protected function setupWildcardListen($event, $listener) + { + $this->wildcards[$event][] = $this->makeListener($listener, true); + + $this->wildcardsCache = []; + } + + /** + * Determine if a given event has listeners. + * + * @param string $eventName + * @return bool + */ + public function hasListeners($eventName) + { + return isset($this->listeners[$eventName]) || + isset($this->wildcards[$eventName]) || + $this->hasWildcardListeners($eventName); + } + + /** + * Determine if the given event has any wildcard listeners. + * + * @param string $eventName + * @return bool + */ + public function hasWildcardListeners($eventName) + { + foreach ($this->wildcards as $key => $listeners) { + if (Str::is($key, $eventName)) { + return true; + } + } + + return false; + } + + /** + * Register an event and payload to be fired later. + * + * @param string $event + * @param array $payload + * @return void + */ + public function push($event, $payload = []) + { + $this->listen($event.'_pushed', function () use ($event, $payload) { + $this->dispatch($event, $payload); + }); + } + + /** + * Flush a set of pushed events. + * + * @param string $event + * @return void + */ + public function flush($event) + { + $this->dispatch($event.'_pushed'); + } + + /** + * Register an event subscriber with the dispatcher. + * + * @param object|string $subscriber + * @return void + */ + public function subscribe($subscriber) + { + $subscriber = $this->resolveSubscriber($subscriber); + + $events = $subscriber->subscribe($this); + + if (is_array($events)) { + foreach ($events as $event => $listeners) { + foreach (Arr::wrap($listeners) as $listener) { + if (is_string($listener) && method_exists($subscriber, $listener)) { + $this->listen($event, [get_class($subscriber), $listener]); + + continue; + } + + $this->listen($event, $listener); + } + } + } + } + + /** + * Resolve the subscriber instance. + * + * @param object|string $subscriber + * @return mixed + */ + protected function resolveSubscriber($subscriber) + { + if (is_string($subscriber)) { + return $this->container->make($subscriber); + } + + return $subscriber; + } + + /** + * Fire an event until the first non-null response is returned. + * + * @param string|object $event + * @param mixed $payload + * @return array|null + */ + public function until($event, $payload = []) + { + return $this->dispatch($event, $payload, true); + } + + /** + * Fire an event and call the listeners. + * + * @param string|object $event + * @param mixed $payload + * @param bool $halt + * @return array|null + */ + public function dispatch($event, $payload = [], $halt = false) + { + // When the given "event" is actually an object we will assume it is an event + // object and use the class as the event name and this event itself as the + // payload to the handler, which makes object based events quite simple. + [$event, $payload] = $this->parseEventAndPayload( + $event, $payload + ); + + if ($this->shouldBroadcast($payload)) { + $this->broadcastEvent($payload[0]); + } + + $responses = []; + + foreach ($this->getListeners($event) as $listener) { + $response = $listener($event, $payload); + + // If a response is returned from the listener and event halting is enabled + // we will just return this response, and not call the rest of the event + // listeners. Otherwise we will add the response on the response list. + if ($halt && ! is_null($response)) { + return $response; + } + + // If a boolean false is returned from a listener, we will stop propagating + // the event to any further listeners down in the chain, else we keep on + // looping through the listeners and firing every one in our sequence. + if ($response === false) { + break; + } + + $responses[] = $response; + } + + return $halt ? null : $responses; + } + + /** + * Parse the given event and payload and prepare them for dispatching. + * + * @param mixed $event + * @param mixed $payload + * @return array + */ + protected function parseEventAndPayload($event, $payload) + { + if (is_object($event)) { + [$payload, $event] = [[$event], get_class($event)]; + } + + return [$event, Arr::wrap($payload)]; + } + + /** + * Determine if the payload has a broadcastable event. + * + * @param array $payload + * @return bool + */ + protected function shouldBroadcast(array $payload) + { + return isset($payload[0]) && + $payload[0] instanceof ShouldBroadcast && + $this->broadcastWhen($payload[0]); + } + + /** + * Check if the event should be broadcasted by the condition. + * + * @param mixed $event + * @return bool + */ + protected function broadcastWhen($event) + { + return method_exists($event, 'broadcastWhen') + ? $event->broadcastWhen() : true; + } + + /** + * Broadcast the given event class. + * + * @param \Illuminate\Contracts\Broadcasting\ShouldBroadcast $event + * @return void + */ + protected function broadcastEvent($event) + { + $this->container->make(BroadcastFactory::class)->queue($event); + } + + /** + * Get all of the listeners for a given event name. + * + * @param string $eventName + * @return array + */ + public function getListeners($eventName) + { + $listeners = $this->listeners[$eventName] ?? []; + + $listeners = array_merge( + $listeners, + $this->wildcardsCache[$eventName] ?? $this->getWildcardListeners($eventName) + ); + + return class_exists($eventName, false) + ? $this->addInterfaceListeners($eventName, $listeners) + : $listeners; + } + + /** + * Get the wildcard listeners for the event. + * + * @param string $eventName + * @return array + */ + protected function getWildcardListeners($eventName) + { + $wildcards = []; + + foreach ($this->wildcards as $key => $listeners) { + if (Str::is($key, $eventName)) { + $wildcards = array_merge($wildcards, $listeners); + } + } + + return $this->wildcardsCache[$eventName] = $wildcards; + } + + /** + * Add the listeners for the event's interfaces to the given array. + * + * @param string $eventName + * @param array $listeners + * @return array + */ + protected function addInterfaceListeners($eventName, array $listeners = []) + { + foreach (class_implements($eventName) as $interface) { + if (isset($this->listeners[$interface])) { + foreach ($this->listeners[$interface] as $names) { + $listeners = array_merge($listeners, (array) $names); + } + } + } + + return $listeners; + } + + /** + * Register an event listener with the dispatcher. + * + * @param \Closure|string|array $listener + * @param bool $wildcard + * @return \Closure + */ + public function makeListener($listener, $wildcard = false) + { + if (is_string($listener)) { + return $this->createClassListener($listener, $wildcard); + } + + if (is_array($listener) && isset($listener[0]) && is_string($listener[0])) { + return $this->createClassListener($listener, $wildcard); + } + + return function ($event, $payload) use ($listener, $wildcard) { + if ($wildcard) { + return $listener($event, $payload); + } + + return $listener(...array_values($payload)); + }; + } + + /** + * Create a class based listener using the IoC container. + * + * @param string $listener + * @param bool $wildcard + * @return \Closure + */ + public function createClassListener($listener, $wildcard = false) + { + return function ($event, $payload) use ($listener, $wildcard) { + if ($wildcard) { + return call_user_func($this->createClassCallable($listener), $event, $payload); + } + + $callable = $this->createClassCallable($listener); + + return $callable(...array_values($payload)); + }; + } + + /** + * Create the class based event callable. + * + * @param array|string $listener + * @return callable + */ + protected function createClassCallable($listener) + { + [$class, $method] = is_array($listener) + ? $listener + : $this->parseClassCallable($listener); + + if (! method_exists($class, $method)) { + $method = '__invoke'; + } + + if ($this->handlerShouldBeQueued($class)) { + return $this->createQueuedHandlerCallable($class, $method); + } + + $listener = $this->container->make($class); + + return $this->handlerShouldBeDispatchedAfterDatabaseTransactions($listener) + ? $this->createCallbackForListenerRunningAfterCommits($listener, $method) + : [$listener, $method]; + } + + /** + * Parse the class listener into class and method. + * + * @param string $listener + * @return array + */ + protected function parseClassCallable($listener) + { + return Str::parseCallback($listener, 'handle'); + } + + /** + * Determine if the event handler class should be queued. + * + * @param string $class + * @return bool + */ + protected function handlerShouldBeQueued($class) + { + try { + return (new ReflectionClass($class))->implementsInterface( + ShouldQueue::class + ); + } catch (Exception $e) { + return false; + } + } + + /** + * Create a callable for putting an event handler on the queue. + * + * @param string $class + * @param string $method + * @return \Closure + */ + protected function createQueuedHandlerCallable($class, $method) + { + return function () use ($class, $method) { + $arguments = array_map(function ($a) { + return is_object($a) ? clone $a : $a; + }, func_get_args()); + + if ($this->handlerWantsToBeQueued($class, $arguments)) { + $this->queueHandler($class, $method, $arguments); + } + }; + } + + /** + * Determine if the given event handler should be dispatched after all database transactions have committed. + * + * @param object|mixed $listener + * @return bool + */ + protected function handlerShouldBeDispatchedAfterDatabaseTransactions($listener) + { + return ($listener->afterCommit ?? null) && $this->container->bound('db.transactions'); + } + + /** + * Create a callable for dispatching a listener after database transactions. + * + * @param mixed $listener + * @param string $method + * @return \Closure + */ + protected function createCallbackForListenerRunningAfterCommits($listener, $method) + { + return function () use ($method, $listener) { + $payload = func_get_args(); + + $this->container->make('db.transactions')->addCallback( + function () use ($listener, $method, $payload) { + $listener->$method(...$payload); + } + ); + }; + } + + /** + * Determine if the event handler wants to be queued. + * + * @param string $class + * @param array $arguments + * @return bool + */ + protected function handlerWantsToBeQueued($class, $arguments) + { + $instance = $this->container->make($class); + + if (method_exists($instance, 'shouldQueue')) { + return $instance->shouldQueue($arguments[0]); + } + + return true; + } + + /** + * Queue the handler class. + * + * @param string $class + * @param string $method + * @param array $arguments + * @return void + */ + protected function queueHandler($class, $method, $arguments) + { + [$listener, $job] = $this->createListenerAndJob($class, $method, $arguments); + + $connection = $this->resolveQueue()->connection(method_exists($listener, 'viaConnection') + ? $listener->viaConnection() + : $listener->connection ?? null); + + $queue = method_exists($listener, 'viaQueue') + ? $listener->viaQueue() + : $listener->queue ?? null; + + isset($listener->delay) + ? $connection->laterOn($queue, $listener->delay, $job) + : $connection->pushOn($queue, $job); + } + + /** + * Create the listener and job for a queued listener. + * + * @param string $class + * @param string $method + * @param array $arguments + * @return array + */ + protected function createListenerAndJob($class, $method, $arguments) + { + $listener = (new ReflectionClass($class))->newInstanceWithoutConstructor(); + + return [$listener, $this->propagateListenerOptions( + $listener, new CallQueuedListener($class, $method, $arguments) + )]; + } + + /** + * Propagate listener options to the job. + * + * @param mixed $listener + * @param mixed $job + * @return mixed + */ + protected function propagateListenerOptions($listener, $job) + { + return tap($job, function ($job) use ($listener) { + $job->afterCommit = property_exists($listener, 'afterCommit') ? $listener->afterCommit : null; + $job->backoff = method_exists($listener, 'backoff') ? $listener->backoff() : ($listener->backoff ?? null); + $job->maxExceptions = $listener->maxExceptions ?? null; + $job->retryUntil = method_exists($listener, 'retryUntil') ? $listener->retryUntil() : null; + $job->shouldBeEncrypted = $listener instanceof ShouldBeEncrypted; + $job->timeout = $listener->timeout ?? null; + $job->tries = $listener->tries ?? null; + + $job->through(array_merge( + method_exists($listener, 'middleware') ? $listener->middleware() : [], + $listener->middleware ?? [] + )); + }); + } + + /** + * Remove a set of listeners from the dispatcher. + * + * @param string $event + * @return void + */ + public function forget($event) + { + if (Str::contains($event, '*')) { + unset($this->wildcards[$event]); + } else { + unset($this->listeners[$event]); + } + + foreach ($this->wildcardsCache as $key => $listeners) { + if (Str::is($event, $key)) { + unset($this->wildcardsCache[$key]); + } + } + } + + /** + * Forget all of the pushed listeners. + * + * @return void + */ + public function forgetPushed() + { + foreach ($this->listeners as $key => $value) { + if (Str::endsWith($key, '_pushed')) { + $this->forget($key); + } + } + } + + /** + * Get the queue implementation from the resolver. + * + * @return \Illuminate\Contracts\Queue\Queue + */ + protected function resolveQueue() + { + return call_user_func($this->queueResolver); + } + + /** + * Set the queue resolver implementation. + * + * @param callable $resolver + * @return $this + */ + public function setQueueResolver(callable $resolver) + { + $this->queueResolver = $resolver; + + return $this; + } +} diff --git a/vendor/illuminate/events/EventServiceProvider.php b/vendor/illuminate/events/EventServiceProvider.php new file mode 100644 index 0000000..15fb60b --- /dev/null +++ b/vendor/illuminate/events/EventServiceProvider.php @@ -0,0 +1,23 @@ +app->singleton('events', function ($app) { + return (new Dispatcher($app))->setQueueResolver(function () use ($app) { + return $app->make(QueueFactoryContract::class); + }); + }); + } +} diff --git a/vendor/illuminate/events/InvokeQueuedClosure.php b/vendor/illuminate/events/InvokeQueuedClosure.php new file mode 100644 index 0000000..bc68b19 --- /dev/null +++ b/vendor/illuminate/events/InvokeQueuedClosure.php @@ -0,0 +1,34 @@ +getClosure(), ...$arguments); + } + + /** + * Handle a job failure. + * + * @param \Laravel\SerializableClosure\SerializableClosure $closure + * @param array $arguments + * @param array $catchCallbacks + * @param \Throwable $exception + * @return void + */ + public function failed($closure, array $arguments, array $catchCallbacks, $exception) + { + $arguments[] = $exception; + + collect($catchCallbacks)->each->__invoke(...$arguments); + } +} diff --git a/vendor/illuminate/events/LICENSE.md b/vendor/illuminate/events/LICENSE.md new file mode 100644 index 0000000..79810c8 --- /dev/null +++ b/vendor/illuminate/events/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) Taylor Otwell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/illuminate/events/NullDispatcher.php b/vendor/illuminate/events/NullDispatcher.php new file mode 100644 index 0000000..5c539d5 --- /dev/null +++ b/vendor/illuminate/events/NullDispatcher.php @@ -0,0 +1,144 @@ +dispatcher = $dispatcher; + } + + /** + * Don't fire an event. + * + * @param string|object $event + * @param mixed $payload + * @param bool $halt + * @return void + */ + public function dispatch($event, $payload = [], $halt = false) + { + // + } + + /** + * Don't register an event and payload to be fired later. + * + * @param string $event + * @param array $payload + * @return void + */ + public function push($event, $payload = []) + { + // + } + + /** + * Don't dispatch an event. + * + * @param string|object $event + * @param mixed $payload + * @return array|null + */ + public function until($event, $payload = []) + { + // + } + + /** + * Register an event listener with the dispatcher. + * + * @param \Closure|string|array $events + * @param \Closure|string|array|null $listener + * @return void + */ + public function listen($events, $listener = null) + { + $this->dispatcher->listen($events, $listener); + } + + /** + * Determine if a given event has listeners. + * + * @param string $eventName + * @return bool + */ + public function hasListeners($eventName) + { + return $this->dispatcher->hasListeners($eventName); + } + + /** + * Register an event subscriber with the dispatcher. + * + * @param object|string $subscriber + * @return void + */ + public function subscribe($subscriber) + { + $this->dispatcher->subscribe($subscriber); + } + + /** + * Flush a set of pushed events. + * + * @param string $event + * @return void + */ + public function flush($event) + { + $this->dispatcher->flush($event); + } + + /** + * Remove a set of listeners from the dispatcher. + * + * @param string $event + * @return void + */ + public function forget($event) + { + $this->dispatcher->forget($event); + } + + /** + * Forget all of the queued listeners. + * + * @return void + */ + public function forgetPushed() + { + $this->dispatcher->forgetPushed(); + } + + /** + * Dynamically pass method calls to the underlying dispatcher. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + return $this->forwardCallTo($this->dispatcher, $method, $parameters); + } +} diff --git a/vendor/illuminate/events/QueuedClosure.php b/vendor/illuminate/events/QueuedClosure.php new file mode 100644 index 0000000..8259059 --- /dev/null +++ b/vendor/illuminate/events/QueuedClosure.php @@ -0,0 +1,125 @@ +closure = $closure; + } + + /** + * Set the desired connection for the job. + * + * @param string|null $connection + * @return $this + */ + public function onConnection($connection) + { + $this->connection = $connection; + + return $this; + } + + /** + * Set the desired queue for the job. + * + * @param string|null $queue + * @return $this + */ + public function onQueue($queue) + { + $this->queue = $queue; + + return $this; + } + + /** + * Set the desired delay for the job. + * + * @param \DateTimeInterface|\DateInterval|int|null $delay + * @return $this + */ + public function delay($delay) + { + $this->delay = $delay; + + return $this; + } + + /** + * Specify a callback that should be invoked if the queued listener job fails. + * + * @param \Closure $closure + * @return $this + */ + public function catch(Closure $closure) + { + $this->catchCallbacks[] = $closure; + + return $this; + } + + /** + * Resolve the actual event listener callback. + * + * @return \Closure + */ + public function resolve() + { + return function (...$arguments) { + dispatch(new CallQueuedListener(InvokeQueuedClosure::class, 'handle', [ + 'closure' => SerializableClosureFactory::make($this->closure), + 'arguments' => $arguments, + 'catch' => collect($this->catchCallbacks)->map(function ($callback) { + return SerializableClosureFactory::make($callback); + })->all(), + ]))->onConnection($this->connection)->onQueue($this->queue)->delay($this->delay); + }; + } +} diff --git a/vendor/illuminate/events/functions.php b/vendor/illuminate/events/functions.php new file mode 100644 index 0000000..df1b0fe --- /dev/null +++ b/vendor/illuminate/events/functions.php @@ -0,0 +1,18 @@ +getMethods( + ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED + ); + + foreach ($methods as $method) { + if ($replace || ! static::hasMacro($method->name)) { + $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::$macros[$name]); + } + + /** + * Flush the existing macros. + * + * @return void + */ + public static function flushMacros() + { + static::$macros = []; + } + + /** + * Dynamically handle calls to the class. + * + * @param string $method + * @param array $parameters + * @return mixed + * + * @throws \BadMethodCallException + */ + public static function __callStatic($method, $parameters) + { + if (! static::hasMacro($method)) { + throw new BadMethodCallException(sprintf( + 'Method %s::%s does not exist.', static::class, $method + )); + } + + $macro = static::$macros[$method]; + + if ($macro instanceof Closure) { + $macro = $macro->bindTo(null, static::class); + } + + return $macro(...$parameters); + } + + /** + * Dynamically handle calls to the class. + * + * @param string $method + * @param array $parameters + * @return mixed + * + * @throws \BadMethodCallException + */ + public function __call($method, $parameters) + { + if (! static::hasMacro($method)) { + throw new BadMethodCallException(sprintf( + 'Method %s::%s does not exist.', static::class, $method + )); + } + + $macro = static::$macros[$method]; + + if ($macro instanceof Closure) { + $macro = $macro->bindTo($this, static::class); + } + + return $macro(...$parameters); + } +} diff --git a/vendor/illuminate/pagination/AbstractCursorPaginator.php b/vendor/illuminate/pagination/AbstractCursorPaginator.php new file mode 100644 index 0000000..1234485 --- /dev/null +++ b/vendor/illuminate/pagination/AbstractCursorPaginator.php @@ -0,0 +1,676 @@ +cursorName => $cursor->encode()]; + + if (count($this->query) > 0) { + $parameters = array_merge($this->query, $parameters); + } + + return $this->path() + .(Str::contains($this->path(), '?') ? '&' : '?') + .Arr::query($parameters) + .$this->buildFragment(); + } + + /** + * Get the URL for the previous page. + * + * @return string|null + */ + public function previousPageUrl() + { + if (is_null($previousCursor = $this->previousCursor())) { + return null; + } + + return $this->url($previousCursor); + } + + /** + * The URL for the next page, or null. + * + * @return string|null + */ + public function nextPageUrl() + { + if (is_null($nextCursor = $this->nextCursor())) { + return null; + } + + return $this->url($nextCursor); + } + + /** + * Get the "cursor" that points to the previous set of items. + * + * @return \Illuminate\Pagination\Cursor|null + */ + public function previousCursor() + { + if (is_null($this->cursor) || + ($this->cursor->pointsToPreviousItems() && ! $this->hasMore)) { + return null; + } + + if ($this->items->isEmpty()) { + return null; + } + + return $this->getCursorForItem($this->items->first(), false); + } + + /** + * Get the "cursor" that points to the next set of items. + * + * @return \Illuminate\Pagination\Cursor|null + */ + public function nextCursor() + { + if ((is_null($this->cursor) && ! $this->hasMore) || + (! is_null($this->cursor) && $this->cursor->pointsToNextItems() && ! $this->hasMore)) { + return null; + } + + if ($this->items->isEmpty()) { + return null; + } + + return $this->getCursorForItem($this->items->last(), true); + } + + /** + * Get a cursor instance for the given item. + * + * @param \ArrayAccess|\stdClass $item + * @param bool $isNext + * @return \Illuminate\Pagination\Cursor + */ + public function getCursorForItem($item, $isNext = true) + { + return new Cursor($this->getParametersForItem($item), $isNext); + } + + /** + * Get the cursor parameters for a given object. + * + * @param \ArrayAccess|\stdClass $item + * @return array + * + * @throws \Exception + */ + public function getParametersForItem($item) + { + return collect($this->parameters) + ->flip() + ->map(function ($_, $parameterName) use ($item) { + if ($item instanceof JsonResource) { + $item = $item->resource; + } + + if ($item instanceof Model && + ! is_null($parameter = $this->getPivotParameterForItem($item, $parameterName))) { + return $parameter; + } elseif ($item instanceof ArrayAccess || is_array($item)) { + return $this->ensureParameterIsPrimitive( + $item[$parameterName] ?? $item[Str::afterLast($parameterName, '.')] + ); + } elseif (is_object($item)) { + return $this->ensureParameterIsPrimitive( + $item->{$parameterName} ?? $item->{Str::afterLast($parameterName, '.')} + ); + } + + throw new Exception('Only arrays and objects are supported when cursor paginating items.'); + })->toArray(); + } + + /** + * Get the cursor parameter value from a pivot model if applicable. + * + * @param \ArrayAccess|\stdClass $item + * @param string $parameterName + * @return string|null + */ + protected function getPivotParameterForItem($item, $parameterName) + { + $table = Str::beforeLast($parameterName, '.'); + + foreach ($item->getRelations() as $relation) { + if ($relation instanceof Pivot && $relation->getTable() === $table) { + return $this->ensureParameterIsPrimitive( + $relation->getAttribute(Str::afterLast($parameterName, '.')) + ); + } + } + } + + /** + * Ensure the parameter is a primitive type. + * + * This can resolve issues that arise the developer uses a value object for an attribute. + * + * @param mixed $parameter + * @return mixed + */ + protected function ensureParameterIsPrimitive($parameter) + { + return is_object($parameter) && method_exists($parameter, '__toString') + ? (string) $parameter + : $parameter; + } + + /** + * Get / set the URL fragment to be appended to URLs. + * + * @param string|null $fragment + * @return $this|string|null + */ + public function fragment($fragment = null) + { + if (is_null($fragment)) { + return $this->fragment; + } + + $this->fragment = $fragment; + + return $this; + } + + /** + * Add a set of query string values to the paginator. + * + * @param array|string|null $key + * @param string|null $value + * @return $this + */ + public function appends($key, $value = null) + { + if (is_null($key)) { + return $this; + } + + if (is_array($key)) { + return $this->appendArray($key); + } + + return $this->addQuery($key, $value); + } + + /** + * Add an array of query string values. + * + * @param array $keys + * @return $this + */ + protected function appendArray(array $keys) + { + foreach ($keys as $key => $value) { + $this->addQuery($key, $value); + } + + return $this; + } + + /** + * Add all current query string values to the paginator. + * + * @return $this + */ + public function withQueryString() + { + if (! is_null($query = Paginator::resolveQueryString())) { + return $this->appends($query); + } + + return $this; + } + + /** + * Add a query string value to the paginator. + * + * @param string $key + * @param string $value + * @return $this + */ + protected function addQuery($key, $value) + { + if ($key !== $this->cursorName) { + $this->query[$key] = $value; + } + + return $this; + } + + /** + * Build the full fragment portion of a URL. + * + * @return string + */ + protected function buildFragment() + { + return $this->fragment ? '#'.$this->fragment : ''; + } + + /** + * Load a set of relationships onto the mixed relationship collection. + * + * @param string $relation + * @param array $relations + * @return $this + */ + public function loadMorph($relation, $relations) + { + $this->getCollection()->loadMorph($relation, $relations); + + return $this; + } + + /** + * Load a set of relationship counts onto the mixed relationship collection. + * + * @param string $relation + * @param array $relations + * @return $this + */ + public function loadMorphCount($relation, $relations) + { + $this->getCollection()->loadMorphCount($relation, $relations); + + return $this; + } + + /** + * Get the slice of items being paginated. + * + * @return array + */ + public function items() + { + return $this->items->all(); + } + + /** + * Transform each item in the slice of items using a callback. + * + * @param callable $callback + * @return $this + */ + public function through(callable $callback) + { + $this->items->transform($callback); + + return $this; + } + + /** + * Get the number of items shown per page. + * + * @return int + */ + public function perPage() + { + return $this->perPage; + } + + /** + * Get the current cursor being paginated. + * + * @return \Illuminate\Pagination\Cursor|null + */ + public function cursor() + { + return $this->cursor; + } + + /** + * Get the query string variable used to store the cursor. + * + * @return string + */ + public function getCursorName() + { + return $this->cursorName; + } + + /** + * Set the query string variable used to store the cursor. + * + * @param string $name + * @return $this + */ + public function setCursorName($name) + { + $this->cursorName = $name; + + return $this; + } + + /** + * Set the base path to assign to all URLs. + * + * @param string $path + * @return $this + */ + public function withPath($path) + { + return $this->setPath($path); + } + + /** + * Set the base path to assign to all URLs. + * + * @param string $path + * @return $this + */ + public function setPath($path) + { + $this->path = $path; + + return $this; + } + + /** + * Get the base path for paginator generated URLs. + * + * @return string|null + */ + public function path() + { + return $this->path; + } + + /** + * Resolve the current cursor or return the default value. + * + * @param string $cursorName + * @return \Illuminate\Pagination\Cursor|null + */ + public static function resolveCurrentCursor($cursorName = 'cursor', $default = null) + { + if (isset(static::$currentCursorResolver)) { + return call_user_func(static::$currentCursorResolver, $cursorName); + } + + return $default; + } + + /** + * Set the current cursor resolver callback. + * + * @param \Closure $resolver + * @return void + */ + public static function currentCursorResolver(Closure $resolver) + { + static::$currentCursorResolver = $resolver; + } + + /** + * Get an instance of the view factory from the resolver. + * + * @return \Illuminate\Contracts\View\Factory + */ + public static function viewFactory() + { + return Paginator::viewFactory(); + } + + /** + * Get an iterator for the items. + * + * @return \ArrayIterator + */ + #[\ReturnTypeWillChange] + public function getIterator() + { + return $this->items->getIterator(); + } + + /** + * Determine if the list of items is empty. + * + * @return bool + */ + public function isEmpty() + { + return $this->items->isEmpty(); + } + + /** + * Determine if the list of items is not empty. + * + * @return bool + */ + public function isNotEmpty() + { + return $this->items->isNotEmpty(); + } + + /** + * Get the number of items for the current page. + * + * @return int + */ + #[\ReturnTypeWillChange] + public function count() + { + return $this->items->count(); + } + + /** + * Get the paginator's underlying collection. + * + * @return \Illuminate\Support\Collection + */ + public function getCollection() + { + return $this->items; + } + + /** + * Set the paginator's underlying collection. + * + * @param \Illuminate\Support\Collection $collection + * @return $this + */ + public function setCollection(Collection $collection) + { + $this->items = $collection; + + return $this; + } + + /** + * Get the paginator options. + * + * @return array + */ + public function getOptions() + { + return $this->options; + } + + /** + * Determine if the given item exists. + * + * @param mixed $key + * @return bool + */ + #[\ReturnTypeWillChange] + public function offsetExists($key) + { + return $this->items->has($key); + } + + /** + * Get the item at the given offset. + * + * @param mixed $key + * @return mixed + */ + #[\ReturnTypeWillChange] + public function offsetGet($key) + { + return $this->items->get($key); + } + + /** + * Set the item at the given offset. + * + * @param mixed $key + * @param mixed $value + * @return void + */ + #[\ReturnTypeWillChange] + public function offsetSet($key, $value) + { + $this->items->put($key, $value); + } + + /** + * Unset the item at the given key. + * + * @param mixed $key + * @return void + */ + #[\ReturnTypeWillChange] + public function offsetUnset($key) + { + $this->items->forget($key); + } + + /** + * Render the contents of the paginator to HTML. + * + * @return string + */ + public function toHtml() + { + return (string) $this->render(); + } + + /** + * Make dynamic calls into the collection. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + return $this->forwardCallTo($this->getCollection(), $method, $parameters); + } + + /** + * Render the contents of the paginator when casting to a string. + * + * @return string + */ + public function __toString() + { + return (string) $this->render(); + } +} diff --git a/vendor/illuminate/pagination/AbstractPaginator.php b/vendor/illuminate/pagination/AbstractPaginator.php new file mode 100644 index 0000000..ac9ef40 --- /dev/null +++ b/vendor/illuminate/pagination/AbstractPaginator.php @@ -0,0 +1,782 @@ += 1 && filter_var($page, FILTER_VALIDATE_INT) !== false; + } + + /** + * Get the URL for the previous page. + * + * @return string|null + */ + public function previousPageUrl() + { + if ($this->currentPage() > 1) { + return $this->url($this->currentPage() - 1); + } + } + + /** + * Create a range of pagination URLs. + * + * @param int $start + * @param int $end + * @return array + */ + public function getUrlRange($start, $end) + { + return collect(range($start, $end))->mapWithKeys(function ($page) { + return [$page => $this->url($page)]; + })->all(); + } + + /** + * Get the URL for a given page number. + * + * @param int $page + * @return string + */ + public function url($page) + { + if ($page <= 0) { + $page = 1; + } + + // If we have any extra query string key / value pairs that need to be added + // onto the URL, we will put them in query string form and then attach it + // to the URL. This allows for extra information like sortings storage. + $parameters = [$this->pageName => $page]; + + if (count($this->query) > 0) { + $parameters = array_merge($this->query, $parameters); + } + + return $this->path() + .(Str::contains($this->path(), '?') ? '&' : '?') + .Arr::query($parameters) + .$this->buildFragment(); + } + + /** + * Get / set the URL fragment to be appended to URLs. + * + * @param string|null $fragment + * @return $this|string|null + */ + public function fragment($fragment = null) + { + if (is_null($fragment)) { + return $this->fragment; + } + + $this->fragment = $fragment; + + return $this; + } + + /** + * Add a set of query string values to the paginator. + * + * @param array|string|null $key + * @param string|null $value + * @return $this + */ + public function appends($key, $value = null) + { + if (is_null($key)) { + return $this; + } + + if (is_array($key)) { + return $this->appendArray($key); + } + + return $this->addQuery($key, $value); + } + + /** + * Add an array of query string values. + * + * @param array $keys + * @return $this + */ + protected function appendArray(array $keys) + { + foreach ($keys as $key => $value) { + $this->addQuery($key, $value); + } + + return $this; + } + + /** + * Add all current query string values to the paginator. + * + * @return $this + */ + public function withQueryString() + { + if (isset(static::$queryStringResolver)) { + return $this->appends(call_user_func(static::$queryStringResolver)); + } + + return $this; + } + + /** + * Add a query string value to the paginator. + * + * @param string $key + * @param string $value + * @return $this + */ + protected function addQuery($key, $value) + { + if ($key !== $this->pageName) { + $this->query[$key] = $value; + } + + return $this; + } + + /** + * Build the full fragment portion of a URL. + * + * @return string + */ + protected function buildFragment() + { + return $this->fragment ? '#'.$this->fragment : ''; + } + + /** + * Load a set of relationships onto the mixed relationship collection. + * + * @param string $relation + * @param array $relations + * @return $this + */ + public function loadMorph($relation, $relations) + { + $this->getCollection()->loadMorph($relation, $relations); + + return $this; + } + + /** + * Load a set of relationship counts onto the mixed relationship collection. + * + * @param string $relation + * @param array $relations + * @return $this + */ + public function loadMorphCount($relation, $relations) + { + $this->getCollection()->loadMorphCount($relation, $relations); + + return $this; + } + + /** + * Get the slice of items being paginated. + * + * @return array + */ + public function items() + { + return $this->items->all(); + } + + /** + * Get the number of the first item in the slice. + * + * @return int + */ + public function firstItem() + { + return count($this->items) > 0 ? ($this->currentPage - 1) * $this->perPage + 1 : null; + } + + /** + * Get the number of the last item in the slice. + * + * @return int + */ + public function lastItem() + { + return count($this->items) > 0 ? $this->firstItem() + $this->count() - 1 : null; + } + + /** + * Transform each item in the slice of items using a callback. + * + * @param callable $callback + * @return $this + */ + public function through(callable $callback) + { + $this->items->transform($callback); + + return $this; + } + + /** + * Get the number of items shown per page. + * + * @return int + */ + public function perPage() + { + return $this->perPage; + } + + /** + * Determine if there are enough items to split into multiple pages. + * + * @return bool + */ + public function hasPages() + { + return $this->currentPage() != 1 || $this->hasMorePages(); + } + + /** + * Determine if the paginator is on the first page. + * + * @return bool + */ + public function onFirstPage() + { + return $this->currentPage() <= 1; + } + + /** + * Determine if the paginator is on the last page. + * + * @return bool + */ + public function onLastPage() + { + return ! $this->hasMorePages(); + } + + /** + * Get the current page. + * + * @return int + */ + public function currentPage() + { + return $this->currentPage; + } + + /** + * Get the query string variable used to store the page. + * + * @return string + */ + public function getPageName() + { + return $this->pageName; + } + + /** + * Set the query string variable used to store the page. + * + * @param string $name + * @return $this + */ + public function setPageName($name) + { + $this->pageName = $name; + + return $this; + } + + /** + * Set the base path to assign to all URLs. + * + * @param string $path + * @return $this + */ + public function withPath($path) + { + return $this->setPath($path); + } + + /** + * Set the base path to assign to all URLs. + * + * @param string $path + * @return $this + */ + public function setPath($path) + { + $this->path = $path; + + return $this; + } + + /** + * Set the number of links to display on each side of current page link. + * + * @param int $count + * @return $this + */ + public function onEachSide($count) + { + $this->onEachSide = $count; + + return $this; + } + + /** + * Get the base path for paginator generated URLs. + * + * @return string|null + */ + public function path() + { + return $this->path; + } + + /** + * Resolve the current request path or return the default value. + * + * @param string $default + * @return string + */ + public static function resolveCurrentPath($default = '/') + { + if (isset(static::$currentPathResolver)) { + return call_user_func(static::$currentPathResolver); + } + + return $default; + } + + /** + * Set the current request path resolver callback. + * + * @param \Closure $resolver + * @return void + */ + public static function currentPathResolver(Closure $resolver) + { + static::$currentPathResolver = $resolver; + } + + /** + * Resolve the current page or return the default value. + * + * @param string $pageName + * @param int $default + * @return int + */ + public static function resolveCurrentPage($pageName = 'page', $default = 1) + { + if (isset(static::$currentPageResolver)) { + return (int) call_user_func(static::$currentPageResolver, $pageName); + } + + return $default; + } + + /** + * Set the current page resolver callback. + * + * @param \Closure $resolver + * @return void + */ + public static function currentPageResolver(Closure $resolver) + { + static::$currentPageResolver = $resolver; + } + + /** + * Resolve the query string or return the default value. + * + * @param string|array|null $default + * @return string + */ + public static function resolveQueryString($default = null) + { + if (isset(static::$queryStringResolver)) { + return (static::$queryStringResolver)(); + } + + return $default; + } + + /** + * Set with query string resolver callback. + * + * @param \Closure $resolver + * @return void + */ + public static function queryStringResolver(Closure $resolver) + { + static::$queryStringResolver = $resolver; + } + + /** + * Get an instance of the view factory from the resolver. + * + * @return \Illuminate\Contracts\View\Factory + */ + public static function viewFactory() + { + return call_user_func(static::$viewFactoryResolver); + } + + /** + * Set the view factory resolver callback. + * + * @param \Closure $resolver + * @return void + */ + public static function viewFactoryResolver(Closure $resolver) + { + static::$viewFactoryResolver = $resolver; + } + + /** + * Set the default pagination view. + * + * @param string $view + * @return void + */ + public static function defaultView($view) + { + static::$defaultView = $view; + } + + /** + * Set the default "simple" pagination view. + * + * @param string $view + * @return void + */ + public static function defaultSimpleView($view) + { + static::$defaultSimpleView = $view; + } + + /** + * Indicate that Tailwind styling should be used for generated links. + * + * @return void + */ + public static function useTailwind() + { + static::defaultView('pagination::tailwind'); + static::defaultSimpleView('pagination::simple-tailwind'); + } + + /** + * Indicate that Bootstrap 4 styling should be used for generated links. + * + * @return void + */ + public static function useBootstrap() + { + static::defaultView('pagination::bootstrap-4'); + static::defaultSimpleView('pagination::simple-bootstrap-4'); + } + + /** + * Indicate that Bootstrap 3 styling should be used for generated links. + * + * @return void + */ + public static function useBootstrapThree() + { + static::defaultView('pagination::default'); + static::defaultSimpleView('pagination::simple-default'); + } + + /** + * Get an iterator for the items. + * + * @return \ArrayIterator + */ + #[\ReturnTypeWillChange] + public function getIterator() + { + return $this->items->getIterator(); + } + + /** + * Determine if the list of items is empty. + * + * @return bool + */ + public function isEmpty() + { + return $this->items->isEmpty(); + } + + /** + * Determine if the list of items is not empty. + * + * @return bool + */ + public function isNotEmpty() + { + return $this->items->isNotEmpty(); + } + + /** + * Get the number of items for the current page. + * + * @return int + */ + #[\ReturnTypeWillChange] + public function count() + { + return $this->items->count(); + } + + /** + * Get the paginator's underlying collection. + * + * @return \Illuminate\Support\Collection + */ + public function getCollection() + { + return $this->items; + } + + /** + * Set the paginator's underlying collection. + * + * @param \Illuminate\Support\Collection $collection + * @return $this + */ + public function setCollection(Collection $collection) + { + $this->items = $collection; + + return $this; + } + + /** + * Get the paginator options. + * + * @return array + */ + public function getOptions() + { + return $this->options; + } + + /** + * Determine if the given item exists. + * + * @param mixed $key + * @return bool + */ + #[\ReturnTypeWillChange] + public function offsetExists($key) + { + return $this->items->has($key); + } + + /** + * Get the item at the given offset. + * + * @param mixed $key + * @return mixed + */ + #[\ReturnTypeWillChange] + public function offsetGet($key) + { + return $this->items->get($key); + } + + /** + * Set the item at the given offset. + * + * @param mixed $key + * @param mixed $value + * @return void + */ + #[\ReturnTypeWillChange] + public function offsetSet($key, $value) + { + $this->items->put($key, $value); + } + + /** + * Unset the item at the given key. + * + * @param mixed $key + * @return void + */ + #[\ReturnTypeWillChange] + public function offsetUnset($key) + { + $this->items->forget($key); + } + + /** + * Render the contents of the paginator to HTML. + * + * @return string + */ + public function toHtml() + { + return (string) $this->render(); + } + + /** + * Make dynamic calls into the collection. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + return $this->forwardCallTo($this->getCollection(), $method, $parameters); + } + + /** + * Render the contents of the paginator when casting to a string. + * + * @return string + */ + public function __toString() + { + return (string) $this->render(); + } +} diff --git a/vendor/illuminate/pagination/Cursor.php b/vendor/illuminate/pagination/Cursor.php new file mode 100644 index 0000000..e8edf65 --- /dev/null +++ b/vendor/illuminate/pagination/Cursor.php @@ -0,0 +1,132 @@ +parameters = $parameters; + $this->pointsToNextItems = $pointsToNextItems; + } + + /** + * Get the given parameter from the cursor. + * + * @param string $parameterName + * @return string|null + * + * @throws \UnexpectedValueException + */ + public function parameter(string $parameterName) + { + if (! array_key_exists($parameterName, $this->parameters)) { + throw new UnexpectedValueException("Unable to find parameter [{$parameterName}] in pagination item."); + } + + return $this->parameters[$parameterName]; + } + + /** + * Get the given parameters from the cursor. + * + * @param array $parameterNames + * @return array + */ + public function parameters(array $parameterNames) + { + return collect($parameterNames)->map(function ($parameterName) { + return $this->parameter($parameterName); + })->toArray(); + } + + /** + * Determine whether the cursor points to the next set of items. + * + * @return bool + */ + public function pointsToNextItems() + { + return $this->pointsToNextItems; + } + + /** + * Determine whether the cursor points to the previous set of items. + * + * @return bool + */ + public function pointsToPreviousItems() + { + return ! $this->pointsToNextItems; + } + + /** + * Get the array representation of the cursor. + * + * @return array + */ + public function toArray() + { + return array_merge($this->parameters, [ + '_pointsToNextItems' => $this->pointsToNextItems, + ]); + } + + /** + * Get the encoded string representation of the cursor to construct a URL. + * + * @return string + */ + public function encode() + { + return str_replace(['+', '/', '='], ['-', '_', ''], base64_encode(json_encode($this->toArray()))); + } + + /** + * Get a cursor instance from the encoded string representation. + * + * @param string|null $encodedString + * @return static|null + */ + public static function fromEncoded($encodedString) + { + if (is_null($encodedString) || ! is_string($encodedString)) { + return null; + } + + $parameters = json_decode(base64_decode(str_replace(['-', '_'], ['+', '/'], $encodedString)), true); + + if (json_last_error() !== JSON_ERROR_NONE) { + return null; + } + + $pointsToNextItems = $parameters['_pointsToNextItems']; + + unset($parameters['_pointsToNextItems']); + + return new static($parameters, $pointsToNextItems); + } +} diff --git a/vendor/illuminate/pagination/CursorPaginationException.php b/vendor/illuminate/pagination/CursorPaginationException.php new file mode 100644 index 0000000..b12ca60 --- /dev/null +++ b/vendor/illuminate/pagination/CursorPaginationException.php @@ -0,0 +1,13 @@ +options = $options; + + foreach ($options as $key => $value) { + $this->{$key} = $value; + } + + $this->perPage = $perPage; + $this->cursor = $cursor; + $this->path = $this->path !== '/' ? rtrim($this->path, '/') : $this->path; + + $this->setItems($items); + } + + /** + * Set the items for the paginator. + * + * @param mixed $items + * @return void + */ + protected function setItems($items) + { + $this->items = $items instanceof Collection ? $items : Collection::make($items); + + $this->hasMore = $this->items->count() > $this->perPage; + + $this->items = $this->items->slice(0, $this->perPage); + + if (! is_null($this->cursor) && $this->cursor->pointsToPreviousItems()) { + $this->items = $this->items->reverse()->values(); + } + } + + /** + * Render the paginator using the given view. + * + * @param string|null $view + * @param array $data + * @return \Illuminate\Contracts\Support\Htmlable + */ + public function links($view = null, $data = []) + { + return $this->render($view, $data); + } + + /** + * Render the paginator using the given view. + * + * @param string|null $view + * @param array $data + * @return \Illuminate\Contracts\Support\Htmlable + */ + public function render($view = null, $data = []) + { + return static::viewFactory()->make($view ?: Paginator::$defaultSimpleView, array_merge($data, [ + 'paginator' => $this, + ])); + } + + /** + * Determine if there are more items in the data source. + * + * @return bool + */ + public function hasMorePages() + { + return (is_null($this->cursor) && $this->hasMore) || + (! is_null($this->cursor) && $this->cursor->pointsToNextItems() && $this->hasMore) || + (! is_null($this->cursor) && $this->cursor->pointsToPreviousItems()); + } + + /** + * Determine if there are enough items to split into multiple pages. + * + * @return bool + */ + public function hasPages() + { + return ! $this->onFirstPage() || $this->hasMorePages(); + } + + /** + * Determine if the paginator is on the first page. + * + * @return bool + */ + public function onFirstPage() + { + return is_null($this->cursor) || ($this->cursor->pointsToPreviousItems() && ! $this->hasMore); + } + + /** + * Get the instance as an array. + * + * @return array + */ + public function toArray() + { + return [ + 'data' => $this->items->toArray(), + 'path' => $this->path(), + 'per_page' => $this->perPage(), + 'next_page_url' => $this->nextPageUrl(), + 'prev_page_url' => $this->previousPageUrl(), + ]; + } + + /** + * Convert the object into something JSON serializable. + * + * @return array + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return $this->toArray(); + } + + /** + * Convert the object to its JSON representation. + * + * @param int $options + * @return string + */ + public function toJson($options = 0) + { + return json_encode($this->jsonSerialize(), $options); + } +} diff --git a/vendor/illuminate/pagination/LICENSE.md b/vendor/illuminate/pagination/LICENSE.md new file mode 100644 index 0000000..79810c8 --- /dev/null +++ b/vendor/illuminate/pagination/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) Taylor Otwell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/illuminate/pagination/LengthAwarePaginator.php b/vendor/illuminate/pagination/LengthAwarePaginator.php new file mode 100644 index 0000000..24f68b1 --- /dev/null +++ b/vendor/illuminate/pagination/LengthAwarePaginator.php @@ -0,0 +1,232 @@ +options = $options; + + foreach ($options as $key => $value) { + $this->{$key} = $value; + } + + $this->total = $total; + $this->perPage = $perPage; + $this->lastPage = max((int) ceil($total / $perPage), 1); + $this->path = $this->path !== '/' ? rtrim($this->path, '/') : $this->path; + $this->currentPage = $this->setCurrentPage($currentPage, $this->pageName); + $this->items = $items instanceof Collection ? $items : Collection::make($items); + } + + /** + * Get the current page for the request. + * + * @param int $currentPage + * @param string $pageName + * @return int + */ + protected function setCurrentPage($currentPage, $pageName) + { + $currentPage = $currentPage ?: static::resolveCurrentPage($pageName); + + return $this->isValidPageNumber($currentPage) ? (int) $currentPage : 1; + } + + /** + * Render the paginator using the given view. + * + * @param string|null $view + * @param array $data + * @return \Illuminate\Contracts\Support\Htmlable + */ + public function links($view = null, $data = []) + { + return $this->render($view, $data); + } + + /** + * Render the paginator using the given view. + * + * @param string|null $view + * @param array $data + * @return \Illuminate\Contracts\Support\Htmlable + */ + public function render($view = null, $data = []) + { + return static::viewFactory()->make($view ?: static::$defaultView, array_merge($data, [ + 'paginator' => $this, + 'elements' => $this->elements(), + ])); + } + + /** + * Get the paginator links as a collection (for JSON responses). + * + * @return \Illuminate\Support\Collection + */ + public function linkCollection() + { + return collect($this->elements())->flatMap(function ($item) { + if (! is_array($item)) { + return [['url' => null, 'label' => '...', 'active' => false]]; + } + + return collect($item)->map(function ($url, $page) { + return [ + 'url' => $url, + 'label' => (string) $page, + 'active' => $this->currentPage() === $page, + ]; + }); + })->prepend([ + 'url' => $this->previousPageUrl(), + 'label' => function_exists('__') ? __('pagination.previous') : 'Previous', + 'active' => false, + ])->push([ + 'url' => $this->nextPageUrl(), + 'label' => function_exists('__') ? __('pagination.next') : 'Next', + 'active' => false, + ]); + } + + /** + * Get the array of elements to pass to the view. + * + * @return array + */ + protected function elements() + { + $window = UrlWindow::make($this); + + return array_filter([ + $window['first'], + is_array($window['slider']) ? '...' : null, + $window['slider'], + is_array($window['last']) ? '...' : null, + $window['last'], + ]); + } + + /** + * Get the total number of items being paginated. + * + * @return int + */ + public function total() + { + return $this->total; + } + + /** + * Determine if there are more items in the data source. + * + * @return bool + */ + public function hasMorePages() + { + return $this->currentPage() < $this->lastPage(); + } + + /** + * Get the URL for the next page. + * + * @return string|null + */ + public function nextPageUrl() + { + if ($this->hasMorePages()) { + return $this->url($this->currentPage() + 1); + } + } + + /** + * Get the last page. + * + * @return int + */ + public function lastPage() + { + return $this->lastPage; + } + + /** + * Get the instance as an array. + * + * @return array + */ + public function toArray() + { + return [ + 'current_page' => $this->currentPage(), + 'data' => $this->items->toArray(), + 'first_page_url' => $this->url(1), + 'from' => $this->firstItem(), + 'last_page' => $this->lastPage(), + 'last_page_url' => $this->url($this->lastPage()), + 'links' => $this->linkCollection()->toArray(), + 'next_page_url' => $this->nextPageUrl(), + 'path' => $this->path(), + 'per_page' => $this->perPage(), + 'prev_page_url' => $this->previousPageUrl(), + 'to' => $this->lastItem(), + 'total' => $this->total(), + ]; + } + + /** + * Convert the object into something JSON serializable. + * + * @return array + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return $this->toArray(); + } + + /** + * Convert the object to its JSON representation. + * + * @param int $options + * @return string + */ + public function toJson($options = 0) + { + return json_encode($this->jsonSerialize(), $options); + } +} diff --git a/vendor/illuminate/pagination/PaginationServiceProvider.php b/vendor/illuminate/pagination/PaginationServiceProvider.php new file mode 100644 index 0000000..e94cebd --- /dev/null +++ b/vendor/illuminate/pagination/PaginationServiceProvider.php @@ -0,0 +1,34 @@ +loadViewsFrom(__DIR__.'/resources/views', 'pagination'); + + if ($this->app->runningInConsole()) { + $this->publishes([ + __DIR__.'/resources/views' => $this->app->resourcePath('views/vendor/pagination'), + ], 'laravel-pagination'); + } + } + + /** + * Register the service provider. + * + * @return void + */ + public function register() + { + PaginationState::resolveUsing($this->app); + } +} diff --git a/vendor/illuminate/pagination/PaginationState.php b/vendor/illuminate/pagination/PaginationState.php new file mode 100644 index 0000000..ff8150f --- /dev/null +++ b/vendor/illuminate/pagination/PaginationState.php @@ -0,0 +1,41 @@ +url(); + }); + + Paginator::currentPageResolver(function ($pageName = 'page') use ($app) { + $page = $app['request']->input($pageName); + + if (filter_var($page, FILTER_VALIDATE_INT) !== false && (int) $page >= 1) { + return (int) $page; + } + + return 1; + }); + + Paginator::queryStringResolver(function () use ($app) { + return $app['request']->query(); + }); + + CursorPaginator::currentCursorResolver(function ($cursorName = 'cursor') use ($app) { + return Cursor::fromEncoded($app['request']->input($cursorName)); + }); + } +} diff --git a/vendor/illuminate/pagination/Paginator.php b/vendor/illuminate/pagination/Paginator.php new file mode 100644 index 0000000..733edb8 --- /dev/null +++ b/vendor/illuminate/pagination/Paginator.php @@ -0,0 +1,177 @@ +options = $options; + + foreach ($options as $key => $value) { + $this->{$key} = $value; + } + + $this->perPage = $perPage; + $this->currentPage = $this->setCurrentPage($currentPage); + $this->path = $this->path !== '/' ? rtrim($this->path, '/') : $this->path; + + $this->setItems($items); + } + + /** + * Get the current page for the request. + * + * @param int $currentPage + * @return int + */ + protected function setCurrentPage($currentPage) + { + $currentPage = $currentPage ?: static::resolveCurrentPage(); + + return $this->isValidPageNumber($currentPage) ? (int) $currentPage : 1; + } + + /** + * Set the items for the paginator. + * + * @param mixed $items + * @return void + */ + protected function setItems($items) + { + $this->items = $items instanceof Collection ? $items : Collection::make($items); + + $this->hasMore = $this->items->count() > $this->perPage; + + $this->items = $this->items->slice(0, $this->perPage); + } + + /** + * Get the URL for the next page. + * + * @return string|null + */ + public function nextPageUrl() + { + if ($this->hasMorePages()) { + return $this->url($this->currentPage() + 1); + } + } + + /** + * Render the paginator using the given view. + * + * @param string|null $view + * @param array $data + * @return string + */ + public function links($view = null, $data = []) + { + return $this->render($view, $data); + } + + /** + * Render the paginator using the given view. + * + * @param string|null $view + * @param array $data + * @return \Illuminate\Contracts\Support\Htmlable + */ + public function render($view = null, $data = []) + { + return static::viewFactory()->make($view ?: static::$defaultSimpleView, array_merge($data, [ + 'paginator' => $this, + ])); + } + + /** + * Manually indicate that the paginator does have more pages. + * + * @param bool $hasMore + * @return $this + */ + public function hasMorePagesWhen($hasMore = true) + { + $this->hasMore = $hasMore; + + return $this; + } + + /** + * Determine if there are more items in the data source. + * + * @return bool + */ + public function hasMorePages() + { + return $this->hasMore; + } + + /** + * Get the instance as an array. + * + * @return array + */ + public function toArray() + { + return [ + 'current_page' => $this->currentPage(), + 'data' => $this->items->toArray(), + 'first_page_url' => $this->url(1), + 'from' => $this->firstItem(), + 'next_page_url' => $this->nextPageUrl(), + 'path' => $this->path(), + 'per_page' => $this->perPage(), + 'prev_page_url' => $this->previousPageUrl(), + 'to' => $this->lastItem(), + ]; + } + + /** + * Convert the object into something JSON serializable. + * + * @return array + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return $this->toArray(); + } + + /** + * Convert the object to its JSON representation. + * + * @param int $options + * @return string + */ + public function toJson($options = 0) + { + return json_encode($this->jsonSerialize(), $options); + } +} diff --git a/vendor/illuminate/pagination/UrlWindow.php b/vendor/illuminate/pagination/UrlWindow.php new file mode 100644 index 0000000..31c7cc2 --- /dev/null +++ b/vendor/illuminate/pagination/UrlWindow.php @@ -0,0 +1,220 @@ +paginator = $paginator; + } + + /** + * Create a new URL window instance. + * + * @param \Illuminate\Contracts\Pagination\LengthAwarePaginator $paginator + * @return array + */ + public static function make(PaginatorContract $paginator) + { + return (new static($paginator))->get(); + } + + /** + * Get the window of URLs to be shown. + * + * @return array + */ + public function get() + { + $onEachSide = $this->paginator->onEachSide; + + if ($this->paginator->lastPage() < ($onEachSide * 2) + 8) { + return $this->getSmallSlider(); + } + + return $this->getUrlSlider($onEachSide); + } + + /** + * Get the slider of URLs there are not enough pages to slide. + * + * @return array + */ + protected function getSmallSlider() + { + return [ + 'first' => $this->paginator->getUrlRange(1, $this->lastPage()), + 'slider' => null, + 'last' => null, + ]; + } + + /** + * Create a URL slider links. + * + * @param int $onEachSide + * @return array + */ + protected function getUrlSlider($onEachSide) + { + $window = $onEachSide + 4; + + if (! $this->hasPages()) { + return ['first' => null, 'slider' => null, 'last' => null]; + } + + // If the current page is very close to the beginning of the page range, we will + // just render the beginning of the page range, followed by the last 2 of the + // links in this list, since we will not have room to create a full slider. + if ($this->currentPage() <= $window) { + return $this->getSliderTooCloseToBeginning($window, $onEachSide); + } + + // If the current page is close to the ending of the page range we will just get + // this first couple pages, followed by a larger window of these ending pages + // since we're too close to the end of the list to create a full on slider. + elseif ($this->currentPage() > ($this->lastPage() - $window)) { + return $this->getSliderTooCloseToEnding($window, $onEachSide); + } + + // If we have enough room on both sides of the current page to build a slider we + // will surround it with both the beginning and ending caps, with this window + // of pages in the middle providing a Google style sliding paginator setup. + return $this->getFullSlider($onEachSide); + } + + /** + * Get the slider of URLs when too close to beginning of window. + * + * @param int $window + * @param int $onEachSide + * @return array + */ + protected function getSliderTooCloseToBeginning($window, $onEachSide) + { + return [ + 'first' => $this->paginator->getUrlRange(1, $window + $onEachSide), + 'slider' => null, + 'last' => $this->getFinish(), + ]; + } + + /** + * Get the slider of URLs when too close to ending of window. + * + * @param int $window + * @param int $onEachSide + * @return array + */ + protected function getSliderTooCloseToEnding($window, $onEachSide) + { + $last = $this->paginator->getUrlRange( + $this->lastPage() - ($window + ($onEachSide - 1)), + $this->lastPage() + ); + + return [ + 'first' => $this->getStart(), + 'slider' => null, + 'last' => $last, + ]; + } + + /** + * Get the slider of URLs when a full slider can be made. + * + * @param int $onEachSide + * @return array + */ + protected function getFullSlider($onEachSide) + { + return [ + 'first' => $this->getStart(), + 'slider' => $this->getAdjacentUrlRange($onEachSide), + 'last' => $this->getFinish(), + ]; + } + + /** + * Get the page range for the current page window. + * + * @param int $onEachSide + * @return array + */ + public function getAdjacentUrlRange($onEachSide) + { + return $this->paginator->getUrlRange( + $this->currentPage() - $onEachSide, + $this->currentPage() + $onEachSide + ); + } + + /** + * Get the starting URLs of a pagination slider. + * + * @return array + */ + public function getStart() + { + return $this->paginator->getUrlRange(1, 2); + } + + /** + * Get the ending URLs of a pagination slider. + * + * @return array + */ + public function getFinish() + { + return $this->paginator->getUrlRange( + $this->lastPage() - 1, + $this->lastPage() + ); + } + + /** + * Determine if the underlying paginator being presented has pages to show. + * + * @return bool + */ + public function hasPages() + { + return $this->paginator->lastPage() > 1; + } + + /** + * Get the current page from the paginator. + * + * @return int + */ + protected function currentPage() + { + return $this->paginator->currentPage(); + } + + /** + * Get the last page from the paginator. + * + * @return int + */ + protected function lastPage() + { + return $this->paginator->lastPage(); + } +} diff --git a/vendor/illuminate/pagination/resources/views/bootstrap-4.blade.php b/vendor/illuminate/pagination/resources/views/bootstrap-4.blade.php new file mode 100644 index 0000000..63c6f56 --- /dev/null +++ b/vendor/illuminate/pagination/resources/views/bootstrap-4.blade.php @@ -0,0 +1,46 @@ +@if ($paginator->hasPages()) + +@endif diff --git a/vendor/illuminate/pagination/resources/views/default.blade.php b/vendor/illuminate/pagination/resources/views/default.blade.php new file mode 100644 index 0000000..0db70b5 --- /dev/null +++ b/vendor/illuminate/pagination/resources/views/default.blade.php @@ -0,0 +1,46 @@ +@if ($paginator->hasPages()) + +@endif diff --git a/vendor/illuminate/pagination/resources/views/semantic-ui.blade.php b/vendor/illuminate/pagination/resources/views/semantic-ui.blade.php new file mode 100644 index 0000000..ef0dbb1 --- /dev/null +++ b/vendor/illuminate/pagination/resources/views/semantic-ui.blade.php @@ -0,0 +1,36 @@ +@if ($paginator->hasPages()) + +@endif diff --git a/vendor/illuminate/pagination/resources/views/simple-bootstrap-4.blade.php b/vendor/illuminate/pagination/resources/views/simple-bootstrap-4.blade.php new file mode 100644 index 0000000..4bb4917 --- /dev/null +++ b/vendor/illuminate/pagination/resources/views/simple-bootstrap-4.blade.php @@ -0,0 +1,27 @@ +@if ($paginator->hasPages()) + +@endif diff --git a/vendor/illuminate/pagination/resources/views/simple-default.blade.php b/vendor/illuminate/pagination/resources/views/simple-default.blade.php new file mode 100644 index 0000000..36bdbc1 --- /dev/null +++ b/vendor/illuminate/pagination/resources/views/simple-default.blade.php @@ -0,0 +1,19 @@ +@if ($paginator->hasPages()) + +@endif diff --git a/vendor/illuminate/pagination/resources/views/simple-tailwind.blade.php b/vendor/illuminate/pagination/resources/views/simple-tailwind.blade.php new file mode 100644 index 0000000..6872cca --- /dev/null +++ b/vendor/illuminate/pagination/resources/views/simple-tailwind.blade.php @@ -0,0 +1,25 @@ +@if ($paginator->hasPages()) + +@endif diff --git a/vendor/illuminate/pagination/resources/views/tailwind.blade.php b/vendor/illuminate/pagination/resources/views/tailwind.blade.php new file mode 100644 index 0000000..5bf323b --- /dev/null +++ b/vendor/illuminate/pagination/resources/views/tailwind.blade.php @@ -0,0 +1,106 @@ +@if ($paginator->hasPages()) + +@endif diff --git a/vendor/illuminate/pipeline/Hub.php b/vendor/illuminate/pipeline/Hub.php new file mode 100644 index 0000000..91e9b3f --- /dev/null +++ b/vendor/illuminate/pipeline/Hub.php @@ -0,0 +1,97 @@ +container = $container; + } + + /** + * Define the default named pipeline. + * + * @param \Closure $callback + * @return void + */ + public function defaults(Closure $callback) + { + return $this->pipeline('default', $callback); + } + + /** + * Define a new named pipeline. + * + * @param string $name + * @param \Closure $callback + * @return void + */ + public function pipeline($name, Closure $callback) + { + $this->pipelines[$name] = $callback; + } + + /** + * Send an object through one of the available pipelines. + * + * @param mixed $object + * @param string|null $pipeline + * @return mixed + */ + public function pipe($object, $pipeline = null) + { + $pipeline = $pipeline ?: 'default'; + + return call_user_func( + $this->pipelines[$pipeline], new Pipeline($this->container), $object + ); + } + + /** + * Get the container instance used by the hub. + * + * @return \Illuminate\Contracts\Container\Container + */ + public function getContainer() + { + return $this->container; + } + + /** + * Set the container instance used by the hub. + * + * @param \Illuminate\Contracts\Container\Container $container + * @return $this + */ + public function setContainer(Container $container) + { + $this->container = $container; + + return $this; + } +} diff --git a/vendor/illuminate/pipeline/LICENSE.md b/vendor/illuminate/pipeline/LICENSE.md new file mode 100644 index 0000000..79810c8 --- /dev/null +++ b/vendor/illuminate/pipeline/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) Taylor Otwell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/illuminate/pipeline/Pipeline.php b/vendor/illuminate/pipeline/Pipeline.php new file mode 100644 index 0000000..d2924e5 --- /dev/null +++ b/vendor/illuminate/pipeline/Pipeline.php @@ -0,0 +1,258 @@ +container = $container; + } + + /** + * Set the object being sent through the pipeline. + * + * @param mixed $passable + * @return $this + */ + public function send($passable) + { + $this->passable = $passable; + + return $this; + } + + /** + * Set the array of pipes. + * + * @param array|mixed $pipes + * @return $this + */ + public function through($pipes) + { + $this->pipes = is_array($pipes) ? $pipes : func_get_args(); + + return $this; + } + + /** + * Set the method to call on the pipes. + * + * @param string $method + * @return $this + */ + public function via($method) + { + $this->method = $method; + + return $this; + } + + /** + * Run the pipeline with a final destination callback. + * + * @param \Closure $destination + * @return mixed + */ + public function then(Closure $destination) + { + $pipeline = array_reduce( + array_reverse($this->pipes()), $this->carry(), $this->prepareDestination($destination) + ); + + return $pipeline($this->passable); + } + + /** + * Run the pipeline and return the result. + * + * @return mixed + */ + public function thenReturn() + { + return $this->then(function ($passable) { + return $passable; + }); + } + + /** + * Get the final piece of the Closure onion. + * + * @param \Closure $destination + * @return \Closure + */ + protected function prepareDestination(Closure $destination) + { + return function ($passable) use ($destination) { + try { + return $destination($passable); + } catch (Throwable $e) { + return $this->handleException($passable, $e); + } + }; + } + + /** + * Get a Closure that represents a slice of the application onion. + * + * @return \Closure + */ + protected function carry() + { + return function ($stack, $pipe) { + return function ($passable) use ($stack, $pipe) { + try { + if (is_callable($pipe)) { + // If the pipe is a callable, then we will call it directly, but otherwise we + // will resolve the pipes out of the dependency container and call it with + // the appropriate method and arguments, returning the results back out. + return $pipe($passable, $stack); + } elseif (! is_object($pipe)) { + [$name, $parameters] = $this->parsePipeString($pipe); + + // If the pipe is a string we will parse the string and resolve the class out + // of the dependency injection container. We can then build a callable and + // execute the pipe function giving in the parameters that are required. + $pipe = $this->getContainer()->make($name); + + $parameters = array_merge([$passable, $stack], $parameters); + } else { + // If the pipe is already an object we'll just make a callable and pass it to + // the pipe as-is. There is no need to do any extra parsing and formatting + // since the object we're given was already a fully instantiated object. + $parameters = [$passable, $stack]; + } + + $carry = method_exists($pipe, $this->method) + ? $pipe->{$this->method}(...$parameters) + : $pipe(...$parameters); + + return $this->handleCarry($carry); + } catch (Throwable $e) { + return $this->handleException($passable, $e); + } + }; + }; + } + + /** + * Parse full pipe string to get name and parameters. + * + * @param string $pipe + * @return array + */ + protected function parsePipeString($pipe) + { + [$name, $parameters] = array_pad(explode(':', $pipe, 2), 2, []); + + if (is_string($parameters)) { + $parameters = explode(',', $parameters); + } + + return [$name, $parameters]; + } + + /** + * Get the array of configured pipes. + * + * @return array + */ + protected function pipes() + { + return $this->pipes; + } + + /** + * Get the container instance. + * + * @return \Illuminate\Contracts\Container\Container + * + * @throws \RuntimeException + */ + protected function getContainer() + { + if (! $this->container) { + throw new RuntimeException('A container instance has not been passed to the Pipeline.'); + } + + return $this->container; + } + + /** + * Set the container instance. + * + * @param \Illuminate\Contracts\Container\Container $container + * @return $this + */ + public function setContainer(Container $container) + { + $this->container = $container; + + return $this; + } + + /** + * Handle the value returned from each pipe before passing it to the next. + * + * @param mixed $carry + * @return mixed + */ + protected function handleCarry($carry) + { + return $carry; + } + + /** + * Handle the given exception. + * + * @param mixed $passable + * @param \Throwable $e + * @return mixed + * + * @throws \Throwable + */ + protected function handleException($passable, Throwable $e) + { + throw $e; + } +} diff --git a/vendor/illuminate/pipeline/PipelineServiceProvider.php b/vendor/illuminate/pipeline/PipelineServiceProvider.php new file mode 100644 index 0000000..982608b --- /dev/null +++ b/vendor/illuminate/pipeline/PipelineServiceProvider.php @@ -0,0 +1,34 @@ +app->singleton( + PipelineHubContract::class, Hub::class + ); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return [ + PipelineHubContract::class, + ]; + } +} diff --git a/vendor/illuminate/redis/Connections/Connection.php b/vendor/illuminate/redis/Connections/Connection.php new file mode 100644 index 0000000..95af91e --- /dev/null +++ b/vendor/illuminate/redis/Connections/Connection.php @@ -0,0 +1,222 @@ +client; + } + + /** + * Subscribe to a set of given channels for messages. + * + * @param array|string $channels + * @param \Closure $callback + * @return void + */ + public function subscribe($channels, Closure $callback) + { + return $this->createSubscription($channels, $callback, __FUNCTION__); + } + + /** + * Subscribe to a set of given channels with wildcards. + * + * @param array|string $channels + * @param \Closure $callback + * @return void + */ + public function psubscribe($channels, Closure $callback) + { + return $this->createSubscription($channels, $callback, __FUNCTION__); + } + + /** + * Run a command against the Redis database. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function command($method, array $parameters = []) + { + $start = microtime(true); + + $result = $this->client->{$method}(...$parameters); + + $time = round((microtime(true) - $start) * 1000, 2); + + if (isset($this->events)) { + $this->event(new CommandExecuted($method, $parameters, $time, $this)); + } + + return $result; + } + + /** + * Fire the given event if possible. + * + * @param mixed $event + * @return void + */ + protected function event($event) + { + if (isset($this->events)) { + $this->events->dispatch($event); + } + } + + /** + * Register a Redis command listener with the connection. + * + * @param \Closure $callback + * @return void + */ + public function listen(Closure $callback) + { + if (isset($this->events)) { + $this->events->listen(CommandExecuted::class, $callback); + } + } + + /** + * Get the connection name. + * + * @return string|null + */ + public function getName() + { + return $this->name; + } + + /** + * Set the connections name. + * + * @param string $name + * @return $this + */ + public function setName($name) + { + $this->name = $name; + + return $this; + } + + /** + * Get the event dispatcher used by the connection. + * + * @return \Illuminate\Contracts\Events\Dispatcher + */ + public function getEventDispatcher() + { + return $this->events; + } + + /** + * Set the event dispatcher instance on the connection. + * + * @param \Illuminate\Contracts\Events\Dispatcher $events + * @return void + */ + public function setEventDispatcher(Dispatcher $events) + { + $this->events = $events; + } + + /** + * Unset the event dispatcher instance on the connection. + * + * @return void + */ + public function unsetEventDispatcher() + { + $this->events = null; + } + + /** + * Pass other method calls down to the underlying client. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + if (static::hasMacro($method)) { + return $this->macroCall($method, $parameters); + } + + return $this->command($method, $parameters); + } +} diff --git a/vendor/illuminate/redis/Connections/PacksPhpRedisValues.php b/vendor/illuminate/redis/Connections/PacksPhpRedisValues.php new file mode 100644 index 0000000..4d27ff5 --- /dev/null +++ b/vendor/illuminate/redis/Connections/PacksPhpRedisValues.php @@ -0,0 +1,183 @@ + $values + * @return array + */ + public function pack(array $values): array + { + if (empty($values)) { + return $values; + } + + if ($this->supportsPacking()) { + return array_map([$this->client, '_pack'], $values); + } + + if ($this->compressed()) { + if ($this->supportsLzf() && $this->lzfCompressed()) { + if (! function_exists('lzf_compress')) { + throw new RuntimeException("'lzf' extension required to call 'lzf_compress'."); + } + + $processor = function ($value) { + return \lzf_compress($this->client->_serialize($value)); + }; + } elseif ($this->supportsZstd() && $this->zstdCompressed()) { + if (! function_exists('zstd_compress')) { + throw new RuntimeException("'zstd' extension required to call 'zstd_compress'."); + } + + $compressionLevel = $this->client->getOption(Redis::OPT_COMPRESSION_LEVEL); + + $processor = function ($value) use ($compressionLevel) { + return \zstd_compress( + $this->client->_serialize($value), + $compressionLevel === 0 ? Redis::COMPRESSION_ZSTD_DEFAULT : $compressionLevel + ); + }; + } else { + throw new UnexpectedValueException(sprintf( + 'Unsupported phpredis compression in use [%d].', + $this->client->getOption(Redis::OPT_COMPRESSION) + )); + } + } else { + $processor = function ($value) { + return $this->client->_serialize($value); + }; + } + + return array_map($processor, $values); + } + + /** + * Determine if compression is enabled. + * + * @return bool + */ + public function compressed(): bool + { + return defined('Redis::OPT_COMPRESSION') && + $this->client->getOption(Redis::OPT_COMPRESSION) !== Redis::COMPRESSION_NONE; + } + + /** + * Determine if LZF compression is enabled. + * + * @return bool + */ + public function lzfCompressed(): bool + { + return defined('Redis::COMPRESSION_LZF') && + $this->client->getOption(Redis::OPT_COMPRESSION) === Redis::COMPRESSION_LZF; + } + + /** + * Determine if ZSTD compression is enabled. + * + * @return bool + */ + public function zstdCompressed(): bool + { + return defined('Redis::COMPRESSION_ZSTD') && + $this->client->getOption(Redis::OPT_COMPRESSION) === Redis::COMPRESSION_ZSTD; + } + + /** + * Determine if LZ4 compression is enabled. + * + * @return bool + */ + public function lz4Compressed(): bool + { + return defined('Redis::COMPRESSION_LZ4') && + $this->client->getOption(Redis::OPT_COMPRESSION) === Redis::COMPRESSION_LZ4; + } + + /** + * Determine if the current PhpRedis extension version supports packing. + * + * @return bool + */ + protected function supportsPacking(): bool + { + if ($this->supportsPacking === null) { + $this->supportsPacking = $this->phpRedisVersionAtLeast('5.3.5'); + } + + return $this->supportsPacking; + } + + /** + * Determine if the current PhpRedis extension version supports LZF compression. + * + * @return bool + */ + protected function supportsLzf(): bool + { + if ($this->supportsLzf === null) { + $this->supportsLzf = $this->phpRedisVersionAtLeast('4.3.0'); + } + + return $this->supportsLzf; + } + + /** + * Determine if the current PhpRedis extension version supports Zstd compression. + * + * @return bool + */ + protected function supportsZstd(): bool + { + if ($this->supportsZstd === null) { + $this->supportsZstd = $this->phpRedisVersionAtLeast('5.1.0'); + } + + return $this->supportsZstd; + } + + /** + * Determine if the PhpRedis extension version is at least the given version. + * + * @param string $version + * @return bool + */ + protected function phpRedisVersionAtLeast(string $version): bool + { + $phpredisVersion = phpversion('redis'); + + return $phpredisVersion !== false && version_compare($phpredisVersion, $version, '>='); + } +} diff --git a/vendor/illuminate/redis/Connections/PhpRedisClusterConnection.php b/vendor/illuminate/redis/Connections/PhpRedisClusterConnection.php new file mode 100644 index 0000000..bf4816a --- /dev/null +++ b/vendor/illuminate/redis/Connections/PhpRedisClusterConnection.php @@ -0,0 +1,24 @@ +client->_masters() as $master) { + $async + ? $this->command('rawCommand', [$master, 'flushdb', 'async']) + : $this->command('flushdb', [$master]); + } + } +} diff --git a/vendor/illuminate/redis/Connections/PhpRedisConnection.php b/vendor/illuminate/redis/Connections/PhpRedisConnection.php new file mode 100644 index 0000000..4e68547 --- /dev/null +++ b/vendor/illuminate/redis/Connections/PhpRedisConnection.php @@ -0,0 +1,577 @@ +client = $client; + $this->config = $config; + $this->connector = $connector; + } + + /** + * Returns the value of the given key. + * + * @param string $key + * @return string|null + */ + public function get($key) + { + $result = $this->command('get', [$key]); + + return $result !== false ? $result : null; + } + + /** + * Get the values of all the given keys. + * + * @param array $keys + * @return array + */ + public function mget(array $keys) + { + return array_map(function ($value) { + return $value !== false ? $value : null; + }, $this->command('mget', [$keys])); + } + + /** + * Set the string value in the argument as the value of the key. + * + * @param string $key + * @param mixed $value + * @param string|null $expireResolution + * @param int|null $expireTTL + * @param string|null $flag + * @return bool + */ + public function set($key, $value, $expireResolution = null, $expireTTL = null, $flag = null) + { + return $this->command('set', [ + $key, + $value, + $expireResolution ? [$flag, $expireResolution => $expireTTL] : null, + ]); + } + + /** + * Set the given key if it doesn't exist. + * + * @param string $key + * @param string $value + * @return int + */ + public function setnx($key, $value) + { + return (int) $this->command('setnx', [$key, $value]); + } + + /** + * Get the value of the given hash fields. + * + * @param string $key + * @param mixed $dictionary + * @return array + */ + public function hmget($key, ...$dictionary) + { + if (count($dictionary) === 1) { + $dictionary = $dictionary[0]; + } + + return array_values($this->command('hmget', [$key, $dictionary])); + } + + /** + * Set the given hash fields to their respective values. + * + * @param string $key + * @param mixed $dictionary + * @return int + */ + public function hmset($key, ...$dictionary) + { + if (count($dictionary) === 1) { + $dictionary = $dictionary[0]; + } else { + $input = collect($dictionary); + + $dictionary = $input->nth(2)->combine($input->nth(2, 1))->toArray(); + } + + return $this->command('hmset', [$key, $dictionary]); + } + + /** + * Set the given hash field if it doesn't exist. + * + * @param string $hash + * @param string $key + * @param string $value + * @return int + */ + public function hsetnx($hash, $key, $value) + { + return (int) $this->command('hsetnx', [$hash, $key, $value]); + } + + /** + * Removes the first count occurrences of the value element from the list. + * + * @param string $key + * @param int $count + * @param mixed $value + * @return int|false + */ + public function lrem($key, $count, $value) + { + return $this->command('lrem', [$key, $value, $count]); + } + + /** + * Removes and returns the first element of the list stored at key. + * + * @param mixed $arguments + * @return array|null + */ + public function blpop(...$arguments) + { + $result = $this->command('blpop', $arguments); + + return empty($result) ? null : $result; + } + + /** + * Removes and returns the last element of the list stored at key. + * + * @param mixed $arguments + * @return array|null + */ + public function brpop(...$arguments) + { + $result = $this->command('brpop', $arguments); + + return empty($result) ? null : $result; + } + + /** + * Removes and returns a random element from the set value at key. + * + * @param string $key + * @param int|null $count + * @return mixed|false + */ + public function spop($key, $count = 1) + { + return $this->command('spop', func_get_args()); + } + + /** + * Add one or more members to a sorted set or update its score if it already exists. + * + * @param string $key + * @param mixed $dictionary + * @return int + */ + public function zadd($key, ...$dictionary) + { + if (is_array(end($dictionary))) { + foreach (array_pop($dictionary) as $member => $score) { + $dictionary[] = $score; + $dictionary[] = $member; + } + } + + $options = []; + + foreach (array_slice($dictionary, 0, 3) as $i => $value) { + if (in_array($value, ['nx', 'xx', 'ch', 'incr', 'gt', 'lt', 'NX', 'XX', 'CH', 'INCR', 'GT', 'LT'], true)) { + $options[] = $value; + + unset($dictionary[$i]); + } + } + + return $this->command('zadd', array_merge([$key], [$options], array_values($dictionary))); + } + + /** + * Return elements with score between $min and $max. + * + * @param string $key + * @param mixed $min + * @param mixed $max + * @param array $options + * @return array + */ + public function zrangebyscore($key, $min, $max, $options = []) + { + if (isset($options['limit']) && Arr::isAssoc($options['limit'])) { + $options['limit'] = [ + $options['limit']['offset'], + $options['limit']['count'], + ]; + } + + return $this->command('zRangeByScore', [$key, $min, $max, $options]); + } + + /** + * Return elements with score between $min and $max. + * + * @param string $key + * @param mixed $min + * @param mixed $max + * @param array $options + * @return array + */ + public function zrevrangebyscore($key, $min, $max, $options = []) + { + if (isset($options['limit']) && Arr::isAssoc($options['limit'])) { + $options['limit'] = [ + $options['limit']['offset'], + $options['limit']['count'], + ]; + } + + return $this->command('zRevRangeByScore', [$key, $min, $max, $options]); + } + + /** + * Find the intersection between sets and store in a new set. + * + * @param string $output + * @param array $keys + * @param array $options + * @return int + */ + public function zinterstore($output, $keys, $options = []) + { + return $this->command('zinterstore', [$output, $keys, + $options['weights'] ?? null, + $options['aggregate'] ?? 'sum', + ]); + } + + /** + * Find the union between sets and store in a new set. + * + * @param string $output + * @param array $keys + * @param array $options + * @return int + */ + public function zunionstore($output, $keys, $options = []) + { + return $this->command('zunionstore', [$output, $keys, + $options['weights'] ?? null, + $options['aggregate'] ?? 'sum', + ]); + } + + /** + * Scans all keys based on options. + * + * @param mixed $cursor + * @param array $options + * @return mixed + */ + public function scan($cursor, $options = []) + { + $result = $this->client->scan($cursor, + $options['match'] ?? '*', + $options['count'] ?? 10 + ); + + if ($result === false) { + $result = []; + } + + return $cursor === 0 && empty($result) ? false : [$cursor, $result]; + } + + /** + * Scans the given set for all values based on options. + * + * @param string $key + * @param mixed $cursor + * @param array $options + * @return mixed + */ + public function zscan($key, $cursor, $options = []) + { + $result = $this->client->zscan($key, $cursor, + $options['match'] ?? '*', + $options['count'] ?? 10 + ); + + if ($result === false) { + $result = []; + } + + return $cursor === 0 && empty($result) ? false : [$cursor, $result]; + } + + /** + * Scans the given hash for all values based on options. + * + * @param string $key + * @param mixed $cursor + * @param array $options + * @return mixed + */ + public function hscan($key, $cursor, $options = []) + { + $result = $this->client->hscan($key, $cursor, + $options['match'] ?? '*', + $options['count'] ?? 10 + ); + + if ($result === false) { + $result = []; + } + + return $cursor === 0 && empty($result) ? false : [$cursor, $result]; + } + + /** + * Scans the given set for all values based on options. + * + * @param string $key + * @param mixed $cursor + * @param array $options + * @return mixed + */ + public function sscan($key, $cursor, $options = []) + { + $result = $this->client->sscan($key, $cursor, + $options['match'] ?? '*', + $options['count'] ?? 10 + ); + + if ($result === false) { + $result = []; + } + + return $cursor === 0 && empty($result) ? false : [$cursor, $result]; + } + + /** + * Execute commands in a pipeline. + * + * @param callable|null $callback + * @return \Redis|array + */ + public function pipeline(callable $callback = null) + { + $pipeline = $this->client()->pipeline(); + + return is_null($callback) + ? $pipeline + : tap($pipeline, $callback)->exec(); + } + + /** + * Execute commands in a transaction. + * + * @param callable|null $callback + * @return \Redis|array + */ + public function transaction(callable $callback = null) + { + $transaction = $this->client()->multi(); + + return is_null($callback) + ? $transaction + : tap($transaction, $callback)->exec(); + } + + /** + * Evaluate a LUA script serverside, from the SHA1 hash of the script instead of the script itself. + * + * @param string $script + * @param int $numkeys + * @param mixed $arguments + * @return mixed + */ + public function evalsha($script, $numkeys, ...$arguments) + { + return $this->command('evalsha', [ + $this->script('load', $script), $arguments, $numkeys, + ]); + } + + /** + * Evaluate a script and return its result. + * + * @param string $script + * @param int $numberOfKeys + * @param dynamic $arguments + * @return mixed + */ + public function eval($script, $numberOfKeys, ...$arguments) + { + return $this->command('eval', [$script, $arguments, $numberOfKeys]); + } + + /** + * Subscribe to a set of given channels for messages. + * + * @param array|string $channels + * @param \Closure $callback + * @return void + */ + public function subscribe($channels, Closure $callback) + { + $this->client->subscribe((array) $channels, function ($redis, $channel, $message) use ($callback) { + $callback($message, $channel); + }); + } + + /** + * Subscribe to a set of given channels with wildcards. + * + * @param array|string $channels + * @param \Closure $callback + * @return void + */ + public function psubscribe($channels, Closure $callback) + { + $this->client->psubscribe((array) $channels, function ($redis, $pattern, $channel, $message) use ($callback) { + $callback($message, $channel); + }); + } + + /** + * Subscribe to a set of given channels for messages. + * + * @param array|string $channels + * @param \Closure $callback + * @param string $method + * @return void + */ + public function createSubscription($channels, Closure $callback, $method = 'subscribe') + { + // + } + + /** + * Flush the selected Redis database. + * + * @return mixed + */ + public function flushdb() + { + $arguments = func_get_args(); + + if (strtoupper((string) ($arguments[0] ?? null)) === 'ASYNC') { + return $this->command('flushdb', [true]); + } + + return $this->command('flushdb'); + } + + /** + * Execute a raw command. + * + * @param array $parameters + * @return mixed + */ + public function executeRaw(array $parameters) + { + return $this->command('rawCommand', $parameters); + } + + /** + * Run a command against the Redis database. + * + * @param string $method + * @param array $parameters + * @return mixed + * + * @throws \RedisException + */ + public function command($method, array $parameters = []) + { + try { + return parent::command($method, $parameters); + } catch (RedisException $e) { + if (Str::contains($e->getMessage(), 'went away')) { + $this->client = $this->connector ? call_user_func($this->connector) : $this->client; + } + + throw $e; + } + } + + /** + * Disconnects from the Redis instance. + * + * @return void + */ + public function disconnect() + { + $this->client->close(); + } + + /** + * Apply a prefix to the given key if necessary. + * + * @param string $key + * @return string + */ + private function applyPrefix($key) + { + $prefix = (string) $this->client->getOption(Redis::OPT_PREFIX); + + return $prefix.$key; + } + + /** + * Pass other method calls down to the underlying client. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + return parent::__call(strtolower($method), $parameters); + } +} diff --git a/vendor/illuminate/redis/Connections/PredisClusterConnection.php b/vendor/illuminate/redis/Connections/PredisClusterConnection.php new file mode 100644 index 0000000..6d07de1 --- /dev/null +++ b/vendor/illuminate/redis/Connections/PredisClusterConnection.php @@ -0,0 +1,20 @@ +client->executeCommandOnNodes( + tap(new ServerFlushDatabase)->setArguments(func_get_args()) + ); + } +} diff --git a/vendor/illuminate/redis/Connections/PredisConnection.php b/vendor/illuminate/redis/Connections/PredisConnection.php new file mode 100644 index 0000000..e0a8be0 --- /dev/null +++ b/vendor/illuminate/redis/Connections/PredisConnection.php @@ -0,0 +1,53 @@ +client = $client; + } + + /** + * Subscribe to a set of given channels for messages. + * + * @param array|string $channels + * @param \Closure $callback + * @param string $method + * @return void + */ + public function createSubscription($channels, Closure $callback, $method = 'subscribe') + { + $loop = $this->pubSubLoop(); + + $loop->{$method}(...array_values((array) $channels)); + + foreach ($loop as $message) { + if ($message->kind === 'message' || $message->kind === 'pmessage') { + call_user_func($callback, $message->payload, $message->channel); + } + } + + unset($loop); + } +} diff --git a/vendor/illuminate/redis/Connectors/PhpRedisConnector.php b/vendor/illuminate/redis/Connectors/PhpRedisConnector.php new file mode 100644 index 0000000..1c48bdf --- /dev/null +++ b/vendor/illuminate/redis/Connectors/PhpRedisConnector.php @@ -0,0 +1,228 @@ +createClient(array_merge( + $config, $options, Arr::pull($config, 'options', []) + )); + }; + + return new PhpRedisConnection($connector(), $connector, $config); + } + + /** + * Create a new clustered PhpRedis connection. + * + * @param array $config + * @param array $clusterOptions + * @param array $options + * @return \Illuminate\Redis\Connections\PhpRedisClusterConnection + */ + public function connectToCluster(array $config, array $clusterOptions, array $options) + { + $options = array_merge($options, $clusterOptions, Arr::pull($config, 'options', [])); + + return new PhpRedisClusterConnection($this->createRedisClusterInstance( + array_map([$this, 'buildClusterConnectionString'], $config), $options + )); + } + + /** + * Build a single cluster seed string from an array. + * + * @param array $server + * @return string + */ + protected function buildClusterConnectionString(array $server) + { + return $this->formatHost($server).':'.$server['port'].'?'.Arr::query(Arr::only($server, [ + 'database', 'password', 'prefix', 'read_timeout', + ])); + } + + /** + * Create the Redis client instance. + * + * @param array $config + * @return \Redis + * + * @throws \LogicException + */ + protected function createClient(array $config) + { + return tap(new Redis, function ($client) use ($config) { + if ($client instanceof RedisFacade) { + throw new LogicException( + extension_loaded('redis') + ? 'Please remove or rename the Redis facade alias in your "app" configuration file in order to avoid collision with the PHP Redis extension.' + : 'Please make sure the PHP Redis extension is installed and enabled.' + ); + } + + $this->establishConnection($client, $config); + + if (! empty($config['password'])) { + $client->auth($config['password']); + } + + if (isset($config['database'])) { + $client->select((int) $config['database']); + } + + if (! empty($config['prefix'])) { + $client->setOption(Redis::OPT_PREFIX, $config['prefix']); + } + + if (! empty($config['read_timeout'])) { + $client->setOption(Redis::OPT_READ_TIMEOUT, $config['read_timeout']); + } + + if (! empty($config['scan'])) { + $client->setOption(Redis::OPT_SCAN, $config['scan']); + } + + if (! empty($config['name'])) { + $client->client('SETNAME', $config['name']); + } + + if (array_key_exists('serializer', $config)) { + $client->setOption(Redis::OPT_SERIALIZER, $config['serializer']); + } + + if (array_key_exists('compression', $config)) { + $client->setOption(Redis::OPT_COMPRESSION, $config['compression']); + } + + if (array_key_exists('compression_level', $config)) { + $client->setOption(Redis::OPT_COMPRESSION_LEVEL, $config['compression_level']); + } + }); + } + + /** + * Establish a connection with the Redis host. + * + * @param \Redis $client + * @param array $config + * @return void + */ + protected function establishConnection($client, array $config) + { + $persistent = $config['persistent'] ?? false; + + $parameters = [ + $this->formatHost($config), + $config['port'], + Arr::get($config, 'timeout', 0.0), + $persistent ? Arr::get($config, 'persistent_id', null) : null, + Arr::get($config, 'retry_interval', 0), + ]; + + if (version_compare(phpversion('redis'), '3.1.3', '>=')) { + $parameters[] = Arr::get($config, 'read_timeout', 0.0); + } + + if (version_compare(phpversion('redis'), '5.3.0', '>=')) { + if (! is_null($context = Arr::get($config, 'context'))) { + $parameters[] = $context; + } + } + + $client->{($persistent ? 'pconnect' : 'connect')}(...$parameters); + } + + /** + * Create a new redis cluster instance. + * + * @param array $servers + * @param array $options + * @return \RedisCluster + */ + protected function createRedisClusterInstance(array $servers, array $options) + { + $parameters = [ + null, + array_values($servers), + $options['timeout'] ?? 0, + $options['read_timeout'] ?? 0, + isset($options['persistent']) && $options['persistent'], + ]; + + if (version_compare(phpversion('redis'), '4.3.0', '>=')) { + $parameters[] = $options['password'] ?? null; + } + + if (version_compare(phpversion('redis'), '5.3.2', '>=')) { + if (! is_null($context = Arr::get($options, 'context'))) { + $parameters[] = $context; + } + } + + return tap(new RedisCluster(...$parameters), function ($client) use ($options) { + if (! empty($options['prefix'])) { + $client->setOption(RedisCluster::OPT_PREFIX, $options['prefix']); + } + + if (! empty($options['scan'])) { + $client->setOption(RedisCluster::OPT_SCAN, $options['scan']); + } + + if (! empty($options['failover'])) { + $client->setOption(RedisCluster::OPT_SLAVE_FAILOVER, $options['failover']); + } + + if (! empty($options['name'])) { + $client->client('SETNAME', $options['name']); + } + + if (array_key_exists('serializer', $options)) { + $client->setOption(RedisCluster::OPT_SERIALIZER, $options['serializer']); + } + + if (array_key_exists('compression', $options)) { + $client->setOption(RedisCluster::OPT_COMPRESSION, $options['compression']); + } + + if (array_key_exists('compression_level', $options)) { + $client->setOption(RedisCluster::OPT_COMPRESSION_LEVEL, $options['compression_level']); + } + }); + } + + /** + * Format the host using the scheme if available. + * + * @param array $options + * @return string + */ + protected function formatHost(array $options) + { + if (isset($options['scheme'])) { + return Str::start($options['host'], "{$options['scheme']}://"); + } + + return $options['host']; + } +} diff --git a/vendor/illuminate/redis/Connectors/PredisConnector.php b/vendor/illuminate/redis/Connectors/PredisConnector.php new file mode 100644 index 0000000..6222a4b --- /dev/null +++ b/vendor/illuminate/redis/Connectors/PredisConnector.php @@ -0,0 +1,53 @@ + 10.0], $options, Arr::pull($config, 'options', []) + ); + + if (isset($config['prefix'])) { + $formattedOptions['prefix'] = $config['prefix']; + } + + return new PredisConnection(new Client($config, $formattedOptions)); + } + + /** + * Create a new clustered Predis connection. + * + * @param array $config + * @param array $clusterOptions + * @param array $options + * @return \Illuminate\Redis\Connections\PredisClusterConnection + */ + public function connectToCluster(array $config, array $clusterOptions, array $options) + { + $clusterSpecificOptions = Arr::pull($config, 'options', []); + + if (isset($config['prefix'])) { + $clusterSpecificOptions['prefix'] = $config['prefix']; + } + + return new PredisClusterConnection(new Client(array_values($config), array_merge( + $options, $clusterOptions, $clusterSpecificOptions + ))); + } +} diff --git a/vendor/illuminate/redis/Events/CommandExecuted.php b/vendor/illuminate/redis/Events/CommandExecuted.php new file mode 100644 index 0000000..fa65719 --- /dev/null +++ b/vendor/illuminate/redis/Events/CommandExecuted.php @@ -0,0 +1,59 @@ +time = $time; + $this->command = $command; + $this->parameters = $parameters; + $this->connection = $connection; + $this->connectionName = $connection->getName(); + } +} diff --git a/vendor/illuminate/redis/LICENSE.md b/vendor/illuminate/redis/LICENSE.md new file mode 100644 index 0000000..79810c8 --- /dev/null +++ b/vendor/illuminate/redis/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) Taylor Otwell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/illuminate/redis/Limiters/ConcurrencyLimiter.php b/vendor/illuminate/redis/Limiters/ConcurrencyLimiter.php new file mode 100644 index 0000000..5fcc921 --- /dev/null +++ b/vendor/illuminate/redis/Limiters/ConcurrencyLimiter.php @@ -0,0 +1,166 @@ +name = $name; + $this->redis = $redis; + $this->maxLocks = $maxLocks; + $this->releaseAfter = $releaseAfter; + } + + /** + * Attempt to acquire the lock for the given number of seconds. + * + * @param int $timeout + * @param callable|null $callback + * @return bool + * + * @throws \Illuminate\Contracts\Redis\LimiterTimeoutException + * @throws \Throwable + */ + public function block($timeout, $callback = null) + { + $starting = time(); + + $id = Str::random(20); + + while (! $slot = $this->acquire($id)) { + if (time() - $timeout >= $starting) { + throw new LimiterTimeoutException; + } + + usleep(250 * 1000); + } + + if (is_callable($callback)) { + try { + return tap($callback(), function () use ($slot, $id) { + $this->release($slot, $id); + }); + } catch (Throwable $exception) { + $this->release($slot, $id); + + throw $exception; + } + } + + return true; + } + + /** + * Attempt to acquire the lock. + * + * @param string $id A unique identifier for this lock + * @return mixed + */ + protected function acquire($id) + { + $slots = array_map(function ($i) { + return $this->name.$i; + }, range(1, $this->maxLocks)); + + return $this->redis->eval(...array_merge( + [$this->lockScript(), count($slots)], + array_merge($slots, [$this->name, $this->releaseAfter, $id]) + )); + } + + /** + * Get the Lua script for acquiring a lock. + * + * KEYS - The keys that represent available slots + * ARGV[1] - The limiter name + * ARGV[2] - The number of seconds the slot should be reserved + * ARGV[3] - The unique identifier for this lock + * + * @return string + */ + protected function lockScript() + { + return <<<'LUA' +for index, value in pairs(redis.call('mget', unpack(KEYS))) do + if not value then + redis.call('set', KEYS[index], ARGV[3], "EX", ARGV[2]) + return ARGV[1]..index + end +end +LUA; + } + + /** + * Release the lock. + * + * @param string $key + * @param string $id + * @return void + */ + protected function release($key, $id) + { + $this->redis->eval($this->releaseScript(), 1, $key, $id); + } + + /** + * Get the Lua script to atomically release a lock. + * + * KEYS[1] - The name of the lock + * ARGV[1] - The unique identifier for this lock + * + * @return string + */ + protected function releaseScript() + { + return <<<'LUA' +if redis.call('get', KEYS[1]) == ARGV[1] +then + return redis.call('del', KEYS[1]) +else + return 0 +end +LUA; + } +} diff --git a/vendor/illuminate/redis/Limiters/ConcurrencyLimiterBuilder.php b/vendor/illuminate/redis/Limiters/ConcurrencyLimiterBuilder.php new file mode 100644 index 0000000..e66259f --- /dev/null +++ b/vendor/illuminate/redis/Limiters/ConcurrencyLimiterBuilder.php @@ -0,0 +1,122 @@ +name = $name; + $this->connection = $connection; + } + + /** + * Set the maximum number of locks that can be obtained per time window. + * + * @param int $maxLocks + * @return $this + */ + public function limit($maxLocks) + { + $this->maxLocks = $maxLocks; + + return $this; + } + + /** + * Set the number of seconds until the lock will be released. + * + * @param int $releaseAfter + * @return $this + */ + public function releaseAfter($releaseAfter) + { + $this->releaseAfter = $this->secondsUntil($releaseAfter); + + return $this; + } + + /** + * Set the amount of time to block until a lock is available. + * + * @param int $timeout + * @return $this + */ + public function block($timeout) + { + $this->timeout = $timeout; + + return $this; + } + + /** + * Execute the given callback if a lock is obtained, otherwise call the failure callback. + * + * @param callable $callback + * @param callable|null $failure + * @return mixed + * + * @throws \Illuminate\Contracts\Redis\LimiterTimeoutException + */ + public function then(callable $callback, callable $failure = null) + { + try { + return (new ConcurrencyLimiter( + $this->connection, $this->name, $this->maxLocks, $this->releaseAfter + ))->block($this->timeout, $callback); + } catch (LimiterTimeoutException $e) { + if ($failure) { + return $failure($e); + } + + throw $e; + } + } +} diff --git a/vendor/illuminate/redis/Limiters/DurationLimiter.php b/vendor/illuminate/redis/Limiters/DurationLimiter.php new file mode 100644 index 0000000..56dbba5 --- /dev/null +++ b/vendor/illuminate/redis/Limiters/DurationLimiter.php @@ -0,0 +1,202 @@ +name = $name; + $this->decay = $decay; + $this->redis = $redis; + $this->maxLocks = $maxLocks; + } + + /** + * Attempt to acquire the lock for the given number of seconds. + * + * @param int $timeout + * @param callable|null $callback + * @return mixed + * + * @throws \Illuminate\Contracts\Redis\LimiterTimeoutException + */ + public function block($timeout, $callback = null) + { + $starting = time(); + + while (! $this->acquire()) { + if (time() - $timeout >= $starting) { + throw new LimiterTimeoutException; + } + + usleep(750 * 1000); + } + + if (is_callable($callback)) { + return $callback(); + } + + return true; + } + + /** + * Attempt to acquire the lock. + * + * @return bool + */ + public function acquire() + { + $results = $this->redis->eval( + $this->luaScript(), 1, $this->name, microtime(true), time(), $this->decay, $this->maxLocks + ); + + $this->decaysAt = $results[1]; + + $this->remaining = max(0, $results[2]); + + return (bool) $results[0]; + } + + /** + * Determine if the key has been "accessed" too many times. + * + * @return bool + */ + public function tooManyAttempts() + { + [$this->decaysAt, $this->remaining] = $this->redis->eval( + $this->tooManyAttemptsLuaScript(), 1, $this->name, microtime(true), time(), $this->decay, $this->maxLocks + ); + + return $this->remaining <= 0; + } + + /** + * Clear the limiter. + * + * @return void + */ + public function clear() + { + $this->redis->del($this->name); + } + + /** + * Get the Lua script for acquiring a lock. + * + * KEYS[1] - The limiter name + * ARGV[1] - Current time in microseconds + * ARGV[2] - Current time in seconds + * ARGV[3] - Duration of the bucket + * ARGV[4] - Allowed number of tasks + * + * @return string + */ + protected function luaScript() + { + return <<<'LUA' +local function reset() + redis.call('HMSET', KEYS[1], 'start', ARGV[2], 'end', ARGV[2] + ARGV[3], 'count', 1) + return redis.call('EXPIRE', KEYS[1], ARGV[3] * 2) +end + +if redis.call('EXISTS', KEYS[1]) == 0 then + return {reset(), ARGV[2] + ARGV[3], ARGV[4] - 1} +end + +if ARGV[1] >= redis.call('HGET', KEYS[1], 'start') and ARGV[1] <= redis.call('HGET', KEYS[1], 'end') then + return { + tonumber(redis.call('HINCRBY', KEYS[1], 'count', 1)) <= tonumber(ARGV[4]), + redis.call('HGET', KEYS[1], 'end'), + ARGV[4] - redis.call('HGET', KEYS[1], 'count') + } +end + +return {reset(), ARGV[2] + ARGV[3], ARGV[4] - 1} +LUA; + } + + /** + * Get the Lua script to determine if the key has been "accessed" too many times. + * + * KEYS[1] - The limiter name + * ARGV[1] - Current time in microseconds + * ARGV[2] - Current time in seconds + * ARGV[3] - Duration of the bucket + * ARGV[4] - Allowed number of tasks + * + * @return string + */ + protected function tooManyAttemptsLuaScript() + { + return <<<'LUA' + +if redis.call('EXISTS', KEYS[1]) == 0 then + return {0, ARGV[2] + ARGV[3]} +end + +if ARGV[1] >= redis.call('HGET', KEYS[1], 'start') and ARGV[1] <= redis.call('HGET', KEYS[1], 'end') then + return { + redis.call('HGET', KEYS[1], 'end'), + ARGV[4] - redis.call('HGET', KEYS[1], 'count') + } +end + +return {0, ARGV[2] + ARGV[3]} +LUA; + } +} diff --git a/vendor/illuminate/redis/Limiters/DurationLimiterBuilder.php b/vendor/illuminate/redis/Limiters/DurationLimiterBuilder.php new file mode 100644 index 0000000..c32cb50 --- /dev/null +++ b/vendor/illuminate/redis/Limiters/DurationLimiterBuilder.php @@ -0,0 +1,122 @@ +name = $name; + $this->connection = $connection; + } + + /** + * Set the maximum number of locks that can be obtained per time window. + * + * @param int $maxLocks + * @return $this + */ + public function allow($maxLocks) + { + $this->maxLocks = $maxLocks; + + return $this; + } + + /** + * Set the amount of time the lock window is maintained. + * + * @param \DateTimeInterface|\DateInterval|int $decay + * @return $this + */ + public function every($decay) + { + $this->decay = $this->secondsUntil($decay); + + return $this; + } + + /** + * Set the amount of time to block until a lock is available. + * + * @param int $timeout + * @return $this + */ + public function block($timeout) + { + $this->timeout = $timeout; + + return $this; + } + + /** + * Execute the given callback if a lock is obtained, otherwise call the failure callback. + * + * @param callable $callback + * @param callable|null $failure + * @return mixed + * + * @throws \Illuminate\Contracts\Redis\LimiterTimeoutException + */ + public function then(callable $callback, callable $failure = null) + { + try { + return (new DurationLimiter( + $this->connection, $this->name, $this->maxLocks, $this->decay + ))->block($this->timeout, $callback); + } catch (LimiterTimeoutException $e) { + if ($failure) { + return $failure($e); + } + + throw $e; + } + } +} diff --git a/vendor/illuminate/redis/RedisManager.php b/vendor/illuminate/redis/RedisManager.php new file mode 100644 index 0000000..d69f211 --- /dev/null +++ b/vendor/illuminate/redis/RedisManager.php @@ -0,0 +1,279 @@ +app = $app; + $this->driver = $driver; + $this->config = $config; + } + + /** + * Get a Redis connection by name. + * + * @param string|null $name + * @return \Illuminate\Redis\Connections\Connection + */ + public function connection($name = null) + { + $name = $name ?: 'default'; + + if (isset($this->connections[$name])) { + return $this->connections[$name]; + } + + return $this->connections[$name] = $this->configure( + $this->resolve($name), $name + ); + } + + /** + * Resolve the given connection by name. + * + * @param string|null $name + * @return \Illuminate\Redis\Connections\Connection + * + * @throws \InvalidArgumentException + */ + public function resolve($name = null) + { + $name = $name ?: 'default'; + + $options = $this->config['options'] ?? []; + + if (isset($this->config[$name])) { + return $this->connector()->connect( + $this->parseConnectionConfiguration($this->config[$name]), + array_merge(Arr::except($options, 'parameters'), ['parameters' => Arr::get($options, 'parameters.'.$name, Arr::get($options, 'parameters', []))]) + ); + } + + if (isset($this->config['clusters'][$name])) { + return $this->resolveCluster($name); + } + + throw new InvalidArgumentException("Redis connection [{$name}] not configured."); + } + + /** + * Resolve the given cluster connection by name. + * + * @param string $name + * @return \Illuminate\Redis\Connections\Connection + */ + protected function resolveCluster($name) + { + return $this->connector()->connectToCluster( + array_map(function ($config) { + return $this->parseConnectionConfiguration($config); + }, $this->config['clusters'][$name]), + $this->config['clusters']['options'] ?? [], + $this->config['options'] ?? [] + ); + } + + /** + * Configure the given connection to prepare it for commands. + * + * @param \Illuminate\Redis\Connections\Connection $connection + * @param string $name + * @return \Illuminate\Redis\Connections\Connection + */ + protected function configure(Connection $connection, $name) + { + $connection->setName($name); + + if ($this->events && $this->app->bound('events')) { + $connection->setEventDispatcher($this->app->make('events')); + } + + return $connection; + } + + /** + * Get the connector instance for the current driver. + * + * @return \Illuminate\Contracts\Redis\Connector + */ + protected function connector() + { + $customCreator = $this->customCreators[$this->driver] ?? null; + + if ($customCreator) { + return $customCreator(); + } + + switch ($this->driver) { + case 'predis': + return new PredisConnector; + case 'phpredis': + return new PhpRedisConnector; + } + } + + /** + * Parse the Redis connection configuration. + * + * @param mixed $config + * @return array + */ + protected function parseConnectionConfiguration($config) + { + $parsed = (new ConfigurationUrlParser)->parseConfiguration($config); + + $driver = strtolower($parsed['driver'] ?? ''); + + if (in_array($driver, ['tcp', 'tls'])) { + $parsed['scheme'] = $driver; + } + + return array_filter($parsed, function ($key) { + return ! in_array($key, ['driver'], true); + }, ARRAY_FILTER_USE_KEY); + } + + /** + * Return all of the created connections. + * + * @return array + */ + public function connections() + { + return $this->connections; + } + + /** + * Enable the firing of Redis command events. + * + * @return void + */ + public function enableEvents() + { + $this->events = true; + } + + /** + * Disable the firing of Redis command events. + * + * @return void + */ + public function disableEvents() + { + $this->events = false; + } + + /** + * Set the default driver. + * + * @param string $driver + * @return void + */ + public function setDriver($driver) + { + $this->driver = $driver; + } + + /** + * Disconnect the given connection and remove from local cache. + * + * @param string|null $name + * @return void + */ + public function purge($name = null) + { + $name = $name ?: 'default'; + + unset($this->connections[$name]); + } + + /** + * Register a custom driver creator Closure. + * + * @param string $driver + * @param \Closure $callback + * @return $this + */ + public function extend($driver, Closure $callback) + { + $this->customCreators[$driver] = $callback->bindTo($this, $this); + + return $this; + } + + /** + * Pass methods onto the default Redis connection. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + return $this->connection()->{$method}(...$parameters); + } +} diff --git a/vendor/illuminate/redis/RedisServiceProvider.php b/vendor/illuminate/redis/RedisServiceProvider.php new file mode 100644 index 0000000..66282e5 --- /dev/null +++ b/vendor/illuminate/redis/RedisServiceProvider.php @@ -0,0 +1,38 @@ +app->singleton('redis', function ($app) { + $config = $app->make('config')->get('database.redis', []); + + return new RedisManager($app, Arr::pull($config, 'client', 'phpredis'), $config); + }); + + $this->app->bind('redis.connection', function ($app) { + return $app['redis']->connection(); + }); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return ['redis', 'redis.connection']; + } +} diff --git a/vendor/illuminate/support/AggregateServiceProvider.php b/vendor/illuminate/support/AggregateServiceProvider.php new file mode 100644 index 0000000..d7425c5 --- /dev/null +++ b/vendor/illuminate/support/AggregateServiceProvider.php @@ -0,0 +1,52 @@ +instances = []; + + foreach ($this->providers as $provider) { + $this->instances[] = $this->app->register($provider); + } + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + $provides = []; + + foreach ($this->providers as $provider) { + $instance = $this->app->resolveProvider($provider); + + $provides = array_merge($provides, $instance->provides()); + } + + return $provides; + } +} diff --git a/vendor/illuminate/support/Carbon.php b/vendor/illuminate/support/Carbon.php new file mode 100644 index 0000000..004b27b --- /dev/null +++ b/vendor/illuminate/support/Carbon.php @@ -0,0 +1,18 @@ +files = $files; + $this->workingPath = $workingPath; + } + + /** + * Regenerate the Composer autoloader files. + * + * @param string|array $extra + * @return int + */ + public function dumpAutoloads($extra = '') + { + $extra = $extra ? (array) $extra : []; + + $command = array_merge($this->findComposer(), ['dump-autoload'], $extra); + + return $this->getProcess($command)->run(); + } + + /** + * Regenerate the optimized Composer autoloader files. + * + * @return int + */ + public function dumpOptimized() + { + return $this->dumpAutoloads('--optimize'); + } + + /** + * Get the composer command for the environment. + * + * @return array + */ + protected function findComposer() + { + if ($this->files->exists($this->workingPath.'/composer.phar')) { + return [$this->phpBinary(), 'composer.phar']; + } + + return ['composer']; + } + + /** + * Get the PHP binary. + * + * @return string + */ + protected function phpBinary() + { + return ProcessUtils::escapeArgument((new PhpExecutableFinder)->find(false)); + } + + /** + * Get a new Symfony process instance. + * + * @param array $command + * @return \Symfony\Component\Process\Process + */ + protected function getProcess(array $command) + { + return (new Process($command, $this->workingPath))->setTimeout(null); + } + + /** + * Set the working path used by the class. + * + * @param string $path + * @return $this + */ + public function setWorkingPath($path) + { + $this->workingPath = realpath($path); + + return $this; + } +} diff --git a/vendor/illuminate/support/ConfigurationUrlParser.php b/vendor/illuminate/support/ConfigurationUrlParser.php new file mode 100644 index 0000000..be54b9a --- /dev/null +++ b/vendor/illuminate/support/ConfigurationUrlParser.php @@ -0,0 +1,193 @@ + 'sqlsrv', + 'mysql2' => 'mysql', // RDS + 'postgres' => 'pgsql', + 'postgresql' => 'pgsql', + 'sqlite3' => 'sqlite', + 'redis' => 'tcp', + 'rediss' => 'tls', + ]; + + /** + * Parse the database configuration, hydrating options using a database configuration URL if possible. + * + * @param array|string $config + * @return array + */ + public function parseConfiguration($config) + { + if (is_string($config)) { + $config = ['url' => $config]; + } + + $url = Arr::pull($config, 'url'); + + if (! $url) { + return $config; + } + + $rawComponents = $this->parseUrl($url); + + $decodedComponents = $this->parseStringsToNativeTypes( + array_map('rawurldecode', $rawComponents) + ); + + return array_merge( + $config, + $this->getPrimaryOptions($decodedComponents), + $this->getQueryOptions($rawComponents) + ); + } + + /** + * Get the primary database connection options. + * + * @param array $url + * @return array + */ + protected function getPrimaryOptions($url) + { + return array_filter([ + 'driver' => $this->getDriver($url), + 'database' => $this->getDatabase($url), + 'host' => $url['host'] ?? null, + 'port' => $url['port'] ?? null, + 'username' => $url['user'] ?? null, + 'password' => $url['pass'] ?? null, + ], function ($value) { + return ! is_null($value); + }); + } + + /** + * Get the database driver from the URL. + * + * @param array $url + * @return string|null + */ + protected function getDriver($url) + { + $alias = $url['scheme'] ?? null; + + if (! $alias) { + return; + } + + return static::$driverAliases[$alias] ?? $alias; + } + + /** + * Get the database name from the URL. + * + * @param array $url + * @return string|null + */ + protected function getDatabase($url) + { + $path = $url['path'] ?? null; + + return $path && $path !== '/' ? substr($path, 1) : null; + } + + /** + * Get all of the additional database options from the query string. + * + * @param array $url + * @return array + */ + protected function getQueryOptions($url) + { + $queryString = $url['query'] ?? null; + + if (! $queryString) { + return []; + } + + $query = []; + + parse_str($queryString, $query); + + return $this->parseStringsToNativeTypes($query); + } + + /** + * Parse the string URL to an array of components. + * + * @param string $url + * @return array + * + * @throws \InvalidArgumentException + */ + protected function parseUrl($url) + { + $url = preg_replace('#^(sqlite3?):///#', '$1://null/', $url); + + $parsedUrl = parse_url($url); + + if ($parsedUrl === false) { + throw new InvalidArgumentException('The database configuration URL is malformed.'); + } + + return $parsedUrl; + } + + /** + * Convert string casted values to their native types. + * + * @param mixed $value + * @return mixed + */ + protected function parseStringsToNativeTypes($value) + { + if (is_array($value)) { + return array_map([$this, 'parseStringsToNativeTypes'], $value); + } + + if (! is_string($value)) { + return $value; + } + + $parsedValue = json_decode($value, true); + + if (json_last_error() === JSON_ERROR_NONE) { + return $parsedValue; + } + + return $value; + } + + /** + * Get all of the current drivers' aliases. + * + * @return array + */ + public static function getDriverAliases() + { + return static::$driverAliases; + } + + /** + * Add the given driver alias to the driver aliases array. + * + * @param string $alias + * @param string $driver + * @return void + */ + public static function addDriverAlias($alias, $driver) + { + static::$driverAliases[$alias] = $driver; + } +} diff --git a/vendor/illuminate/support/DateFactory.php b/vendor/illuminate/support/DateFactory.php new file mode 100644 index 0000000..f36cb46 --- /dev/null +++ b/vendor/illuminate/support/DateFactory.php @@ -0,0 +1,231 @@ +$method(...$parameters); + } + + $dateClass = static::$dateClass ?: $defaultClassName; + + // Check if date can be created using public class method... + if (method_exists($dateClass, $method) || + method_exists($dateClass, 'hasMacro') && $dateClass::hasMacro($method)) { + return $dateClass::$method(...$parameters); + } + + // If that fails, create the date with the default class... + $date = $defaultClassName::$method(...$parameters); + + // If the configured class has an "instance" method, we'll try to pass our date into there... + if (method_exists($dateClass, 'instance')) { + return $dateClass::instance($date); + } + + // Otherwise, assume the configured class has a DateTime compatible constructor... + return new $dateClass($date->format('Y-m-d H:i:s.u'), $date->getTimezone()); + } +} diff --git a/vendor/illuminate/support/Env.php b/vendor/illuminate/support/Env.php new file mode 100644 index 0000000..b310073 --- /dev/null +++ b/vendor/illuminate/support/Env.php @@ -0,0 +1,103 @@ +addAdapter(PutenvAdapter::class); + } + + static::$repository = $builder->immutable()->make(); + } + + return static::$repository; + } + + /** + * Gets the value of an environment variable. + * + * @param string $key + * @param mixed $default + * @return mixed + */ + public static function get($key, $default = null) + { + return Option::fromValue(static::getRepository()->get($key)) + ->map(function ($value) { + switch (strtolower($value)) { + case 'true': + case '(true)': + return true; + case 'false': + case '(false)': + return false; + case 'empty': + case '(empty)': + return ''; + case 'null': + case '(null)': + return; + } + + if (preg_match('/\A([\'"])(.*)\1\z/', $value, $matches)) { + return $matches[2]; + } + + return $value; + }) + ->getOrCall(function () use ($default) { + return value($default); + }); + } +} diff --git a/vendor/illuminate/support/Facades/App.php b/vendor/illuminate/support/Facades/App.php new file mode 100644 index 0000000..8fbec3d --- /dev/null +++ b/vendor/illuminate/support/Facades/App.php @@ -0,0 +1,64 @@ +providerIsLoaded(UiServiceProvider::class)) { + throw new RuntimeException('In order to use the Auth::routes() method, please install the laravel/ui package.'); + } + + static::$app->make('router')->auth($options); + } +} diff --git a/vendor/illuminate/support/Facades/Blade.php b/vendor/illuminate/support/Facades/Blade.php new file mode 100644 index 0000000..81019e2 --- /dev/null +++ b/vendor/illuminate/support/Facades/Blade.php @@ -0,0 +1,46 @@ +dispatch(); + } + + /** + * Get the registered name of the component. + * + * @return string + */ + protected static function getFacadeAccessor() + { + return BusDispatcherContract::class; + } +} diff --git a/vendor/illuminate/support/Facades/Cache.php b/vendor/illuminate/support/Facades/Cache.php new file mode 100644 index 0000000..70aa1dc --- /dev/null +++ b/vendor/illuminate/support/Facades/Cache.php @@ -0,0 +1,40 @@ +cookie($key, null)); + } + + /** + * Retrieve a cookie from the request. + * + * @param string|null $key + * @param mixed $default + * @return string|array|null + */ + public static function get($key = null, $default = null) + { + return static::$app['request']->cookie($key, $default); + } + + /** + * Get the registered name of the component. + * + * @return string + */ + protected static function getFacadeAccessor() + { + return 'cookie'; + } +} diff --git a/vendor/illuminate/support/Facades/Crypt.php b/vendor/illuminate/support/Facades/Crypt.php new file mode 100644 index 0000000..61eaaa8 --- /dev/null +++ b/vendor/illuminate/support/Facades/Crypt.php @@ -0,0 +1,27 @@ +resolved($accessor) === true) { + $callback(static::getFacadeRoot()); + } + + static::$app->afterResolving($accessor, function ($service) use ($callback) { + $callback($service); + }); + } + + /** + * Convert the facade into a Mockery spy. + * + * @return \Mockery\MockInterface + */ + public static function spy() + { + if (! static::isMock()) { + $class = static::getMockableClass(); + + return tap($class ? Mockery::spy($class) : Mockery::spy(), function ($spy) { + static::swap($spy); + }); + } + } + + /** + * Initiate a partial mock on the facade. + * + * @return \Mockery\MockInterface + */ + public static function partialMock() + { + $name = static::getFacadeAccessor(); + + $mock = static::isMock() + ? static::$resolvedInstance[$name] + : static::createFreshMockInstance(); + + return $mock->makePartial(); + } + + /** + * Initiate a mock expectation on the facade. + * + * @return \Mockery\Expectation + */ + public static function shouldReceive() + { + $name = static::getFacadeAccessor(); + + $mock = static::isMock() + ? static::$resolvedInstance[$name] + : static::createFreshMockInstance(); + + return $mock->shouldReceive(...func_get_args()); + } + + /** + * Create a fresh mock instance for the given class. + * + * @return \Mockery\MockInterface + */ + protected static function createFreshMockInstance() + { + return tap(static::createMock(), function ($mock) { + static::swap($mock); + + $mock->shouldAllowMockingProtectedMethods(); + }); + } + + /** + * Create a fresh mock instance for the given class. + * + * @return \Mockery\MockInterface + */ + protected static function createMock() + { + $class = static::getMockableClass(); + + return $class ? Mockery::mock($class) : Mockery::mock(); + } + + /** + * Determines whether a mock is set as the instance of the facade. + * + * @return bool + */ + protected static function isMock() + { + $name = static::getFacadeAccessor(); + + return isset(static::$resolvedInstance[$name]) && + static::$resolvedInstance[$name] instanceof LegacyMockInterface; + } + + /** + * Get the mockable class for the bound instance. + * + * @return string|null + */ + protected static function getMockableClass() + { + if ($root = static::getFacadeRoot()) { + return get_class($root); + } + } + + /** + * Hotswap the underlying instance behind the facade. + * + * @param mixed $instance + * @return void + */ + public static function swap($instance) + { + static::$resolvedInstance[static::getFacadeAccessor()] = $instance; + + if (isset(static::$app)) { + static::$app->instance(static::getFacadeAccessor(), $instance); + } + } + + /** + * Get the root object behind the facade. + * + * @return mixed + */ + public static function getFacadeRoot() + { + return static::resolveFacadeInstance(static::getFacadeAccessor()); + } + + /** + * Get the registered name of the component. + * + * @return string + * + * @throws \RuntimeException + */ + protected static function getFacadeAccessor() + { + throw new RuntimeException('Facade does not implement getFacadeAccessor method.'); + } + + /** + * Resolve the facade root instance from the container. + * + * @param object|string $name + * @return mixed + */ + protected static function resolveFacadeInstance($name) + { + if (is_object($name)) { + return $name; + } + + if (isset(static::$resolvedInstance[$name])) { + return static::$resolvedInstance[$name]; + } + + if (static::$app) { + return static::$resolvedInstance[$name] = static::$app[$name]; + } + } + + /** + * Clear a resolved facade instance. + * + * @param string $name + * @return void + */ + public static function clearResolvedInstance($name) + { + unset(static::$resolvedInstance[$name]); + } + + /** + * Clear all of the resolved instances. + * + * @return void + */ + public static function clearResolvedInstances() + { + static::$resolvedInstance = []; + } + + /** + * Get the application instance behind the facade. + * + * @return \Illuminate\Contracts\Foundation\Application + */ + public static function getFacadeApplication() + { + return static::$app; + } + + /** + * Set the application instance. + * + * @param \Illuminate\Contracts\Foundation\Application $app + * @return void + */ + public static function setFacadeApplication($app) + { + static::$app = $app; + } + + /** + * Handle dynamic, static calls to the object. + * + * @param string $method + * @param array $args + * @return mixed + * + * @throws \RuntimeException + */ + public static function __callStatic($method, $args) + { + $instance = static::getFacadeRoot(); + + if (! $instance) { + throw new RuntimeException('A facade root has not been set.'); + } + + return $instance->$method(...$args); + } +} diff --git a/vendor/illuminate/support/Facades/File.php b/vendor/illuminate/support/Facades/File.php new file mode 100644 index 0000000..c22d363 --- /dev/null +++ b/vendor/illuminate/support/Facades/File.php @@ -0,0 +1,62 @@ +route($channel, $route); + } + + /** + * Get the registered name of the component. + * + * @return string + */ + protected static function getFacadeAccessor() + { + return ChannelManager::class; + } +} diff --git a/vendor/illuminate/support/Facades/ParallelTesting.php b/vendor/illuminate/support/Facades/ParallelTesting.php new file mode 100644 index 0000000..c397611 --- /dev/null +++ b/vendor/illuminate/support/Facades/ParallelTesting.php @@ -0,0 +1,26 @@ +connection($name)->getSchemaBuilder(); + } + + /** + * Get a schema builder instance for the default connection. + * + * @return \Illuminate\Database\Schema\Builder + */ + protected static function getFacadeAccessor() + { + return static::$app['db']->connection()->getSchemaBuilder(); + } +} diff --git a/vendor/illuminate/support/Facades/Session.php b/vendor/illuminate/support/Facades/Session.php new file mode 100644 index 0000000..a072321 --- /dev/null +++ b/vendor/illuminate/support/Facades/Session.php @@ -0,0 +1,45 @@ +get('filesystems.default'); + + $root = storage_path('framework/testing/disks/'.$disk); + + if ($token = ParallelTesting::token()) { + $root = "{$root}_test_{$token}"; + } + + (new Filesystem)->cleanDirectory($root); + + static::set($disk, $fake = static::createLocalDriver(array_merge($config, [ + 'root' => $root, + ]))); + + return $fake; + } + + /** + * Replace the given disk with a persistent local testing disk. + * + * @param string|null $disk + * @param array $config + * @return \Illuminate\Contracts\Filesystem\Filesystem + */ + public static function persistentFake($disk = null, array $config = []) + { + $disk = $disk ?: static::$app['config']->get('filesystems.default'); + + static::set($disk, $fake = static::createLocalDriver(array_merge($config, [ + 'root' => storage_path('framework/testing/disks/'.$disk), + ]))); + + return $fake; + } + + /** + * Get the registered name of the component. + * + * @return string + */ + protected static function getFacadeAccessor() + { + return 'filesystem'; + } +} diff --git a/vendor/illuminate/support/Facades/URL.php b/vendor/illuminate/support/Facades/URL.php new file mode 100644 index 0000000..7d9941d --- /dev/null +++ b/vendor/illuminate/support/Facades/URL.php @@ -0,0 +1,38 @@ + $value) { + $this->attributes[$key] = $value; + } + } + + /** + * Get an attribute from the fluent instance. + * + * @param string $key + * @param mixed $default + * @return mixed + */ + public function get($key, $default = null) + { + if (array_key_exists($key, $this->attributes)) { + return $this->attributes[$key]; + } + + return value($default); + } + + /** + * Get the attributes from the fluent instance. + * + * @return array + */ + public function getAttributes() + { + return $this->attributes; + } + + /** + * Convert the fluent instance to an array. + * + * @return array + */ + public function toArray() + { + return $this->attributes; + } + + /** + * Convert the object into something JSON serializable. + * + * @return array + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return $this->toArray(); + } + + /** + * Convert the fluent instance to JSON. + * + * @param int $options + * @return string + */ + public function toJson($options = 0) + { + return json_encode($this->jsonSerialize(), $options); + } + + /** + * Determine if the given offset exists. + * + * @param string $offset + * @return bool + */ + #[\ReturnTypeWillChange] + public function offsetExists($offset) + { + return isset($this->attributes[$offset]); + } + + /** + * Get the value for a given offset. + * + * @param string $offset + * @return mixed + */ + #[\ReturnTypeWillChange] + public function offsetGet($offset) + { + return $this->get($offset); + } + + /** + * Set the value at the given offset. + * + * @param string $offset + * @param mixed $value + * @return void + */ + #[\ReturnTypeWillChange] + public function offsetSet($offset, $value) + { + $this->attributes[$offset] = $value; + } + + /** + * Unset the value at the given offset. + * + * @param string $offset + * @return void + */ + #[\ReturnTypeWillChange] + public function offsetUnset($offset) + { + unset($this->attributes[$offset]); + } + + /** + * Handle dynamic calls to the fluent instance to set attributes. + * + * @param string $method + * @param array $parameters + * @return $this + */ + public function __call($method, $parameters) + { + $this->attributes[$method] = count($parameters) > 0 ? $parameters[0] : true; + + return $this; + } + + /** + * Dynamically retrieve the value of an attribute. + * + * @param string $key + * @return mixed + */ + public function __get($key) + { + return $this->get($key); + } + + /** + * Dynamically set the value of an attribute. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function __set($key, $value) + { + $this->offsetSet($key, $value); + } + + /** + * Dynamically check if an attribute is set. + * + * @param string $key + * @return bool + */ + public function __isset($key) + { + return $this->offsetExists($key); + } + + /** + * Dynamically unset an attribute. + * + * @param string $key + * @return void + */ + public function __unset($key) + { + $this->offsetUnset($key); + } +} diff --git a/vendor/illuminate/support/HigherOrderTapProxy.php b/vendor/illuminate/support/HigherOrderTapProxy.php new file mode 100644 index 0000000..bbf9b2e --- /dev/null +++ b/vendor/illuminate/support/HigherOrderTapProxy.php @@ -0,0 +1,38 @@ +target = $target; + } + + /** + * Dynamically pass method calls to the target. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + $this->target->{$method}(...$parameters); + + return $this->target; + } +} diff --git a/vendor/illuminate/support/HtmlString.php b/vendor/illuminate/support/HtmlString.php new file mode 100644 index 0000000..d6b71d4 --- /dev/null +++ b/vendor/illuminate/support/HtmlString.php @@ -0,0 +1,66 @@ +html = $html; + } + + /** + * Get the HTML string. + * + * @return string + */ + public function toHtml() + { + return $this->html; + } + + /** + * Determine if the given HTML string is empty. + * + * @return bool + */ + public function isEmpty() + { + return $this->html === ''; + } + + /** + * Determine if the given HTML string is not empty. + * + * @return bool + */ + public function isNotEmpty() + { + return ! $this->isEmpty(); + } + + /** + * Get the HTML string. + * + * @return string + */ + public function __toString() + { + return $this->toHtml(); + } +} diff --git a/vendor/illuminate/support/InteractsWithTime.php b/vendor/illuminate/support/InteractsWithTime.php new file mode 100644 index 0000000..2b617c3 --- /dev/null +++ b/vendor/illuminate/support/InteractsWithTime.php @@ -0,0 +1,64 @@ +parseDateInterval($delay); + + return $delay instanceof DateTimeInterface + ? max(0, $delay->getTimestamp() - $this->currentTime()) + : (int) $delay; + } + + /** + * Get the "available at" UNIX timestamp. + * + * @param \DateTimeInterface|\DateInterval|int $delay + * @return int + */ + protected function availableAt($delay = 0) + { + $delay = $this->parseDateInterval($delay); + + return $delay instanceof DateTimeInterface + ? $delay->getTimestamp() + : Carbon::now()->addRealSeconds($delay)->getTimestamp(); + } + + /** + * If the given value is an interval, convert it to a DateTime instance. + * + * @param \DateTimeInterface|\DateInterval|int $delay + * @return \DateTimeInterface|int + */ + protected function parseDateInterval($delay) + { + if ($delay instanceof DateInterval) { + $delay = Carbon::now()->add($delay); + } + + return $delay; + } + + /** + * Get the current system time as a UNIX timestamp. + * + * @return int + */ + protected function currentTime() + { + return Carbon::now()->getTimestamp(); + } +} diff --git a/vendor/illuminate/support/Js.php b/vendor/illuminate/support/Js.php new file mode 100644 index 0000000..6d6de34 --- /dev/null +++ b/vendor/illuminate/support/Js.php @@ -0,0 +1,145 @@ +js = $this->convertDataToJavaScriptExpression($data, $flags, $depth); + } + + /** + * Create a new JavaScript string from the given data. + * + * @param mixed $data + * @param int $flags + * @param int $depth + * @return static + * + * @throws \JsonException + */ + public static function from($data, $flags = 0, $depth = 512) + { + return new static($data, $flags, $depth); + } + + /** + * Convert the given data to a JavaScript expression. + * + * @param mixed $data + * @param int $flags + * @param int $depth + * @return string + * + * @throws \JsonException + */ + protected function convertDataToJavaScriptExpression($data, $flags = 0, $depth = 512) + { + if ($data instanceof self) { + return $data->toHtml(); + } + + $json = $this->jsonEncode($data, $flags, $depth); + + if (is_string($data)) { + return "'".substr($json, 1, -1)."'"; + } + + return $this->convertJsonToJavaScriptExpression($json, $flags); + } + + /** + * Encode the given data as JSON. + * + * @param mixed $data + * @param int $flags + * @param int $depth + * @return string + * + * @throws \JsonException + */ + protected function jsonEncode($data, $flags = 0, $depth = 512) + { + if ($data instanceof Jsonable) { + return $data->toJson($flags | static::REQUIRED_FLAGS); + } + + if ($data instanceof Arrayable && ! ($data instanceof JsonSerializable)) { + $data = $data->toArray(); + } + + return json_encode($data, $flags | static::REQUIRED_FLAGS, $depth); + } + + /** + * Convert the given JSON to a JavaScript expression. + * + * @param string $json + * @param int $flags + * @return string + * + * @throws \JsonException + */ + protected function convertJsonToJavaScriptExpression($json, $flags = 0) + { + if ('[]' === $json || '{}' === $json) { + return $json; + } + + if (Str::startsWith($json, ['"', '{', '['])) { + return "JSON.parse('".substr(json_encode($json, $flags | static::REQUIRED_FLAGS), 1, -1)."')"; + } + + return $json; + } + + /** + * Get the string representation of the data for use in HTML. + * + * @return string + */ + public function toHtml() + { + return $this->js; + } + + /** + * Get the string representation of the data for use in HTML. + * + * @return string + */ + public function __toString() + { + return $this->toHtml(); + } +} diff --git a/vendor/illuminate/support/LICENSE.md b/vendor/illuminate/support/LICENSE.md new file mode 100644 index 0000000..79810c8 --- /dev/null +++ b/vendor/illuminate/support/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) Taylor Otwell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/illuminate/support/Manager.php b/vendor/illuminate/support/Manager.php new file mode 100644 index 0000000..f8ae072 --- /dev/null +++ b/vendor/illuminate/support/Manager.php @@ -0,0 +1,193 @@ +container = $container; + $this->config = $container->make('config'); + } + + /** + * Get the default driver name. + * + * @return string + */ + abstract public function getDefaultDriver(); + + /** + * Get a driver instance. + * + * @param string|null $driver + * @return mixed + * + * @throws \InvalidArgumentException + */ + public function driver($driver = null) + { + $driver = $driver ?: $this->getDefaultDriver(); + + if (is_null($driver)) { + throw new InvalidArgumentException(sprintf( + 'Unable to resolve NULL driver for [%s].', static::class + )); + } + + // If the given driver has not been created before, we will create the instances + // here and cache it so we can return it next time very quickly. If there is + // already a driver created by this name, we'll just return that instance. + if (! isset($this->drivers[$driver])) { + $this->drivers[$driver] = $this->createDriver($driver); + } + + return $this->drivers[$driver]; + } + + /** + * Create a new driver instance. + * + * @param string $driver + * @return mixed + * + * @throws \InvalidArgumentException + */ + protected function createDriver($driver) + { + // First, we will determine if a custom driver creator exists for the given driver and + // if it does not we will check for a creator method for the driver. Custom creator + // callbacks allow developers to build their own "drivers" easily using Closures. + if (isset($this->customCreators[$driver])) { + return $this->callCustomCreator($driver); + } else { + $method = 'create'.Str::studly($driver).'Driver'; + + if (method_exists($this, $method)) { + return $this->$method(); + } + } + + throw new InvalidArgumentException("Driver [$driver] not supported."); + } + + /** + * Call a custom driver creator. + * + * @param string $driver + * @return mixed + */ + protected function callCustomCreator($driver) + { + return $this->customCreators[$driver]($this->container); + } + + /** + * Register a custom driver creator Closure. + * + * @param string $driver + * @param \Closure $callback + * @return $this + */ + public function extend($driver, Closure $callback) + { + $this->customCreators[$driver] = $callback; + + return $this; + } + + /** + * Get all of the created "drivers". + * + * @return array + */ + public function getDrivers() + { + return $this->drivers; + } + + /** + * Get the container instance used by the manager. + * + * @return \Illuminate\Contracts\Container\Container + */ + public function getContainer() + { + return $this->container; + } + + /** + * Set the container instance used by the manager. + * + * @param \Illuminate\Contracts\Container\Container $container + * @return $this + */ + public function setContainer(Container $container) + { + $this->container = $container; + + return $this; + } + + /** + * Forget all of the resolved driver instances. + * + * @return $this + */ + public function forgetDrivers() + { + $this->drivers = []; + + return $this; + } + + /** + * Dynamically call the default driver instance. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + return $this->driver()->$method(...$parameters); + } +} diff --git a/vendor/illuminate/support/MessageBag.php b/vendor/illuminate/support/MessageBag.php new file mode 100644 index 0000000..e53d509 --- /dev/null +++ b/vendor/illuminate/support/MessageBag.php @@ -0,0 +1,418 @@ + $value) { + $value = $value instanceof Arrayable ? $value->toArray() : (array) $value; + + $this->messages[$key] = array_unique($value); + } + } + + /** + * Get the keys present in the message bag. + * + * @return array + */ + public function keys() + { + return array_keys($this->messages); + } + + /** + * Add a message to the message bag. + * + * @param string $key + * @param string $message + * @return $this + */ + public function add($key, $message) + { + if ($this->isUnique($key, $message)) { + $this->messages[$key][] = $message; + } + + return $this; + } + + /** + * Add a message to the message bag if the given conditional is "true". + * + * @param bool $boolean + * @param string $key + * @param string $message + * @return $this + */ + public function addIf($boolean, $key, $message) + { + return $boolean ? $this->add($key, $message) : $this; + } + + /** + * Determine if a key and message combination already exists. + * + * @param string $key + * @param string $message + * @return bool + */ + protected function isUnique($key, $message) + { + $messages = (array) $this->messages; + + return ! isset($messages[$key]) || ! in_array($message, $messages[$key]); + } + + /** + * Merge a new array of messages into the message bag. + * + * @param \Illuminate\Contracts\Support\MessageProvider|array $messages + * @return $this + */ + public function merge($messages) + { + if ($messages instanceof MessageProvider) { + $messages = $messages->getMessageBag()->getMessages(); + } + + $this->messages = array_merge_recursive($this->messages, $messages); + + return $this; + } + + /** + * Determine if messages exist for all of the given keys. + * + * @param array|string|null $key + * @return bool + */ + public function has($key) + { + if ($this->isEmpty()) { + return false; + } + + if (is_null($key)) { + return $this->any(); + } + + $keys = is_array($key) ? $key : func_get_args(); + + foreach ($keys as $key) { + if ($this->first($key) === '') { + return false; + } + } + + return true; + } + + /** + * Determine if messages exist for any of the given keys. + * + * @param array|string $keys + * @return bool + */ + public function hasAny($keys = []) + { + if ($this->isEmpty()) { + return false; + } + + $keys = is_array($keys) ? $keys : func_get_args(); + + foreach ($keys as $key) { + if ($this->has($key)) { + return true; + } + } + + return false; + } + + /** + * Get the first message from the message bag for a given key. + * + * @param string|null $key + * @param string|null $format + * @return string + */ + public function first($key = null, $format = null) + { + $messages = is_null($key) ? $this->all($format) : $this->get($key, $format); + + $firstMessage = Arr::first($messages, null, ''); + + return is_array($firstMessage) ? Arr::first($firstMessage) : $firstMessage; + } + + /** + * Get all of the messages from the message bag for a given key. + * + * @param string $key + * @param string|null $format + * @return array + */ + public function get($key, $format = null) + { + // If the message exists in the message bag, we will transform it and return + // the message. Otherwise, we will check if the key is implicit & collect + // all the messages that match the given key and output it as an array. + if (array_key_exists($key, $this->messages)) { + return $this->transform( + $this->messages[$key], $this->checkFormat($format), $key + ); + } + + if (Str::contains($key, '*')) { + return $this->getMessagesForWildcardKey($key, $format); + } + + return []; + } + + /** + * Get the messages for a wildcard key. + * + * @param string $key + * @param string|null $format + * @return array + */ + protected function getMessagesForWildcardKey($key, $format) + { + return collect($this->messages) + ->filter(function ($messages, $messageKey) use ($key) { + return Str::is($key, $messageKey); + }) + ->map(function ($messages, $messageKey) use ($format) { + return $this->transform( + $messages, $this->checkFormat($format), $messageKey + ); + })->all(); + } + + /** + * Get all of the messages for every key in the message bag. + * + * @param string|null $format + * @return array + */ + public function all($format = null) + { + $format = $this->checkFormat($format); + + $all = []; + + foreach ($this->messages as $key => $messages) { + $all = array_merge($all, $this->transform($messages, $format, $key)); + } + + return $all; + } + + /** + * Get all of the unique messages for every key in the message bag. + * + * @param string|null $format + * @return array + */ + public function unique($format = null) + { + return array_unique($this->all($format)); + } + + /** + * Format an array of messages. + * + * @param array $messages + * @param string $format + * @param string $messageKey + * @return array + */ + protected function transform($messages, $format, $messageKey) + { + return collect((array) $messages) + ->map(function ($message) use ($format, $messageKey) { + // We will simply spin through the given messages and transform each one + // replacing the :message place holder with the real message allowing + // the messages to be easily formatted to each developer's desires. + return str_replace([':message', ':key'], [$message, $messageKey], $format); + })->all(); + } + + /** + * Get the appropriate format based on the given format. + * + * @param string $format + * @return string + */ + protected function checkFormat($format) + { + return $format ?: $this->format; + } + + /** + * Get the raw messages in the message bag. + * + * @return array + */ + public function messages() + { + return $this->messages; + } + + /** + * Get the raw messages in the message bag. + * + * @return array + */ + public function getMessages() + { + return $this->messages(); + } + + /** + * Get the messages for the instance. + * + * @return \Illuminate\Support\MessageBag + */ + public function getMessageBag() + { + return $this; + } + + /** + * Get the default message format. + * + * @return string + */ + public function getFormat() + { + return $this->format; + } + + /** + * Set the default message format. + * + * @param string $format + * @return \Illuminate\Support\MessageBag + */ + public function setFormat($format = ':message') + { + $this->format = $format; + + return $this; + } + + /** + * Determine if the message bag has any messages. + * + * @return bool + */ + public function isEmpty() + { + return ! $this->any(); + } + + /** + * Determine if the message bag has any messages. + * + * @return bool + */ + public function isNotEmpty() + { + return $this->any(); + } + + /** + * Determine if the message bag has any messages. + * + * @return bool + */ + public function any() + { + return $this->count() > 0; + } + + /** + * Get the number of messages in the message bag. + * + * @return int + */ + #[\ReturnTypeWillChange] + public function count() + { + return count($this->messages, COUNT_RECURSIVE) - count($this->messages); + } + + /** + * Get the instance as an array. + * + * @return array + */ + public function toArray() + { + return $this->getMessages(); + } + + /** + * Convert the object into something JSON serializable. + * + * @return array + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return $this->toArray(); + } + + /** + * Convert the object to its JSON representation. + * + * @param int $options + * @return string + */ + public function toJson($options = 0) + { + return json_encode($this->jsonSerialize(), $options); + } + + /** + * Convert the message bag to its string representation. + * + * @return string + */ + public function __toString() + { + return $this->toJson(); + } +} diff --git a/vendor/illuminate/support/MultipleInstanceManager.php b/vendor/illuminate/support/MultipleInstanceManager.php new file mode 100644 index 0000000..97cee33 --- /dev/null +++ b/vendor/illuminate/support/MultipleInstanceManager.php @@ -0,0 +1,191 @@ +app = $app; + } + + /** + * Get the default instance name. + * + * @return string + */ + abstract public function getDefaultInstance(); + + /** + * Set the default instance name. + * + * @param string $name + * @return void + */ + abstract public function setDefaultInstance($name); + + /** + * Get the instance specific configuration. + * + * @param string $name + * @return array + */ + abstract public function getInstanceConfig($name); + + /** + * Get an instance instance by name. + * + * @param string|null $name + * @return mixed + */ + public function instance($name = null) + { + $name = $name ?: $this->getDefaultInstance(); + + return $this->instances[$name] = $this->get($name); + } + + /** + * Attempt to get an instance from the local cache. + * + * @param string $name + * @return mixed + */ + protected function get($name) + { + return $this->instances[$name] ?? $this->resolve($name); + } + + /** + * Resolve the given instance. + * + * @param string $name + * @return mixed + * + * @throws \InvalidArgumentException + */ + protected function resolve($name) + { + $config = $this->getInstanceConfig($name); + + if (is_null($config)) { + throw new InvalidArgumentException("Instance [{$name}] is not defined."); + } + + if (! array_key_exists('driver', $config)) { + throw new RuntimeException("Instance [{$name}] does not specify a driver."); + } + + if (isset($this->customCreators[$config['driver']])) { + return $this->callCustomCreator($config); + } else { + $driverMethod = 'create'.ucfirst($config['driver']).'Driver'; + + if (method_exists($this, $driverMethod)) { + return $this->{$driverMethod}($config); + } else { + throw new InvalidArgumentException("Instance driver [{$config['driver']}] is not supported."); + } + } + } + + /** + * Call a custom instance creator. + * + * @param array $config + * @return mixed + */ + protected function callCustomCreator(array $config) + { + return $this->customCreators[$config['driver']]($this->app, $config); + } + + /** + * Unset the given instances. + * + * @param array|string|null $name + * @return $this + */ + public function forgetInstance($name = null) + { + $name = $name ?? $this->getDefaultInstance(); + + foreach ((array) $name as $instanceName) { + if (isset($this->instances[$instanceName])) { + unset($this->instances[$instanceName]); + } + } + + return $this; + } + + /** + * Disconnect the given instance and remove from local cache. + * + * @param string|null $name + * @return void + */ + public function purge($name = null) + { + $name = $name ?? $this->getDefaultInstance(); + + unset($this->instances[$name]); + } + + /** + * Register a custom instance creator Closure. + * + * @param string $name + * @param \Closure $callback + * @return $this + */ + public function extend($name, Closure $callback) + { + $this->customCreators[$name] = $callback->bindTo($this, $this); + + return $this; + } + + /** + * Dynamically call the default instance. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + return $this->instance()->$method(...$parameters); + } +} diff --git a/vendor/illuminate/support/NamespacedItemResolver.php b/vendor/illuminate/support/NamespacedItemResolver.php new file mode 100644 index 0000000..a0d8508 --- /dev/null +++ b/vendor/illuminate/support/NamespacedItemResolver.php @@ -0,0 +1,112 @@ +parsed[$key])) { + return $this->parsed[$key]; + } + + // If the key does not contain a double colon, it means the key is not in a + // namespace, and is just a regular configuration item. Namespaces are a + // tool for organizing configuration items for things such as modules. + if (strpos($key, '::') === false) { + $segments = explode('.', $key); + + $parsed = $this->parseBasicSegments($segments); + } else { + $parsed = $this->parseNamespacedSegments($key); + } + + // Once we have the parsed array of this key's elements, such as its groups + // and namespace, we will cache each array inside a simple list that has + // the key and the parsed array for quick look-ups for later requests. + return $this->parsed[$key] = $parsed; + } + + /** + * Parse an array of basic segments. + * + * @param array $segments + * @return array + */ + protected function parseBasicSegments(array $segments) + { + // The first segment in a basic array will always be the group, so we can go + // ahead and grab that segment. If there is only one total segment we are + // just pulling an entire group out of the array and not a single item. + $group = $segments[0]; + + // If there is more than one segment in this group, it means we are pulling + // a specific item out of a group and will need to return this item name + // as well as the group so we know which item to pull from the arrays. + $item = count($segments) === 1 + ? null + : implode('.', array_slice($segments, 1)); + + return [null, $group, $item]; + } + + /** + * Parse an array of namespaced segments. + * + * @param string $key + * @return array + */ + protected function parseNamespacedSegments($key) + { + [$namespace, $item] = explode('::', $key); + + // First we'll just explode the first segment to get the namespace and group + // since the item should be in the remaining segments. Once we have these + // two pieces of data we can proceed with parsing out the item's value. + $itemSegments = explode('.', $item); + + $groupAndItem = array_slice( + $this->parseBasicSegments($itemSegments), 1 + ); + + return array_merge([$namespace], $groupAndItem); + } + + /** + * Set the parsed value of a key. + * + * @param string $key + * @param array $parsed + * @return void + */ + public function setParsedKey($key, $parsed) + { + $this->parsed[$key] = $parsed; + } + + /** + * Flush the cache of parsed keys. + * + * @return void + */ + public function flushParsedKeys() + { + $this->parsed = []; + } +} diff --git a/vendor/illuminate/support/Optional.php b/vendor/illuminate/support/Optional.php new file mode 100644 index 0000000..816190d --- /dev/null +++ b/vendor/illuminate/support/Optional.php @@ -0,0 +1,135 @@ +value = $value; + } + + /** + * Dynamically access a property on the underlying object. + * + * @param string $key + * @return mixed + */ + public function __get($key) + { + if (is_object($this->value)) { + return $this->value->{$key} ?? null; + } + } + + /** + * Dynamically check a property exists on the underlying object. + * + * @param mixed $name + * @return bool + */ + public function __isset($name) + { + if (is_object($this->value)) { + return isset($this->value->{$name}); + } + + if (is_array($this->value) || $this->value instanceof ArrayObject) { + return isset($this->value[$name]); + } + + return false; + } + + /** + * Determine if an item exists at an offset. + * + * @param mixed $key + * @return bool + */ + #[\ReturnTypeWillChange] + public function offsetExists($key) + { + return Arr::accessible($this->value) && Arr::exists($this->value, $key); + } + + /** + * Get an item at a given offset. + * + * @param mixed $key + * @return mixed + */ + #[\ReturnTypeWillChange] + public function offsetGet($key) + { + return Arr::get($this->value, $key); + } + + /** + * Set the item at a given offset. + * + * @param mixed $key + * @param mixed $value + * @return void + */ + #[\ReturnTypeWillChange] + public function offsetSet($key, $value) + { + if (Arr::accessible($this->value)) { + $this->value[$key] = $value; + } + } + + /** + * Unset the item at a given offset. + * + * @param string $key + * @return void + */ + #[\ReturnTypeWillChange] + public function offsetUnset($key) + { + if (Arr::accessible($this->value)) { + unset($this->value[$key]); + } + } + + /** + * Dynamically pass a method to the underlying object. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + if (static::hasMacro($method)) { + return $this->macroCall($method, $parameters); + } + + if (is_object($this->value)) { + return $this->value->{$method}(...$parameters); + } + } +} diff --git a/vendor/illuminate/support/Pluralizer.php b/vendor/illuminate/support/Pluralizer.php new file mode 100644 index 0000000..109a108 --- /dev/null +++ b/vendor/illuminate/support/Pluralizer.php @@ -0,0 +1,142 @@ +pluralize($value); + + return static::matchCase($plural, $value); + } + + /** + * Get the singular form of an English word. + * + * @param string $value + * @return string + */ + public static function singular($value) + { + $singular = static::inflector()->singularize($value); + + return static::matchCase($singular, $value); + } + + /** + * Determine if the given value is uncountable. + * + * @param string $value + * @return bool + */ + protected static function uncountable($value) + { + return in_array(strtolower($value), static::$uncountable); + } + + /** + * Attempt to match the case on two strings. + * + * @param string $value + * @param string $comparison + * @return string + */ + protected static function matchCase($value, $comparison) + { + $functions = ['mb_strtolower', 'mb_strtoupper', 'ucfirst', 'ucwords']; + + foreach ($functions as $function) { + if ($function($comparison) === $comparison) { + return $function($value); + } + } + + return $value; + } + + /** + * Get the inflector instance. + * + * @return \Doctrine\Inflector\Inflector + */ + public static function inflector() + { + static $inflector; + + if (is_null($inflector)) { + $inflector = InflectorFactory::createForLanguage('english')->build(); + } + + return $inflector; + } +} diff --git a/vendor/illuminate/support/ProcessUtils.php b/vendor/illuminate/support/ProcessUtils.php new file mode 100644 index 0000000..1caa9e1 --- /dev/null +++ b/vendor/illuminate/support/ProcessUtils.php @@ -0,0 +1,69 @@ +isPublic(); + } + + if (is_object($var[0]) && method_exists($class, '__call')) { + return (new ReflectionMethod($class, '__call'))->isPublic(); + } + + if (! is_object($var[0]) && method_exists($class, '__callStatic')) { + return (new ReflectionMethod($class, '__callStatic'))->isPublic(); + } + + return false; + } + + /** + * Get the class name of the given parameter's type, if possible. + * + * @param \ReflectionParameter $parameter + * @return string|null + */ + public static function getParameterClassName($parameter) + { + $type = $parameter->getType(); + + if (! $type instanceof ReflectionNamedType || $type->isBuiltin()) { + return; + } + + return static::getTypeName($parameter, $type); + } + + /** + * Get the class names of the given parameter's type, including union types. + * + * @param \ReflectionParameter $parameter + * @return array + */ + public static function getParameterClassNames($parameter) + { + $type = $parameter->getType(); + + if (! $type instanceof ReflectionUnionType) { + return array_filter([static::getParameterClassName($parameter)]); + } + + $unionTypes = []; + + foreach ($type->getTypes() as $listedType) { + if (! $listedType instanceof ReflectionNamedType || $listedType->isBuiltin()) { + continue; + } + + $unionTypes[] = static::getTypeName($parameter, $listedType); + } + + return array_filter($unionTypes); + } + + /** + * Get the given type's class name. + * + * @param \ReflectionParameter $parameter + * @param \ReflectionNamedType $type + * @return string + */ + protected static function getTypeName($parameter, $type) + { + $name = $type->getName(); + + if (! is_null($class = $parameter->getDeclaringClass())) { + if ($name === 'self') { + return $class->getName(); + } + + if ($name === 'parent' && $parent = $class->getParentClass()) { + return $parent->getName(); + } + } + + return $name; + } + + /** + * Determine if the parameter's type is a subclass of the given type. + * + * @param \ReflectionParameter $parameter + * @param string $className + * @return bool + */ + public static function isParameterSubclassOf($parameter, $className) + { + $paramClassName = static::getParameterClassName($parameter); + + return $paramClassName + && (class_exists($paramClassName) || interface_exists($paramClassName)) + && (new ReflectionClass($paramClassName))->isSubclassOf($className); + } +} diff --git a/vendor/illuminate/support/ServiceProvider.php b/vendor/illuminate/support/ServiceProvider.php new file mode 100644 index 0000000..6c530c1 --- /dev/null +++ b/vendor/illuminate/support/ServiceProvider.php @@ -0,0 +1,437 @@ +app = $app; + } + + /** + * Register any application services. + * + * @return void + */ + public function register() + { + // + } + + /** + * Register a booting callback to be run before the "boot" method is called. + * + * @param \Closure $callback + * @return void + */ + public function booting(Closure $callback) + { + $this->bootingCallbacks[] = $callback; + } + + /** + * Register a booted callback to be run after the "boot" method is called. + * + * @param \Closure $callback + * @return void + */ + public function booted(Closure $callback) + { + $this->bootedCallbacks[] = $callback; + } + + /** + * Call the registered booting callbacks. + * + * @return void + */ + public function callBootingCallbacks() + { + $index = 0; + + while ($index < count($this->bootingCallbacks)) { + $this->app->call($this->bootingCallbacks[$index]); + + $index++; + } + } + + /** + * Call the registered booted callbacks. + * + * @return void + */ + public function callBootedCallbacks() + { + $index = 0; + + while ($index < count($this->bootedCallbacks)) { + $this->app->call($this->bootedCallbacks[$index]); + + $index++; + } + } + + /** + * Merge the given configuration with the existing configuration. + * + * @param string $path + * @param string $key + * @return void + */ + protected function mergeConfigFrom($path, $key) + { + if (! ($this->app instanceof CachesConfiguration && $this->app->configurationIsCached())) { + $config = $this->app->make('config'); + + $config->set($key, array_merge( + require $path, $config->get($key, []) + )); + } + } + + /** + * Load the given routes file if routes are not already cached. + * + * @param string $path + * @return void + */ + protected function loadRoutesFrom($path) + { + if (! ($this->app instanceof CachesRoutes && $this->app->routesAreCached())) { + require $path; + } + } + + /** + * Register a view file namespace. + * + * @param string|array $path + * @param string $namespace + * @return void + */ + protected function loadViewsFrom($path, $namespace) + { + $this->callAfterResolving('view', function ($view) use ($path, $namespace) { + if (isset($this->app->config['view']['paths']) && + is_array($this->app->config['view']['paths'])) { + foreach ($this->app->config['view']['paths'] as $viewPath) { + if (is_dir($appPath = $viewPath.'/vendor/'.$namespace)) { + $view->addNamespace($namespace, $appPath); + } + } + } + + $view->addNamespace($namespace, $path); + }); + } + + /** + * Register the given view components with a custom prefix. + * + * @param string $prefix + * @param array $components + * @return void + */ + protected function loadViewComponentsAs($prefix, array $components) + { + $this->callAfterResolving(BladeCompiler::class, function ($blade) use ($prefix, $components) { + foreach ($components as $alias => $component) { + $blade->component($component, is_string($alias) ? $alias : null, $prefix); + } + }); + } + + /** + * Register a translation file namespace. + * + * @param string $path + * @param string $namespace + * @return void + */ + protected function loadTranslationsFrom($path, $namespace) + { + $this->callAfterResolving('translator', function ($translator) use ($path, $namespace) { + $translator->addNamespace($namespace, $path); + }); + } + + /** + * Register a JSON translation file path. + * + * @param string $path + * @return void + */ + protected function loadJsonTranslationsFrom($path) + { + $this->callAfterResolving('translator', function ($translator) use ($path) { + $translator->addJsonPath($path); + }); + } + + /** + * Register database migration paths. + * + * @param array|string $paths + * @return void + */ + protected function loadMigrationsFrom($paths) + { + $this->callAfterResolving('migrator', function ($migrator) use ($paths) { + foreach ((array) $paths as $path) { + $migrator->path($path); + } + }); + } + + /** + * Register Eloquent model factory paths. + * + * @deprecated Will be removed in a future Laravel version. + * + * @param array|string $paths + * @return void + */ + protected function loadFactoriesFrom($paths) + { + $this->callAfterResolving(ModelFactory::class, function ($factory) use ($paths) { + foreach ((array) $paths as $path) { + $factory->load($path); + } + }); + } + + /** + * Setup an after resolving listener, or fire immediately if already resolved. + * + * @param string $name + * @param callable $callback + * @return void + */ + protected function callAfterResolving($name, $callback) + { + $this->app->afterResolving($name, $callback); + + if ($this->app->resolved($name)) { + $callback($this->app->make($name), $this->app); + } + } + + /** + * Register paths to be published by the publish command. + * + * @param array $paths + * @param mixed $groups + * @return void + */ + protected function publishes(array $paths, $groups = null) + { + $this->ensurePublishArrayInitialized($class = static::class); + + static::$publishes[$class] = array_merge(static::$publishes[$class], $paths); + + foreach ((array) $groups as $group) { + $this->addPublishGroup($group, $paths); + } + } + + /** + * Ensure the publish array for the service provider is initialized. + * + * @param string $class + * @return void + */ + protected function ensurePublishArrayInitialized($class) + { + if (! array_key_exists($class, static::$publishes)) { + static::$publishes[$class] = []; + } + } + + /** + * Add a publish group / tag to the service provider. + * + * @param string $group + * @param array $paths + * @return void + */ + protected function addPublishGroup($group, $paths) + { + if (! array_key_exists($group, static::$publishGroups)) { + static::$publishGroups[$group] = []; + } + + static::$publishGroups[$group] = array_merge( + static::$publishGroups[$group], $paths + ); + } + + /** + * Get the paths to publish. + * + * @param string|null $provider + * @param string|null $group + * @return array + */ + public static function pathsToPublish($provider = null, $group = null) + { + if (! is_null($paths = static::pathsForProviderOrGroup($provider, $group))) { + return $paths; + } + + return collect(static::$publishes)->reduce(function ($paths, $p) { + return array_merge($paths, $p); + }, []); + } + + /** + * Get the paths for the provider or group (or both). + * + * @param string|null $provider + * @param string|null $group + * @return array + */ + protected static function pathsForProviderOrGroup($provider, $group) + { + if ($provider && $group) { + return static::pathsForProviderAndGroup($provider, $group); + } elseif ($group && array_key_exists($group, static::$publishGroups)) { + return static::$publishGroups[$group]; + } elseif ($provider && array_key_exists($provider, static::$publishes)) { + return static::$publishes[$provider]; + } elseif ($group || $provider) { + return []; + } + } + + /** + * Get the paths for the provider and group. + * + * @param string $provider + * @param string $group + * @return array + */ + protected static function pathsForProviderAndGroup($provider, $group) + { + if (! empty(static::$publishes[$provider]) && ! empty(static::$publishGroups[$group])) { + return array_intersect_key(static::$publishes[$provider], static::$publishGroups[$group]); + } + + return []; + } + + /** + * Get the service providers available for publishing. + * + * @return array + */ + public static function publishableProviders() + { + return array_keys(static::$publishes); + } + + /** + * Get the groups available for publishing. + * + * @return array + */ + public static function publishableGroups() + { + return array_keys(static::$publishGroups); + } + + /** + * Register the package's custom Artisan commands. + * + * @param array|mixed $commands + * @return void + */ + public function commands($commands) + { + $commands = is_array($commands) ? $commands : func_get_args(); + + Artisan::starting(function ($artisan) use ($commands) { + $artisan->resolveCommands($commands); + }); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return []; + } + + /** + * Get the events that trigger this service provider to register. + * + * @return array + */ + public function when() + { + return []; + } + + /** + * Determine if the provider is deferred. + * + * @return bool + */ + public function isDeferred() + { + return $this instanceof DeferrableProvider; + } +} diff --git a/vendor/illuminate/support/Str.php b/vendor/illuminate/support/Str.php new file mode 100644 index 0000000..21e1904 --- /dev/null +++ b/vendor/illuminate/support/Str.php @@ -0,0 +1,1033 @@ + 0; + } + + /** + * Convert a string to kebab case. + * + * @param string $value + * @return string + */ + public static function kebab($value) + { + return static::snake($value, '-'); + } + + /** + * Return the length of the given string. + * + * @param string $value + * @param string|null $encoding + * @return int + */ + public static function length($value, $encoding = null) + { + if ($encoding) { + return mb_strlen($value, $encoding); + } + + return mb_strlen($value); + } + + /** + * Limit the number of characters in a string. + * + * @param string $value + * @param int $limit + * @param string $end + * @return string + */ + public static function limit($value, $limit = 100, $end = '...') + { + if (mb_strwidth($value, 'UTF-8') <= $limit) { + return $value; + } + + return rtrim(mb_strimwidth($value, 0, $limit, '', 'UTF-8')).$end; + } + + /** + * Convert the given string to lower-case. + * + * @param string $value + * @return string + */ + public static function lower($value) + { + return mb_strtolower($value, 'UTF-8'); + } + + /** + * Limit the number of words in a string. + * + * @param string $value + * @param int $words + * @param string $end + * @return string + */ + public static function words($value, $words = 100, $end = '...') + { + preg_match('/^\s*+(?:\S++\s*+){1,'.$words.'}/u', $value, $matches); + + if (! isset($matches[0]) || static::length($value) === static::length($matches[0])) { + return $value; + } + + return rtrim($matches[0]).$end; + } + + /** + * Converts GitHub flavored Markdown into HTML. + * + * @param string $string + * @param array $options + * @return string + */ + public static function markdown($string, array $options = []) + { + $converter = new GithubFlavoredMarkdownConverter($options); + + return (string) $converter->convertToHtml($string); + } + + /** + * Masks a portion of a string with a repeated character. + * + * @param string $string + * @param string $character + * @param int $index + * @param int|null $length + * @param string $encoding + * @return string + */ + public static function mask($string, $character, $index, $length = null, $encoding = 'UTF-8') + { + if ($character === '') { + return $string; + } + + if (is_null($length) && PHP_MAJOR_VERSION < 8) { + $length = mb_strlen($string, $encoding); + } + + $segment = mb_substr($string, $index, $length, $encoding); + + if ($segment === '') { + return $string; + } + + $strlen = mb_strlen($string, $encoding); + $startIndex = $index; + + if ($index < 0) { + $startIndex = $index < -$strlen ? 0 : $strlen + $index; + } + + $start = mb_substr($string, 0, $startIndex, $encoding); + $segmentLen = mb_strlen($segment, $encoding); + $end = mb_substr($string, $startIndex + $segmentLen); + + return $start.str_repeat(mb_substr($character, 0, 1, $encoding), $segmentLen).$end; + } + + /** + * Get the string matching the given pattern. + * + * @param string $pattern + * @param string $subject + * @return string + */ + public static function match($pattern, $subject) + { + preg_match($pattern, $subject, $matches); + + if (! $matches) { + return ''; + } + + return $matches[1] ?? $matches[0]; + } + + /** + * Get the string matching the given pattern. + * + * @param string $pattern + * @param string $subject + * @return \Illuminate\Support\Collection + */ + public static function matchAll($pattern, $subject) + { + preg_match_all($pattern, $subject, $matches); + + if (empty($matches[0])) { + return collect(); + } + + return collect($matches[1] ?? $matches[0]); + } + + /** + * Pad both sides of a string with another. + * + * @param string $value + * @param int $length + * @param string $pad + * @return string + */ + public static function padBoth($value, $length, $pad = ' ') + { + return str_pad($value, strlen($value) - mb_strlen($value) + $length, $pad, STR_PAD_BOTH); + } + + /** + * Pad the left side of a string with another. + * + * @param string $value + * @param int $length + * @param string $pad + * @return string + */ + public static function padLeft($value, $length, $pad = ' ') + { + return str_pad($value, strlen($value) - mb_strlen($value) + $length, $pad, STR_PAD_LEFT); + } + + /** + * Pad the right side of a string with another. + * + * @param string $value + * @param int $length + * @param string $pad + * @return string + */ + public static function padRight($value, $length, $pad = ' ') + { + return str_pad($value, strlen($value) - mb_strlen($value) + $length, $pad, STR_PAD_RIGHT); + } + + /** + * Parse a Class[@]method style callback into class and method. + * + * @param string $callback + * @param string|null $default + * @return array + */ + public static function parseCallback($callback, $default = null) + { + return static::contains($callback, '@') ? explode('@', $callback, 2) : [$callback, $default]; + } + + /** + * Get the plural form of an English word. + * + * @param string $value + * @param int|array|\Countable $count + * @return string + */ + public static function plural($value, $count = 2) + { + return Pluralizer::plural($value, $count); + } + + /** + * Pluralize the last word of an English, studly caps case string. + * + * @param string $value + * @param int|array|\Countable $count + * @return string + */ + public static function pluralStudly($value, $count = 2) + { + $parts = preg_split('/(.)(?=[A-Z])/u', $value, -1, PREG_SPLIT_DELIM_CAPTURE); + + $lastWord = array_pop($parts); + + return implode('', $parts).self::plural($lastWord, $count); + } + + /** + * Generate a more truly "random" alpha-numeric string. + * + * @param int $length + * @return string + */ + public static function random($length = 16) + { + $string = ''; + + while (($len = strlen($string)) < $length) { + $size = $length - $len; + + $bytes = random_bytes($size); + + $string .= substr(str_replace(['/', '+', '='], '', base64_encode($bytes)), 0, $size); + } + + return $string; + } + + /** + * Repeat the given string. + * + * @param string $string + * @param int $times + * @return string + */ + public static function repeat(string $string, int $times) + { + return str_repeat($string, $times); + } + + /** + * Replace a given value in the string sequentially with an array. + * + * @param string $search + * @param array $replace + * @param string $subject + * @return string + */ + public static function replaceArray($search, array $replace, $subject) + { + $segments = explode($search, $subject); + + $result = array_shift($segments); + + foreach ($segments as $segment) { + $result .= (array_shift($replace) ?? $search).$segment; + } + + return $result; + } + + /** + * Replace the given value in the given string. + * + * @param string|string[] $search + * @param string|string[] $replace + * @param string|string[] $subject + * @return string + */ + public static function replace($search, $replace, $subject) + { + return str_replace($search, $replace, $subject); + } + + /** + * Replace the first occurrence of a given value in the string. + * + * @param string $search + * @param string $replace + * @param string $subject + * @return string + */ + public static function replaceFirst($search, $replace, $subject) + { + if ($search === '') { + return $subject; + } + + $position = strpos($subject, $search); + + if ($position !== false) { + return substr_replace($subject, $replace, $position, strlen($search)); + } + + return $subject; + } + + /** + * Replace the last occurrence of a given value in the string. + * + * @param string $search + * @param string $replace + * @param string $subject + * @return string + */ + public static function replaceLast($search, $replace, $subject) + { + if ($search === '') { + return $subject; + } + + $position = strrpos($subject, $search); + + if ($position !== false) { + return substr_replace($subject, $replace, $position, strlen($search)); + } + + return $subject; + } + + /** + * Remove any occurrence of the given string in the subject. + * + * @param string|array $search + * @param string $subject + * @param bool $caseSensitive + * @return string + */ + public static function remove($search, $subject, $caseSensitive = true) + { + $subject = $caseSensitive + ? str_replace($search, '', $subject) + : str_ireplace($search, '', $subject); + + return $subject; + } + + /** + * Reverse the given string. + * + * @param string $value + * @return string + */ + public static function reverse(string $value) + { + return implode(array_reverse(mb_str_split($value))); + } + + /** + * Begin a string with a single instance of a given value. + * + * @param string $value + * @param string $prefix + * @return string + */ + public static function start($value, $prefix) + { + $quoted = preg_quote($prefix, '/'); + + return $prefix.preg_replace('/^(?:'.$quoted.')+/u', '', $value); + } + + /** + * Convert the given string to upper-case. + * + * @param string $value + * @return string + */ + public static function upper($value) + { + return mb_strtoupper($value, 'UTF-8'); + } + + /** + * Convert the given string to title case. + * + * @param string $value + * @return string + */ + public static function title($value) + { + return mb_convert_case($value, MB_CASE_TITLE, 'UTF-8'); + } + + /** + * Convert the given string to title case for each word. + * + * @param string $value + * @return string + */ + public static function headline($value) + { + $parts = explode(' ', $value); + + $parts = count($parts) > 1 + ? $parts = array_map([static::class, 'title'], $parts) + : $parts = array_map([static::class, 'title'], static::ucsplit(implode('_', $parts))); + + $collapsed = static::replace(['-', '_', ' '], '_', implode('_', $parts)); + + return implode(' ', array_filter(explode('_', $collapsed))); + } + + /** + * Get the singular form of an English word. + * + * @param string $value + * @return string + */ + public static function singular($value) + { + return Pluralizer::singular($value); + } + + /** + * Generate a URL friendly "slug" from a given string. + * + * @param string $title + * @param string $separator + * @param string|null $language + * @return string + */ + public static function slug($title, $separator = '-', $language = 'en') + { + $title = $language ? static::ascii($title, $language) : $title; + + // Convert all dashes/underscores into separator + $flip = $separator === '-' ? '_' : '-'; + + $title = preg_replace('!['.preg_quote($flip).']+!u', $separator, $title); + + // Replace @ with the word 'at' + $title = str_replace('@', $separator.'at'.$separator, $title); + + // Remove all characters that are not the separator, letters, numbers, or whitespace. + $title = preg_replace('![^'.preg_quote($separator).'\pL\pN\s]+!u', '', static::lower($title)); + + // Replace all separator characters and whitespace by a single separator + $title = preg_replace('!['.preg_quote($separator).'\s]+!u', $separator, $title); + + return trim($title, $separator); + } + + /** + * Convert a string to snake case. + * + * @param string $value + * @param string $delimiter + * @return string + */ + public static function snake($value, $delimiter = '_') + { + $key = $value; + + if (isset(static::$snakeCache[$key][$delimiter])) { + return static::$snakeCache[$key][$delimiter]; + } + + if (! ctype_lower($value)) { + $value = preg_replace('/\s+/u', '', ucwords($value)); + + $value = static::lower(preg_replace('/(.)(?=[A-Z])/u', '$1'.$delimiter, $value)); + } + + return static::$snakeCache[$key][$delimiter] = $value; + } + + /** + * Determine if a given string starts with a given substring. + * + * @param string $haystack + * @param string|string[] $needles + * @return bool + */ + public static function startsWith($haystack, $needles) + { + foreach ((array) $needles as $needle) { + if ((string) $needle !== '' && strncmp($haystack, $needle, strlen($needle)) === 0) { + return true; + } + } + + return false; + } + + /** + * Convert a value to studly caps case. + * + * @param string $value + * @return string + */ + public static function studly($value) + { + $key = $value; + + if (isset(static::$studlyCache[$key])) { + return static::$studlyCache[$key]; + } + + $words = explode(' ', static::replace(['-', '_'], ' ', $value)); + + $studlyWords = array_map(function ($word) { + return static::ucfirst($word); + }, $words); + + return static::$studlyCache[$key] = implode($studlyWords); + } + + /** + * Returns the portion of the string specified by the start and length parameters. + * + * @param string $string + * @param int $start + * @param int|null $length + * @return string + */ + public static function substr($string, $start, $length = null) + { + return mb_substr($string, $start, $length, 'UTF-8'); + } + + /** + * Returns the number of substring occurrences. + * + * @param string $haystack + * @param string $needle + * @param int $offset + * @param int|null $length + * @return int + */ + public static function substrCount($haystack, $needle, $offset = 0, $length = null) + { + if (! is_null($length)) { + return substr_count($haystack, $needle, $offset, $length); + } else { + return substr_count($haystack, $needle, $offset); + } + } + + /** + * Replace text within a portion of a string. + * + * @param string|array $string + * @param string|array $replace + * @param array|int $offset + * @param array|int|null $length + * @return string|array + */ + public static function substrReplace($string, $replace, $offset = 0, $length = null) + { + if ($length === null) { + $length = strlen($string); + } + + return substr_replace($string, $replace, $offset, $length); + } + + /** + * Swap multiple keywords in a string with other keywords. + * + * @param array $map + * @param string $subject + * @return string + */ + public static function swap(array $map, $subject) + { + return strtr($subject, $map); + } + + /** + * Make a string's first character uppercase. + * + * @param string $string + * @return string + */ + public static function ucfirst($string) + { + return static::upper(static::substr($string, 0, 1)).static::substr($string, 1); + } + + /** + * Split a string into pieces by uppercase characters. + * + * @param string $string + * @return array + */ + public static function ucsplit($string) + { + return preg_split('/(?=\p{Lu})/u', $string, -1, PREG_SPLIT_NO_EMPTY); + } + + /** + * Get the number of words a string contains. + * + * @param string $string + * @return int + */ + public static function wordCount($string) + { + return str_word_count($string); + } + + /** + * Generate a UUID (version 4). + * + * @return \Ramsey\Uuid\UuidInterface + */ + public static function uuid() + { + return static::$uuidFactory + ? call_user_func(static::$uuidFactory) + : Uuid::uuid4(); + } + + /** + * Generate a time-ordered UUID (version 4). + * + * @return \Ramsey\Uuid\UuidInterface + */ + public static function orderedUuid() + { + if (static::$uuidFactory) { + return call_user_func(static::$uuidFactory); + } + + $factory = new UuidFactory; + + $factory->setRandomGenerator(new CombGenerator( + $factory->getRandomGenerator(), + $factory->getNumberConverter() + )); + + $factory->setCodec(new TimestampFirstCombCodec( + $factory->getUuidBuilder() + )); + + return $factory->uuid4(); + } + + /** + * Set the callable that will be used to generate UUIDs. + * + * @param callable|null $factory + * @return void + */ + public static function createUuidsUsing(callable $factory = null) + { + static::$uuidFactory = $factory; + } + + /** + * Indicate that UUIDs should be created normally and not using a custom factory. + * + * @return void + */ + public static function createUuidsNormally() + { + static::$uuidFactory = null; + } + + /** + * Remove all strings from the casing caches. + * + * @return void + */ + public static function flushCache() + { + static::$snakeCache = []; + static::$camelCache = []; + static::$studlyCache = []; + } +} diff --git a/vendor/illuminate/support/Stringable.php b/vendor/illuminate/support/Stringable.php new file mode 100644 index 0000000..414be0c --- /dev/null +++ b/vendor/illuminate/support/Stringable.php @@ -0,0 +1,1026 @@ +value = (string) $value; + } + + /** + * Return the remainder of a string after the first occurrence of a given value. + * + * @param string $search + * @return static + */ + public function after($search) + { + return new static(Str::after($this->value, $search)); + } + + /** + * Return the remainder of a string after the last occurrence of a given value. + * + * @param string $search + * @return static + */ + public function afterLast($search) + { + return new static(Str::afterLast($this->value, $search)); + } + + /** + * Append the given values to the string. + * + * @param array $values + * @return static + */ + public function append(...$values) + { + return new static($this->value.implode('', $values)); + } + + /** + * Transliterate a UTF-8 value to ASCII. + * + * @param string $language + * @return static + */ + public function ascii($language = 'en') + { + return new static(Str::ascii($this->value, $language)); + } + + /** + * Get the trailing name component of the path. + * + * @param string $suffix + * @return static + */ + public function basename($suffix = '') + { + return new static(basename($this->value, $suffix)); + } + + /** + * Get the basename of the class path. + * + * @return static + */ + public function classBasename() + { + return new static(class_basename($this->value)); + } + + /** + * Get the portion of a string before the first occurrence of a given value. + * + * @param string $search + * @return static + */ + public function before($search) + { + return new static(Str::before($this->value, $search)); + } + + /** + * Get the portion of a string before the last occurrence of a given value. + * + * @param string $search + * @return static + */ + public function beforeLast($search) + { + return new static(Str::beforeLast($this->value, $search)); + } + + /** + * Get the portion of a string between two given values. + * + * @param string $from + * @param string $to + * @return static + */ + public function between($from, $to) + { + return new static(Str::between($this->value, $from, $to)); + } + + /** + * Convert a value to camel case. + * + * @return static + */ + public function camel() + { + return new static(Str::camel($this->value)); + } + + /** + * Determine if a given string contains a given substring. + * + * @param string|array $needles + * @return bool + */ + public function contains($needles) + { + return Str::contains($this->value, $needles); + } + + /** + * Determine if a given string contains all array values. + * + * @param array $needles + * @return bool + */ + public function containsAll(array $needles) + { + return Str::containsAll($this->value, $needles); + } + + /** + * Get the parent directory's path. + * + * @param int $levels + * @return static + */ + public function dirname($levels = 1) + { + return new static(dirname($this->value, $levels)); + } + + /** + * Determine if a given string ends with a given substring. + * + * @param string|array $needles + * @return bool + */ + public function endsWith($needles) + { + return Str::endsWith($this->value, $needles); + } + + /** + * Determine if the string is an exact match with the given value. + * + * @param string $value + * @return bool + */ + public function exactly($value) + { + return $this->value === $value; + } + + /** + * Explode the string into an array. + * + * @param string $delimiter + * @param int $limit + * @return \Illuminate\Support\Collection + */ + public function explode($delimiter, $limit = PHP_INT_MAX) + { + return collect(explode($delimiter, $this->value, $limit)); + } + + /** + * Split a string using a regular expression or by length. + * + * @param string|int $pattern + * @param int $limit + * @param int $flags + * @return \Illuminate\Support\Collection + */ + public function split($pattern, $limit = -1, $flags = 0) + { + if (filter_var($pattern, FILTER_VALIDATE_INT) !== false) { + return collect(mb_str_split($this->value, $pattern)); + } + + $segments = preg_split($pattern, $this->value, $limit, $flags); + + return ! empty($segments) ? collect($segments) : collect(); + } + + /** + * Cap a string with a single instance of a given value. + * + * @param string $cap + * @return static + */ + public function finish($cap) + { + return new static(Str::finish($this->value, $cap)); + } + + /** + * Determine if a given string matches a given pattern. + * + * @param string|array $pattern + * @return bool + */ + public function is($pattern) + { + return Str::is($pattern, $this->value); + } + + /** + * Determine if a given string is 7 bit ASCII. + * + * @return bool + */ + public function isAscii() + { + return Str::isAscii($this->value); + } + + /** + * Determine if a given string is a valid UUID. + * + * @return bool + */ + public function isUuid() + { + return Str::isUuid($this->value); + } + + /** + * Determine if the given string is empty. + * + * @return bool + */ + public function isEmpty() + { + return $this->value === ''; + } + + /** + * Determine if the given string is not empty. + * + * @return bool + */ + public function isNotEmpty() + { + return ! $this->isEmpty(); + } + + /** + * Convert a string to kebab case. + * + * @return static + */ + public function kebab() + { + return new static(Str::kebab($this->value)); + } + + /** + * Return the length of the given string. + * + * @param string $encoding + * @return int + */ + public function length($encoding = null) + { + return Str::length($this->value, $encoding); + } + + /** + * Limit the number of characters in a string. + * + * @param int $limit + * @param string $end + * @return static + */ + public function limit($limit = 100, $end = '...') + { + return new static(Str::limit($this->value, $limit, $end)); + } + + /** + * Convert the given string to lower-case. + * + * @return static + */ + public function lower() + { + return new static(Str::lower($this->value)); + } + + /** + * Convert GitHub flavored Markdown into HTML. + * + * @param array $options + * @return static + */ + public function markdown(array $options = []) + { + return new static(Str::markdown($this->value, $options)); + } + + /** + * Masks a portion of a string with a repeated character. + * + * @param string $character + * @param int $index + * @param int|null $length + * @param string $encoding + * @return static + */ + public function mask($character, $index, $length = null, $encoding = 'UTF-8') + { + return new static(Str::mask($this->value, $character, $index, $length, $encoding)); + } + + /** + * Get the string matching the given pattern. + * + * @param string $pattern + * @return static + */ + public function match($pattern) + { + return new static(Str::match($pattern, $this->value)); + } + + /** + * Get the string matching the given pattern. + * + * @param string $pattern + * @return \Illuminate\Support\Collection + */ + public function matchAll($pattern) + { + return Str::matchAll($pattern, $this->value); + } + + /** + * Determine if the string matches the given pattern. + * + * @param string $pattern + * @return bool + */ + public function test($pattern) + { + return $this->match($pattern)->isNotEmpty(); + } + + /** + * Pad both sides of the string with another. + * + * @param int $length + * @param string $pad + * @return static + */ + public function padBoth($length, $pad = ' ') + { + return new static(Str::padBoth($this->value, $length, $pad)); + } + + /** + * Pad the left side of the string with another. + * + * @param int $length + * @param string $pad + * @return static + */ + public function padLeft($length, $pad = ' ') + { + return new static(Str::padLeft($this->value, $length, $pad)); + } + + /** + * Pad the right side of the string with another. + * + * @param int $length + * @param string $pad + * @return static + */ + public function padRight($length, $pad = ' ') + { + return new static(Str::padRight($this->value, $length, $pad)); + } + + /** + * Parse a Class@method style callback into class and method. + * + * @param string|null $default + * @return array + */ + public function parseCallback($default = null) + { + return Str::parseCallback($this->value, $default); + } + + /** + * Call the given callback and return a new string. + * + * @param callable $callback + * @return static + */ + public function pipe(callable $callback) + { + return new static(call_user_func($callback, $this)); + } + + /** + * Get the plural form of an English word. + * + * @param int $count + * @return static + */ + public function plural($count = 2) + { + return new static(Str::plural($this->value, $count)); + } + + /** + * Pluralize the last word of an English, studly caps case string. + * + * @param int $count + * @return static + */ + public function pluralStudly($count = 2) + { + return new static(Str::pluralStudly($this->value, $count)); + } + + /** + * Prepend the given values to the string. + * + * @param array $values + * @return static + */ + public function prepend(...$values) + { + return new static(implode('', $values).$this->value); + } + + /** + * Remove any occurrence of the given string in the subject. + * + * @param string|array $search + * @param bool $caseSensitive + * @return static + */ + public function remove($search, $caseSensitive = true) + { + return new static(Str::remove($search, $this->value, $caseSensitive)); + } + + /** + * Reverse the string. + * + * @return static + */ + public function reverse() + { + return new static(Str::reverse($this->value)); + } + + /** + * Repeat the string. + * + * @param int $times + * @return static + */ + public function repeat(int $times) + { + return new static(Str::repeat($this->value, $times)); + } + + /** + * Replace the given value in the given string. + * + * @param string|string[] $search + * @param string|string[] $replace + * @return static + */ + public function replace($search, $replace) + { + return new static(Str::replace($search, $replace, $this->value)); + } + + /** + * Replace a given value in the string sequentially with an array. + * + * @param string $search + * @param array $replace + * @return static + */ + public function replaceArray($search, array $replace) + { + return new static(Str::replaceArray($search, $replace, $this->value)); + } + + /** + * Replace the first occurrence of a given value in the string. + * + * @param string $search + * @param string $replace + * @return static + */ + public function replaceFirst($search, $replace) + { + return new static(Str::replaceFirst($search, $replace, $this->value)); + } + + /** + * Replace the last occurrence of a given value in the string. + * + * @param string $search + * @param string $replace + * @return static + */ + public function replaceLast($search, $replace) + { + return new static(Str::replaceLast($search, $replace, $this->value)); + } + + /** + * Replace the patterns matching the given regular expression. + * + * @param string $pattern + * @param \Closure|string $replace + * @param int $limit + * @return static + */ + public function replaceMatches($pattern, $replace, $limit = -1) + { + if ($replace instanceof Closure) { + return new static(preg_replace_callback($pattern, $replace, $this->value, $limit)); + } + + return new static(preg_replace($pattern, $replace, $this->value, $limit)); + } + + /** + * Parse input from a string to a collection, according to a format. + * + * @param string $format + * @return \Illuminate\Support\Collection + */ + public function scan($format) + { + return collect(sscanf($this->value, $format)); + } + + /** + * Begin a string with a single instance of a given value. + * + * @param string $prefix + * @return static + */ + public function start($prefix) + { + return new static(Str::start($this->value, $prefix)); + } + + /** + * Strip HTML and PHP tags from the given string. + * + * @param string $allowedTags + * @return static + */ + public function stripTags($allowedTags = null) + { + return new static(strip_tags($this->value, $allowedTags)); + } + + /** + * Convert the given string to upper-case. + * + * @return static + */ + public function upper() + { + return new static(Str::upper($this->value)); + } + + /** + * Convert the given string to title case. + * + * @return static + */ + public function title() + { + return new static(Str::title($this->value)); + } + + /** + * Convert the given string to title case for each word. + * + * @return static + */ + public function headline() + { + return new static(Str::headline($this->value)); + } + + /** + * Get the singular form of an English word. + * + * @return static + */ + public function singular() + { + return new static(Str::singular($this->value)); + } + + /** + * Generate a URL friendly "slug" from a given string. + * + * @param string $separator + * @param string|null $language + * @return static + */ + public function slug($separator = '-', $language = 'en') + { + return new static(Str::slug($this->value, $separator, $language)); + } + + /** + * Convert a string to snake case. + * + * @param string $delimiter + * @return static + */ + public function snake($delimiter = '_') + { + return new static(Str::snake($this->value, $delimiter)); + } + + /** + * Determine if a given string starts with a given substring. + * + * @param string|array $needles + * @return bool + */ + public function startsWith($needles) + { + return Str::startsWith($this->value, $needles); + } + + /** + * Convert a value to studly caps case. + * + * @return static + */ + public function studly() + { + return new static(Str::studly($this->value)); + } + + /** + * Returns the portion of the string specified by the start and length parameters. + * + * @param int $start + * @param int|null $length + * @return static + */ + public function substr($start, $length = null) + { + return new static(Str::substr($this->value, $start, $length)); + } + + /** + * Returns the number of substring occurrences. + * + * @param string $needle + * @param int|null $offset + * @param int|null $length + * @return int + */ + public function substrCount($needle, $offset = null, $length = null) + { + return Str::substrCount($this->value, $needle, $offset ?? 0, $length); + } + + /** + * Replace text within a portion of a string. + * + * @param string|array $replace + * @param array|int $offset + * @param array|int|null $length + * @return static + */ + public function substrReplace($replace, $offset = 0, $length = null) + { + return new static(Str::substrReplace($this->value, $replace, $offset, $length)); + } + + /** + * Swap multiple keywords in a string with other keywords. + * + * @param array $map + * @return static + */ + public function swap(array $map) + { + return new static(strtr($this->value, $map)); + } + + /** + * Trim the string of the given characters. + * + * @param string $characters + * @return static + */ + public function trim($characters = null) + { + return new static(trim(...array_merge([$this->value], func_get_args()))); + } + + /** + * Left trim the string of the given characters. + * + * @param string $characters + * @return static + */ + public function ltrim($characters = null) + { + return new static(ltrim(...array_merge([$this->value], func_get_args()))); + } + + /** + * Right trim the string of the given characters. + * + * @param string $characters + * @return static + */ + public function rtrim($characters = null) + { + return new static(rtrim(...array_merge([$this->value], func_get_args()))); + } + + /** + * Make a string's first character uppercase. + * + * @return static + */ + public function ucfirst() + { + return new static(Str::ucfirst($this->value)); + } + + /** + * Split a string by uppercase characters. + * + * @return \Illuminate\Support\Collection + */ + public function ucsplit() + { + return collect(Str::ucsplit($this->value)); + } + + /** + * Execute the given callback if the string contains a given substring. + * + * @param string|array $needles + * @param callable $callback + * @param callable|null $default + * @return static + */ + public function whenContains($needles, $callback, $default = null) + { + return $this->when($this->contains($needles), $callback, $default); + } + + /** + * Execute the given callback if the string contains all array values. + * + * @param array $needles + * @param callable $callback + * @param callable|null $default + * @return static + */ + public function whenContainsAll(array $needles, $callback, $default = null) + { + return $this->when($this->containsAll($needles), $callback, $default); + } + + /** + * Execute the given callback if the string is empty. + * + * @param callable $callback + * @param callable|null $default + * @return static + */ + public function whenEmpty($callback, $default = null) + { + return $this->when($this->isEmpty(), $callback, $default); + } + + /** + * Execute the given callback if the string is not empty. + * + * @param callable $callback + * @param callable|null $default + * @return static + */ + public function whenNotEmpty($callback, $default = null) + { + return $this->when($this->isNotEmpty(), $callback, $default); + } + + /** + * Execute the given callback if the string ends with a given substring. + * + * @param string|array $needles + * @param callable $callback + * @param callable|null $default + * @return static + */ + public function whenEndsWith($needles, $callback, $default = null) + { + return $this->when($this->endsWith($needles), $callback, $default); + } + + /** + * Execute the given callback if the string is an exact match with the given value. + * + * @param string $value + * @param callable $callback + * @param callable|null $default + * @return static + */ + public function whenExactly($value, $callback, $default = null) + { + return $this->when($this->exactly($value), $callback, $default); + } + + /** + * Execute the given callback if the string matches a given pattern. + * + * @param string|array $pattern + * @param callable $callback + * @param callable|null $default + * @return static + */ + public function whenIs($pattern, $callback, $default = null) + { + return $this->when($this->is($pattern), $callback, $default); + } + + /** + * Execute the given callback if the string is 7 bit ASCII. + * + * @param callable $callback + * @param callable|null $default + * @return static + */ + public function whenIsAscii($callback, $default = null) + { + return $this->when($this->isAscii(), $callback, $default); + } + + /** + * Execute the given callback if the string is a valid UUID. + * + * @param callable $callback + * @param callable|null $default + * @return static + */ + public function whenIsUuid($callback, $default = null) + { + return $this->when($this->isUuid(), $callback, $default); + } + + /** + * Execute the given callback if the string starts with a given substring. + * + * @param string|array $needles + * @param callable $callback + * @param callable|null $default + * @return static + */ + public function whenStartsWith($needles, $callback, $default = null) + { + return $this->when($this->startsWith($needles), $callback, $default); + } + + /** + * Execute the given callback if the string matches the given pattern. + * + * @param string $pattern + * @param callable $callback + * @param callable|null $default + * @return static + */ + public function whenTest($pattern, $callback, $default = null) + { + return $this->when($this->test($pattern), $callback, $default); + } + + /** + * Limit the number of words in a string. + * + * @param int $words + * @param string $end + * @return static + */ + public function words($words = 100, $end = '...') + { + return new static(Str::words($this->value, $words, $end)); + } + + /** + * Get the number of words a string contains. + * + * @return int + */ + public function wordCount() + { + return str_word_count($this->value); + } + + /** + * Convert the string into a `HtmlString` instance. + * + * @return \Illuminate\Support\HtmlString + */ + public function toHtmlString() + { + return new HtmlString($this->value); + } + + /** + * Dump the string. + * + * @return $this + */ + public function dump() + { + VarDumper::dump($this->value); + + return $this; + } + + /** + * Dump the string and end the script. + * + * @return never + */ + public function dd() + { + $this->dump(); + + exit(1); + } + + /** + * Convert the object to a string when JSON encoded. + * + * @return string + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return $this->__toString(); + } + + /** + * Proxy dynamic properties onto methods. + * + * @param string $key + * @return mixed + */ + public function __get($key) + { + return $this->{$key}(); + } + + /** + * Get the raw string value. + * + * @return string + */ + public function __toString() + { + return (string) $this->value; + } +} diff --git a/vendor/illuminate/support/Testing/Fakes/BatchRepositoryFake.php b/vendor/illuminate/support/Testing/Fakes/BatchRepositoryFake.php new file mode 100644 index 0000000..d966133 --- /dev/null +++ b/vendor/illuminate/support/Testing/Fakes/BatchRepositoryFake.php @@ -0,0 +1,142 @@ +name, + count($batch->jobs), + count($batch->jobs), + 0, + [], + $batch->options, + CarbonImmutable::now(), + null, + null + ); + } + + /** + * Increment the total number of jobs within the batch. + * + * @param string $batchId + * @param int $amount + * @return void + */ + public function incrementTotalJobs(string $batchId, int $amount) + { + // + } + + /** + * Decrement the total number of pending jobs for the batch. + * + * @param string $batchId + * @param string $jobId + * @return \Illuminate\Bus\UpdatedBatchJobCounts + */ + public function decrementPendingJobs(string $batchId, string $jobId) + { + return new UpdatedBatchJobCounts; + } + + /** + * Increment the total number of failed jobs for the batch. + * + * @param string $batchId + * @param string $jobId + * @return \Illuminate\Bus\UpdatedBatchJobCounts + */ + public function incrementFailedJobs(string $batchId, string $jobId) + { + return new UpdatedBatchJobCounts; + } + + /** + * Mark the batch that has the given ID as finished. + * + * @param string $batchId + * @return void + */ + public function markAsFinished(string $batchId) + { + // + } + + /** + * Cancel the batch that has the given ID. + * + * @param string $batchId + * @return void + */ + public function cancel(string $batchId) + { + // + } + + /** + * Delete the batch that has the given ID. + * + * @param string $batchId + * @return void + */ + public function delete(string $batchId) + { + // + } + + /** + * Execute the given Closure within a storage specific transaction. + * + * @param \Closure $callback + * @return mixed + */ + public function transaction(Closure $callback) + { + return $callback(); + } +} diff --git a/vendor/illuminate/support/Testing/Fakes/BusFake.php b/vendor/illuminate/support/Testing/Fakes/BusFake.php new file mode 100644 index 0000000..122252d --- /dev/null +++ b/vendor/illuminate/support/Testing/Fakes/BusFake.php @@ -0,0 +1,739 @@ +dispatcher = $dispatcher; + + $this->jobsToFake = Arr::wrap($jobsToFake); + } + + /** + * Assert if a job was dispatched based on a truth-test callback. + * + * @param string|\Closure $command + * @param callable|int|null $callback + * @return void + */ + public function assertDispatched($command, $callback = null) + { + if ($command instanceof Closure) { + [$command, $callback] = [$this->firstClosureParameterType($command), $command]; + } + + if (is_numeric($callback)) { + return $this->assertDispatchedTimes($command, $callback); + } + + PHPUnit::assertTrue( + $this->dispatched($command, $callback)->count() > 0 || + $this->dispatchedAfterResponse($command, $callback)->count() > 0 || + $this->dispatchedSync($command, $callback)->count() > 0, + "The expected [{$command}] job was not dispatched." + ); + } + + /** + * Assert if a job was pushed a number of times. + * + * @param string $command + * @param int $times + * @return void + */ + public function assertDispatchedTimes($command, $times = 1) + { + $count = $this->dispatched($command)->count() + + $this->dispatchedAfterResponse($command)->count() + + $this->dispatchedSync($command)->count(); + + PHPUnit::assertSame( + $times, $count, + "The expected [{$command}] job was pushed {$count} times instead of {$times} times." + ); + } + + /** + * Determine if a job was dispatched based on a truth-test callback. + * + * @param string|\Closure $command + * @param callable|null $callback + * @return void + */ + public function assertNotDispatched($command, $callback = null) + { + if ($command instanceof Closure) { + [$command, $callback] = [$this->firstClosureParameterType($command), $command]; + } + + PHPUnit::assertTrue( + $this->dispatched($command, $callback)->count() === 0 && + $this->dispatchedAfterResponse($command, $callback)->count() === 0 && + $this->dispatchedSync($command, $callback)->count() === 0, + "The unexpected [{$command}] job was dispatched." + ); + } + + /** + * Assert that no jobs were dispatched. + * + * @return void + */ + public function assertNothingDispatched() + { + PHPUnit::assertEmpty($this->commands, 'Jobs were dispatched unexpectedly.'); + } + + /** + * Assert if a job was explicitly dispatched synchronously based on a truth-test callback. + * + * @param string|\Closure $command + * @param callable|int|null $callback + * @return void + */ + public function assertDispatchedSync($command, $callback = null) + { + if ($command instanceof Closure) { + [$command, $callback] = [$this->firstClosureParameterType($command), $command]; + } + + if (is_numeric($callback)) { + return $this->assertDispatchedSyncTimes($command, $callback); + } + + PHPUnit::assertTrue( + $this->dispatchedSync($command, $callback)->count() > 0, + "The expected [{$command}] job was not dispatched synchronously." + ); + } + + /** + * Assert if a job was pushed synchronously a number of times. + * + * @param string $command + * @param int $times + * @return void + */ + public function assertDispatchedSyncTimes($command, $times = 1) + { + $count = $this->dispatchedSync($command)->count(); + + PHPUnit::assertSame( + $times, $count, + "The expected [{$command}] job was synchronously pushed {$count} times instead of {$times} times." + ); + } + + /** + * Determine if a job was dispatched based on a truth-test callback. + * + * @param string|\Closure $command + * @param callable|null $callback + * @return void + */ + public function assertNotDispatchedSync($command, $callback = null) + { + if ($command instanceof Closure) { + [$command, $callback] = [$this->firstClosureParameterType($command), $command]; + } + + PHPUnit::assertCount( + 0, $this->dispatchedSync($command, $callback), + "The unexpected [{$command}] job was dispatched synchronously." + ); + } + + /** + * Assert if a job was dispatched after the response was sent based on a truth-test callback. + * + * @param string|\Closure $command + * @param callable|int|null $callback + * @return void + */ + public function assertDispatchedAfterResponse($command, $callback = null) + { + if ($command instanceof Closure) { + [$command, $callback] = [$this->firstClosureParameterType($command), $command]; + } + + if (is_numeric($callback)) { + return $this->assertDispatchedAfterResponseTimes($command, $callback); + } + + PHPUnit::assertTrue( + $this->dispatchedAfterResponse($command, $callback)->count() > 0, + "The expected [{$command}] job was not dispatched after sending the response." + ); + } + + /** + * Assert if a job was pushed after the response was sent a number of times. + * + * @param string $command + * @param int $times + * @return void + */ + public function assertDispatchedAfterResponseTimes($command, $times = 1) + { + $count = $this->dispatchedAfterResponse($command)->count(); + + PHPUnit::assertSame( + $times, $count, + "The expected [{$command}] job was pushed {$count} times instead of {$times} times." + ); + } + + /** + * Determine if a job was dispatched based on a truth-test callback. + * + * @param string|\Closure $command + * @param callable|null $callback + * @return void + */ + public function assertNotDispatchedAfterResponse($command, $callback = null) + { + if ($command instanceof Closure) { + [$command, $callback] = [$this->firstClosureParameterType($command), $command]; + } + + PHPUnit::assertCount( + 0, $this->dispatchedAfterResponse($command, $callback), + "The unexpected [{$command}] job was dispatched after sending the response." + ); + } + + /** + * Assert if a chain of jobs was dispatched. + * + * @param array $expectedChain + * @return void + */ + public function assertChained(array $expectedChain) + { + $command = $expectedChain[0]; + + $expectedChain = array_slice($expectedChain, 1); + + $callback = null; + + if ($command instanceof Closure) { + [$command, $callback] = [$this->firstClosureParameterType($command), $command]; + } elseif (! is_string($command)) { + $instance = $command; + + $command = get_class($instance); + + $callback = function ($job) use ($instance) { + return serialize($this->resetChainPropertiesToDefaults($job)) === serialize($instance); + }; + } + + PHPUnit::assertTrue( + $this->dispatched($command, $callback)->isNotEmpty(), + "The expected [{$command}] job was not dispatched." + ); + + PHPUnit::assertTrue( + collect($expectedChain)->isNotEmpty(), + 'The expected chain can not be empty.' + ); + + $this->isChainOfObjects($expectedChain) + ? $this->assertDispatchedWithChainOfObjects($command, $expectedChain, $callback) + : $this->assertDispatchedWithChainOfClasses($command, $expectedChain, $callback); + } + + /** + * Reset the chain properties to their default values on the job. + * + * @param mixed $job + * @return mixed + */ + protected function resetChainPropertiesToDefaults($job) + { + return tap(clone $job, function ($job) { + $job->chainConnection = null; + $job->chainQueue = null; + $job->chainCatchCallbacks = null; + $job->chained = []; + }); + } + + /** + * Assert if a job was dispatched with an empty chain based on a truth-test callback. + * + * @param string|\Closure $command + * @param callable|null $callback + * @return void + */ + public function assertDispatchedWithoutChain($command, $callback = null) + { + if ($command instanceof Closure) { + [$command, $callback] = [$this->firstClosureParameterType($command), $command]; + } + + PHPUnit::assertTrue( + $this->dispatched($command, $callback)->isNotEmpty(), + "The expected [{$command}] job was not dispatched." + ); + + $this->assertDispatchedWithChainOfClasses($command, [], $callback); + } + + /** + * Assert if a job was dispatched with chained jobs based on a truth-test callback. + * + * @param string $command + * @param array $expectedChain + * @param callable|null $callback + * @return void + */ + protected function assertDispatchedWithChainOfObjects($command, $expectedChain, $callback) + { + $chain = collect($expectedChain)->map(function ($job) { + return serialize($job); + })->all(); + + PHPUnit::assertTrue( + $this->dispatched($command, $callback)->filter(function ($job) use ($chain) { + return $job->chained == $chain; + })->isNotEmpty(), + 'The expected chain was not dispatched.' + ); + } + + /** + * Assert if a job was dispatched with chained jobs based on a truth-test callback. + * + * @param string $command + * @param array $expectedChain + * @param callable|null $callback + * @return void + */ + protected function assertDispatchedWithChainOfClasses($command, $expectedChain, $callback) + { + $matching = $this->dispatched($command, $callback)->map->chained->map(function ($chain) { + return collect($chain)->map(function ($job) { + return get_class(unserialize($job)); + }); + })->filter(function ($chain) use ($expectedChain) { + return $chain->all() === $expectedChain; + }); + + PHPUnit::assertTrue( + $matching->isNotEmpty(), 'The expected chain was not dispatched.' + ); + } + + /** + * Determine if the given chain is entirely composed of objects. + * + * @param array $chain + * @return bool + */ + protected function isChainOfObjects($chain) + { + return ! collect($chain)->contains(function ($job) { + return ! is_object($job); + }); + } + + /** + * Assert if a batch was dispatched based on a truth-test callback. + * + * @param callable $callback + * @return void + */ + public function assertBatched(callable $callback) + { + PHPUnit::assertTrue( + $this->batched($callback)->count() > 0, + 'The expected batch was not dispatched.' + ); + } + + /** + * Assert the number of batches that have been dispatched. + * + * @param int $count + * @return void + */ + public function assertBatchCount($count) + { + PHPUnit::assertCount( + $count, $this->batches, + ); + } + + /** + * Get all of the jobs matching a truth-test callback. + * + * @param string $command + * @param callable|null $callback + * @return \Illuminate\Support\Collection + */ + public function dispatched($command, $callback = null) + { + if (! $this->hasDispatched($command)) { + return collect(); + } + + $callback = $callback ?: function () { + return true; + }; + + return collect($this->commands[$command])->filter(function ($command) use ($callback) { + return $callback($command); + }); + } + + /** + * Get all of the jobs dispatched synchronously matching a truth-test callback. + * + * @param string $command + * @param callable|null $callback + * @return \Illuminate\Support\Collection + */ + public function dispatchedSync(string $command, $callback = null) + { + if (! $this->hasDispatchedSync($command)) { + return collect(); + } + + $callback = $callback ?: function () { + return true; + }; + + return collect($this->commandsSync[$command])->filter(function ($command) use ($callback) { + return $callback($command); + }); + } + + /** + * Get all of the jobs dispatched after the response was sent matching a truth-test callback. + * + * @param string $command + * @param callable|null $callback + * @return \Illuminate\Support\Collection + */ + public function dispatchedAfterResponse(string $command, $callback = null) + { + if (! $this->hasDispatchedAfterResponse($command)) { + return collect(); + } + + $callback = $callback ?: function () { + return true; + }; + + return collect($this->commandsAfterResponse[$command])->filter(function ($command) use ($callback) { + return $callback($command); + }); + } + + /** + * Get all of the pending batches matching a truth-test callback. + * + * @param callable $callback + * @return \Illuminate\Support\Collection + */ + public function batched(callable $callback) + { + if (empty($this->batches)) { + return collect(); + } + + return collect($this->batches)->filter(function ($batch) use ($callback) { + return $callback($batch); + }); + } + + /** + * Determine if there are any stored commands for a given class. + * + * @param string $command + * @return bool + */ + public function hasDispatched($command) + { + return isset($this->commands[$command]) && ! empty($this->commands[$command]); + } + + /** + * Determine if there are any stored commands for a given class. + * + * @param string $command + * @return bool + */ + public function hasDispatchedSync($command) + { + return isset($this->commandsSync[$command]) && ! empty($this->commandsSync[$command]); + } + + /** + * Determine if there are any stored commands for a given class. + * + * @param string $command + * @return bool + */ + public function hasDispatchedAfterResponse($command) + { + return isset($this->commandsAfterResponse[$command]) && ! empty($this->commandsAfterResponse[$command]); + } + + /** + * Dispatch a command to its appropriate handler. + * + * @param mixed $command + * @return mixed + */ + public function dispatch($command) + { + if ($this->shouldFakeJob($command)) { + $this->commands[get_class($command)][] = $command; + } else { + return $this->dispatcher->dispatch($command); + } + } + + /** + * Dispatch a command to its appropriate handler in the current process. + * + * Queueable jobs will be dispatched to the "sync" queue. + * + * @param mixed $command + * @param mixed $handler + * @return mixed + */ + public function dispatchSync($command, $handler = null) + { + if ($this->shouldFakeJob($command)) { + $this->commandsSync[get_class($command)][] = $command; + } else { + return $this->dispatcher->dispatchSync($command, $handler); + } + } + + /** + * Dispatch a command to its appropriate handler in the current process. + * + * @param mixed $command + * @param mixed $handler + * @return mixed + */ + public function dispatchNow($command, $handler = null) + { + if ($this->shouldFakeJob($command)) { + $this->commands[get_class($command)][] = $command; + } else { + return $this->dispatcher->dispatchNow($command, $handler); + } + } + + /** + * Dispatch a command to its appropriate handler behind a queue. + * + * @param mixed $command + * @return mixed + */ + public function dispatchToQueue($command) + { + if ($this->shouldFakeJob($command)) { + $this->commands[get_class($command)][] = $command; + } else { + return $this->dispatcher->dispatchToQueue($command); + } + } + + /** + * Dispatch a command to its appropriate handler. + * + * @param mixed $command + * @return mixed + */ + public function dispatchAfterResponse($command) + { + if ($this->shouldFakeJob($command)) { + $this->commandsAfterResponse[get_class($command)][] = $command; + } else { + return $this->dispatcher->dispatch($command); + } + } + + /** + * Create a new chain of queueable jobs. + * + * @param \Illuminate\Support\Collection|array $jobs + * @return \Illuminate\Foundation\Bus\PendingChain + */ + public function chain($jobs) + { + $jobs = Collection::wrap($jobs); + + return new PendingChainFake($this, $jobs->shift(), $jobs->toArray()); + } + + /** + * Attempt to find the batch with the given ID. + * + * @param string $batchId + * @return \Illuminate\Bus\Batch|null + */ + public function findBatch(string $batchId) + { + // + } + + /** + * Create a new batch of queueable jobs. + * + * @param \Illuminate\Support\Collection|array $jobs + * @return \Illuminate\Bus\PendingBatch + */ + public function batch($jobs) + { + return new PendingBatchFake($this, Collection::wrap($jobs)); + } + + /** + * Record the fake pending batch dispatch. + * + * @param \Illuminate\Bus\PendingBatch $pendingBatch + * @return \Illuminate\Bus\Batch + */ + public function recordPendingBatch(PendingBatch $pendingBatch) + { + $this->batches[] = $pendingBatch; + + return (new BatchRepositoryFake)->store($pendingBatch); + } + + /** + * Determine if a command should be faked or actually dispatched. + * + * @param mixed $command + * @return bool + */ + protected function shouldFakeJob($command) + { + if (empty($this->jobsToFake)) { + return true; + } + + return collect($this->jobsToFake) + ->filter(function ($job) use ($command) { + return $job instanceof Closure + ? $job($command) + : $job === get_class($command); + })->isNotEmpty(); + } + + /** + * Set the pipes commands should be piped through before dispatching. + * + * @param array $pipes + * @return $this + */ + public function pipeThrough(array $pipes) + { + $this->dispatcher->pipeThrough($pipes); + + return $this; + } + + /** + * Determine if the given command has a handler. + * + * @param mixed $command + * @return bool + */ + public function hasCommandHandler($command) + { + return $this->dispatcher->hasCommandHandler($command); + } + + /** + * Retrieve the handler for a command. + * + * @param mixed $command + * @return mixed + */ + public function getCommandHandler($command) + { + return $this->dispatcher->getCommandHandler($command); + } + + /** + * Map a command to a handler. + * + * @param array $map + * @return $this + */ + public function map(array $map) + { + $this->dispatcher->map($map); + + return $this; + } +} diff --git a/vendor/illuminate/support/Testing/Fakes/EventFake.php b/vendor/illuminate/support/Testing/Fakes/EventFake.php new file mode 100644 index 0000000..436173e --- /dev/null +++ b/vendor/illuminate/support/Testing/Fakes/EventFake.php @@ -0,0 +1,325 @@ +dispatcher = $dispatcher; + + $this->eventsToFake = Arr::wrap($eventsToFake); + } + + /** + * Assert if an event has a listener attached to it. + * + * @param string $expectedEvent + * @param string $expectedListener + * @return void + */ + public function assertListening($expectedEvent, $expectedListener) + { + foreach ($this->dispatcher->getListeners($expectedEvent) as $listenerClosure) { + $actualListener = (new ReflectionFunction($listenerClosure)) + ->getStaticVariables()['listener']; + + if (is_string($actualListener) && Str::endsWith($actualListener, '@handle')) { + $actualListener = Str::parseCallback($actualListener)[0]; + } + + if ($actualListener === $expectedListener || + ($actualListener instanceof Closure && + $expectedListener === Closure::class)) { + PHPUnit::assertTrue(true); + + return; + } + } + + PHPUnit::assertTrue( + false, + sprintf( + 'Event [%s] does not have the [%s] listener attached to it', + $expectedEvent, + print_r($expectedListener, true) + ) + ); + } + + /** + * Assert if an event was dispatched based on a truth-test callback. + * + * @param string|\Closure $event + * @param callable|int|null $callback + * @return void + */ + public function assertDispatched($event, $callback = null) + { + if ($event instanceof Closure) { + [$event, $callback] = [$this->firstClosureParameterType($event), $event]; + } + + if (is_int($callback)) { + return $this->assertDispatchedTimes($event, $callback); + } + + PHPUnit::assertTrue( + $this->dispatched($event, $callback)->count() > 0, + "The expected [{$event}] event was not dispatched." + ); + } + + /** + * Assert if an event was dispatched a number of times. + * + * @param string $event + * @param int $times + * @return void + */ + public function assertDispatchedTimes($event, $times = 1) + { + $count = $this->dispatched($event)->count(); + + PHPUnit::assertSame( + $times, $count, + "The expected [{$event}] event was dispatched {$count} times instead of {$times} times." + ); + } + + /** + * Determine if an event was dispatched based on a truth-test callback. + * + * @param string|\Closure $event + * @param callable|null $callback + * @return void + */ + public function assertNotDispatched($event, $callback = null) + { + if ($event instanceof Closure) { + [$event, $callback] = [$this->firstClosureParameterType($event), $event]; + } + + PHPUnit::assertCount( + 0, $this->dispatched($event, $callback), + "The unexpected [{$event}] event was dispatched." + ); + } + + /** + * Assert that no events were dispatched. + * + * @return void + */ + public function assertNothingDispatched() + { + $count = count(Arr::flatten($this->events)); + + PHPUnit::assertSame( + 0, $count, + "{$count} unexpected events were dispatched." + ); + } + + /** + * Get all of the events matching a truth-test callback. + * + * @param string $event + * @param callable|null $callback + * @return \Illuminate\Support\Collection + */ + public function dispatched($event, $callback = null) + { + if (! $this->hasDispatched($event)) { + return collect(); + } + + $callback = $callback ?: function () { + return true; + }; + + return collect($this->events[$event])->filter(function ($arguments) use ($callback) { + return $callback(...$arguments); + }); + } + + /** + * Determine if the given event has been dispatched. + * + * @param string $event + * @return bool + */ + public function hasDispatched($event) + { + return isset($this->events[$event]) && ! empty($this->events[$event]); + } + + /** + * Register an event listener with the dispatcher. + * + * @param \Closure|string|array $events + * @param mixed $listener + * @return void + */ + public function listen($events, $listener = null) + { + $this->dispatcher->listen($events, $listener); + } + + /** + * Determine if a given event has listeners. + * + * @param string $eventName + * @return bool + */ + public function hasListeners($eventName) + { + return $this->dispatcher->hasListeners($eventName); + } + + /** + * Register an event and payload to be dispatched later. + * + * @param string $event + * @param array $payload + * @return void + */ + public function push($event, $payload = []) + { + // + } + + /** + * Register an event subscriber with the dispatcher. + * + * @param object|string $subscriber + * @return void + */ + public function subscribe($subscriber) + { + $this->dispatcher->subscribe($subscriber); + } + + /** + * Flush a set of pushed events. + * + * @param string $event + * @return void + */ + public function flush($event) + { + // + } + + /** + * Fire an event and call the listeners. + * + * @param string|object $event + * @param mixed $payload + * @param bool $halt + * @return array|null + */ + public function dispatch($event, $payload = [], $halt = false) + { + $name = is_object($event) ? get_class($event) : (string) $event; + + if ($this->shouldFakeEvent($name, $payload)) { + $this->events[$name][] = func_get_args(); + } else { + return $this->dispatcher->dispatch($event, $payload, $halt); + } + } + + /** + * Determine if an event should be faked or actually dispatched. + * + * @param string $eventName + * @param mixed $payload + * @return bool + */ + protected function shouldFakeEvent($eventName, $payload) + { + if (empty($this->eventsToFake)) { + return true; + } + + return collect($this->eventsToFake) + ->filter(function ($event) use ($eventName, $payload) { + return $event instanceof Closure + ? $event($eventName, $payload) + : $event === $eventName; + }) + ->isNotEmpty(); + } + + /** + * Remove a set of listeners from the dispatcher. + * + * @param string $event + * @return void + */ + public function forget($event) + { + // + } + + /** + * Forget all of the queued listeners. + * + * @return void + */ + public function forgetPushed() + { + // + } + + /** + * Dispatch an event and call the listeners. + * + * @param string|object $event + * @param mixed $payload + * @return array|null + */ + public function until($event, $payload = []) + { + return $this->dispatch($event, $payload, true); + } +} diff --git a/vendor/illuminate/support/Testing/Fakes/MailFake.php b/vendor/illuminate/support/Testing/Fakes/MailFake.php new file mode 100644 index 0000000..fff5f8f --- /dev/null +++ b/vendor/illuminate/support/Testing/Fakes/MailFake.php @@ -0,0 +1,445 @@ +prepareMailableAndCallback($mailable, $callback); + + if (is_numeric($callback)) { + return $this->assertSentTimes($mailable, $callback); + } + + $message = "The expected [{$mailable}] mailable was not sent."; + + if (count($this->queuedMailables) > 0) { + $message .= ' Did you mean to use assertQueued() instead?'; + } + + PHPUnit::assertTrue( + $this->sent($mailable, $callback)->count() > 0, + $message + ); + } + + /** + * Assert if a mailable was sent a number of times. + * + * @param string $mailable + * @param int $times + * @return void + */ + protected function assertSentTimes($mailable, $times = 1) + { + $count = $this->sent($mailable)->count(); + + PHPUnit::assertSame( + $times, $count, + "The expected [{$mailable}] mailable was sent {$count} times instead of {$times} times." + ); + } + + /** + * Determine if a mailable was not sent or queued to be sent based on a truth-test callback. + * + * @param string|\Closure $mailable + * @param callable|null $callback + * @return void + */ + public function assertNotOutgoing($mailable, $callback = null) + { + $this->assertNotSent($mailable, $callback); + $this->assertNotQueued($mailable, $callback); + } + + /** + * Determine if a mailable was not sent based on a truth-test callback. + * + * @param string|\Closure $mailable + * @param callable|null $callback + * @return void + */ + public function assertNotSent($mailable, $callback = null) + { + [$mailable, $callback] = $this->prepareMailableAndCallback($mailable, $callback); + + PHPUnit::assertCount( + 0, $this->sent($mailable, $callback), + "The unexpected [{$mailable}] mailable was sent." + ); + } + + /** + * Assert that no mailables were sent or queued to be sent. + * + * @return void + */ + public function assertNothingOutgoing() + { + $this->assertNothingSent(); + $this->assertNothingQueued(); + } + + /** + * Assert that no mailables were sent. + * + * @return void + */ + public function assertNothingSent() + { + $mailableNames = collect($this->mailables)->map(function ($mailable) { + return get_class($mailable); + })->join(', '); + + PHPUnit::assertEmpty($this->mailables, 'The following mailables were sent unexpectedly: '.$mailableNames); + } + + /** + * Assert if a mailable was queued based on a truth-test callback. + * + * @param string|\Closure $mailable + * @param callable|int|null $callback + * @return void + */ + public function assertQueued($mailable, $callback = null) + { + [$mailable, $callback] = $this->prepareMailableAndCallback($mailable, $callback); + + if (is_numeric($callback)) { + return $this->assertQueuedTimes($mailable, $callback); + } + + PHPUnit::assertTrue( + $this->queued($mailable, $callback)->count() > 0, + "The expected [{$mailable}] mailable was not queued." + ); + } + + /** + * Assert if a mailable was queued a number of times. + * + * @param string $mailable + * @param int $times + * @return void + */ + protected function assertQueuedTimes($mailable, $times = 1) + { + $count = $this->queued($mailable)->count(); + + PHPUnit::assertSame( + $times, $count, + "The expected [{$mailable}] mailable was queued {$count} times instead of {$times} times." + ); + } + + /** + * Determine if a mailable was not queued based on a truth-test callback. + * + * @param string|\Closure $mailable + * @param callable|null $callback + * @return void + */ + public function assertNotQueued($mailable, $callback = null) + { + [$mailable, $callback] = $this->prepareMailableAndCallback($mailable, $callback); + + PHPUnit::assertCount( + 0, $this->queued($mailable, $callback), + "The unexpected [{$mailable}] mailable was queued." + ); + } + + /** + * Assert that no mailables were queued. + * + * @return void + */ + public function assertNothingQueued() + { + $mailableNames = collect($this->queuedMailables)->map(function ($mailable) { + return get_class($mailable); + })->join(', '); + + PHPUnit::assertEmpty($this->queuedMailables, 'The following mailables were queued unexpectedly: '.$mailableNames); + } + + /** + * Get all of the mailables matching a truth-test callback. + * + * @param string|\Closure $mailable + * @param callable|null $callback + * @return \Illuminate\Support\Collection + */ + public function sent($mailable, $callback = null) + { + [$mailable, $callback] = $this->prepareMailableAndCallback($mailable, $callback); + + if (! $this->hasSent($mailable)) { + return collect(); + } + + $callback = $callback ?: function () { + return true; + }; + + return $this->mailablesOf($mailable)->filter(function ($mailable) use ($callback) { + return $callback($mailable); + }); + } + + /** + * Determine if the given mailable has been sent. + * + * @param string $mailable + * @return bool + */ + public function hasSent($mailable) + { + return $this->mailablesOf($mailable)->count() > 0; + } + + /** + * Get all of the queued mailables matching a truth-test callback. + * + * @param string|\Closure $mailable + * @param callable|null $callback + * @return \Illuminate\Support\Collection + */ + public function queued($mailable, $callback = null) + { + [$mailable, $callback] = $this->prepareMailableAndCallback($mailable, $callback); + + if (! $this->hasQueued($mailable)) { + return collect(); + } + + $callback = $callback ?: function () { + return true; + }; + + return $this->queuedMailablesOf($mailable)->filter(function ($mailable) use ($callback) { + return $callback($mailable); + }); + } + + /** + * Determine if the given mailable has been queued. + * + * @param string $mailable + * @return bool + */ + public function hasQueued($mailable) + { + return $this->queuedMailablesOf($mailable)->count() > 0; + } + + /** + * Get all of the mailed mailables for a given type. + * + * @param string $type + * @return \Illuminate\Support\Collection + */ + protected function mailablesOf($type) + { + return collect($this->mailables)->filter(function ($mailable) use ($type) { + return $mailable instanceof $type; + }); + } + + /** + * Get all of the mailed mailables for a given type. + * + * @param string $type + * @return \Illuminate\Support\Collection + */ + protected function queuedMailablesOf($type) + { + return collect($this->queuedMailables)->filter(function ($mailable) use ($type) { + return $mailable instanceof $type; + }); + } + + /** + * Get a mailer instance by name. + * + * @param string|null $name + * @return \Illuminate\Contracts\Mail\Mailer + */ + public function mailer($name = null) + { + $this->currentMailer = $name; + + return $this; + } + + /** + * Begin the process of mailing a mailable class instance. + * + * @param mixed $users + * @return \Illuminate\Mail\PendingMail + */ + public function to($users) + { + return (new PendingMailFake($this))->to($users); + } + + /** + * Begin the process of mailing a mailable class instance. + * + * @param mixed $users + * @return \Illuminate\Mail\PendingMail + */ + public function bcc($users) + { + return (new PendingMailFake($this))->bcc($users); + } + + /** + * Send a new message with only a raw text part. + * + * @param string $text + * @param \Closure|string $callback + * @return void + */ + public function raw($text, $callback) + { + // + } + + /** + * Send a new message using a view. + * + * @param \Illuminate\Contracts\Mail\Mailable|string|array $view + * @param array $data + * @param \Closure|string|null $callback + * @return void + */ + public function send($view, array $data = [], $callback = null) + { + if (! $view instanceof Mailable) { + return; + } + + $view->mailer($this->currentMailer); + + if ($view instanceof ShouldQueue) { + return $this->queue($view, $data); + } + + $this->currentMailer = null; + + $this->mailables[] = $view; + } + + /** + * Queue a new e-mail message for sending. + * + * @param \Illuminate\Contracts\Mail\Mailable|string|array $view + * @param string|null $queue + * @return mixed + */ + public function queue($view, $queue = null) + { + if (! $view instanceof Mailable) { + return; + } + + $view->mailer($this->currentMailer); + + $this->currentMailer = null; + + $this->queuedMailables[] = $view; + } + + /** + * Queue a new e-mail message for sending after (n) seconds. + * + * @param \DateTimeInterface|\DateInterval|int $delay + * @param \Illuminate\Contracts\Mail\Mailable|string|array $view + * @param string|null $queue + * @return mixed + */ + public function later($delay, $view, $queue = null) + { + $this->queue($view, $queue); + } + + /** + * Get the array of failed recipients. + * + * @return array + */ + public function failures() + { + return []; + } + + /** + * Infer mailable class using reflection if a typehinted closure is passed to assertion. + * + * @param string|\Closure $mailable + * @param callable|null $callback + * @return array + */ + protected function prepareMailableAndCallback($mailable, $callback) + { + if ($mailable instanceof Closure) { + return [$this->firstClosureParameterType($mailable), $mailable]; + } + + return [$mailable, $callback]; + } + + /** + * Forget all of the resolved mailer instances. + * + * @return $this + */ + public function forgetMailers() + { + $this->currentMailer = null; + + return $this; + } +} diff --git a/vendor/illuminate/support/Testing/Fakes/NotificationFake.php b/vendor/illuminate/support/Testing/Fakes/NotificationFake.php new file mode 100644 index 0000000..c7b12f4 --- /dev/null +++ b/vendor/illuminate/support/Testing/Fakes/NotificationFake.php @@ -0,0 +1,327 @@ +assertSentTo(new AnonymousNotifiable, $notification, $callback); + } + + /** + * Assert if a notification was sent based on a truth-test callback. + * + * @param mixed $notifiable + * @param string|\Closure $notification + * @param callable|null $callback + * @return void + * + * @throws \Exception + */ + public function assertSentTo($notifiable, $notification, $callback = null) + { + if (is_array($notifiable) || $notifiable instanceof Collection) { + if (count($notifiable) === 0) { + throw new Exception('No notifiable given.'); + } + + foreach ($notifiable as $singleNotifiable) { + $this->assertSentTo($singleNotifiable, $notification, $callback); + } + + return; + } + + if ($notification instanceof Closure) { + [$notification, $callback] = [$this->firstClosureParameterType($notification), $notification]; + } + + if (is_numeric($callback)) { + return $this->assertSentToTimes($notifiable, $notification, $callback); + } + + PHPUnit::assertTrue( + $this->sent($notifiable, $notification, $callback)->count() > 0, + "The expected [{$notification}] notification was not sent." + ); + } + + /** + * Assert if a notification was sent on-demand a number of times. + * + * @param string $notification + * @param int $times + * @return void + */ + public function assertSentOnDemandTimes($notification, $times = 1) + { + return $this->assertSentToTimes(new AnonymousNotifiable, $notification, $times); + } + + /** + * Assert if a notification was sent a number of times. + * + * @param mixed $notifiable + * @param string $notification + * @param int $times + * @return void + */ + public function assertSentToTimes($notifiable, $notification, $times = 1) + { + $count = $this->sent($notifiable, $notification)->count(); + + PHPUnit::assertSame( + $times, $count, + "Expected [{$notification}] to be sent {$times} times, but was sent {$count} times." + ); + } + + /** + * Determine if a notification was sent based on a truth-test callback. + * + * @param mixed $notifiable + * @param string|\Closure $notification + * @param callable|null $callback + * @return void + * + * @throws \Exception + */ + public function assertNotSentTo($notifiable, $notification, $callback = null) + { + if (is_array($notifiable) || $notifiable instanceof Collection) { + if (count($notifiable) === 0) { + throw new Exception('No notifiable given.'); + } + + foreach ($notifiable as $singleNotifiable) { + $this->assertNotSentTo($singleNotifiable, $notification, $callback); + } + + return; + } + + if ($notification instanceof Closure) { + [$notification, $callback] = [$this->firstClosureParameterType($notification), $notification]; + } + + PHPUnit::assertCount( + 0, $this->sent($notifiable, $notification, $callback), + "The unexpected [{$notification}] notification was sent." + ); + } + + /** + * Assert that no notifications were sent. + * + * @return void + */ + public function assertNothingSent() + { + PHPUnit::assertEmpty($this->notifications, 'Notifications were sent unexpectedly.'); + } + + /** + * Assert the total amount of times a notification was sent. + * + * @param string $notification + * @param int $expectedCount + * @return void + */ + public function assertSentTimes($notification, $expectedCount) + { + $actualCount = collect($this->notifications) + ->flatten(1) + ->reduce(function ($count, $sent) use ($notification) { + return $count + count($sent[$notification] ?? []); + }, 0); + + PHPUnit::assertSame( + $expectedCount, $actualCount, + "Expected [{$notification}] to be sent {$expectedCount} times, but was sent {$actualCount} times." + ); + } + + /** + * Assert the total amount of times a notification was sent. + * + * @param int $expectedCount + * @param string $notification + * @return void + * + * @deprecated Use the assertSentTimes method instead + */ + public function assertTimesSent($expectedCount, $notification) + { + $this->assertSentTimes($notification, $expectedCount); + } + + /** + * Get all of the notifications matching a truth-test callback. + * + * @param mixed $notifiable + * @param string $notification + * @param callable|null $callback + * @return \Illuminate\Support\Collection + */ + public function sent($notifiable, $notification, $callback = null) + { + if (! $this->hasSent($notifiable, $notification)) { + return collect(); + } + + $callback = $callback ?: function () { + return true; + }; + + $notifications = collect($this->notificationsFor($notifiable, $notification)); + + return $notifications->filter(function ($arguments) use ($callback) { + return $callback(...array_values($arguments)); + })->pluck('notification'); + } + + /** + * Determine if there are more notifications left to inspect. + * + * @param mixed $notifiable + * @param string $notification + * @return bool + */ + public function hasSent($notifiable, $notification) + { + return ! empty($this->notificationsFor($notifiable, $notification)); + } + + /** + * Get all of the notifications for a notifiable entity by type. + * + * @param mixed $notifiable + * @param string $notification + * @return array + */ + protected function notificationsFor($notifiable, $notification) + { + return $this->notifications[get_class($notifiable)][$notifiable->getKey()][$notification] ?? []; + } + + /** + * Send the given notification to the given notifiable entities. + * + * @param \Illuminate\Support\Collection|array|mixed $notifiables + * @param mixed $notification + * @return void + */ + public function send($notifiables, $notification) + { + $this->sendNow($notifiables, $notification); + } + + /** + * Send the given notification immediately. + * + * @param \Illuminate\Support\Collection|array|mixed $notifiables + * @param mixed $notification + * @param array|null $channels + * @return void + */ + public function sendNow($notifiables, $notification, array $channels = null) + { + if (! $notifiables instanceof Collection && ! is_array($notifiables)) { + $notifiables = [$notifiables]; + } + + foreach ($notifiables as $notifiable) { + if (! $notification->id) { + $notification->id = Str::uuid()->toString(); + } + + $notifiableChannels = $channels ?: $notification->via($notifiable); + + if (method_exists($notification, 'shouldSend')) { + $notifiableChannels = array_filter( + $notifiableChannels, + function ($channel) use ($notification, $notifiable) { + return $notification->shouldSend($notifiable, $channel) !== false; + } + ); + + if (empty($notifiableChannels)) { + continue; + } + } + + $this->notifications[get_class($notifiable)][$notifiable->getKey()][get_class($notification)][] = [ + 'notification' => $notification, + 'channels' => $notifiableChannels, + 'notifiable' => $notifiable, + 'locale' => $notification->locale ?? $this->locale ?? value(function () use ($notifiable) { + if ($notifiable instanceof HasLocalePreference) { + return $notifiable->preferredLocale(); + } + }), + ]; + } + } + + /** + * Get a channel instance by name. + * + * @param string|null $name + * @return mixed + */ + public function channel($name = null) + { + // + } + + /** + * Set the locale of notifications. + * + * @param string $locale + * @return $this + */ + public function locale($locale) + { + $this->locale = $locale; + + return $this; + } +} diff --git a/vendor/illuminate/support/Testing/Fakes/PendingBatchFake.php b/vendor/illuminate/support/Testing/Fakes/PendingBatchFake.php new file mode 100644 index 0000000..c60b4b5 --- /dev/null +++ b/vendor/illuminate/support/Testing/Fakes/PendingBatchFake.php @@ -0,0 +1,39 @@ +bus = $bus; + $this->jobs = $jobs; + } + + /** + * Dispatch the batch. + * + * @return \Illuminate\Bus\Batch + */ + public function dispatch() + { + return $this->bus->recordPendingBatch($this); + } +} diff --git a/vendor/illuminate/support/Testing/Fakes/PendingChainFake.php b/vendor/illuminate/support/Testing/Fakes/PendingChainFake.php new file mode 100644 index 0000000..533c649 --- /dev/null +++ b/vendor/illuminate/support/Testing/Fakes/PendingChainFake.php @@ -0,0 +1,56 @@ +bus = $bus; + $this->job = $job; + $this->chain = $chain; + } + + /** + * Dispatch the job with the given arguments. + * + * @return \Illuminate\Foundation\Bus\PendingDispatch + */ + public function dispatch() + { + if (is_string($this->job)) { + $firstJob = new $this->job(...func_get_args()); + } elseif ($this->job instanceof Closure) { + $firstJob = CallQueuedClosure::create($this->job); + } else { + $firstJob = $this->job; + } + + $firstJob->allOnConnection($this->connection); + $firstJob->allOnQueue($this->queue); + $firstJob->chain($this->chain); + $firstJob->delay($this->delay); + $firstJob->chainCatchCallbacks = $this->catchCallbacks(); + + return $this->bus->dispatch($firstJob); + } +} diff --git a/vendor/illuminate/support/Testing/Fakes/PendingMailFake.php b/vendor/illuminate/support/Testing/Fakes/PendingMailFake.php new file mode 100644 index 0000000..5225130 --- /dev/null +++ b/vendor/illuminate/support/Testing/Fakes/PendingMailFake.php @@ -0,0 +1,42 @@ +mailer = $mailer; + } + + /** + * Send a new mailable message instance. + * + * @param \Illuminate\Contracts\Mail\Mailable $mailable + * @return void + */ + public function send(Mailable $mailable) + { + $this->mailer->send($this->fill($mailable)); + } + + /** + * Push the given mailable onto the queue. + * + * @param \Illuminate\Contracts\Mail\Mailable $mailable + * @return mixed + */ + public function queue(Mailable $mailable) + { + return $this->mailer->queue($this->fill($mailable)); + } +} diff --git a/vendor/illuminate/support/Testing/Fakes/QueueFake.php b/vendor/illuminate/support/Testing/Fakes/QueueFake.php new file mode 100644 index 0000000..d37cd67 --- /dev/null +++ b/vendor/illuminate/support/Testing/Fakes/QueueFake.php @@ -0,0 +1,414 @@ +firstClosureParameterType($job), $job]; + } + + if (is_numeric($callback)) { + return $this->assertPushedTimes($job, $callback); + } + + PHPUnit::assertTrue( + $this->pushed($job, $callback)->count() > 0, + "The expected [{$job}] job was not pushed." + ); + } + + /** + * Assert if a job was pushed a number of times. + * + * @param string $job + * @param int $times + * @return void + */ + protected function assertPushedTimes($job, $times = 1) + { + $count = $this->pushed($job)->count(); + + PHPUnit::assertSame( + $times, $count, + "The expected [{$job}] job was pushed {$count} times instead of {$times} times." + ); + } + + /** + * Assert if a job was pushed based on a truth-test callback. + * + * @param string $queue + * @param string|\Closure $job + * @param callable|null $callback + * @return void + */ + public function assertPushedOn($queue, $job, $callback = null) + { + if ($job instanceof Closure) { + [$job, $callback] = [$this->firstClosureParameterType($job), $job]; + } + + $this->assertPushed($job, function ($job, $pushedQueue) use ($callback, $queue) { + if ($pushedQueue !== $queue) { + return false; + } + + return $callback ? $callback(...func_get_args()) : true; + }); + } + + /** + * Assert if a job was pushed with chained jobs based on a truth-test callback. + * + * @param string $job + * @param array $expectedChain + * @param callable|null $callback + * @return void + */ + public function assertPushedWithChain($job, $expectedChain = [], $callback = null) + { + PHPUnit::assertTrue( + $this->pushed($job, $callback)->isNotEmpty(), + "The expected [{$job}] job was not pushed." + ); + + PHPUnit::assertTrue( + collect($expectedChain)->isNotEmpty(), + 'The expected chain can not be empty.' + ); + + $this->isChainOfObjects($expectedChain) + ? $this->assertPushedWithChainOfObjects($job, $expectedChain, $callback) + : $this->assertPushedWithChainOfClasses($job, $expectedChain, $callback); + } + + /** + * Assert if a job was pushed with an empty chain based on a truth-test callback. + * + * @param string $job + * @param callable|null $callback + * @return void + */ + public function assertPushedWithoutChain($job, $callback = null) + { + PHPUnit::assertTrue( + $this->pushed($job, $callback)->isNotEmpty(), + "The expected [{$job}] job was not pushed." + ); + + $this->assertPushedWithChainOfClasses($job, [], $callback); + } + + /** + * Assert if a job was pushed with chained jobs based on a truth-test callback. + * + * @param string $job + * @param array $expectedChain + * @param callable|null $callback + * @return void + */ + protected function assertPushedWithChainOfObjects($job, $expectedChain, $callback) + { + $chain = collect($expectedChain)->map(function ($job) { + return serialize($job); + })->all(); + + PHPUnit::assertTrue( + $this->pushed($job, $callback)->filter(function ($job) use ($chain) { + return $job->chained == $chain; + })->isNotEmpty(), + 'The expected chain was not pushed.' + ); + } + + /** + * Assert if a job was pushed with chained jobs based on a truth-test callback. + * + * @param string $job + * @param array $expectedChain + * @param callable|null $callback + * @return void + */ + protected function assertPushedWithChainOfClasses($job, $expectedChain, $callback) + { + $matching = $this->pushed($job, $callback)->map->chained->map(function ($chain) { + return collect($chain)->map(function ($job) { + return get_class(unserialize($job)); + }); + })->filter(function ($chain) use ($expectedChain) { + return $chain->all() === $expectedChain; + }); + + PHPUnit::assertTrue( + $matching->isNotEmpty(), 'The expected chain was not pushed.' + ); + } + + /** + * Determine if the given chain is entirely composed of objects. + * + * @param array $chain + * @return bool + */ + protected function isChainOfObjects($chain) + { + return ! collect($chain)->contains(function ($job) { + return ! is_object($job); + }); + } + + /** + * Determine if a job was pushed based on a truth-test callback. + * + * @param string|\Closure $job + * @param callable|null $callback + * @return void + */ + public function assertNotPushed($job, $callback = null) + { + if ($job instanceof Closure) { + [$job, $callback] = [$this->firstClosureParameterType($job), $job]; + } + + PHPUnit::assertCount( + 0, $this->pushed($job, $callback), + "The unexpected [{$job}] job was pushed." + ); + } + + /** + * Assert that no jobs were pushed. + * + * @return void + */ + public function assertNothingPushed() + { + PHPUnit::assertEmpty($this->jobs, 'Jobs were pushed unexpectedly.'); + } + + /** + * Get all of the jobs matching a truth-test callback. + * + * @param string $job + * @param callable|null $callback + * @return \Illuminate\Support\Collection + */ + public function pushed($job, $callback = null) + { + if (! $this->hasPushed($job)) { + return collect(); + } + + $callback = $callback ?: function () { + return true; + }; + + return collect($this->jobs[$job])->filter(function ($data) use ($callback) { + return $callback($data['job'], $data['queue']); + })->pluck('job'); + } + + /** + * Determine if there are any stored jobs for a given class. + * + * @param string $job + * @return bool + */ + public function hasPushed($job) + { + return isset($this->jobs[$job]) && ! empty($this->jobs[$job]); + } + + /** + * Resolve a queue connection instance. + * + * @param mixed $value + * @return \Illuminate\Contracts\Queue\Queue + */ + public function connection($value = null) + { + return $this; + } + + /** + * Get the size of the queue. + * + * @param string|null $queue + * @return int + */ + public function size($queue = null) + { + return collect($this->jobs)->flatten(1)->filter(function ($job) use ($queue) { + return $job['queue'] === $queue; + })->count(); + } + + /** + * Push a new job onto the queue. + * + * @param string|object $job + * @param mixed $data + * @param string|null $queue + * @return mixed + */ + public function push($job, $data = '', $queue = null) + { + $this->jobs[is_object($job) ? get_class($job) : $job][] = [ + 'job' => $job, + 'queue' => $queue, + ]; + } + + /** + * Push a raw payload onto the queue. + * + * @param string $payload + * @param string|null $queue + * @param array $options + * @return mixed + */ + public function pushRaw($payload, $queue = null, array $options = []) + { + // + } + + /** + * Push a new job onto the queue after a delay. + * + * @param \DateTimeInterface|\DateInterval|int $delay + * @param string|object $job + * @param mixed $data + * @param string|null $queue + * @return mixed + */ + public function later($delay, $job, $data = '', $queue = null) + { + return $this->push($job, $data, $queue); + } + + /** + * Push a new job onto the queue. + * + * @param string $queue + * @param string|object $job + * @param mixed $data + * @return mixed + */ + public function pushOn($queue, $job, $data = '') + { + return $this->push($job, $data, $queue); + } + + /** + * Push a new job onto the queue after a delay. + * + * @param string $queue + * @param \DateTimeInterface|\DateInterval|int $delay + * @param string|object $job + * @param mixed $data + * @return mixed + */ + public function laterOn($queue, $delay, $job, $data = '') + { + return $this->push($job, $data, $queue); + } + + /** + * Pop the next job off of the queue. + * + * @param string|null $queue + * @return \Illuminate\Contracts\Queue\Job|null + */ + public function pop($queue = null) + { + // + } + + /** + * Push an array of jobs onto the queue. + * + * @param array $jobs + * @param mixed $data + * @param string|null $queue + * @return mixed + */ + public function bulk($jobs, $data = '', $queue = null) + { + foreach ($jobs as $job) { + $this->push($job, $data, $queue); + } + } + + /** + * Get the jobs that have been pushed. + * + * @return array + */ + public function pushedJobs() + { + return $this->jobs; + } + + /** + * Get the connection name for the queue. + * + * @return string + */ + public function getConnectionName() + { + // + } + + /** + * Set the connection name for the queue. + * + * @param string $name + * @return $this + */ + public function setConnectionName($name) + { + return $this; + } + + /** + * Override the QueueManager to prevent circular dependency. + * + * @param string $method + * @param array $parameters + * @return mixed + * + * @throws \BadMethodCallException + */ + public function __call($method, $parameters) + { + throw new BadMethodCallException(sprintf( + 'Call to undefined method %s::%s()', static::class, $method + )); + } +} diff --git a/vendor/illuminate/support/Timebox.php b/vendor/illuminate/support/Timebox.php new file mode 100644 index 0000000..32fd607 --- /dev/null +++ b/vendor/illuminate/support/Timebox.php @@ -0,0 +1,70 @@ +earlyReturn && $remainder > 0) { + $this->usleep($remainder); + } + + return $result; + } + + /** + * Indicate that the timebox can return early. + * + * @return $this + */ + public function returnEarly() + { + $this->earlyReturn = true; + + return $this; + } + + /** + * Indicate that the timebox cannot return early. + * + * @return $this + */ + public function dontReturnEarly() + { + $this->earlyReturn = false; + + return $this; + } + + /** + * Sleep for the specified number of microseconds. + * + * @param $microseconds + * @return void + */ + protected function usleep($microseconds) + { + usleep($microseconds); + } +} diff --git a/vendor/illuminate/support/Traits/CapsuleManagerTrait.php b/vendor/illuminate/support/Traits/CapsuleManagerTrait.php new file mode 100644 index 0000000..0532755 --- /dev/null +++ b/vendor/illuminate/support/Traits/CapsuleManagerTrait.php @@ -0,0 +1,69 @@ +container = $container; + + if (! $this->container->bound('config')) { + $this->container->instance('config', new Fluent); + } + } + + /** + * Make this capsule instance available globally. + * + * @return void + */ + public function setAsGlobal() + { + static::$instance = $this; + } + + /** + * Get the IoC container instance. + * + * @return \Illuminate\Contracts\Container\Container + */ + public function getContainer() + { + return $this->container; + } + + /** + * Set the IoC container instance. + * + * @param \Illuminate\Contracts\Container\Container $container + * @return void + */ + public function setContainer(Container $container) + { + $this->container = $container; + } +} diff --git a/vendor/illuminate/support/Traits/Conditionable.php b/vendor/illuminate/support/Traits/Conditionable.php new file mode 100644 index 0000000..7980827 --- /dev/null +++ b/vendor/illuminate/support/Traits/Conditionable.php @@ -0,0 +1,44 @@ +{$method}(...$parameters); + } catch (Error|BadMethodCallException $e) { + $pattern = '~^Call to undefined method (?P[^:]+)::(?P[^\(]+)\(\)$~'; + + if (! preg_match($pattern, $e->getMessage(), $matches)) { + throw $e; + } + + if ($matches['class'] != get_class($object) || + $matches['method'] != $method) { + throw $e; + } + + static::throwBadMethodCallException($method); + } + } + + /** + * Forward a method call to the given object, returning $this if the forwarded call returned itself. + * + * @param mixed $object + * @param string $method + * @param array $parameters + * @return mixed + * + * @throws \BadMethodCallException + */ + protected function forwardDecoratedCallTo($object, $method, $parameters) + { + $result = $this->forwardCallTo($object, $method, $parameters); + + if ($result === $object) { + return $this; + } + + return $result; + } + + /** + * Throw a bad method call exception for the given method. + * + * @param string $method + * @return void + * + * @throws \BadMethodCallException + */ + protected static function throwBadMethodCallException($method) + { + throw new BadMethodCallException(sprintf( + 'Call to undefined method %s::%s()', static::class, $method + )); + } +} diff --git a/vendor/illuminate/support/Traits/Localizable.php b/vendor/illuminate/support/Traits/Localizable.php new file mode 100644 index 0000000..1e9fa58 --- /dev/null +++ b/vendor/illuminate/support/Traits/Localizable.php @@ -0,0 +1,34 @@ +getLocale(); + + try { + $app->setLocale($locale); + + return $callback(); + } finally { + $app->setLocale($original); + } + } +} diff --git a/vendor/illuminate/support/Traits/ReflectsClosures.php b/vendor/illuminate/support/Traits/ReflectsClosures.php new file mode 100644 index 0000000..bf47d7e --- /dev/null +++ b/vendor/illuminate/support/Traits/ReflectsClosures.php @@ -0,0 +1,88 @@ +closureParameterTypes($closure)); + + if (! $types) { + throw new RuntimeException('The given Closure has no parameters.'); + } + + if ($types[0] === null) { + throw new RuntimeException('The first parameter of the given Closure is missing a type hint.'); + } + + return $types[0]; + } + + /** + * Get the class names of the first parameter of the given Closure, including union types. + * + * @param \Closure $closure + * @return array + * + * @throws \ReflectionException + * @throws \RuntimeException + */ + protected function firstClosureParameterTypes(Closure $closure) + { + $reflection = new ReflectionFunction($closure); + + $types = collect($reflection->getParameters())->mapWithKeys(function ($parameter) { + if ($parameter->isVariadic()) { + return [$parameter->getName() => null]; + } + + return [$parameter->getName() => Reflector::getParameterClassNames($parameter)]; + })->filter()->values()->all(); + + if (empty($types)) { + throw new RuntimeException('The given Closure has no parameters.'); + } + + if (isset($types[0]) && empty($types[0])) { + throw new RuntimeException('The first parameter of the given Closure is missing a type hint.'); + } + + return $types[0]; + } + + /** + * Get the class names / types of the parameters of the given Closure. + * + * @param \Closure $closure + * @return array + * + * @throws \ReflectionException + */ + protected function closureParameterTypes(Closure $closure) + { + $reflection = new ReflectionFunction($closure); + + return collect($reflection->getParameters())->mapWithKeys(function ($parameter) { + if ($parameter->isVariadic()) { + return [$parameter->getName() => null]; + } + + return [$parameter->getName() => Reflector::getParameterClassName($parameter)]; + })->all(); + } +} diff --git a/vendor/illuminate/support/Traits/Tappable.php b/vendor/illuminate/support/Traits/Tappable.php new file mode 100644 index 0000000..9353451 --- /dev/null +++ b/vendor/illuminate/support/Traits/Tappable.php @@ -0,0 +1,17 @@ +input = $input; + } + + /** + * Get a subset containing the provided keys with values from the input data. + * + * @param array|mixed $keys + * @return array + */ + public function only($keys) + { + $results = []; + + $input = $this->input; + + $placeholder = new stdClass; + + foreach (is_array($keys) ? $keys : func_get_args() as $key) { + $value = data_get($input, $key, $placeholder); + + if ($value !== $placeholder) { + Arr::set($results, $key, $value); + } + } + + return $results; + } + + /** + * Get all of the input except for a specified array of items. + * + * @param array|mixed $keys + * @return array + */ + public function except($keys) + { + $keys = is_array($keys) ? $keys : func_get_args(); + + $results = $this->input; + + Arr::forget($results, $keys); + + return $results; + } + + /** + * Merge the validated input with the given array of additional data. + * + * @param array $items + * @return static + */ + public function merge(array $items) + { + return new static(array_merge($this->input, $items)); + } + + /** + * Get the input as a collection. + * + * @return \Illuminate\Support\Collection + */ + public function collect() + { + return new Collection($this->input); + } + + /** + * Get the raw, underlying input array. + * + * @return array + */ + public function all() + { + return $this->input; + } + + /** + * Get the instance as an array. + * + * @return array + */ + public function toArray() + { + return $this->all(); + } + + /** + * Dynamically access input data. + * + * @param string $name + * @return mixed + */ + public function __get($name) + { + return $this->input[$name]; + } + + /** + * Dynamically set input data. + * + * @param string $name + * @param mixed $value + * @return mixed + */ + public function __set($name, $value) + { + $this->input[$name] = $value; + } + + /** + * Determine if an input key is set. + * + * @return bool + */ + public function __isset($name) + { + return isset($this->input[$name]); + } + + /** + * Remove an input key. + * + * @param string $name + * @return void + */ + public function __unset($name) + { + unset($this->input[$name]); + } + + /** + * Determine if an item exists at an offset. + * + * @param mixed $key + * @return bool + */ + #[\ReturnTypeWillChange] + public function offsetExists($key) + { + return isset($this->input[$key]); + } + + /** + * Get an item at a given offset. + * + * @param mixed $key + * @return mixed + */ + #[\ReturnTypeWillChange] + public function offsetGet($key) + { + return $this->input[$key]; + } + + /** + * Set the item at a given offset. + * + * @param mixed $key + * @param mixed $value + * @return void + */ + #[\ReturnTypeWillChange] + public function offsetSet($key, $value) + { + if (is_null($key)) { + $this->input[] = $value; + } else { + $this->input[$key] = $value; + } + } + + /** + * Unset the item at a given offset. + * + * @param string $key + * @return void + */ + #[\ReturnTypeWillChange] + public function offsetUnset($key) + { + unset($this->input[$key]); + } + + /** + * Get an iterator for the input. + * + * @return \ArrayIterator + */ + #[\ReturnTypeWillChange] + public function getIterator() + { + return new ArrayIterator($this->input); + } +} diff --git a/vendor/illuminate/support/ViewErrorBag.php b/vendor/illuminate/support/ViewErrorBag.php new file mode 100644 index 0000000..d51bb53 --- /dev/null +++ b/vendor/illuminate/support/ViewErrorBag.php @@ -0,0 +1,131 @@ +bags[$key]); + } + + /** + * Get a MessageBag instance from the bags. + * + * @param string $key + * @return \Illuminate\Contracts\Support\MessageBag + */ + public function getBag($key) + { + return Arr::get($this->bags, $key) ?: new MessageBag; + } + + /** + * Get all the bags. + * + * @return array + */ + public function getBags() + { + return $this->bags; + } + + /** + * Add a new MessageBag instance to the bags. + * + * @param string $key + * @param \Illuminate\Contracts\Support\MessageBag $bag + * @return $this + */ + public function put($key, MessageBagContract $bag) + { + $this->bags[$key] = $bag; + + return $this; + } + + /** + * Determine if the default message bag has any messages. + * + * @return bool + */ + public function any() + { + return $this->count() > 0; + } + + /** + * Get the number of messages in the default bag. + * + * @return int + */ + #[\ReturnTypeWillChange] + public function count() + { + return $this->getBag('default')->count(); + } + + /** + * Dynamically call methods on the default bag. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + return $this->getBag('default')->$method(...$parameters); + } + + /** + * Dynamically access a view error bag. + * + * @param string $key + * @return \Illuminate\Contracts\Support\MessageBag + */ + public function __get($key) + { + return $this->getBag($key); + } + + /** + * Dynamically set a view error bag. + * + * @param string $key + * @param \Illuminate\Contracts\Support\MessageBag $value + * @return void + */ + public function __set($key, $value) + { + $this->put($key, $value); + } + + /** + * Convert the default bag to its string representation. + * + * @return string + */ + public function __toString() + { + return (string) $this->getBag('default'); + } +} diff --git a/vendor/illuminate/support/helpers.php b/vendor/illuminate/support/helpers.php new file mode 100644 index 0000000..0b82fe7 --- /dev/null +++ b/vendor/illuminate/support/helpers.php @@ -0,0 +1,379 @@ + $value) { + if (is_numeric($key)) { + $start++; + + $array[$start] = Arr::pull($array, $key); + } + } + + return $array; + } +} + +if (! function_exists('blank')) { + /** + * Determine if the given value is "blank". + * + * @param mixed $value + * @return bool + */ + function blank($value) + { + if (is_null($value)) { + return true; + } + + if (is_string($value)) { + return trim($value) === ''; + } + + if (is_numeric($value) || is_bool($value)) { + return false; + } + + if ($value instanceof Countable) { + return count($value) === 0; + } + + return empty($value); + } +} + +if (! function_exists('class_basename')) { + /** + * Get the class "basename" of the given object / class. + * + * @param string|object $class + * @return string + */ + function class_basename($class) + { + $class = is_object($class) ? get_class($class) : $class; + + return basename(str_replace('\\', '/', $class)); + } +} + +if (! function_exists('class_uses_recursive')) { + /** + * Returns all traits used by a class, its parent classes and trait of their traits. + * + * @param object|string $class + * @return array + */ + function class_uses_recursive($class) + { + if (is_object($class)) { + $class = get_class($class); + } + + $results = []; + + foreach (array_reverse(class_parents($class)) + [$class => $class] as $class) { + $results += trait_uses_recursive($class); + } + + return array_unique($results); + } +} + +if (! function_exists('e')) { + /** + * Encode HTML special characters in a string. + * + * @param \Illuminate\Contracts\Support\DeferringDisplayableValue|\Illuminate\Contracts\Support\Htmlable|string|null $value + * @param bool $doubleEncode + * @return string + */ + function e($value, $doubleEncode = true) + { + if ($value instanceof DeferringDisplayableValue) { + $value = $value->resolveDisplayableValue(); + } + + if ($value instanceof Htmlable) { + return $value->toHtml(); + } + + return htmlspecialchars($value ?? '', ENT_QUOTES, 'UTF-8', $doubleEncode); + } +} + +if (! function_exists('env')) { + /** + * Gets the value of an environment variable. + * + * @param string $key + * @param mixed $default + * @return mixed + */ + function env($key, $default = null) + { + return Env::get($key, $default); + } +} + +if (! function_exists('filled')) { + /** + * Determine if a value is "filled". + * + * @param mixed $value + * @return bool + */ + function filled($value) + { + return ! blank($value); + } +} + +if (! function_exists('object_get')) { + /** + * Get an item from an object using "dot" notation. + * + * @param object $object + * @param string|null $key + * @param mixed $default + * @return mixed + */ + function object_get($object, $key, $default = null) + { + if (is_null($key) || trim($key) === '') { + return $object; + } + + foreach (explode('.', $key) as $segment) { + if (! is_object($object) || ! isset($object->{$segment})) { + return value($default); + } + + $object = $object->{$segment}; + } + + return $object; + } +} + +if (! function_exists('optional')) { + /** + * Provide access to optional objects. + * + * @param mixed $value + * @param callable|null $callback + * @return mixed + */ + function optional($value = null, callable $callback = null) + { + if (is_null($callback)) { + return new Optional($value); + } elseif (! is_null($value)) { + return $callback($value); + } + } +} + +if (! function_exists('preg_replace_array')) { + /** + * Replace a given pattern with each value in the array in sequentially. + * + * @param string $pattern + * @param array $replacements + * @param string $subject + * @return string + */ + function preg_replace_array($pattern, array $replacements, $subject) + { + return preg_replace_callback($pattern, function () use (&$replacements) { + foreach ($replacements as $key => $value) { + return array_shift($replacements); + } + }, $subject); + } +} + +if (! function_exists('retry')) { + /** + * Retry an operation a given number of times. + * + * @param int $times + * @param callable $callback + * @param int|\Closure $sleepMilliseconds + * @param callable|null $when + * @return mixed + * + * @throws \Exception + */ + function retry($times, callable $callback, $sleepMilliseconds = 0, $when = null) + { + $attempts = 0; + + beginning: + $attempts++; + $times--; + + try { + return $callback($attempts); + } catch (Exception $e) { + if ($times < 1 || ($when && ! $when($e))) { + throw $e; + } + + if ($sleepMilliseconds) { + usleep(value($sleepMilliseconds, $attempts) * 1000); + } + + goto beginning; + } + } +} + +if (! function_exists('tap')) { + /** + * Call the given Closure with the given value then return the value. + * + * @param mixed $value + * @param callable|null $callback + * @return mixed + */ + function tap($value, $callback = null) + { + if (is_null($callback)) { + return new HigherOrderTapProxy($value); + } + + $callback($value); + + return $value; + } +} + +if (! function_exists('throw_if')) { + /** + * Throw the given exception if the given condition is true. + * + * @param mixed $condition + * @param \Throwable|string $exception + * @param mixed ...$parameters + * @return mixed + * + * @throws \Throwable + */ + function throw_if($condition, $exception = 'RuntimeException', ...$parameters) + { + if ($condition) { + if (is_string($exception) && class_exists($exception)) { + $exception = new $exception(...$parameters); + } + + throw is_string($exception) ? new RuntimeException($exception) : $exception; + } + + return $condition; + } +} + +if (! function_exists('throw_unless')) { + /** + * Throw the given exception unless the given condition is true. + * + * @param mixed $condition + * @param \Throwable|string $exception + * @param mixed ...$parameters + * @return mixed + * + * @throws \Throwable + */ + function throw_unless($condition, $exception = 'RuntimeException', ...$parameters) + { + throw_if(! $condition, $exception, ...$parameters); + + return $condition; + } +} + +if (! function_exists('trait_uses_recursive')) { + /** + * Returns all traits used by a trait and its traits. + * + * @param string $trait + * @return array + */ + function trait_uses_recursive($trait) + { + $traits = class_uses($trait) ?: []; + + foreach ($traits as $trait) { + $traits += trait_uses_recursive($trait); + } + + return $traits; + } +} + +if (! function_exists('transform')) { + /** + * Transform the given value if it is present. + * + * @param mixed $value + * @param callable $callback + * @param mixed $default + * @return mixed|null + */ + function transform($value, callable $callback, $default = null) + { + if (filled($value)) { + return $callback($value); + } + + if (is_callable($default)) { + return $default($value); + } + + return $default; + } +} + +if (! function_exists('windows_os')) { + /** + * Determine whether the current environment is Windows based. + * + * @return bool + */ + function windows_os() + { + return PHP_OS_FAMILY === 'Windows'; + } +} + +if (! function_exists('with')) { + /** + * Return the given value, optionally passed through the given callback. + * + * @param mixed $value + * @param callable|null $callback + * @return mixed + */ + function with($value, callable $callback = null) + { + return is_null($callback) ? $value : $callback($value); + } +} diff --git a/vendor/intervention/image/LICENSE b/vendor/intervention/image/LICENSE new file mode 100644 index 0000000..bc444ba --- /dev/null +++ b/vendor/intervention/image/LICENSE @@ -0,0 +1,9 @@ +The MIT License (MIT) + +Copyright (c) 2014 Oliver Vogel + +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. \ No newline at end of file diff --git a/vendor/intervention/image/provides.json b/vendor/intervention/image/provides.json new file mode 100644 index 0000000..a8cd1b6 --- /dev/null +++ b/vendor/intervention/image/provides.json @@ -0,0 +1,11 @@ +{ + "providers": [ + "Intervention\\Image\\ImageServiceProvider" + ], + "aliases": [ + { + "alias": "Image", + "facade": "Intervention\\Image\\Facades\\Image" + } + ] +} diff --git a/vendor/intervention/image/src/Intervention/Image/AbstractColor.php b/vendor/intervention/image/src/Intervention/Image/AbstractColor.php new file mode 100644 index 0000000..c9846c6 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/AbstractColor.php @@ -0,0 +1,229 @@ +parse($value); + } + + /** + * Parses given value as color + * + * @param mixed $value + * @return \Intervention\Image\AbstractColor + */ + public function parse($value) + { + switch (true) { + + case is_string($value): + $this->initFromString($value); + break; + + case is_int($value): + $this->initFromInteger($value); + break; + + case is_array($value): + $this->initFromArray($value); + break; + + case is_object($value): + $this->initFromObject($value); + break; + + case is_null($value): + $this->initFromArray([255, 255, 255, 0]); + break; + + default: + throw new NotReadableException( + "Color format ({$value}) cannot be read." + ); + } + + return $this; + } + + /** + * Formats current color instance into given format + * + * @param string $type + * @return mixed + */ + public function format($type) + { + switch (strtolower($type)) { + + case 'rgba': + return $this->getRgba(); + + case 'hex': + return $this->getHex('#'); + + case 'int': + case 'integer': + return $this->getInt(); + + case 'array': + return $this->getArray(); + + case 'obj': + case 'object': + return $this; + + default: + throw new NotSupportedException( + "Color format ({$type}) is not supported." + ); + } + } + + /** + * Reads RGBA values from string into array + * + * @param string $value + * @return array + */ + protected function rgbaFromString($value) + { + $result = false; + + // parse color string in hexidecimal format like #cccccc or cccccc or ccc + $hexPattern = '/^#?([a-f0-9]{1,2})([a-f0-9]{1,2})([a-f0-9]{1,2})$/i'; + + // parse color string in format rgb(140, 140, 140) + $rgbPattern = '/^rgb ?\(([0-9]{1,3}), ?([0-9]{1,3}), ?([0-9]{1,3})\)$/i'; + + // parse color string in format rgba(255, 0, 0, 0.5) + $rgbaPattern = '/^rgba ?\(([0-9]{1,3}), ?([0-9]{1,3}), ?([0-9]{1,3}), ?([0-9.]{1,4})\)$/i'; + + if (preg_match($hexPattern, $value, $matches)) { + $result = []; + $result[0] = strlen($matches[1]) == '1' ? hexdec($matches[1].$matches[1]) : hexdec($matches[1]); + $result[1] = strlen($matches[2]) == '1' ? hexdec($matches[2].$matches[2]) : hexdec($matches[2]); + $result[2] = strlen($matches[3]) == '1' ? hexdec($matches[3].$matches[3]) : hexdec($matches[3]); + $result[3] = 1; + } elseif (preg_match($rgbPattern, $value, $matches)) { + $result = []; + $result[0] = ($matches[1] >= 0 && $matches[1] <= 255) ? intval($matches[1]) : 0; + $result[1] = ($matches[2] >= 0 && $matches[2] <= 255) ? intval($matches[2]) : 0; + $result[2] = ($matches[3] >= 0 && $matches[3] <= 255) ? intval($matches[3]) : 0; + $result[3] = 1; + } elseif (preg_match($rgbaPattern, $value, $matches)) { + $result = []; + $result[0] = ($matches[1] >= 0 && $matches[1] <= 255) ? intval($matches[1]) : 0; + $result[1] = ($matches[2] >= 0 && $matches[2] <= 255) ? intval($matches[2]) : 0; + $result[2] = ($matches[3] >= 0 && $matches[3] <= 255) ? intval($matches[3]) : 0; + $result[3] = ($matches[4] >= 0 && $matches[4] <= 1) ? $matches[4] : 0; + } else { + throw new NotReadableException( + "Unable to read color ({$value})." + ); + } + + return $result; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/AbstractDecoder.php b/vendor/intervention/image/src/Intervention/Image/AbstractDecoder.php new file mode 100644 index 0000000..e51d4da --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/AbstractDecoder.php @@ -0,0 +1,364 @@ +data = $data; + } + + /** + * Init from given URL + * + * @param string $url + * @return \Intervention\Image\Image + */ + public function initFromUrl($url) + { + + $options = [ + 'http' => [ + 'method'=>"GET", + 'protocol_version'=>1.1, // force use HTTP 1.1 for service mesh environment with envoy + 'header'=>"Accept-language: en\r\n". + "User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36\r\n" + ] + ]; + + $context = stream_context_create($options); + + + if ($data = @file_get_contents($url, false, $context)) { + return $this->initFromBinary($data); + } + + throw new NotReadableException( + "Unable to init from given url (".$url.")." + ); + } + + /** + * Init from given stream + * + * @param StreamInterface|resource $stream + * @return \Intervention\Image\Image + */ + public function initFromStream($stream) + { + if (!$stream instanceof StreamInterface) { + $stream = new Stream($stream); + } + + try { + $offset = $stream->tell(); + } catch (\RuntimeException $e) { + $offset = 0; + } + + $shouldAndCanSeek = $offset !== 0 && $stream->isSeekable(); + + if ($shouldAndCanSeek) { + $stream->rewind(); + } + + try { + $data = $stream->getContents(); + } catch (\RuntimeException $e) { + $data = null; + } + + if ($shouldAndCanSeek) { + $stream->seek($offset); + } + + if ($data) { + return $this->initFromBinary($data); + } + + throw new NotReadableException( + "Unable to init from given stream" + ); + } + + /** + * Determines if current source data is GD resource + * + * @return boolean + */ + public function isGdResource() + { + if (is_resource($this->data)) { + return (get_resource_type($this->data) == 'gd'); + } + + if ($this->data instanceof \GdImage) { + return true; + } + + return false; + } + + /** + * Determines if current source data is Imagick object + * + * @return boolean + */ + public function isImagick() + { + return is_a($this->data, 'Imagick'); + } + + /** + * Determines if current source data is Intervention\Image\Image object + * + * @return boolean + */ + public function isInterventionImage() + { + return is_a($this->data, '\Intervention\Image\Image'); + } + + /** + * Determines if current data is SplFileInfo object + * + * @return boolean + */ + public function isSplFileInfo() + { + return is_a($this->data, 'SplFileInfo'); + } + + /** + * Determines if current data is Symfony UploadedFile component + * + * @return boolean + */ + public function isSymfonyUpload() + { + return is_a($this->data, 'Symfony\Component\HttpFoundation\File\UploadedFile'); + } + + /** + * Determines if current source data is file path + * + * @return boolean + */ + public function isFilePath() + { + if (is_string($this->data)) { + try { + return is_file($this->data); + } catch (\Exception $e) { + return false; + } + } + + return false; + } + + /** + * Determines if current source data is url + * + * @return boolean + */ + public function isUrl() + { + return (bool) filter_var($this->data, FILTER_VALIDATE_URL); + } + + /** + * Determines if current source data is a stream resource + * + * @return boolean + */ + public function isStream() + { + if ($this->data instanceof StreamInterface) return true; + if (!is_resource($this->data)) return false; + if (get_resource_type($this->data) !== 'stream') return false; + + return true; + } + + /** + * Determines if current source data is binary data + * + * @return boolean + */ + public function isBinary() + { + if (is_string($this->data)) { + $mime = finfo_buffer(finfo_open(FILEINFO_MIME_TYPE), $this->data); + return (substr($mime, 0, 4) != 'text' && $mime != 'application/x-empty'); + } + + return false; + } + + /** + * Determines if current source data is data-url + * + * @return boolean + */ + public function isDataUrl() + { + $data = $this->decodeDataUrl($this->data); + + return is_null($data) ? false : true; + } + + /** + * Determines if current source data is base64 encoded + * + * @return boolean + */ + public function isBase64() + { + if (!is_string($this->data)) { + return false; + } + + return base64_encode(base64_decode($this->data)) === str_replace(["\n", "\r"], '', $this->data); + } + + /** + * Initiates new Image from Intervention\Image\Image + * + * @param Image $object + * @return \Intervention\Image\Image + */ + public function initFromInterventionImage($object) + { + return $object; + } + + /** + * Parses and decodes binary image data from data-url + * + * @param string $data_url + * @return string + */ + private function decodeDataUrl($data_url) + { + if (!is_string($data_url)) { + return null; + } + + $pattern = "/^data:(?:image\/[a-zA-Z\-\.]+)(?:charset=\".+\")?;base64,(?P.+)$/"; + preg_match($pattern, str_replace(["\n", "\r"], '', $data_url), $matches); + + if (is_array($matches) && array_key_exists('data', $matches)) { + return base64_decode($matches['data']); + } + + return null; + } + + /** + * Initiates new image from mixed data + * + * @param mixed $data + * @return \Intervention\Image\Image + */ + public function init($data) + { + $this->data = $data; + + switch (true) { + + case $this->isGdResource(): + return $this->initFromGdResource($this->data); + + case $this->isImagick(): + return $this->initFromImagick($this->data); + + case $this->isInterventionImage(): + return $this->initFromInterventionImage($this->data); + + case $this->isSplFileInfo(): + return $this->initFromPath($this->data->getRealPath()); + + case $this->isBinary(): + return $this->initFromBinary($this->data); + + case $this->isUrl(): + return $this->initFromUrl($this->data); + + case $this->isStream(): + return $this->initFromStream($this->data); + + case $this->isDataUrl(): + return $this->initFromBinary($this->decodeDataUrl($this->data)); + + case $this->isFilePath(): + return $this->initFromPath($this->data); + + // isBase64 has to be after isFilePath to prevent false positives + case $this->isBase64(): + return $this->initFromBinary(base64_decode($this->data)); + + default: + throw new NotReadableException("Image source not readable"); + } + } + + /** + * Decoder object transforms to string source data + * + * @return string + */ + public function __toString() + { + return (string) $this->data; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/AbstractDriver.php b/vendor/intervention/image/src/Intervention/Image/AbstractDriver.php new file mode 100644 index 0000000..6c591a7 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/AbstractDriver.php @@ -0,0 +1,140 @@ +decoder->init($data); + } + + /** + * Encodes given image + * + * @param Image $image + * @param string $format + * @param int $quality + * @return \Intervention\Image\Image + */ + public function encode($image, $format, $quality) + { + return $this->encoder->process($image, $format, $quality); + } + + /** + * Executes named command on given image + * + * @param Image $image + * @param string $name + * @param array $arguments + * @return \Intervention\Image\Commands\AbstractCommand + */ + public function executeCommand($image, $name, $arguments) + { + $commandName = $this->getCommandClassName($name); + $command = new $commandName($arguments); + $command->execute($image); + + return $command; + } + + /** + * Returns classname of given command name + * + * @param string $name + * @return string + */ + private function getCommandClassName($name) + { + if (extension_loaded('mbstring')) { + $name = mb_strtoupper(mb_substr($name, 0, 1)) . mb_substr($name, 1); + } else { + $name = strtoupper(substr($name, 0, 1)) . substr($name, 1); + } + + $drivername = $this->getDriverName(); + $classnameLocal = sprintf('\Intervention\Image\%s\Commands\%sCommand', $drivername, ucfirst($name)); + $classnameGlobal = sprintf('\Intervention\Image\Commands\%sCommand', ucfirst($name)); + + if (class_exists($classnameLocal)) { + return $classnameLocal; + } elseif (class_exists($classnameGlobal)) { + return $classnameGlobal; + } + + throw new NotSupportedException( + "Command ({$name}) is not available for driver ({$drivername})." + ); + } + + /** + * Returns name of current driver instance + * + * @return string + */ + public function getDriverName() + { + $reflect = new \ReflectionClass($this); + $namespace = $reflect->getNamespaceName(); + + return substr(strrchr($namespace, "\\"), 1); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/AbstractEncoder.php b/vendor/intervention/image/src/Intervention/Image/AbstractEncoder.php new file mode 100644 index 0000000..c25f8bf --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/AbstractEncoder.php @@ -0,0 +1,271 @@ +setImage($image); + $this->setFormat($format); + $this->setQuality($quality); + + switch (strtolower($this->format)) { + + case 'data-url': + $this->result = $this->processDataUrl(); + break; + + case 'gif': + case 'image/gif': + $this->result = $this->processGif(); + break; + + case 'png': + case 'image/png': + case 'image/x-png': + $this->result = $this->processPng(); + break; + + case 'jpg': + case 'jpeg': + case 'jfif': + case 'image/jp2': + case 'image/jpg': + case 'image/jpeg': + case 'image/pjpeg': + case 'image/jfif': + $this->result = $this->processJpeg(); + break; + + case 'tif': + case 'tiff': + case 'image/tiff': + case 'image/tif': + case 'image/x-tif': + case 'image/x-tiff': + $this->result = $this->processTiff(); + break; + + case 'bmp': + case 'ms-bmp': + case 'x-bitmap': + case 'x-bmp': + case 'x-ms-bmp': + case 'x-win-bitmap': + case 'x-windows-bmp': + case 'x-xbitmap': + case 'image/ms-bmp': + case 'image/x-bitmap': + case 'image/x-bmp': + case 'image/x-ms-bmp': + case 'image/x-win-bitmap': + case 'image/x-windows-bmp': + case 'image/x-xbitmap': + $this->result = $this->processBmp(); + break; + + case 'ico': + case 'image/x-ico': + case 'image/x-icon': + case 'image/vnd.microsoft.icon': + $this->result = $this->processIco(); + break; + + case 'psd': + case 'image/vnd.adobe.photoshop': + $this->result = $this->processPsd(); + break; + + case 'webp': + case 'image/webp': + case 'image/x-webp': + $this->result = $this->processWebp(); + break; + + case 'avif': + case 'image/avif': + $this->result = $this->processAvif(); + break; + + case 'heic': + case 'image/heic': + case 'image/heif': + $this->result = $this->processHeic(); + break; + + default: + throw new NotSupportedException( + "Encoding format ({$this->format}) is not supported." + ); + } + + $this->setImage(null); + + return $image->setEncoded($this->result); + } + + /** + * Processes and returns encoded image as data-url string + * + * @return string + */ + protected function processDataUrl() + { + $mime = $this->image->mime ? $this->image->mime : 'image/png'; + + return sprintf('data:%s;base64,%s', + $mime, + base64_encode($this->process($this->image, $mime, $this->quality)) + ); + } + + /** + * Sets image to process + * + * @param Image $image + */ + protected function setImage($image) + { + $this->image = $image; + } + + /** + * Determines output format + * + * @param string $format + */ + protected function setFormat($format = null) + { + if ($format == '' && $this->image instanceof Image) { + $format = $this->image->mime; + } + + $this->format = $format ? $format : 'jpg'; + + return $this; + } + + /** + * Determines output quality + * + * @param int $quality + */ + protected function setQuality($quality) + { + $quality = is_null($quality) ? 90 : $quality; + $quality = $quality === 0 ? 1 : $quality; + + if ($quality < 0 || $quality > 100) { + throw new InvalidArgumentException( + 'Quality must range from 0 to 100.' + ); + } + + $this->quality = intval($quality); + + return $this; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/AbstractFont.php b/vendor/intervention/image/src/Intervention/Image/AbstractFont.php new file mode 100644 index 0000000..35c1825 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/AbstractFont.php @@ -0,0 +1,295 @@ +text = $text; + } + + /** + * Set text to be written + * + * @param String $text + * @return self + */ + public function text($text) + { + $this->text = $text; + + return $this; + } + + /** + * Get text to be written + * + * @return String + */ + public function getText() + { + return $this->text; + } + + /** + * Set font size in pixels + * + * @param int $size + * @return self + */ + public function size($size) + { + $this->size = $size; + + return $this; + } + + /** + * Get font size in pixels + * + * @return int + */ + public function getSize() + { + return $this->size; + } + + /** + * Set color of text to be written + * + * @param mixed $color + * @return self + */ + public function color($color) + { + $this->color = $color; + + return $this; + } + + /** + * Get color of text + * + * @return mixed + */ + public function getColor() + { + return $this->color; + } + + /** + * Set rotation angle of text + * + * @param int $angle + * @return self + */ + public function angle($angle) + { + $this->angle = $angle; + + return $this; + } + + /** + * Get rotation angle of text + * + * @return int + */ + public function getAngle() + { + return $this->angle; + } + + /** + * Set horizontal text alignment + * + * @param string $align + * @return self + */ + public function align($align) + { + $this->align = $align; + + return $this; + } + + /** + * Get horizontal text alignment + * + * @return string + */ + public function getAlign() + { + return $this->align; + } + + /** + * Set vertical text alignment + * + * @param string $valign + * @return self + */ + public function valign($valign) + { + $this->valign = $valign; + + return $this; + } + + /** + * Get vertical text alignment + * + * @return string + */ + public function getValign() + { + return $this->valign; + } + + /** + * Set text kerning + * + * @param string $kerning + * @return void + */ + public function kerning($kerning) + { + $this->kerning = $kerning; + } + + /** + * Get kerning + * + * @return float + */ + public function getKerning() + { + return $this->kerning; + } + + /** + * Set path to font file + * + * @param string $file + * @return self + */ + public function file($file) + { + $this->file = $file; + + return $this; + } + + /** + * Get path to font file + * + * @return string + */ + public function getFile() + { + return $this->file; + } + + /** + * Checks if current font has access to an applicable font file + * + * @return boolean + */ + protected function hasApplicableFontFile() + { + if (is_string($this->file)) { + return file_exists($this->file); + } + + return false; + } + + /** + * Counts lines of text to be written + * + * @return int + */ + public function countLines() + { + return count(explode(PHP_EOL, $this->text)); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/AbstractShape.php b/vendor/intervention/image/src/Intervention/Image/AbstractShape.php new file mode 100644 index 0000000..cd4a9f1 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/AbstractShape.php @@ -0,0 +1,71 @@ +background = $color; + } + + /** + * Set border width and color of current shape + * + * @param int $width + * @param string $color + * @return void + */ + public function border($width, $color = null) + { + $this->border_width = is_numeric($width) ? intval($width) : 0; + $this->border_color = is_null($color) ? '#000000' : $color; + } + + /** + * Determines if current shape has border + * + * @return boolean + */ + public function hasBorder() + { + return ($this->border_width >= 1); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Commands/AbstractCommand.php b/vendor/intervention/image/src/Intervention/Image/Commands/AbstractCommand.php new file mode 100644 index 0000000..e31078c --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Commands/AbstractCommand.php @@ -0,0 +1,81 @@ +arguments = $arguments; + } + + /** + * Creates new argument instance from given argument key + * + * @param int $key + * @return \Intervention\Image\Commands\Argument + */ + public function argument($key) + { + return new Argument($this, $key); + } + + /** + * Returns output data of current command + * + * @return mixed + */ + public function getOutput() + { + return $this->output ? $this->output : null; + } + + /** + * Determines if current instance has output data + * + * @return boolean + */ + public function hasOutput() + { + return ! is_null($this->output); + } + + /** + * Sets output data of current command + * + * @param mixed $value + */ + public function setOutput($value) + { + $this->output = $value; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Commands/Argument.php b/vendor/intervention/image/src/Intervention/Image/Commands/Argument.php new file mode 100644 index 0000000..9538199 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Commands/Argument.php @@ -0,0 +1,225 @@ +command = $command; + $this->key = $key; + } + + /** + * Returns name of current arguments command + * + * @return string + */ + public function getCommandName() + { + preg_match("/\\\\([\w]+)Command$/", get_class($this->command), $matches); + return isset($matches[1]) ? lcfirst($matches[1]).'()' : 'Method'; + } + + /** + * Returns value of current argument + * + * @param mixed $default + * @return mixed + */ + public function value($default = null) + { + $arguments = $this->command->arguments; + + if (is_array($arguments)) { + return isset($arguments[$this->key]) ? $arguments[$this->key] : $default; + } + + return $default; + } + + /** + * Defines current argument as required + * + * @return \Intervention\Image\Commands\Argument + */ + public function required() + { + if ( ! array_key_exists($this->key, $this->command->arguments)) { + throw new InvalidArgumentException( + sprintf("Missing argument %d for %s", $this->key + 1, $this->getCommandName()) + ); + } + + return $this; + } + + /** + * Determines that current argument must be of given type + * + * @return \Intervention\Image\Commands\Argument + */ + public function type($type) + { + $valid = true; + $value = $this->value(); + + if ($value === null) { + return $this; + } + + switch (strtolower($type)) { + case 'bool': + case 'boolean': + $valid = \is_bool($value); + $message = '%s accepts only boolean values as argument %d.'; + break; + case 'int': + case 'integer': + $valid = \is_int($value); + $message = '%s accepts only integer values as argument %d.'; + break; + case 'num': + case 'numeric': + $valid = is_numeric($value); + $message = '%s accepts only numeric values as argument %d.'; + break; + case 'str': + case 'string': + $valid = \is_string($value); + $message = '%s accepts only string values as argument %d.'; + break; + case 'array': + $valid = \is_array($value); + $message = '%s accepts only array as argument %d.'; + break; + case 'closure': + $valid = is_a($value, '\Closure'); + $message = '%s accepts only Closure as argument %d.'; + break; + case 'digit': + $valid = $this->isDigit($value); + $message = '%s accepts only integer values as argument %d.'; + break; + } + + if (! $valid) { + $commandName = $this->getCommandName(); + $argument = $this->key + 1; + + if (isset($message)) { + $message = sprintf($message, $commandName, $argument); + } else { + $message = sprintf('Missing argument for %d.', $argument); + } + + throw new InvalidArgumentException( + $message + ); + } + + return $this; + } + + /** + * Determines that current argument value must be numeric between given values + * + * @return \Intervention\Image\Commands\Argument + */ + public function between($x, $y) + { + $value = $this->type('numeric')->value(); + + if (is_null($value)) { + return $this; + } + + $alpha = min($x, $y); + $omega = max($x, $y); + + if ($value < $alpha || $value > $omega) { + throw new InvalidArgumentException( + sprintf('Argument %d must be between %s and %s.', $this->key, $x, $y) + ); + } + + return $this; + } + + /** + * Determines that current argument must be over a minimum value + * + * @return \Intervention\Image\Commands\Argument + */ + public function min($value) + { + $v = $this->type('numeric')->value(); + + if (is_null($v)) { + return $this; + } + + if ($v < $value) { + throw new InvalidArgumentException( + sprintf('Argument %d must be at least %s.', $this->key, $value) + ); + } + + return $this; + } + + /** + * Determines that current argument must be under a maxiumum value + * + * @return \Intervention\Image\Commands\Argument + */ + public function max($value) + { + $v = $this->type('numeric')->value(); + + if (is_null($v)) { + return $this; + } + + if ($v > $value) { + throw new InvalidArgumentException( + sprintf('Argument %d may not be greater than %s.', $this->key, $value) + ); + } + + return $this; + } + + /** + * Checks if value is "PHP" integer (120 but also 120.0) + * + * @param mixed $value + * @return boolean + */ + private function isDigit($value) + { + return is_numeric($value) ? intval($value) == $value : false; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Commands/ChecksumCommand.php b/vendor/intervention/image/src/Intervention/Image/Commands/ChecksumCommand.php new file mode 100644 index 0000000..9acc403 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Commands/ChecksumCommand.php @@ -0,0 +1,29 @@ +getSize(); + + for ($x=0; $x <= ($size->width-1); $x++) { + for ($y=0; $y <= ($size->height-1); $y++) { + $colors[] = $image->pickColor($x, $y, 'array'); + } + } + + $this->setOutput(md5(serialize($colors))); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Commands/CircleCommand.php b/vendor/intervention/image/src/Intervention/Image/Commands/CircleCommand.php new file mode 100644 index 0000000..c627818 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Commands/CircleCommand.php @@ -0,0 +1,35 @@ +argument(0)->type('numeric')->required()->value(); + $x = $this->argument(1)->type('numeric')->required()->value(); + $y = $this->argument(2)->type('numeric')->required()->value(); + $callback = $this->argument(3)->type('closure')->value(); + + $circle_classname = sprintf('\Intervention\Image\%s\Shapes\CircleShape', + $image->getDriver()->getDriverName()); + + $circle = new $circle_classname($diameter); + + if ($callback instanceof Closure) { + $callback($circle); + } + + $circle->applyToImage($image, $x, $y); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Commands/EllipseCommand.php b/vendor/intervention/image/src/Intervention/Image/Commands/EllipseCommand.php new file mode 100644 index 0000000..4637e02 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Commands/EllipseCommand.php @@ -0,0 +1,36 @@ +argument(0)->type('numeric')->required()->value(); + $height = $this->argument(1)->type('numeric')->required()->value(); + $x = $this->argument(2)->type('numeric')->required()->value(); + $y = $this->argument(3)->type('numeric')->required()->value(); + $callback = $this->argument(4)->type('closure')->value(); + + $ellipse_classname = sprintf('\Intervention\Image\%s\Shapes\EllipseShape', + $image->getDriver()->getDriverName()); + + $ellipse = new $ellipse_classname($width, $height); + + if ($callback instanceof Closure) { + $callback($ellipse); + } + + $ellipse->applyToImage($image, $x, $y); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Commands/ExifCommand.php b/vendor/intervention/image/src/Intervention/Image/Commands/ExifCommand.php new file mode 100644 index 0000000..063df6e --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Commands/ExifCommand.php @@ -0,0 +1,61 @@ +argument(0)->value(); + + // try to read exif data from image file + try { + if ($image->dirname && $image->basename) { + $stream = $image->dirname . '/' . $image->basename; + } elseif (version_compare(PHP_VERSION, '7.2.0', '>=')) { + // https://www.php.net/manual/en/function.exif-read-data.php#refsect1-function.exif-read-data-changelog + $stream = $image->stream()->detach(); + } else { + // https://bugs.php.net/bug.php?id=65187 + $stream = $image->encode('data-url')->encoded; + } + + $data = @exif_read_data($stream); + + if (!is_null($key) && is_array($data)) { + $data = array_key_exists($key, $data) ? $data[$key] : false; + } + + } catch (\Exception $e) { + throw new NotReadableException( + sprintf( + "Cannot read the Exif data from the filename (%s) provided ", + $image->dirname . '/' . $image->basename + ), + $e->getCode(), + $e + ); + } + + $this->setOutput($data); + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Commands/IptcCommand.php b/vendor/intervention/image/src/Intervention/Image/Commands/IptcCommand.php new file mode 100644 index 0000000..b78a2a8 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Commands/IptcCommand.php @@ -0,0 +1,68 @@ +argument(0)->value(); + + $info = []; + @getimagesize($image->dirname .'/'. $image->basename, $info); + + $data = []; + + if (array_key_exists('APP13', $info)) { + $iptc = iptcparse($info['APP13']); + + if (is_array($iptc)) { + $data['DocumentTitle'] = isset($iptc["2#005"][0]) ? $iptc["2#005"][0] : null; + $data['Urgency'] = isset($iptc["2#010"][0]) ? $iptc["2#010"][0] : null; + $data['Category'] = isset($iptc["2#015"][0]) ? $iptc["2#015"][0] : null; + $data['Subcategories'] = isset($iptc["2#020"][0]) ? $iptc["2#020"][0] : null; + $data['Keywords'] = isset($iptc["2#025"][0]) ? $iptc["2#025"] : null; + $data['ReleaseDate'] = isset($iptc["2#030"][0]) ? $iptc["2#030"][0] : null; + $data['ReleaseTime'] = isset($iptc["2#035"][0]) ? $iptc["2#035"][0] : null; + $data['SpecialInstructions'] = isset($iptc["2#040"][0]) ? $iptc["2#040"][0] : null; + $data['CreationDate'] = isset($iptc["2#055"][0]) ? $iptc["2#055"][0] : null; + $data['CreationTime'] = isset($iptc["2#060"][0]) ? $iptc["2#060"][0] : null; + $data['AuthorByline'] = isset($iptc["2#080"][0]) ? $iptc["2#080"][0] : null; + $data['AuthorTitle'] = isset($iptc["2#085"][0]) ? $iptc["2#085"][0] : null; + $data['City'] = isset($iptc["2#090"][0]) ? $iptc["2#090"][0] : null; + $data['SubLocation'] = isset($iptc["2#092"][0]) ? $iptc["2#092"][0] : null; + $data['State'] = isset($iptc["2#095"][0]) ? $iptc["2#095"][0] : null; + $data['Country'] = isset($iptc["2#101"][0]) ? $iptc["2#101"][0] : null; + $data['OTR'] = isset($iptc["2#103"][0]) ? $iptc["2#103"][0] : null; + $data['Headline'] = isset($iptc["2#105"][0]) ? $iptc["2#105"][0] : null; + $data['Source'] = isset($iptc["2#110"][0]) ? $iptc["2#110"][0] : null; + $data['PhotoSource'] = isset($iptc["2#115"][0]) ? $iptc["2#115"][0] : null; + $data['Copyright'] = isset($iptc["2#116"][0]) ? $iptc["2#116"][0] : null; + $data['Caption'] = isset($iptc["2#120"][0]) ? $iptc["2#120"][0] : null; + $data['CaptionWriter'] = isset($iptc["2#122"][0]) ? $iptc["2#122"][0] : null; + } + } + + if (! is_null($key) && is_array($data)) { + $data = array_key_exists($key, $data) ? $data[$key] : false; + } + + $this->setOutput($data); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Commands/LineCommand.php b/vendor/intervention/image/src/Intervention/Image/Commands/LineCommand.php new file mode 100644 index 0000000..a068c66 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Commands/LineCommand.php @@ -0,0 +1,36 @@ +argument(0)->type('numeric')->required()->value(); + $y1 = $this->argument(1)->type('numeric')->required()->value(); + $x2 = $this->argument(2)->type('numeric')->required()->value(); + $y2 = $this->argument(3)->type('numeric')->required()->value(); + $callback = $this->argument(4)->type('closure')->value(); + + $line_classname = sprintf('\Intervention\Image\%s\Shapes\LineShape', + $image->getDriver()->getDriverName()); + + $line = new $line_classname($x2, $y2); + + if ($callback instanceof Closure) { + $callback($line); + } + + $line->applyToImage($image, $x1, $y1); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Commands/OrientateCommand.php b/vendor/intervention/image/src/Intervention/Image/Commands/OrientateCommand.php new file mode 100644 index 0000000..552482c --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Commands/OrientateCommand.php @@ -0,0 +1,48 @@ +exif('Orientation')) { + + case 2: + $image->flip(); + break; + + case 3: + $image->rotate(180); + break; + + case 4: + $image->rotate(180)->flip(); + break; + + case 5: + $image->rotate(270)->flip(); + break; + + case 6: + $image->rotate(270); + break; + + case 7: + $image->rotate(90)->flip(); + break; + + case 8: + $image->rotate(90); + break; + } + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Commands/PolygonCommand.php b/vendor/intervention/image/src/Intervention/Image/Commands/PolygonCommand.php new file mode 100644 index 0000000..a2fa997 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Commands/PolygonCommand.php @@ -0,0 +1,49 @@ +argument(0)->type('array')->required()->value(); + $callback = $this->argument(1)->type('closure')->value(); + + $vertices_count = count($points); + + // check if number if coordinates is even + if ($vertices_count % 2 !== 0) { + throw new InvalidArgumentException( + "The number of given polygon vertices must be even." + ); + } + + if ($vertices_count < 6) { + throw new InvalidArgumentException( + "You must have at least 3 points in your array." + ); + } + + $polygon_classname = sprintf('\Intervention\Image\%s\Shapes\PolygonShape', + $image->getDriver()->getDriverName()); + + $polygon = new $polygon_classname($points); + + if ($callback instanceof Closure) { + $callback($polygon); + } + + $polygon->applyToImage($image); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Commands/PsrResponseCommand.php b/vendor/intervention/image/src/Intervention/Image/Commands/PsrResponseCommand.php new file mode 100644 index 0000000..d75cd90 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Commands/PsrResponseCommand.php @@ -0,0 +1,45 @@ +argument(0)->value(); + $quality = $this->argument(1)->between(0, 100)->value(); + + //Encoded property will be populated at this moment + $stream = $image->stream($format, $quality); + + $mimetype = finfo_buffer( + finfo_open(FILEINFO_MIME_TYPE), + $image->getEncoded() + ); + + $this->setOutput(new Response( + 200, + [ + 'Content-Type' => $mimetype, + 'Content-Length' => strlen($image->getEncoded()) + ], + $stream + )); + + return true; + } +} \ No newline at end of file diff --git a/vendor/intervention/image/src/Intervention/Image/Commands/RectangleCommand.php b/vendor/intervention/image/src/Intervention/Image/Commands/RectangleCommand.php new file mode 100644 index 0000000..24378b3 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Commands/RectangleCommand.php @@ -0,0 +1,36 @@ +argument(0)->type('numeric')->required()->value(); + $y1 = $this->argument(1)->type('numeric')->required()->value(); + $x2 = $this->argument(2)->type('numeric')->required()->value(); + $y2 = $this->argument(3)->type('numeric')->required()->value(); + $callback = $this->argument(4)->type('closure')->value(); + + $rectangle_classname = sprintf('\Intervention\Image\%s\Shapes\RectangleShape', + $image->getDriver()->getDriverName()); + + $rectangle = new $rectangle_classname($x1, $y1, $x2, $y2); + + if ($callback instanceof Closure) { + $callback($rectangle); + } + + $rectangle->applyToImage($image, $x1, $y1); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Commands/ResponseCommand.php b/vendor/intervention/image/src/Intervention/Image/Commands/ResponseCommand.php new file mode 100644 index 0000000..7903b5a --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Commands/ResponseCommand.php @@ -0,0 +1,26 @@ +argument(0)->value(); + $quality = $this->argument(1)->between(0, 100)->value(); + + $response = new Response($image, $format, $quality); + + $this->setOutput($response->make()); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Commands/StreamCommand.php b/vendor/intervention/image/src/Intervention/Image/Commands/StreamCommand.php new file mode 100644 index 0000000..bc2ff9e --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Commands/StreamCommand.php @@ -0,0 +1,39 @@ +argument(0)->value(); + $quality = $this->argument(1)->between(0, 100)->value(); + $data = $image->encode($format, $quality)->getEncoded(); + + $this->setOutput($this->getStream($data)); + + return true; + } + + /** + * Create stream from given data + * + * @param string $data + * @return \Psr\Http\Message\StreamInterface + */ + protected function getStream($data) + { + if (class_exists(\GuzzleHttp\Psr7\Utils::class)) { + return \GuzzleHttp\Psr7\Utils::streamFor($data); // guzzlehttp/psr7 >= 2.0 + } + + return \GuzzleHttp\Psr7\stream_for($data); // guzzlehttp/psr7 < 2.0 + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Commands/TextCommand.php b/vendor/intervention/image/src/Intervention/Image/Commands/TextCommand.php new file mode 100644 index 0000000..3c60b4e --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Commands/TextCommand.php @@ -0,0 +1,34 @@ +argument(0)->required()->value(); + $x = $this->argument(1)->type('numeric')->value(0); + $y = $this->argument(2)->type('numeric')->value(0); + $callback = $this->argument(3)->type('closure')->value(); + + $fontclassname = sprintf('\Intervention\Image\%s\Font', + $image->getDriver()->getDriverName()); + + $font = new $fontclassname($text); + + if ($callback instanceof Closure) { + $callback($font); + } + + $font->applyToImage($image, $x, $y); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Constraint.php b/vendor/intervention/image/src/Intervention/Image/Constraint.php new file mode 100644 index 0000000..44bdd67 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Constraint.php @@ -0,0 +1,92 @@ +size = $size; + } + + /** + * Returns current size of constraint + * + * @return \Intervention\Image\Size + */ + public function getSize() + { + return $this->size; + } + + /** + * Fix the given argument in current constraint + * + * @param int $type + * @return void + */ + public function fix($type) + { + $this->fixed = ($this->fixed & ~(1 << $type)) | (1 << $type); + } + + /** + * Checks if given argument is fixed in current constraint + * + * @param int $type + * @return boolean + */ + public function isFixed($type) + { + return (bool) ($this->fixed & (1 << $type)); + } + + /** + * Fixes aspect ratio in current constraint + * + * @return void + */ + public function aspectRatio() + { + $this->fix(self::ASPECTRATIO); + } + + /** + * Fixes possibility to size up in current constraint + * + * @return void + */ + public function upsize() + { + $this->fix(self::UPSIZE); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Exception/ImageException.php b/vendor/intervention/image/src/Intervention/Image/Exception/ImageException.php new file mode 100644 index 0000000..83e6b91 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Exception/ImageException.php @@ -0,0 +1,8 @@ +dirname = array_key_exists('dirname', $info) ? $info['dirname'] : null; + $this->basename = array_key_exists('basename', $info) ? $info['basename'] : null; + $this->extension = array_key_exists('extension', $info) ? $info['extension'] : null; + $this->filename = array_key_exists('filename', $info) ? $info['filename'] : null; + + if (file_exists($path) && is_file($path)) { + $this->mime = finfo_file(finfo_open(FILEINFO_MIME_TYPE), $path); + } + + return $this; + } + + /** + * Get file size + * + * @return mixed + */ + public function filesize() + { + $path = $this->basePath(); + + if (file_exists($path) && is_file($path)) { + return filesize($path); + } + + return false; + } + + /** + * Get fully qualified path + * + * @return string + */ + public function basePath() + { + if ($this->dirname && $this->basename) { + return ($this->dirname .'/'. $this->basename); + } + + return null; + } + +} diff --git a/vendor/intervention/image/src/Intervention/Image/Filters/DemoFilter.php b/vendor/intervention/image/src/Intervention/Image/Filters/DemoFilter.php new file mode 100644 index 0000000..4e8f92b --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Filters/DemoFilter.php @@ -0,0 +1,44 @@ +size = is_numeric($size) ? intval($size) : self::DEFAULT_SIZE; + } + + /** + * Applies filter effects to given image + * + * @param \Intervention\Image\Image $image + * @return \Intervention\Image\Image + */ + public function applyFilter(Image $image) + { + $image->pixelate($this->size); + $image->greyscale(); + + return $image; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Filters/FilterInterface.php b/vendor/intervention/image/src/Intervention/Image/Filters/FilterInterface.php new file mode 100644 index 0000000..88f6cbf --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Filters/FilterInterface.php @@ -0,0 +1,16 @@ +a = ($value >> 24) & 0xFF; + $this->r = ($value >> 16) & 0xFF; + $this->g = ($value >> 8) & 0xFF; + $this->b = $value & 0xFF; + } + + /** + * Initiates color object from given array + * + * @param array $value + * @return \Intervention\Image\AbstractColor + */ + public function initFromArray($array) + { + $array = array_values($array); + + if (count($array) == 4) { + + // color array with alpha value + list($r, $g, $b, $a) = $array; + $this->a = $this->alpha2gd($a); + + } elseif (count($array) == 3) { + + // color array without alpha value + list($r, $g, $b) = $array; + $this->a = 0; + + } + + $this->r = $r; + $this->g = $g; + $this->b = $b; + } + + /** + * Initiates color object from given string + * + * @param string $value + * @return \Intervention\Image\AbstractColor + */ + public function initFromString($value) + { + if ($color = $this->rgbaFromString($value)) { + $this->r = $color[0]; + $this->g = $color[1]; + $this->b = $color[2]; + $this->a = $this->alpha2gd($color[3]); + } + } + + /** + * Initiates color object from given R, G and B values + * + * @param int $r + * @param int $g + * @param int $b + * @return \Intervention\Image\AbstractColor + */ + public function initFromRgb($r, $g, $b) + { + $this->r = intval($r); + $this->g = intval($g); + $this->b = intval($b); + $this->a = 0; + } + + /** + * Initiates color object from given R, G, B and A values + * + * @param int $r + * @param int $g + * @param int $b + * @param float $a + * @return \Intervention\Image\AbstractColor + */ + public function initFromRgba($r, $g, $b, $a = 1) + { + $this->r = intval($r); + $this->g = intval($g); + $this->b = intval($b); + $this->a = $this->alpha2gd($a); + } + + /** + * Initiates color object from given ImagickPixel object + * + * @param ImagickPixel $value + * @return \Intervention\Image\AbstractColor + */ + public function initFromObject($value) + { + throw new NotSupportedException( + "GD colors cannot init from ImagickPixel objects." + ); + } + + /** + * Calculates integer value of current color instance + * + * @return int + */ + public function getInt() + { + return ($this->a << 24) + ($this->r << 16) + ($this->g << 8) + $this->b; + } + + /** + * Calculates hexadecimal value of current color instance + * + * @param string $prefix + * @return string + */ + public function getHex($prefix = '') + { + return sprintf('%s%02x%02x%02x', $prefix, $this->r, $this->g, $this->b); + } + + /** + * Calculates RGB(A) in array format of current color instance + * + * @return array + */ + public function getArray() + { + return [$this->r, $this->g, $this->b, round(1 - $this->a / 127, 2)]; + } + + /** + * Calculates RGBA in string format of current color instance + * + * @return string + */ + public function getRgba() + { + return sprintf('rgba(%d, %d, %d, %.2F)', $this->r, $this->g, $this->b, round(1 - $this->a / 127, 2)); + } + + /** + * Determines if current color is different from given color + * + * @param AbstractColor $color + * @param int $tolerance + * @return boolean + */ + public function differs(AbstractColor $color, $tolerance = 0) + { + $color_tolerance = round($tolerance * 2.55); + $alpha_tolerance = round($tolerance * 1.27); + + $delta = [ + 'r' => abs($color->r - $this->r), + 'g' => abs($color->g - $this->g), + 'b' => abs($color->b - $this->b), + 'a' => abs($color->a - $this->a) + ]; + + return ( + $delta['r'] > $color_tolerance or + $delta['g'] > $color_tolerance or + $delta['b'] > $color_tolerance or + $delta['a'] > $alpha_tolerance + ); + } + + /** + * Convert rgba alpha (0-1) value to gd value (0-127) + * + * @param float $input + * @return int + */ + private function alpha2gd($input) + { + $oldMin = 0; + $oldMax = 1; + + $newMin = 127; + $newMax = 0; + + return ceil(((($input- $oldMin) * ($newMax - $newMin)) / ($oldMax - $oldMin)) + $newMin); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/BackupCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/BackupCommand.php new file mode 100644 index 0000000..310dc03 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/BackupCommand.php @@ -0,0 +1,25 @@ +argument(0)->value(); + + // clone current image resource + $clone = clone $image; + $image->setBackup($clone->getCore(), $backupName); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/BlurCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/BlurCommand.php new file mode 100644 index 0000000..c4fca0e --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/BlurCommand.php @@ -0,0 +1,25 @@ +argument(0)->between(0, 100)->value(1); + + for ($i=0; $i < intval($amount); $i++) { + imagefilter($image->getCore(), IMG_FILTER_GAUSSIAN_BLUR); + } + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/BrightnessCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/BrightnessCommand.php new file mode 100644 index 0000000..4e48464 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/BrightnessCommand.php @@ -0,0 +1,21 @@ +argument(0)->between(-100, 100)->required()->value(); + + return imagefilter($image->getCore(), IMG_FILTER_BRIGHTNESS, ($level * 2.55)); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/ColorizeCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/ColorizeCommand.php new file mode 100644 index 0000000..410917b --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/ColorizeCommand.php @@ -0,0 +1,29 @@ +argument(0)->between(-100, 100)->required()->value(); + $green = $this->argument(1)->between(-100, 100)->required()->value(); + $blue = $this->argument(2)->between(-100, 100)->required()->value(); + + // normalize colorize levels + $red = round($red * 2.55); + $green = round($green * 2.55); + $blue = round($blue * 2.55); + + // apply filter + return imagefilter($image->getCore(), IMG_FILTER_COLORIZE, $red, $green, $blue); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/ContrastCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/ContrastCommand.php new file mode 100644 index 0000000..916c41f --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/ContrastCommand.php @@ -0,0 +1,21 @@ +argument(0)->between(-100, 100)->required()->value(); + + return imagefilter($image->getCore(), IMG_FILTER_CONTRAST, ($level * -1)); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/CropCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/CropCommand.php new file mode 100644 index 0000000..b7f5954 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/CropCommand.php @@ -0,0 +1,40 @@ +argument(0)->type('digit')->required()->value(); + $height = $this->argument(1)->type('digit')->required()->value(); + $x = $this->argument(2)->type('digit')->value(); + $y = $this->argument(3)->type('digit')->value(); + + if (is_null($width) || is_null($height)) { + throw new \Intervention\Image\Exception\InvalidArgumentException( + "Width and height of cutout needs to be defined." + ); + } + + $cropped = new Size($width, $height); + $position = new Point($x, $y); + + // align boxes + if (is_null($x) && is_null($y)) { + $position = $image->getSize()->align('center')->relativePosition($cropped->align('center')); + } + + // crop image core + return $this->modify($image, 0, 0, $position->x, $position->y, $cropped->width, $cropped->height, $cropped->width, $cropped->height); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/DestroyCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/DestroyCommand.php new file mode 100644 index 0000000..4503d10 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/DestroyCommand.php @@ -0,0 +1,27 @@ +getCore()); + + // destroy backups + foreach ($image->getBackups() as $backup) { + imagedestroy($backup); + } + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/FillCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/FillCommand.php new file mode 100644 index 0000000..cf19082 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/FillCommand.php @@ -0,0 +1,69 @@ +argument(0)->value(); + $x = $this->argument(1)->type('digit')->value(); + $y = $this->argument(2)->type('digit')->value(); + + $width = $image->getWidth(); + $height = $image->getHeight(); + $resource = $image->getCore(); + + try { + + // set image tile filling + $source = new Decoder; + $tile = $source->init($filling); + imagesettile($image->getCore(), $tile->getCore()); + $filling = IMG_COLOR_TILED; + + } catch (\Intervention\Image\Exception\NotReadableException $e) { + + // set solid color filling + $color = new Color($filling); + $filling = $color->getInt(); + } + + imagealphablending($resource, true); + + if (is_int($x) && is_int($y)) { + + // resource should be visible through transparency + $base = $image->getDriver()->newImage($width, $height)->getCore(); + imagecopy($base, $resource, 0, 0, 0, 0, $width, $height); + + // floodfill if exact position is defined + imagefill($resource, $x, $y, $filling); + + // copy filled original over base + imagecopy($base, $resource, 0, 0, 0, 0, $width, $height); + + // set base as new resource-core + $image->setCore($base); + imagedestroy($resource); + + } else { + // fill whole image otherwise + imagefilledrectangle($resource, 0, 0, $width - 1, $height - 1, $filling); + } + + isset($tile) ? imagedestroy($tile->getCore()) : null; + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/FitCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/FitCommand.php new file mode 100644 index 0000000..d861ad9 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/FitCommand.php @@ -0,0 +1,32 @@ +argument(0)->type('digit')->required()->value(); + $height = $this->argument(1)->type('digit')->value($width); + $constraints = $this->argument(2)->type('closure')->value(); + $position = $this->argument(3)->type('string')->value('center'); + + // calculate size + $cropped = $image->getSize()->fit(new Size($width, $height), $position); + $resized = clone $cropped; + $resized = $resized->resize($width, $height, $constraints); + + // modify image + $this->modify($image, 0, 0, $cropped->pivot->x, $cropped->pivot->y, $resized->getWidth(), $resized->getHeight(), $cropped->getWidth(), $cropped->getHeight()); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/FlipCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/FlipCommand.php new file mode 100644 index 0000000..aa8f230 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/FlipCommand.php @@ -0,0 +1,37 @@ +argument(0)->value('h'); + + $size = $image->getSize(); + $dst = clone $size; + + switch (strtolower($mode)) { + case 2: + case 'v': + case 'vert': + case 'vertical': + $size->pivot->y = $size->height - 1; + $size->height = $size->height * (-1); + break; + + default: + $size->pivot->x = $size->width - 1; + $size->width = $size->width * (-1); + break; + } + + return $this->modify($image, 0, 0, $size->pivot->x, $size->pivot->y, $dst->width, $dst->height, $size->width, $size->height); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/GammaCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/GammaCommand.php new file mode 100644 index 0000000..7de0fb8 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/GammaCommand.php @@ -0,0 +1,21 @@ +argument(0)->type('numeric')->required()->value(); + + return imagegammacorrect($image->getCore(), 1, $gamma); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/GetSizeCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/GetSizeCommand.php new file mode 100644 index 0000000..9eb7e20 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/GetSizeCommand.php @@ -0,0 +1,25 @@ +setOutput(new Size( + imagesx($image->getCore()), + imagesy($image->getCore()) + )); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/GreyscaleCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/GreyscaleCommand.php new file mode 100644 index 0000000..12921e7 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/GreyscaleCommand.php @@ -0,0 +1,19 @@ +getCore(), IMG_FILTER_GRAYSCALE); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/HeightenCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/HeightenCommand.php new file mode 100644 index 0000000..d31e9cd --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/HeightenCommand.php @@ -0,0 +1,28 @@ +argument(0)->type('digit')->required()->value(); + $additionalConstraints = $this->argument(1)->type('closure')->value(); + + $this->arguments[0] = null; + $this->arguments[1] = $height; + $this->arguments[2] = function ($constraint) use ($additionalConstraints) { + $constraint->aspectRatio(); + if(is_callable($additionalConstraints)) + $additionalConstraints($constraint); + }; + + return parent::execute($image); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/InsertCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/InsertCommand.php new file mode 100644 index 0000000..47c7703 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/InsertCommand.php @@ -0,0 +1,34 @@ +argument(0)->required()->value(); + $position = $this->argument(1)->type('string')->value(); + $x = $this->argument(2)->type('digit')->value(0); + $y = $this->argument(3)->type('digit')->value(0); + + // build watermark + $watermark = $image->getDriver()->init($source); + + // define insertion point + $image_size = $image->getSize()->align($position, $x, $y); + $watermark_size = $watermark->getSize()->align($position); + $target = $image_size->relativePosition($watermark_size); + + // insert image at position + imagealphablending($image->getCore(), true); + return imagecopy($image->getCore(), $watermark->getCore(), $target->x, $target->y, 0, 0, $watermark_size->width, $watermark_size->height); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/InterlaceCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/InterlaceCommand.php new file mode 100644 index 0000000..e3461cb --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/InterlaceCommand.php @@ -0,0 +1,23 @@ +argument(0)->type('bool')->value(true); + + imageinterlace($image->getCore(), $mode); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/InvertCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/InvertCommand.php new file mode 100644 index 0000000..1a514f1 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/InvertCommand.php @@ -0,0 +1,19 @@ +getCore(), IMG_FILTER_NEGATE); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/LimitColorsCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/LimitColorsCommand.php new file mode 100644 index 0000000..0baed7e --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/LimitColorsCommand.php @@ -0,0 +1,53 @@ +argument(0)->value(); + $matte = $this->argument(1)->value(); + + // get current image size + $size = $image->getSize(); + + // create empty canvas + $resource = imagecreatetruecolor($size->width, $size->height); + + // define matte + if (is_null($matte)) { + $matte = imagecolorallocatealpha($resource, 255, 255, 255, 127); + } else { + $matte = $image->getDriver()->parseColor($matte)->getInt(); + } + + // fill with matte and copy original image + imagefill($resource, 0, 0, $matte); + + // set transparency + imagecolortransparent($resource, $matte); + + // copy original image + imagecopy($resource, $image->getCore(), 0, 0, 0, 0, $size->width, $size->height); + + if (is_numeric($count) && $count <= 256) { + // decrease colors + imagetruecolortopalette($resource, true, $count); + } + + // set new resource + $image->setCore($resource); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/MaskCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/MaskCommand.php new file mode 100644 index 0000000..4c61429 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/MaskCommand.php @@ -0,0 +1,83 @@ +argument(0)->value(); + $mask_w_alpha = $this->argument(1)->type('bool')->value(false); + + $image_size = $image->getSize(); + + // create empty canvas + $canvas = $image->getDriver()->newImage($image_size->width, $image_size->height, [0,0,0,0]); + + // build mask image from source + $mask = $image->getDriver()->init($mask_source); + $mask_size = $mask->getSize(); + + // resize mask to size of current image (if necessary) + if ($mask_size != $image_size) { + $mask->resize($image_size->width, $image_size->height); + } + + imagealphablending($canvas->getCore(), false); + + if ( ! $mask_w_alpha) { + // mask from greyscale image + imagefilter($mask->getCore(), IMG_FILTER_GRAYSCALE); + } + + // redraw old image pixel by pixel considering alpha map + for ($x=0; $x < $image_size->width; $x++) { + for ($y=0; $y < $image_size->height; $y++) { + + $color = $image->pickColor($x, $y, 'array'); + $alpha = $mask->pickColor($x, $y, 'array'); + + if ($mask_w_alpha) { + $alpha = $alpha[3]; // use alpha channel as mask + } else { + + if ($alpha[3] == 0) { // transparent as black + $alpha = 0; + } else { + + // $alpha = floatval(round((($alpha[0] + $alpha[1] + $alpha[3]) / 3) / 255, 2)); + + // image is greyscale, so channel doesn't matter (use red channel) + $alpha = floatval(round($alpha[0] / 255, 2)); + } + + } + + // preserve alpha of original image... + if ($color[3] < $alpha) { + $alpha = $color[3]; + } + + // replace alpha value + $color[3] = $alpha; + + // redraw pixel + $canvas->pixel($color, $x, $y); + } + } + + + // replace current image with masked instance + $image->setCore($canvas->getCore()); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/OpacityCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/OpacityCommand.php new file mode 100644 index 0000000..48492d2 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/OpacityCommand.php @@ -0,0 +1,31 @@ +argument(0)->between(0, 100)->required()->value(); + + // get size of image + $size = $image->getSize(); + + // build temp alpha mask + $mask_color = sprintf('rgba(0, 0, 0, %.1F)', $transparency / 100); + $mask = $image->getDriver()->newImage($size->width, $size->height, $mask_color); + + // mask image + $image->mask($mask->getCore(), true); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/PickColorCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/PickColorCommand.php new file mode 100644 index 0000000..bad96f4 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/PickColorCommand.php @@ -0,0 +1,37 @@ +argument(0)->type('digit')->required()->value(); + $y = $this->argument(1)->type('digit')->required()->value(); + $format = $this->argument(2)->type('string')->value('array'); + + // pick color + $color = imagecolorat($image->getCore(), $x, $y); + + if ( ! imageistruecolor($image->getCore())) { + $color = imagecolorsforindex($image->getCore(), $color); + $color['alpha'] = round(1 - $color['alpha'] / 127, 2); + } + + $color = new Color($color); + + // format to output + $this->setOutput($color->format($format)); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/PixelCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/PixelCommand.php new file mode 100644 index 0000000..2a90ce3 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/PixelCommand.php @@ -0,0 +1,25 @@ +argument(0)->required()->value(); + $color = new Color($color); + $x = $this->argument(1)->type('digit')->required()->value(); + $y = $this->argument(2)->type('digit')->required()->value(); + + return imagesetpixel($image->getCore(), $x, $y, $color->getInt()); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/PixelateCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/PixelateCommand.php new file mode 100644 index 0000000..0934797 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/PixelateCommand.php @@ -0,0 +1,21 @@ +argument(0)->type('digit')->value(10); + + return imagefilter($image->getCore(), IMG_FILTER_PIXELATE, $size, true); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/ResetCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/ResetCommand.php new file mode 100644 index 0000000..a68b75e --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/ResetCommand.php @@ -0,0 +1,39 @@ +argument(0)->value(); + $backup = $image->getBackup($backupName); + + if (is_resource($backup) || $backup instanceof \GdImage) { + + // destroy current resource + imagedestroy($image->getCore()); + + // clone backup + $backup = $image->getDriver()->cloneCore($backup); + + // reset to new resource + $image->setCore($backup); + + return true; + } + + throw new RuntimeException( + "Backup not available. Call backup() before reset()." + ); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/ResizeCanvasCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/ResizeCanvasCommand.php new file mode 100644 index 0000000..73f3df3 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/ResizeCanvasCommand.php @@ -0,0 +1,83 @@ +argument(0)->type('digit')->required()->value(); + $height = $this->argument(1)->type('digit')->required()->value(); + $anchor = $this->argument(2)->value('center'); + $relative = $this->argument(3)->type('boolean')->value(false); + $bgcolor = $this->argument(4)->value(); + + $original_width = $image->getWidth(); + $original_height = $image->getHeight(); + + // check of only width or height is set + $width = is_null($width) ? $original_width : intval($width); + $height = is_null($height) ? $original_height : intval($height); + + // check on relative width/height + if ($relative) { + $width = $original_width + $width; + $height = $original_height + $height; + } + + // check for negative width/height + $width = ($width <= 0) ? $width + $original_width : $width; + $height = ($height <= 0) ? $height + $original_height : $height; + + // create new canvas + $canvas = $image->getDriver()->newImage($width, $height, $bgcolor); + + // set copy position + $canvas_size = $canvas->getSize()->align($anchor); + $image_size = $image->getSize()->align($anchor); + $canvas_pos = $image_size->relativePosition($canvas_size); + $image_pos = $canvas_size->relativePosition($image_size); + + if ($width <= $original_width) { + $dst_x = 0; + $src_x = $canvas_pos->x; + $src_w = $canvas_size->width; + } else { + $dst_x = $image_pos->x; + $src_x = 0; + $src_w = $original_width; + } + + if ($height <= $original_height) { + $dst_y = 0; + $src_y = $canvas_pos->y; + $src_h = $canvas_size->height; + } else { + $dst_y = $image_pos->y; + $src_y = 0; + $src_h = $original_height; + } + + // make image area transparent to keep transparency + // even if background-color is set + $transparent = imagecolorallocatealpha($canvas->getCore(), 255, 255, 255, 127); + imagealphablending($canvas->getCore(), false); // do not blend / just overwrite + imagefilledrectangle($canvas->getCore(), $dst_x, $dst_y, $dst_x + $src_w - 1, $dst_y + $src_h - 1, $transparent); + + // copy image into new canvas + imagecopy($canvas->getCore(), $image->getCore(), $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h); + + // set new core to canvas + $image->setCore($canvas->getCore()); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/ResizeCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/ResizeCommand.php new file mode 100644 index 0000000..e8ef7c7 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/ResizeCommand.php @@ -0,0 +1,84 @@ +argument(0)->value(); + $height = $this->argument(1)->value(); + $constraints = $this->argument(2)->type('closure')->value(); + + // resize box + $resized = $image->getSize()->resize($width, $height, $constraints); + + // modify image + $this->modify($image, 0, 0, 0, 0, $resized->getWidth(), $resized->getHeight(), $image->getWidth(), $image->getHeight()); + + return true; + } + + /** + * Wrapper function for 'imagecopyresampled' + * + * @param Image $image + * @param int $dst_x + * @param int $dst_y + * @param int $src_x + * @param int $src_y + * @param int $dst_w + * @param int $dst_h + * @param int $src_w + * @param int $src_h + * @return boolean + */ + protected function modify($image, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h) + { + // create new image + $modified = imagecreatetruecolor(intval($dst_w), intval($dst_h)); + + // get current image + $resource = $image->getCore(); + + // preserve transparency + $transIndex = imagecolortransparent($resource); + + if ($transIndex != -1) { + $rgba = imagecolorsforindex($modified, $transIndex); + $transColor = imagecolorallocatealpha($modified, $rgba['red'], $rgba['green'], $rgba['blue'], 127); + imagefill($modified, 0, 0, $transColor); + imagecolortransparent($modified, $transColor); + } else { + imagealphablending($modified, false); + imagesavealpha($modified, true); + } + + // copy content from resource + $result = imagecopyresampled( + $modified, + $resource, + $dst_x, + $dst_y, + $src_x, + $src_y, + intval($dst_w), + intval($dst_h), + $src_w, + $src_h + ); + + // set new content as recource + $image->setCore($modified); + + return $result; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/RotateCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/RotateCommand.php new file mode 100644 index 0000000..682ec0d --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/RotateCommand.php @@ -0,0 +1,30 @@ +argument(0)->type('numeric')->required()->value(); + $color = $this->argument(1)->value(); + $color = new Color($color); + + // restrict rotations beyond 360 degrees, since the end result is the same + $angle = fmod($angle, 360); + + // rotate image + $image->setCore(imagerotate($image->getCore(), $angle, $color->getInt())); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/SharpenCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/SharpenCommand.php new file mode 100644 index 0000000..782e565 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/SharpenCommand.php @@ -0,0 +1,34 @@ +argument(0)->between(0, 100)->value(10); + + // build matrix + $min = $amount >= 10 ? $amount * -0.01 : 0; + $max = $amount * -0.025; + $abs = ((4 * $min + 4 * $max) * -1) + 1; + $div = 1; + + $matrix = [ + [$min, $max, $min], + [$max, $abs, $max], + [$min, $max, $min] + ]; + + // apply the matrix + return imageconvolution($image->getCore(), $matrix, $div, 0); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/TrimCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/TrimCommand.php new file mode 100644 index 0000000..2e36975 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/TrimCommand.php @@ -0,0 +1,176 @@ +argument(0)->type('string')->value(); + $away = $this->argument(1)->value(); + $tolerance = $this->argument(2)->type('numeric')->value(0); + $feather = $this->argument(3)->type('numeric')->value(0); + + $width = $image->getWidth(); + $height = $image->getHeight(); + + // default values + $checkTransparency = false; + + // define borders to trim away + if (is_null($away)) { + $away = ['top', 'right', 'bottom', 'left']; + } elseif (is_string($away)) { + $away = [$away]; + } + + // lower border names + foreach ($away as $key => $value) { + $away[$key] = strtolower($value); + } + + // define base color position + switch (strtolower($base)) { + case 'transparent': + case 'trans': + $checkTransparency = true; + $base_x = 0; + $base_y = 0; + break; + + case 'bottom-right': + case 'right-bottom': + $base_x = $width - 1; + $base_y = $height - 1; + break; + + default: + case 'top-left': + case 'left-top': + $base_x = 0; + $base_y = 0; + break; + } + + // pick base color + if ($checkTransparency) { + $color = new Color; // color will only be used to compare alpha channel + } else { + $color = $image->pickColor($base_x, $base_y, 'object'); + } + + $top_x = 0; + $top_y = 0; + $bottom_x = $width; + $bottom_y = $height; + + // search upper part of image for colors to trim away + if (in_array('top', $away)) { + + for ($y=0; $y < ceil($height/2); $y++) { + for ($x=0; $x < $width; $x++) { + + $checkColor = $image->pickColor($x, $y, 'object'); + + if ($checkTransparency) { + $checkColor->r = $color->r; + $checkColor->g = $color->g; + $checkColor->b = $color->b; + } + + if ($color->differs($checkColor, $tolerance)) { + $top_y = max(0, $y - $feather); + break 2; + } + + } + } + + } + + // search left part of image for colors to trim away + if (in_array('left', $away)) { + + for ($x=0; $x < ceil($width/2); $x++) { + for ($y=$top_y; $y < $height; $y++) { + + $checkColor = $image->pickColor($x, $y, 'object'); + + if ($checkTransparency) { + $checkColor->r = $color->r; + $checkColor->g = $color->g; + $checkColor->b = $color->b; + } + + if ($color->differs($checkColor, $tolerance)) { + $top_x = max(0, $x - $feather); + break 2; + } + + } + } + + } + + // search lower part of image for colors to trim away + if (in_array('bottom', $away)) { + + for ($y=($height-1); $y >= floor($height/2)-1; $y--) { + for ($x=$top_x; $x < $width; $x++) { + + $checkColor = $image->pickColor($x, $y, 'object'); + + if ($checkTransparency) { + $checkColor->r = $color->r; + $checkColor->g = $color->g; + $checkColor->b = $color->b; + } + + if ($color->differs($checkColor, $tolerance)) { + $bottom_y = min($height, $y+1 + $feather); + break 2; + } + + } + } + + } + + // search right part of image for colors to trim away + if (in_array('right', $away)) { + + for ($x=($width-1); $x >= floor($width/2)-1; $x--) { + for ($y=$top_y; $y < $bottom_y; $y++) { + + $checkColor = $image->pickColor($x, $y, 'object'); + + if ($checkTransparency) { + $checkColor->r = $color->r; + $checkColor->g = $color->g; + $checkColor->b = $color->b; + } + + if ($color->differs($checkColor, $tolerance)) { + $bottom_x = min($width, $x+1 + $feather); + break 2; + } + + } + } + + } + + + // trim parts of image + return $this->modify($image, 0, 0, $top_x, $top_y, ($bottom_x-$top_x), ($bottom_y-$top_y), ($bottom_x-$top_x), ($bottom_y-$top_y)); + + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/WidenCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/WidenCommand.php new file mode 100644 index 0000000..43000d5 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/WidenCommand.php @@ -0,0 +1,28 @@ +argument(0)->type('digit')->required()->value(); + $additionalConstraints = $this->argument(1)->type('closure')->value(); + + $this->arguments[0] = $width; + $this->arguments[1] = null; + $this->arguments[2] = function ($constraint) use ($additionalConstraints) { + $constraint->aspectRatio(); + if(is_callable($additionalConstraints)) + $additionalConstraints($constraint); + }; + + return parent::execute($image); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Decoder.php b/vendor/intervention/image/src/Intervention/Image/Gd/Decoder.php new file mode 100644 index 0000000..f5c34aa --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Decoder.php @@ -0,0 +1,171 @@ +gdResourceToTruecolor($core); + + // build image + $image = $this->initFromGdResource($core); + $image->mime = $mime; + $image->setFileInfoFromPath($path); + + return $image; + } + + /** + * Initiates new image from GD resource + * + * @param Resource $resource + * @return \Intervention\Image\Image + */ + public function initFromGdResource($resource) + { + return new Image(new Driver, $resource); + } + + /** + * Initiates new image from Imagick object + * + * @param Imagick $object + * @return \Intervention\Image\Image + */ + public function initFromImagick(\Imagick $object) + { + throw new NotSupportedException( + "Gd driver is unable to init from Imagick object." + ); + } + + /** + * Initiates new image from binary data + * + * @param string $data + * @return \Intervention\Image\Image + */ + public function initFromBinary($binary) + { + $resource = @imagecreatefromstring($binary); + + if ($resource === false) { + throw new NotReadableException( + "Unable to init from given binary data." + ); + } + + $image = $this->initFromGdResource($resource); + $image->mime = finfo_buffer(finfo_open(FILEINFO_MIME_TYPE), $binary); + + return $image; + } + + /** + * Transform GD resource into Truecolor version + * + * @param resource $resource + * @return bool + */ + public function gdResourceToTruecolor(&$resource) + { + $width = imagesx($resource); + $height = imagesy($resource); + + // new canvas + $canvas = imagecreatetruecolor($width, $height); + + // fill with transparent color + imagealphablending($canvas, false); + $transparent = imagecolorallocatealpha($canvas, 255, 255, 255, 127); + imagefilledrectangle($canvas, 0, 0, $width, $height, $transparent); + imagecolortransparent($canvas, $transparent); + imagealphablending($canvas, true); + + // copy original + imagecopy($canvas, $resource, 0, 0, 0, 0, $width, $height); + imagedestroy($resource); + + $resource = $canvas; + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Driver.php b/vendor/intervention/image/src/Intervention/Image/Gd/Driver.php new file mode 100644 index 0000000..5f2f23e --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Driver.php @@ -0,0 +1,89 @@ +coreAvailable()) { + throw new NotSupportedException( + "GD Library extension not available with this PHP installation." + ); + } + + $this->decoder = $decoder ? $decoder : new Decoder; + $this->encoder = $encoder ? $encoder : new Encoder; + } + + /** + * Creates new image instance + * + * @param int $width + * @param int $height + * @param mixed $background + * @return \Intervention\Image\Image + */ + public function newImage($width, $height, $background = null) + { + // create empty resource + $core = imagecreatetruecolor($width, $height); + $image = new Image(new static, $core); + + // set background color + $background = new Color($background); + imagefill($image->getCore(), 0, 0, $background->getInt()); + + return $image; + } + + /** + * Reads given string into color object + * + * @param string $value + * @return AbstractColor + */ + public function parseColor($value) + { + return new Color($value); + } + + /** + * Checks if core module installation is available + * + * @return boolean + */ + protected function coreAvailable() + { + return (extension_loaded('gd') && function_exists('gd_info')); + } + + /** + * Returns clone of given core + * + * @return mixed + */ + public function cloneCore($core) + { + $width = imagesx($core); + $height = imagesy($core); + $clone = imagecreatetruecolor($width, $height); + imagealphablending($clone, false); + imagesavealpha($clone, true); + $transparency = imagecolorallocatealpha($clone, 0, 0, 0, 127); + imagefill($clone, 0, 0, $transparency); + + imagecopy($clone, $core, 0, 0, 0, 0, $width, $height); + + return $clone; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Encoder.php b/vendor/intervention/image/src/Intervention/Image/Gd/Encoder.php new file mode 100644 index 0000000..559d60d --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Encoder.php @@ -0,0 +1,180 @@ +image->getCore(), null, $this->quality); + $this->image->mime = image_type_to_mime_type(IMAGETYPE_JPEG); + $buffer = ob_get_contents(); + ob_end_clean(); + + return $buffer; + } + + /** + * Processes and returns encoded image as PNG string + * + * @return string + */ + protected function processPng() + { + ob_start(); + $resource = $this->image->getCore(); + imagealphablending($resource, false); + imagesavealpha($resource, true); + imagepng($resource, null, -1); + $this->image->mime = image_type_to_mime_type(IMAGETYPE_PNG); + $buffer = ob_get_contents(); + ob_end_clean(); + + return $buffer; + } + + /** + * Processes and returns encoded image as GIF string + * + * @return string + */ + protected function processGif() + { + ob_start(); + imagegif($this->image->getCore()); + $this->image->mime = image_type_to_mime_type(IMAGETYPE_GIF); + $buffer = ob_get_contents(); + ob_end_clean(); + + return $buffer; + } + + /** + * Processes and returns encoded image as WEBP string + * + * @return string + */ + protected function processWebp() + { + if ( ! function_exists('imagewebp')) { + throw new NotSupportedException( + "Webp format is not supported by PHP installation." + ); + } + + ob_start(); + imagepalettetotruecolor($this->image->getCore()); + imagealphablending($this->image->getCore(), true); + imagesavealpha($this->image->getCore(), true); + imagewebp($this->image->getCore(), null, $this->quality); + $this->image->mime = defined('IMAGETYPE_WEBP') ? image_type_to_mime_type(IMAGETYPE_WEBP) : 'image/webp'; + $buffer = ob_get_contents(); + ob_end_clean(); + + return $buffer; + } + + /** + * Processes and returns encoded image as TIFF string + * + * @return string + */ + protected function processTiff() + { + throw new NotSupportedException( + "TIFF format is not supported by Gd Driver." + ); + } + + /** + * Processes and returns encoded image as BMP string + * + * @return string + */ + protected function processBmp() + { + if ( ! function_exists('imagebmp')) { + throw new NotSupportedException( + "BMP format is not supported by PHP installation." + ); + } + + ob_start(); + imagebmp($this->image->getCore()); + $this->image->mime = defined('IMAGETYPE_BMP') ? image_type_to_mime_type(IMAGETYPE_BMP) : 'image/bmp'; + $buffer = ob_get_contents(); + ob_end_clean(); + + return $buffer; + } + + /** + * Processes and returns encoded image as ICO string + * + * @return string + */ + protected function processIco() + { + throw new NotSupportedException( + "ICO format is not supported by Gd Driver." + ); + } + + /** + * Processes and returns encoded image as PSD string + * + * @return string + */ + protected function processPsd() + { + throw new NotSupportedException( + "PSD format is not supported by Gd Driver." + ); + } + + /** + * Processes and returns encoded image as AVIF string + * + * @return string + */ + protected function processAvif() + { + if ( ! function_exists('imageavif')) { + throw new NotSupportedException( + "AVIF format is not supported by PHP installation." + ); + } + + ob_start(); + $resource = $this->image->getCore(); + imagepalettetotruecolor($resource); + imagealphablending($resource, true); + imagesavealpha($resource, true); + imageavif($resource, null, $this->quality); + $this->image->mime = defined('IMAGETYPE_AVIF') ? image_type_to_mime_type(IMAGETYPE_AVIF) : 'image/avif'; + $buffer = ob_get_contents(); + ob_end_clean(); + + return $buffer; + } + + /** + * Processes and returns encoded image as HEIC string + * + * @return string + */ + protected function processHeic() + { + throw new NotSupportedException( + "HEIC format is not supported by Gd Driver." + ); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Font.php b/vendor/intervention/image/src/Intervention/Image/Gd/Font.php new file mode 100644 index 0000000..3f58887 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Font.php @@ -0,0 +1,277 @@ +size * 0.75)); + } + + /** + * Filter function to access internal integer font values + * + * @return int + */ + private function getInternalFont() + { + $internalfont = is_null($this->file) ? 1 : $this->file; + $internalfont = is_numeric($internalfont) ? $internalfont : false; + + if ( ! in_array($internalfont, [1, 2, 3, 4, 5])) { + throw new NotSupportedException( + sprintf('Internal GD font (%s) not available. Use only 1-5.', $internalfont) + ); + } + + return intval($internalfont); + } + + /** + * Get width of an internal font character + * + * @return int + */ + private function getInternalFontWidth() + { + return $this->getInternalFont() + 4; + } + + /** + * Get height of an internal font character + * + * @return int + */ + private function getInternalFontHeight() + { + switch ($this->getInternalFont()) { + case 1: + return 8; + + case 2: + return 14; + + case 3: + return 14; + + case 4: + return 16; + + case 5: + return 16; + } + } + + /** + * Calculates bounding box of current font setting + * + * @return Array + */ + public function getBoxSize() + { + $box = []; + + if ($this->hasApplicableFontFile()) { + + // imagettfbbox() converts numeric entities to their respective + // character. Preserve any originally double encoded entities to be + // represented as is. + // eg: &#160; will render   rather than its character. + $this->text = preg_replace('/&(#(?:x[a-fA-F0-9]+|[0-9]+);)/', '&\1', $this->text); + $this->text = mb_encode_numericentity($this->text, array(0x0080, 0xffff, 0, 0xffff), 'UTF-8'); + + // get bounding box with angle 0 + $box = imagettfbbox($this->getPointSize(), 0, $this->file, $this->text); + + // rotate points manually + if ($this->angle != 0) { + + $angle = pi() * 2 - $this->angle * pi() * 2 / 360; + + for ($i=0; $i<4; $i++) { + $x = $box[$i * 2]; + $y = $box[$i * 2 + 1]; + $box[$i * 2] = cos($angle) * $x - sin($angle) * $y; + $box[$i * 2 + 1] = sin($angle) * $x + cos($angle) * $y; + } + } + + $box['width'] = intval(abs($box[4] - $box[0])); + $box['height'] = intval(abs($box[5] - $box[1])); + + } else { + + // get current internal font size + $width = $this->getInternalFontWidth(); + $height = $this->getInternalFontHeight(); + + if (strlen($this->text) == 0) { + // no text -> no boxsize + $box['width'] = 0; + $box['height'] = 0; + } else { + // calculate boxsize + $box['width'] = strlen($this->text) * $width; + $box['height'] = $height; + } + } + + return $box; + } + + /** + * Draws font to given image at given position + * + * @param Image $image + * @param int $posx + * @param int $posy + * @return void + */ + public function applyToImage(Image $image, $posx = 0, $posy = 0) + { + // parse text color + $color = new Color($this->color); + + if ($this->hasApplicableFontFile()) { + + if ($this->angle != 0 || is_string($this->align) || is_string($this->valign)) { + + $box = $this->getBoxSize(); + + $align = is_null($this->align) ? 'left' : strtolower($this->align); + $valign = is_null($this->valign) ? 'bottom' : strtolower($this->valign); + + // correction on position depending on v/h alignment + switch ($align.'-'.$valign) { + + case 'center-top': + $posx = $posx - round(($box[6]+$box[4])/2); + $posy = $posy - round(($box[7]+$box[5])/2); + break; + + case 'right-top': + $posx = $posx - $box[4]; + $posy = $posy - $box[5]; + break; + + case 'left-top': + $posx = $posx - $box[6]; + $posy = $posy - $box[7]; + break; + + case 'center-center': + case 'center-middle': + $posx = $posx - round(($box[0]+$box[4])/2); + $posy = $posy - round(($box[1]+$box[5])/2); + break; + + case 'right-center': + case 'right-middle': + $posx = $posx - round(($box[2]+$box[4])/2); + $posy = $posy - round(($box[3]+$box[5])/2); + break; + + case 'left-center': + case 'left-middle': + $posx = $posx - round(($box[0]+$box[6])/2); + $posy = $posy - round(($box[1]+$box[7])/2); + break; + + case 'center-bottom': + $posx = $posx - round(($box[0]+$box[2])/2); + $posy = $posy - round(($box[1]+$box[3])/2); + break; + + case 'right-bottom': + $posx = $posx - $box[2]; + $posy = $posy - $box[3]; + break; + + case 'left-bottom': + $posx = $posx - $box[0]; + $posy = $posy - $box[1]; + break; + } + } + + // enable alphablending for imagettftext + imagealphablending($image->getCore(), true); + + // draw ttf text + imagettftext($image->getCore(), $this->getPointSize(), $this->angle, $posx, $posy, $color->getInt(), $this->file, $this->text); + + } else { + + // get box size + $box = $this->getBoxSize(); + $width = $box['width']; + $height = $box['height']; + + // internal font specific position corrections + if ($this->getInternalFont() == 1) { + $top_correction = 1; + $bottom_correction = 2; + } elseif ($this->getInternalFont() == 3) { + $top_correction = 2; + $bottom_correction = 4; + } else { + $top_correction = 3; + $bottom_correction = 4; + } + + // x-position corrections for horizontal alignment + switch (strtolower($this->align)) { + case 'center': + $posx = ceil($posx - ($width / 2)); + break; + + case 'right': + $posx = ceil($posx - $width) + 1; + break; + } + + // y-position corrections for vertical alignment + switch (strtolower($this->valign)) { + case 'center': + case 'middle': + $posy = ceil($posy - ($height / 2)); + break; + + case 'top': + $posy = ceil($posy - $top_correction); + break; + + default: + case 'bottom': + $posy = round($posy - $height + $bottom_correction); + break; + } + + // draw text + imagestring($image->getCore(), $this->getInternalFont(), $posx, $posy, $this->text, $color->getInt()); + } + } + + /** + * Set text kerning + * + * @param string $kerning + * @return void + */ + public function kerning($kerning) + { + throw new \Intervention\Image\Exception\NotSupportedException( + "Kerning is not supported by GD driver." + ); + } + +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Shapes/CircleShape.php b/vendor/intervention/image/src/Intervention/Image/Gd/Shapes/CircleShape.php new file mode 100644 index 0000000..c3c4214 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Shapes/CircleShape.php @@ -0,0 +1,40 @@ +width = is_numeric($diameter) ? intval($diameter) : $this->diameter; + $this->height = is_numeric($diameter) ? intval($diameter) : $this->diameter; + $this->diameter = is_numeric($diameter) ? intval($diameter) : $this->diameter; + } + + /** + * Draw current circle on given image + * + * @param Image $image + * @param int $x + * @param int $y + * @return boolean + */ + public function applyToImage(Image $image, $x = 0, $y = 0) + { + return parent::applyToImage($image, $x, $y); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Shapes/EllipseShape.php b/vendor/intervention/image/src/Intervention/Image/Gd/Shapes/EllipseShape.php new file mode 100644 index 0000000..78e5e4a --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Shapes/EllipseShape.php @@ -0,0 +1,65 @@ +width = is_numeric($width) ? intval($width) : $this->width; + $this->height = is_numeric($height) ? intval($height) : $this->height; + } + + /** + * Draw ellipse instance on given image + * + * @param Image $image + * @param int $x + * @param int $y + * @return boolean + */ + public function applyToImage(Image $image, $x = 0, $y = 0) + { + // parse background color + $background = new Color($this->background); + + if ($this->hasBorder()) { + // slightly smaller ellipse to keep 1px bordered edges clean + imagefilledellipse($image->getCore(), $x, $y, $this->width-1, $this->height-1, $background->getInt()); + + $border_color = new Color($this->border_color); + imagesetthickness($image->getCore(), $this->border_width); + + // gd's imageellipse doesn't respect imagesetthickness so i use imagearc with 359.9 degrees here + imagearc($image->getCore(), $x, $y, $this->width, $this->height, 0, 359.99, $border_color->getInt()); + } else { + imagefilledellipse($image->getCore(), $x, $y, $this->width, $this->height, $background->getInt()); + } + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Shapes/LineShape.php b/vendor/intervention/image/src/Intervention/Image/Gd/Shapes/LineShape.php new file mode 100644 index 0000000..ea38b51 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Shapes/LineShape.php @@ -0,0 +1,90 @@ +x = is_numeric($x) ? intval($x) : $this->x; + $this->y = is_numeric($y) ? intval($y) : $this->y; + } + + /** + * Set current line color + * + * @param string $color + * @return void + */ + public function color($color) + { + $this->color = $color; + } + + /** + * Set current line width in pixels + * + * @param int $width + * @return void + */ + public function width($width) + { + throw new \Intervention\Image\Exception\NotSupportedException( + "Line width is not supported by GD driver." + ); + } + + /** + * Draw current instance of line to given endpoint on given image + * + * @param Image $image + * @param int $x + * @param int $y + * @return boolean + */ + public function applyToImage(Image $image, $x = 0, $y = 0) + { + $color = new Color($this->color); + imageline($image->getCore(), $x, $y, $this->x, $this->y, $color->getInt()); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Shapes/PolygonShape.php b/vendor/intervention/image/src/Intervention/Image/Gd/Shapes/PolygonShape.php new file mode 100644 index 0000000..5e14df4 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Shapes/PolygonShape.php @@ -0,0 +1,49 @@ +points = $points; + } + + /** + * Draw polygon on given image + * + * @param Image $image + * @param int $x + * @param int $y + * @return boolean + */ + public function applyToImage(Image $image, $x = 0, $y = 0) + { + $background = new Color($this->background); + imagefilledpolygon($image->getCore(), $this->points, intval(count($this->points) / 2), $background->getInt()); + + if ($this->hasBorder()) { + $border_color = new Color($this->border_color); + imagesetthickness($image->getCore(), $this->border_width); + imagepolygon($image->getCore(), $this->points, intval(count($this->points) / 2), $border_color->getInt()); + } + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Shapes/RectangleShape.php b/vendor/intervention/image/src/Intervention/Image/Gd/Shapes/RectangleShape.php new file mode 100644 index 0000000..5f69a7f --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Shapes/RectangleShape.php @@ -0,0 +1,76 @@ +x1 = is_numeric($x1) ? intval($x1) : $this->x1; + $this->y1 = is_numeric($y1) ? intval($y1) : $this->y1; + $this->x2 = is_numeric($x2) ? intval($x2) : $this->x2; + $this->y2 = is_numeric($y2) ? intval($y2) : $this->y2; + } + + /** + * Draw rectangle to given image at certain position + * + * @param Image $image + * @param int $x + * @param int $y + * @return boolean + */ + public function applyToImage(Image $image, $x = 0, $y = 0) + { + $background = new Color($this->background); + imagefilledrectangle($image->getCore(), $this->x1, $this->y1, $this->x2, $this->y2, $background->getInt()); + + if ($this->hasBorder()) { + $border_color = new Color($this->border_color); + imagesetthickness($image->getCore(), $this->border_width); + imagerectangle($image->getCore(), $this->x1, $this->y1, $this->x2, $this->y2, $border_color->getInt()); + } + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Image.php b/vendor/intervention/image/src/Intervention/Image/Image.php new file mode 100644 index 0000000..3b26ea2 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Image.php @@ -0,0 +1,370 @@ +driver = $driver; + $this->core = $core; + } + + /** + * Magic method to catch all image calls + * usually any AbstractCommand + * + * @param string $name + * @param Array $arguments + * @return mixed + */ + public function __call($name, $arguments) + { + $command = $this->driver->executeCommand($this, $name, $arguments); + return $command->hasOutput() ? $command->getOutput() : $this; + } + + /** + * Starts encoding of current image + * + * @param string $format + * @param int $quality + * @return \Intervention\Image\Image + */ + public function encode($format = null, $quality = 90) + { + return $this->driver->encode($this, $format, $quality); + } + + /** + * Saves encoded image in filesystem + * + * @param string $path + * @param int $quality + * @param string $format + * @return \Intervention\Image\Image + */ + public function save($path = null, $quality = null, $format = null) + { + $path = is_null($path) ? $this->basePath() : $path; + + if (is_null($path)) { + throw new NotWritableException( + "Can't write to undefined path." + ); + } + + if ($format === null) { + $format = pathinfo($path, PATHINFO_EXTENSION); + } + + $data = $this->encode($format, $quality); + $saved = @file_put_contents($path, $data); + + if ($saved === false) { + throw new NotWritableException( + "Can't write image data to path ({$path})" + ); + } + + // set new file info + $this->setFileInfoFromPath($path); + + return $this; + } + + /** + * Runs a given filter on current image + * + * @param FiltersFilterInterface $filter + * @return \Intervention\Image\Image + */ + public function filter(Filters\FilterInterface $filter) + { + return $filter->applyFilter($this); + } + + /** + * Returns current image driver + * + * @return \Intervention\Image\AbstractDriver + */ + public function getDriver() + { + return $this->driver; + } + + /** + * Sets current image driver + * @param AbstractDriver $driver + */ + public function setDriver(AbstractDriver $driver) + { + $this->driver = $driver; + + return $this; + } + + /** + * Returns current image resource/obj + * + * @return mixed + */ + public function getCore() + { + return $this->core; + } + + /** + * Sets current image resource + * + * @param mixed $core + */ + public function setCore($core) + { + $this->core = $core; + + return $this; + } + + /** + * Returns current image backup + * + * @param string $name + * @return mixed + */ + public function getBackup($name = null) + { + $name = is_null($name) ? 'default' : $name; + + if ( ! $this->backupExists($name)) { + throw new RuntimeException( + "Backup with name ({$name}) not available. Call backup() before reset()." + ); + } + + return $this->backups[$name]; + } + + /** + * Returns all backups attached to image + * + * @return array + */ + public function getBackups() + { + return $this->backups; + } + + /** + * Sets current image backup + * + * @param mixed $resource + * @param string $name + * @return self + */ + public function setBackup($resource, $name = null) + { + $name = is_null($name) ? 'default' : $name; + + $this->backups[$name] = $resource; + + return $this; + } + + /** + * Checks if named backup exists + * + * @param string $name + * @return bool + */ + private function backupExists($name) + { + return array_key_exists($name, $this->backups); + } + + /** + * Checks if current image is already encoded + * + * @return boolean + */ + public function isEncoded() + { + return ! empty($this->encoded); + } + + /** + * Returns encoded image data of current image + * + * @return string + */ + public function getEncoded() + { + return $this->encoded; + } + + /** + * Sets encoded image buffer + * + * @param string $value + */ + public function setEncoded($value) + { + $this->encoded = $value; + + return $this; + } + + /** + * Calculates current image width + * + * @return int + */ + public function getWidth() + { + return $this->getSize()->width; + } + + /** + * Alias of getWidth() + * + * @return int + */ + public function width() + { + return $this->getWidth(); + } + + /** + * Calculates current image height + * + * @return int + */ + public function getHeight() + { + return $this->getSize()->height; + } + + /** + * Alias of getHeight + * + * @return int + */ + public function height() + { + return $this->getHeight(); + } + + /** + * Reads mime type + * + * @return string + */ + public function mime() + { + return $this->mime; + } + + /** + * Returns encoded image data in string conversion + * + * @return string + */ + public function __toString() + { + return $this->encoded; + } + + /** + * Cloning an image + */ + public function __clone() + { + $this->core = $this->driver->cloneCore($this->core); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/ImageManager.php b/vendor/intervention/image/src/Intervention/Image/ImageManager.php new file mode 100644 index 0000000..324fe7f --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/ImageManager.php @@ -0,0 +1,142 @@ + 'gd' + ]; + + /** + * Creates new instance of Image Manager + * + * @param array $config + */ + public function __construct(array $config = []) + { + $this->checkRequirements(); + $this->configure($config); + } + + /** + * Overrides configuration settings + * + * @param array $config + * + * @return self + */ + public function configure(array $config = []) + { + $this->config = array_replace($this->config, $config); + + return $this; + } + + /** + * Initiates an Image instance from different input types + * + * @param mixed $data + * + * @return \Intervention\Image\Image + */ + public function make($data) + { + return $this->createDriver()->init($data); + } + + /** + * Creates an empty image canvas + * + * @param int $width + * @param int $height + * @param mixed $background + * + * @return \Intervention\Image\Image + */ + public function canvas($width, $height, $background = null) + { + return $this->createDriver()->newImage($width, $height, $background); + } + + /** + * Create new cached image and run callback + * (requires additional package intervention/imagecache) + * + * @param Closure $callback + * @param int $lifetime + * @param boolean $returnObj + * + * @return Image + */ + public function cache(Closure $callback, $lifetime = null, $returnObj = false) + { + if (class_exists('Intervention\\Image\\ImageCache')) { + // create imagecache + $imagecache = new ImageCache($this); + + // run callback + if (is_callable($callback)) { + $callback($imagecache); + } + + return $imagecache->get($lifetime, $returnObj); + } + + throw new MissingDependencyException( + "Please install package intervention/imagecache before running this function." + ); + } + + /** + * Creates a driver instance according to config settings + * + * @return \Intervention\Image\AbstractDriver + */ + private function createDriver() + { + if (is_string($this->config['driver'])) { + $drivername = ucfirst($this->config['driver']); + $driverclass = sprintf('Intervention\\Image\\%s\\Driver', $drivername); + + if (class_exists($driverclass)) { + return new $driverclass; + } + + throw new NotSupportedException( + "Driver ({$drivername}) could not be instantiated." + ); + } + + if ($this->config['driver'] instanceof AbstractDriver) { + return $this->config['driver']; + } + + throw new NotSupportedException( + "Unknown driver type." + ); + } + + /** + * Check if all requirements are available + * + * @return void + */ + private function checkRequirements() + { + if ( ! function_exists('finfo_buffer')) { + throw new MissingDependencyException( + "PHP Fileinfo extension must be installed/enabled to use Intervention Image." + ); + } + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/ImageManagerStatic.php b/vendor/intervention/image/src/Intervention/Image/ImageManagerStatic.php new file mode 100644 index 0000000..a1b5642 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/ImageManagerStatic.php @@ -0,0 +1,88 @@ +configure($config); + } + + /** + * Statically initiates an Image instance from different input types + * + * @param mixed $data + * + * @return \Intervention\Image\Image + * @throws \Intervention\Image\Exception\NotReadableException + */ + public static function make($data) + { + return self::getManager()->make($data); + } + + /** + * Statically creates an empty image canvas + * + * @param int $width + * @param int $height + * @param mixed $background + * + * @return \Intervention\Image\Image + */ + public static function canvas($width, $height, $background = null) + { + return self::getManager()->canvas($width, $height, $background); + } + + /** + * Create new cached image and run callback statically + * + * @param Closure $callback + * @param int $lifetime + * @param boolean $returnObj + * + * @return mixed + */ + public static function cache(Closure $callback, $lifetime = null, $returnObj = false) + { + return self::getManager()->cache($callback, $lifetime, $returnObj); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/ImageServiceProvider.php b/vendor/intervention/image/src/Intervention/Image/ImageServiceProvider.php new file mode 100644 index 0000000..f99fe4a --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/ImageServiceProvider.php @@ -0,0 +1,87 @@ +provider = $this->getProvider(); + } + + /** + * Bootstrap the application events. + * + * @return void + */ + public function boot() + { + if (method_exists($this->provider, 'boot')) { + return $this->provider->boot(); + } + } + + /** + * Register the service provider. + * + * @return void + */ + public function register() + { + return $this->provider->register(); + } + + /** + * Return ServiceProvider according to Laravel version + * + * @return \Intervention\Image\Provider\ProviderInterface + */ + private function getProvider() + { + if ($this->app instanceof LumenApplication) { + $provider = '\Intervention\Image\ImageServiceProviderLumen'; + } elseif (version_compare(IlluminateApplication::VERSION, '5.0', '<')) { + $provider = '\Intervention\Image\ImageServiceProviderLaravel4'; + } else { + $provider = '\Intervention\Image\ImageServiceProviderLaravelRecent'; + } + + return new $provider($this->app); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return ['image']; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/ImageServiceProviderLaravel4.php b/vendor/intervention/image/src/Intervention/Image/ImageServiceProviderLaravel4.php new file mode 100644 index 0000000..3b1388f --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/ImageServiceProviderLaravel4.php @@ -0,0 +1,112 @@ +package('intervention/image'); + + // try to create imagecache route only if imagecache is present + if (class_exists('Intervention\\Image\\ImageCache')) { + + $app = $this->app; + + // load imagecache config + $app['config']->package('intervention/imagecache', __DIR__.'/../../../../imagecache/src/config', 'imagecache'); + $config = $app['config']; + + // create dynamic manipulation route + if (is_string($config->get('imagecache::route'))) { + + // add original to route templates + $config->set('imagecache::templates.original', null); + + // setup image manipulator route + $app['router']->get($config->get('imagecache::route').'/{template}/{filename}', ['as' => 'imagecache', function ($template, $filename) use ($app, $config) { + + // disable session cookies for image route + $app['config']->set('session.driver', 'array'); + + // find file + foreach ($config->get('imagecache::paths') as $path) { + // don't allow '..' in filenames + $image_path = $path.'/'.str_replace('..', '', $filename); + if (file_exists($image_path) && is_file($image_path)) { + break; + } else { + $image_path = false; + } + } + + // abort if file not found + if ($image_path === false) { + $app->abort(404); + } + + // define template callback + $callback = $config->get("imagecache::templates.{$template}"); + + if (is_callable($callback) || class_exists($callback)) { + + // image manipulation based on callback + $content = $app['image']->cache(function ($image) use ($image_path, $callback) { + + switch (true) { + case is_callable($callback): + return $callback($image->make($image_path)); + break; + + case class_exists($callback): + return $image->make($image_path)->filter(new $callback); + break; + } + + }, $config->get('imagecache::lifetime')); + + } else { + + // get original image file contents + $content = file_get_contents($image_path); + } + + // define mime type + $mime = finfo_buffer(finfo_open(FILEINFO_MIME_TYPE), $content); + + // return http response + return new IlluminateResponse($content, 200, [ + 'Content-Type' => $mime, + 'Cache-Control' => 'max-age='.($config->get('imagecache::lifetime')*60).', public', + 'Etag' => md5($content) + ]); + + }])->where(['template' => join('|', array_keys($config->get('imagecache::templates'))), 'filename' => '[ \w\\.\\/\\-]+']); + } + } + } + + /** + * Register the service provider. + * + * @return void + */ + public function register() + { + $app = $this->app; + + $app['image'] = $app->share(function ($app) { + return new ImageManager($app['config']->get('image::config')); + }); + + $app->alias('image', 'Intervention\Image\ImageManager'); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/ImageServiceProviderLaravelRecent.php b/vendor/intervention/image/src/Intervention/Image/ImageServiceProviderLaravelRecent.php new file mode 100644 index 0000000..17a84d0 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/ImageServiceProviderLaravelRecent.php @@ -0,0 +1,106 @@ +publishes([ + __DIR__.'/../../config/config.php' => config_path('image.php') + ]); + + // setup intervention/imagecache if package is installed + $this->cacheIsInstalled() ? $this->bootstrapImageCache() : null; + } + + /** + * Register the service provider. + * + * @return void + */ + public function register() + { + $app = $this->app; + + // merge default config + $this->mergeConfigFrom( + __DIR__.'/../../config/config.php', + 'image' + ); + + // create image + $app->singleton('image', function ($app) { + return new ImageManager($this->getImageConfig($app)); + }); + + $app->alias('image', 'Intervention\Image\ImageManager'); + } + + /** + * Bootstrap imagecache + * + * @return void + */ + protected function bootstrapImageCache() + { + $app = $this->app; + $config = __DIR__.'/../../../../imagecache/src/config/config.php'; + + $this->publishes([ + $config => config_path('imagecache.php') + ]); + + // merge default config + $this->mergeConfigFrom( + $config, + 'imagecache' + ); + + // imagecache route + if (is_string(config('imagecache.route'))) { + + $filename_pattern = '[ \w\\.\\/\\-\\@\(\)\=]+'; + + // route to access template applied image file + $app['router']->get(config('imagecache.route').'/{template}/{filename}', [ + 'uses' => 'Intervention\Image\ImageCacheController@getResponse', + 'as' => 'imagecache' + ])->where(['filename' => $filename_pattern]); + } + } + + /** + * Return image configuration as array + * + * @param Application $app + * @return array + */ + private function getImageConfig($app) + { + $config = $app['config']->get('image'); + + if (is_null($config)) { + return []; + } + + return $config; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/ImageServiceProviderLeague.php b/vendor/intervention/image/src/Intervention/Image/ImageServiceProviderLeague.php new file mode 100644 index 0000000..b756a61 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/ImageServiceProviderLeague.php @@ -0,0 +1,42 @@ +config = $config; + } + + /** + * Register the server provider. + * + * @return void + */ + public function register() + { + $this->getContainer()->share('Intervention\Image\ImageManager', function () { + return new ImageManager($this->config); + }); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/ImageServiceProviderLumen.php b/vendor/intervention/image/src/Intervention/Image/ImageServiceProviderLumen.php new file mode 100644 index 0000000..4a381cc --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/ImageServiceProviderLumen.php @@ -0,0 +1,34 @@ +app; + + // merge default config + $this->mergeConfigFrom( + __DIR__.'/../../config/config.php', + 'image' + ); + + // set configuration + $app->configure('image'); + + // create image + $app->singleton('image',function ($app) { + return new ImageManager($app['config']->get('image')); + }); + + $app->alias('image', 'Intervention\Image\ImageManager'); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Color.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Color.php new file mode 100644 index 0000000..1e109b1 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Color.php @@ -0,0 +1,279 @@ +> 24) & 0xFF; + $r = ($value >> 16) & 0xFF; + $g = ($value >> 8) & 0xFF; + $b = $value & 0xFF; + $a = $this->rgb2alpha($a); + + $this->setPixel($r, $g, $b, $a); + } + + /** + * Initiates color object from given array + * + * @param array $value + * @return \Intervention\Image\AbstractColor + */ + public function initFromArray($array) + { + $array = array_values($array); + + if (count($array) == 4) { + + // color array with alpha value + list($r, $g, $b, $a) = $array; + + } elseif (count($array) == 3) { + + // color array without alpha value + list($r, $g, $b) = $array; + $a = 1; + } + + $this->setPixel($r, $g, $b, $a); + } + + /** + * Initiates color object from given string + * + * @param string $value + * + * @return \Intervention\Image\AbstractColor + */ + public function initFromString($value) + { + if ($color = $this->rgbaFromString($value)) { + $this->setPixel($color[0], $color[1], $color[2], $color[3]); + } + } + + /** + * Initiates color object from given ImagickPixel object + * + * @param ImagickPixel $value + * + * @return \Intervention\Image\AbstractColor + */ + public function initFromObject($value) + { + if (is_a($value, '\ImagickPixel')) { + $this->pixel = $value; + } + } + + /** + * Initiates color object from given R, G and B values + * + * @param int $r + * @param int $g + * @param int $b + * + * @return \Intervention\Image\AbstractColor + */ + public function initFromRgb($r, $g, $b) + { + $this->setPixel($r, $g, $b); + } + + /** + * Initiates color object from given R, G, B and A values + * + * @param int $r + * @param int $g + * @param int $b + * @param float $a + * + * @return \Intervention\Image\AbstractColor + */ + public function initFromRgba($r, $g, $b, $a) + { + $this->setPixel($r, $g, $b, $a); + } + + /** + * Calculates integer value of current color instance + * + * @return int + */ + public function getInt() + { + $r = $this->getRedValue(); + $g = $this->getGreenValue(); + $b = $this->getBlueValue(); + $a = intval(round($this->getAlphaValue() * 255)); + + return intval(($a << 24) + ($r << 16) + ($g << 8) + $b); + } + + /** + * Calculates hexadecimal value of current color instance + * + * @param string $prefix + * + * @return string + */ + public function getHex($prefix = '') + { + return sprintf('%s%02x%02x%02x', $prefix, + $this->getRedValue(), + $this->getGreenValue(), + $this->getBlueValue() + ); + } + + /** + * Calculates RGB(A) in array format of current color instance + * + * @return array + */ + public function getArray() + { + return [ + $this->getRedValue(), + $this->getGreenValue(), + $this->getBlueValue(), + $this->getAlphaValue() + ]; + } + + /** + * Calculates RGBA in string format of current color instance + * + * @return string + */ + public function getRgba() + { + return sprintf('rgba(%d, %d, %d, %.2F)', + $this->getRedValue(), + $this->getGreenValue(), + $this->getBlueValue(), + $this->getAlphaValue() + ); + } + + /** + * Determines if current color is different from given color + * + * @param AbstractColor $color + * @param int $tolerance + * @return boolean + */ + public function differs(AbstractColor $color, $tolerance = 0) + { + $color_tolerance = round($tolerance * 2.55); + $alpha_tolerance = round($tolerance); + + $delta = [ + 'r' => abs($color->getRedValue() - $this->getRedValue()), + 'g' => abs($color->getGreenValue() - $this->getGreenValue()), + 'b' => abs($color->getBlueValue() - $this->getBlueValue()), + 'a' => abs($color->getAlphaValue() - $this->getAlphaValue()) + ]; + + return ( + $delta['r'] > $color_tolerance or + $delta['g'] > $color_tolerance or + $delta['b'] > $color_tolerance or + $delta['a'] > $alpha_tolerance + ); + } + + /** + * Returns RGB red value of current color + * + * @return int + */ + public function getRedValue() + { + return intval(round($this->pixel->getColorValue(\Imagick::COLOR_RED) * 255)); + } + + /** + * Returns RGB green value of current color + * + * @return int + */ + public function getGreenValue() + { + return intval(round($this->pixel->getColorValue(\Imagick::COLOR_GREEN) * 255)); + } + + /** + * Returns RGB blue value of current color + * + * @return int + */ + public function getBlueValue() + { + return intval(round($this->pixel->getColorValue(\Imagick::COLOR_BLUE) * 255)); + } + + /** + * Returns RGB alpha value of current color + * + * @return float + */ + public function getAlphaValue() + { + return round($this->pixel->getColorValue(\Imagick::COLOR_ALPHA), 2); + } + + /** + * Initiates ImagickPixel from given RGBA values + * + * @return \ImagickPixel + */ + private function setPixel($r, $g, $b, $a = null) + { + $a = is_null($a) ? 1 : $a; + + return $this->pixel = new \ImagickPixel( + sprintf('rgba(%d, %d, %d, %.2F)', $r, $g, $b, $a) + ); + } + + /** + * Returns current color as ImagickPixel + * + * @return \ImagickPixel + */ + public function getPixel() + { + return $this->pixel; + } + + /** + * Calculates RGBA integer alpha value into float value + * + * @param int $value + * @return float + */ + private function rgb2alpha($value) + { + // (255 -> 1.0) / (0 -> 0.0) + return (float) round($value/255, 2); + } + +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/BackupCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/BackupCommand.php new file mode 100644 index 0000000..76b4f72 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/BackupCommand.php @@ -0,0 +1,25 @@ +argument(0)->value(); + + // clone current image resource + $clone = clone $image; + $image->setBackup($clone->getCore(), $backupName); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/BlurCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/BlurCommand.php new file mode 100644 index 0000000..d2533e0 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/BlurCommand.php @@ -0,0 +1,21 @@ +argument(0)->between(0, 100)->value(1); + + return $image->getCore()->blurImage(1 * $amount, 0.5 * $amount); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/BrightnessCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/BrightnessCommand.php new file mode 100644 index 0000000..03ac847 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/BrightnessCommand.php @@ -0,0 +1,21 @@ +argument(0)->between(-100, 100)->required()->value(); + + return $image->getCore()->modulateImage(100 + $level, 100, 100); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/ColorizeCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/ColorizeCommand.php new file mode 100644 index 0000000..3a6506f --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/ColorizeCommand.php @@ -0,0 +1,44 @@ +argument(0)->between(-100, 100)->required()->value(); + $green = $this->argument(1)->between(-100, 100)->required()->value(); + $blue = $this->argument(2)->between(-100, 100)->required()->value(); + + // normalize colorize levels + $red = $this->normalizeLevel($red); + $green = $this->normalizeLevel($green); + $blue = $this->normalizeLevel($blue); + + $qrange = $image->getCore()->getQuantumRange(); + + // apply + $image->getCore()->levelImage(0, $red, $qrange['quantumRangeLong'], \Imagick::CHANNEL_RED); + $image->getCore()->levelImage(0, $green, $qrange['quantumRangeLong'], \Imagick::CHANNEL_GREEN); + $image->getCore()->levelImage(0, $blue, $qrange['quantumRangeLong'], \Imagick::CHANNEL_BLUE); + + return true; + } + + private function normalizeLevel($level) + { + if ($level > 0) { + return $level/5; + } else { + return ($level+100)/100; + } + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/ContrastCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/ContrastCommand.php new file mode 100644 index 0000000..c4847c6 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/ContrastCommand.php @@ -0,0 +1,21 @@ +argument(0)->between(-100, 100)->required()->value(); + + return $image->getCore()->sigmoidalContrastImage($level > 0, $level / 4, 0); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/CropCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/CropCommand.php new file mode 100644 index 0000000..618edea --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/CropCommand.php @@ -0,0 +1,45 @@ +argument(0)->type('digit')->required()->value(); + $height = $this->argument(1)->type('digit')->required()->value(); + $x = $this->argument(2)->type('digit')->value(); + $y = $this->argument(3)->type('digit')->value(); + + if (is_null($width) || is_null($height)) { + throw new InvalidArgumentException( + "Width and height of cutout needs to be defined." + ); + } + + $cropped = new Size($width, $height); + $position = new Point($x, $y); + + // align boxes + if (is_null($x) && is_null($y)) { + $position = $image->getSize()->align('center')->relativePosition($cropped->align('center')); + } + + // crop image core + $image->getCore()->cropImage($cropped->width, $cropped->height, $position->x, $position->y); + $image->getCore()->setImagePage(0,0,0,0); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/DestroyCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/DestroyCommand.php new file mode 100644 index 0000000..58c9556 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/DestroyCommand.php @@ -0,0 +1,27 @@ +getCore()->clear(); + + // destroy backups + foreach ($image->getBackups() as $backup) { + $backup->clear(); + } + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/ExifCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/ExifCommand.php new file mode 100644 index 0000000..521b38b --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/ExifCommand.php @@ -0,0 +1,63 @@ +preferExtension = false; + } + + /** + * Read Exif data from the given image + * + * @param \Intervention\Image\Image $image + * @return boolean + */ + public function execute($image) + { + if ($this->preferExtension && function_exists('exif_read_data')) { + return parent::execute($image); + } + + $core = $image->getCore(); + + if ( ! method_exists($core, 'getImageProperties')) { + throw new NotSupportedException( + "Reading Exif data is not supported by this PHP installation." + ); + } + + $requestedKey = $this->argument(0)->value(); + if ($requestedKey !== null) { + $this->setOutput($core->getImageProperty('exif:' . $requestedKey)); + return true; + } + + $exif = []; + $properties = $core->getImageProperties(); + foreach ($properties as $key => $value) { + if (substr($key, 0, 5) !== 'exif:') { + continue; + } + + $exif[substr($key, 5)] = $value; + } + + $this->setOutput($exif); + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/FillCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/FillCommand.php new file mode 100644 index 0000000..82baac5 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/FillCommand.php @@ -0,0 +1,105 @@ +argument(0)->value(); + $x = $this->argument(1)->type('digit')->value(); + $y = $this->argument(2)->type('digit')->value(); + + $imagick = $image->getCore(); + + try { + // set image filling + $source = new Decoder; + $filling = $source->init($filling); + + } catch (NotReadableException $e) { + + // set solid color filling + $filling = new Color($filling); + } + + // flood fill if coordinates are set + if (is_int($x) && is_int($y)) { + + // flood fill with texture + if ($filling instanceof Image) { + + // create tile + $tile = clone $image->getCore(); + + // mask away color at position + $tile->transparentPaintImage($tile->getImagePixelColor($x, $y), 0, 0, false); + + // create canvas + $canvas = clone $image->getCore(); + + // fill canvas with texture + $canvas = $canvas->textureImage($filling->getCore()); + + // merge canvas and tile + $canvas->compositeImage($tile, \Imagick::COMPOSITE_DEFAULT, 0, 0); + + // replace image core + $image->setCore($canvas); + + // flood fill with color + } elseif ($filling instanceof Color) { + + // create canvas with filling + $canvas = new \Imagick; + $canvas->newImage($image->getWidth(), $image->getHeight(), $filling->getPixel(), 'png'); + + // create tile to put on top + $tile = clone $image->getCore(); + + // mask away color at pos. + $tile->transparentPaintImage($tile->getImagePixelColor($x, $y), 0, 0, false); + + // save alpha channel of original image + $alpha = clone $image->getCore(); + + // merge original with canvas and tile + $image->getCore()->compositeImage($canvas, \Imagick::COMPOSITE_DEFAULT, 0, 0); + $image->getCore()->compositeImage($tile, \Imagick::COMPOSITE_DEFAULT, 0, 0); + + // restore alpha channel of original image + $image->getCore()->compositeImage($alpha, \Imagick::COMPOSITE_COPYOPACITY, 0, 0); + } + + } else { + + if ($filling instanceof Image) { + + // fill whole image with texture + $image->setCore($image->getCore()->textureImage($filling->getCore())); + + } elseif ($filling instanceof Color) { + + // fill whole image with color + $draw = new \ImagickDraw(); + $draw->setFillColor($filling->getPixel()); + $draw->rectangle(0, 0, $image->getWidth(), $image->getHeight()); + $image->getCore()->drawImage($draw); + } + } + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/FitCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/FitCommand.php new file mode 100644 index 0000000..6d62ba6 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/FitCommand.php @@ -0,0 +1,42 @@ +argument(0)->type('digit')->required()->value(); + $height = $this->argument(1)->type('digit')->value($width); + $constraints = $this->argument(2)->type('closure')->value(); + $position = $this->argument(3)->type('string')->value('center'); + + // calculate size + $cropped = $image->getSize()->fit(new Size($width, $height), $position); + $resized = clone $cropped; + $resized = $resized->resize($width, $height, $constraints); + + // crop image + $image->getCore()->cropImage( + $cropped->width, + $cropped->height, + $cropped->pivot->x, + $cropped->pivot->y + ); + + // resize image + $image->getCore()->scaleImage($resized->getWidth(), $resized->getHeight()); + $image->getCore()->setImagePage(0,0,0,0); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/FlipCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/FlipCommand.php new file mode 100644 index 0000000..abae16a --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/FlipCommand.php @@ -0,0 +1,27 @@ +argument(0)->value('h'); + + if (in_array(strtolower($mode), [2, 'v', 'vert', 'vertical'])) { + // flip vertical + return $image->getCore()->flipImage(); + } else { + // flip horizontal + return $image->getCore()->flopImage(); + } + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/GammaCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/GammaCommand.php new file mode 100644 index 0000000..200515f --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/GammaCommand.php @@ -0,0 +1,21 @@ +argument(0)->type('numeric')->required()->value(); + + return $image->getCore()->gammaImage($gamma); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/GetSizeCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/GetSizeCommand.php new file mode 100644 index 0000000..ccccedb --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/GetSizeCommand.php @@ -0,0 +1,28 @@ +getCore(); + + $this->setOutput(new Size( + $core->getImageWidth(), + $core->getImageHeight() + )); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/GreyscaleCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/GreyscaleCommand.php new file mode 100644 index 0000000..df0ff5b --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/GreyscaleCommand.php @@ -0,0 +1,19 @@ +getCore()->modulateImage(100, 0, 100); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/HeightenCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/HeightenCommand.php new file mode 100644 index 0000000..0b61e50 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/HeightenCommand.php @@ -0,0 +1,28 @@ +argument(0)->type('digit')->required()->value(); + $additionalConstraints = $this->argument(1)->type('closure')->value(); + + $this->arguments[0] = null; + $this->arguments[1] = $height; + $this->arguments[2] = function ($constraint) use ($additionalConstraints) { + $constraint->aspectRatio(); + if(is_callable($additionalConstraints)) + $additionalConstraints($constraint); + }; + + return parent::execute($image); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/InsertCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/InsertCommand.php new file mode 100644 index 0000000..2a99743 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/InsertCommand.php @@ -0,0 +1,33 @@ +argument(0)->required()->value(); + $position = $this->argument(1)->type('string')->value(); + $x = $this->argument(2)->type('digit')->value(0); + $y = $this->argument(3)->type('digit')->value(0); + + // build watermark + $watermark = $image->getDriver()->init($source); + + // define insertion point + $image_size = $image->getSize()->align($position, $x, $y); + $watermark_size = $watermark->getSize()->align($position); + $target = $image_size->relativePosition($watermark_size); + + // insert image at position + return $image->getCore()->compositeImage($watermark->getCore(), \Imagick::COMPOSITE_DEFAULT, $target->x, $target->y); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/InterlaceCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/InterlaceCommand.php new file mode 100644 index 0000000..913cab7 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/InterlaceCommand.php @@ -0,0 +1,29 @@ +argument(0)->type('bool')->value(true); + + if ($mode) { + $mode = \Imagick::INTERLACE_LINE; + } else { + $mode = \Imagick::INTERLACE_NO; + } + + $image->getCore()->setInterlaceScheme($mode); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/InvertCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/InvertCommand.php new file mode 100644 index 0000000..1d13430 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/InvertCommand.php @@ -0,0 +1,19 @@ +getCore()->negateImage(false); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/LimitColorsCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/LimitColorsCommand.php new file mode 100644 index 0000000..16f9b82 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/LimitColorsCommand.php @@ -0,0 +1,59 @@ +argument(0)->value(); + $matte = $this->argument(1)->value(); + + // get current image size + $size = $image->getSize(); + + // build 2 color alpha mask from original alpha + $alpha = clone $image->getCore(); + $alpha->separateImageChannel(\Imagick::CHANNEL_ALPHA); + $alpha->transparentPaintImage('#ffffff', 0, 0, false); + $alpha->separateImageChannel(\Imagick::CHANNEL_ALPHA); + $alpha->negateImage(false); + + if ($matte) { + + // get matte color + $mattecolor = $image->getDriver()->parseColor($matte)->getPixel(); + + // create matte image + $canvas = new \Imagick; + $canvas->newImage($size->width, $size->height, $mattecolor, 'png'); + + // lower colors of original and copy to matte + $image->getCore()->quantizeImage($count, \Imagick::COLORSPACE_RGB, 0, false, false); + $canvas->compositeImage($image->getCore(), \Imagick::COMPOSITE_DEFAULT, 0, 0); + + // copy new alpha to canvas + $canvas->compositeImage($alpha, \Imagick::COMPOSITE_COPYOPACITY, 0, 0); + + // replace core + $image->setCore($canvas); + + } else { + + $image->getCore()->quantizeImage($count, \Imagick::COLORSPACE_RGB, 0, false, false); + $image->getCore()->compositeImage($alpha, \Imagick::COMPOSITE_COPYOPACITY, 0, 0); + + } + + return true; + + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/MaskCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/MaskCommand.php new file mode 100644 index 0000000..af9d6b2 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/MaskCommand.php @@ -0,0 +1,60 @@ +argument(0)->value(); + $mask_w_alpha = $this->argument(1)->type('bool')->value(false); + + // get imagick + $imagick = $image->getCore(); + + // build mask image from source + $mask = $image->getDriver()->init($mask_source); + + // resize mask to size of current image (if necessary) + $image_size = $image->getSize(); + if ($mask->getSize() != $image_size) { + $mask->resize($image_size->width, $image_size->height); + } + + $imagick->setImageMatte(true); + + if ($mask_w_alpha) { + + // just mask with alpha map + $imagick->compositeImage($mask->getCore(), \Imagick::COMPOSITE_DSTIN, 0, 0); + + } else { + + // get alpha channel of original as greyscale image + $original_alpha = clone $imagick; + $original_alpha->separateImageChannel(\Imagick::CHANNEL_ALPHA); + + // use red channel from mask ask alpha + $mask_alpha = clone $mask->getCore(); + $mask_alpha->compositeImage($mask->getCore(), \Imagick::COMPOSITE_DEFAULT, 0, 0); + // $mask_alpha->setImageAlphaChannel(\Imagick::ALPHACHANNEL_DEACTIVATE); + $mask_alpha->separateImageChannel(\Imagick::CHANNEL_ALL); + + // combine both alphas from original and mask + $original_alpha->compositeImage($mask_alpha, \Imagick::COMPOSITE_COPYOPACITY, 0, 0); + + // mask the image with the alpha combination + $imagick->compositeImage($original_alpha, \Imagick::COMPOSITE_DSTIN, 0, 0); + } + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/OpacityCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/OpacityCommand.php new file mode 100644 index 0000000..b4708d8 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/OpacityCommand.php @@ -0,0 +1,23 @@ +argument(0)->between(0, 100)->required()->value(); + + $transparency = $transparency > 0 ? (100 / $transparency) : 1000; + + return $image->getCore()->evaluateImage(\Imagick::EVALUATE_DIVIDE, $transparency, \Imagick::CHANNEL_ALPHA); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/PickColorCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/PickColorCommand.php new file mode 100644 index 0000000..978a128 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/PickColorCommand.php @@ -0,0 +1,30 @@ +argument(0)->type('digit')->required()->value(); + $y = $this->argument(1)->type('digit')->required()->value(); + $format = $this->argument(2)->type('string')->value('array'); + + // pick color + $color = new Color($image->getCore()->getImagePixelColor($x, $y)); + + // format to output + $this->setOutput($color->format($format)); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/PixelCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/PixelCommand.php new file mode 100644 index 0000000..6eb6ae7 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/PixelCommand.php @@ -0,0 +1,31 @@ +argument(0)->required()->value(); + $color = new Color($color); + $x = $this->argument(1)->type('digit')->required()->value(); + $y = $this->argument(2)->type('digit')->required()->value(); + + // prepare pixel + $draw = new \ImagickDraw; + $draw->setFillColor($color->getPixel()); + $draw->point($x, $y); + + // apply pixel + return $image->getCore()->drawImage($draw); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/PixelateCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/PixelateCommand.php new file mode 100644 index 0000000..66a3205 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/PixelateCommand.php @@ -0,0 +1,27 @@ +argument(0)->type('digit')->value(10); + + $width = $image->getWidth(); + $height = $image->getHeight(); + + $image->getCore()->scaleImage(max(1, intval($width / $size)), max(1, intval($height / $size))); + $image->getCore()->scaleImage($width, $height); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/ResetCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/ResetCommand.php new file mode 100644 index 0000000..77b7f33 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/ResetCommand.php @@ -0,0 +1,40 @@ +argument(0)->value(); + + $backup = $image->getBackup($backupName); + + if ($backup instanceof \Imagick) { + + // destroy current core + $image->getCore()->clear(); + + // clone backup + $backup = clone $backup; + + // reset to new resource + $image->setCore($backup); + + return true; + } + + throw new RuntimeException( + "Backup not available. Call backup({$backupName}) before reset()." + ); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/ResizeCanvasCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/ResizeCanvasCommand.php new file mode 100644 index 0000000..ecf0761 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/ResizeCanvasCommand.php @@ -0,0 +1,91 @@ +argument(0)->type('digit')->required()->value(); + $height = $this->argument(1)->type('digit')->required()->value(); + $anchor = $this->argument(2)->value('center'); + $relative = $this->argument(3)->type('boolean')->value(false); + $bgcolor = $this->argument(4)->value(); + + $original_width = $image->getWidth(); + $original_height = $image->getHeight(); + + // check of only width or height is set + $width = is_null($width) ? $original_width : intval($width); + $height = is_null($height) ? $original_height : intval($height); + + // check on relative width/height + if ($relative) { + $width = $original_width + $width; + $height = $original_height + $height; + } + + // check for negative width/height + $width = ($width <= 0) ? $width + $original_width : $width; + $height = ($height <= 0) ? $height + $original_height : $height; + + // create new canvas + $canvas = $image->getDriver()->newImage($width, $height, $bgcolor); + + // set copy position + $canvas_size = $canvas->getSize()->align($anchor); + $image_size = $image->getSize()->align($anchor); + $canvas_pos = $image_size->relativePosition($canvas_size); + $image_pos = $canvas_size->relativePosition($image_size); + + if ($width <= $original_width) { + $dst_x = 0; + $src_x = $canvas_pos->x; + $src_w = $canvas_size->width; + } else { + $dst_x = $image_pos->x; + $src_x = 0; + $src_w = $original_width; + } + + if ($height <= $original_height) { + $dst_y = 0; + $src_y = $canvas_pos->y; + $src_h = $canvas_size->height; + } else { + $dst_y = $image_pos->y; + $src_y = 0; + $src_h = $original_height; + } + + // make image area transparent to keep transparency + // even if background-color is set + $rect = new \ImagickDraw; + $fill = $canvas->pickColor(0, 0, 'hex'); + $fill = $fill == '#ff0000' ? '#00ff00' : '#ff0000'; + $rect->setFillColor($fill); + $rect->rectangle($dst_x, $dst_y, $dst_x + $src_w - 1, $dst_y + $src_h - 1); + $canvas->getCore()->drawImage($rect); + $canvas->getCore()->transparentPaintImage($fill, 0, 0, false); + + $canvas->getCore()->setImageColorspace($image->getCore()->getImageColorspace()); + + // copy image into new canvas + $image->getCore()->cropImage($src_w, $src_h, $src_x, $src_y); + $canvas->getCore()->compositeImage($image->getCore(), \Imagick::COMPOSITE_DEFAULT, $dst_x, $dst_y); + $canvas->getCore()->setImagePage(0,0,0,0); + + // set new core to canvas + $image->setCore($canvas->getCore()); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/ResizeCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/ResizeCommand.php new file mode 100644 index 0000000..3d4dc5b --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/ResizeCommand.php @@ -0,0 +1,29 @@ +argument(0)->value(); + $height = $this->argument(1)->value(); + $constraints = $this->argument(2)->type('closure')->value(); + + // resize box + $resized = $image->getSize()->resize($width, $height, $constraints); + + // modify image + $image->getCore()->scaleImage($resized->getWidth(), $resized->getHeight()); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/RotateCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/RotateCommand.php new file mode 100644 index 0000000..b3e12a5 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/RotateCommand.php @@ -0,0 +1,30 @@ +argument(0)->type('numeric')->required()->value(); + $color = $this->argument(1)->value(); + $color = new Color($color); + + // restrict rotations beyond 360 degrees, since the end result is the same + $angle = fmod($angle, 360); + + // rotate image + $image->getCore()->rotateImage($color->getPixel(), ($angle * -1)); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/SharpenCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/SharpenCommand.php new file mode 100644 index 0000000..bc5e393 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/SharpenCommand.php @@ -0,0 +1,21 @@ +argument(0)->between(0, 100)->value(10); + + return $image->getCore()->unsharpMaskImage(1, 1, $amount / 6.25, 0); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/TrimCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/TrimCommand.php new file mode 100644 index 0000000..bdc897b --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/TrimCommand.php @@ -0,0 +1,121 @@ +argument(0)->type('string')->value(); + $away = $this->argument(1)->value(); + $tolerance = $this->argument(2)->type('numeric')->value(0); + $feather = $this->argument(3)->type('numeric')->value(0); + + $width = $image->getWidth(); + $height = $image->getHeight(); + + $checkTransparency = false; + + // define borders to trim away + if (is_null($away)) { + $away = ['top', 'right', 'bottom', 'left']; + } elseif (is_string($away)) { + $away = [$away]; + } + + // lower border names + foreach ($away as $key => $value) { + $away[$key] = strtolower($value); + } + + // define base color position + switch (strtolower($base)) { + case 'transparent': + case 'trans': + $checkTransparency = true; + $base_x = 0; + $base_y = 0; + break; + + case 'bottom-right': + case 'right-bottom': + $base_x = $width - 1; + $base_y = $height - 1; + break; + + default: + case 'top-left': + case 'left-top': + $base_x = 0; + $base_y = 0; + break; + } + + // pick base color + if ($checkTransparency) { + $base_color = new Color; // color will only be used to compare alpha channel + } else { + $base_color = $image->pickColor($base_x, $base_y, 'object'); + } + + // trim on clone to get only coordinates + $trimed = clone $image->getCore(); + + // add border to trim specific color + $trimed->borderImage($base_color->getPixel(), 1, 1); + + // trim image + $trimed->trimImage(65850 / 100 * $tolerance); + + // get coordinates of trim + $imagePage = $trimed->getImagePage(); + list($crop_x, $crop_y) = [$imagePage['x']-1, $imagePage['y']-1]; + // $trimed->setImagePage(0, 0, 0, 0); + list($crop_width, $crop_height) = [$trimed->width, $trimed->height]; + + // adjust settings if right should not be trimed + if ( ! in_array('right', $away)) { + $crop_width = $crop_width + ($width - ($width - $crop_x)); + } + + // adjust settings if bottom should not be trimed + if ( ! in_array('bottom', $away)) { + $crop_height = $crop_height + ($height - ($height - $crop_y)); + } + + // adjust settings if left should not be trimed + if ( ! in_array('left', $away)) { + $crop_width = $crop_width + $crop_x; + $crop_x = 0; + } + + // adjust settings if top should not be trimed + if ( ! in_array('top', $away)) { + $crop_height = $crop_height + $crop_y; + $crop_y = 0; + } + + // add feather + $crop_width = min($width, ($crop_width + $feather * 2)); + $crop_height = min($height, ($crop_height + $feather * 2)); + $crop_x = max(0, ($crop_x - $feather)); + $crop_y = max(0, ($crop_y - $feather)); + + // finally crop based on page + $image->getCore()->cropImage($crop_width, $crop_height, $crop_x, $crop_y); + $image->getCore()->setImagePage(0,0,0,0); + + $trimed->destroy(); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/WidenCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/WidenCommand.php new file mode 100644 index 0000000..a196753 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/WidenCommand.php @@ -0,0 +1,28 @@ +argument(0)->type('digit')->required()->value(); + $additionalConstraints = $this->argument(1)->type('closure')->value(); + + $this->arguments[0] = $width; + $this->arguments[1] = null; + $this->arguments[2] = function ($constraint) use ($additionalConstraints) { + $constraint->aspectRatio(); + if(is_callable($additionalConstraints)) + $additionalConstraints($constraint); + }; + + return parent::execute($image); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Decoder.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Decoder.php new file mode 100644 index 0000000..f4dde9a --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Decoder.php @@ -0,0 +1,124 @@ +setBackgroundColor(new \ImagickPixel('transparent')); + $core->readImage($path); + $core->setImageType(defined('\Imagick::IMGTYPE_TRUECOLORALPHA') ? \Imagick::IMGTYPE_TRUECOLORALPHA : \Imagick::IMGTYPE_TRUECOLORMATTE); + + } catch (\ImagickException $e) { + throw new \Intervention\Image\Exception\NotReadableException( + "Unable to read image from path ({$path}).", + 0, + $e + ); + } + + // build image + $image = $this->initFromImagick($core); + $image->setFileInfoFromPath($path); + + return $image; + } + + /** + * Initiates new image from GD resource + * + * @param Resource $resource + * @return \Intervention\Image\Image + */ + public function initFromGdResource($resource) + { + throw new NotSupportedException( + 'Imagick driver is unable to init from GD resource.' + ); + } + + /** + * Initiates new image from Imagick object + * + * @param Imagick $object + * @return \Intervention\Image\Image + */ + public function initFromImagick(\Imagick $object) + { + // currently animations are not supported + // so all images are turned into static + $object = $this->removeAnimation($object); + + // reset image orientation + $object->setImageOrientation(\Imagick::ORIENTATION_UNDEFINED); + + return new Image(new Driver, $object); + } + + /** + * Initiates new image from binary data + * + * @param string $data + * @return \Intervention\Image\Image + */ + public function initFromBinary($binary) + { + $core = new \Imagick; + + try { + $core->setBackgroundColor(new \ImagickPixel('transparent')); + + $core->readImageBlob($binary); + + } catch (\ImagickException $e) { + throw new NotReadableException( + "Unable to read image from binary data.", + 0, + $e + ); + } + + // build image + $image = $this->initFromImagick($core); + $image->mime = finfo_buffer(finfo_open(FILEINFO_MIME_TYPE), $binary); + + return $image; + } + + /** + * Turns object into one frame Imagick object + * by removing all frames except first + * + * @param Imagick $object + * @return Imagick + */ + private function removeAnimation(\Imagick $object) + { + $imagick = new \Imagick; + + foreach ($object as $frame) { + $imagick->addImage($frame->getImage()); + break; + } + + $object->destroy(); + + return $imagick; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Driver.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Driver.php new file mode 100644 index 0000000..bb26ca0 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Driver.php @@ -0,0 +1,74 @@ +coreAvailable()) { + throw new NotSupportedException( + "ImageMagick module not available with this PHP installation." + ); + } + + $this->decoder = $decoder ? $decoder : new Decoder; + $this->encoder = $encoder ? $encoder : new Encoder; + } + + /** + * Creates new image instance + * + * @param int $width + * @param int $height + * @param mixed $background + * @return \Intervention\Image\Image + */ + public function newImage($width, $height, $background = null) + { + $background = new Color($background); + + // create empty core + $core = new \Imagick; + $core->newImage($width, $height, $background->getPixel(), 'png'); + $core->setType(\Imagick::IMGTYPE_UNDEFINED); + $core->setImageType(\Imagick::IMGTYPE_UNDEFINED); + $core->setColorspace(\Imagick::COLORSPACE_UNDEFINED); + + // build image + $image = new Image(new static, $core); + + return $image; + } + + /** + * Reads given string into color object + * + * @param string $value + * @return AbstractColor + */ + public function parseColor($value) + { + return new Color($value); + } + + /** + * Checks if core module installation is available + * + * @return boolean + */ + protected function coreAvailable() + { + return (extension_loaded('imagick') && class_exists('Imagick')); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Encoder.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Encoder.php new file mode 100644 index 0000000..feb8a9d --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Encoder.php @@ -0,0 +1,239 @@ +image->getCore(); + $imagick->setImageBackgroundColor('white'); + $imagick->setBackgroundColor('white'); + $imagick = $imagick->mergeImageLayers(\Imagick::LAYERMETHOD_MERGE); + $imagick->setFormat($format); + $imagick->setImageFormat($format); + $imagick->setCompression($compression); + $imagick->setImageCompression($compression); + $imagick->setCompressionQuality($this->quality); + $imagick->setImageCompressionQuality($this->quality); + + return $imagick->getImagesBlob(); + } + + /** + * Processes and returns encoded image as PNG string + * + * @return string + */ + protected function processPng() + { + $format = 'png'; + $compression = \Imagick::COMPRESSION_ZIP; + + $imagick = $this->image->getCore(); + $imagick->setFormat($format); + $imagick->setImageFormat($format); + $imagick->setCompression($compression); + $imagick->setImageCompression($compression); + + $this->image->mime = image_type_to_mime_type(IMAGETYPE_PNG); + + return $imagick->getImagesBlob(); + } + + /** + * Processes and returns encoded image as GIF string + * + * @return string + */ + protected function processGif() + { + $format = 'gif'; + $compression = \Imagick::COMPRESSION_LZW; + + $imagick = $this->image->getCore(); + $imagick->setFormat($format); + $imagick->setImageFormat($format); + $imagick->setCompression($compression); + $imagick->setImageCompression($compression); + + $this->image->mime = image_type_to_mime_type(IMAGETYPE_GIF); + + return $imagick->getImagesBlob(); + } + + protected function processWebp() + { + if ( ! \Imagick::queryFormats('WEBP')) { + throw new NotSupportedException( + "Webp format is not supported by Imagick installation." + ); + } + + $format = 'webp'; + $compression = \Imagick::COMPRESSION_JPEG; + + $imagick = $this->image->getCore(); + $imagick->setImageBackgroundColor(new \ImagickPixel('transparent')); + + $imagick = $imagick->mergeImageLayers(\Imagick::LAYERMETHOD_MERGE); + $imagick->setFormat($format); + $imagick->setImageFormat($format); + $imagick->setCompression($compression); + $imagick->setImageCompression($compression); + $imagick->setImageCompressionQuality($this->quality); + + return $imagick->getImagesBlob(); + } + + /** + * Processes and returns encoded image as TIFF string + * + * @return string + */ + protected function processTiff() + { + $format = 'tiff'; + $compression = \Imagick::COMPRESSION_UNDEFINED; + + $imagick = $this->image->getCore(); + $imagick->setFormat($format); + $imagick->setImageFormat($format); + $imagick->setCompression($compression); + $imagick->setImageCompression($compression); + $imagick->setCompressionQuality($this->quality); + $imagick->setImageCompressionQuality($this->quality); + + $this->image->mime = image_type_to_mime_type(IMAGETYPE_TIFF_II); + + return $imagick->getImagesBlob(); + } + + /** + * Processes and returns encoded image as BMP string + * + * @return string + */ + protected function processBmp() + { + $format = 'bmp'; + $compression = \Imagick::COMPRESSION_UNDEFINED; + + $imagick = $this->image->getCore(); + $imagick->setFormat($format); + $imagick->setImageFormat($format); + $imagick->setCompression($compression); + $imagick->setImageCompression($compression); + + $this->image->mime = image_type_to_mime_type(IMAGETYPE_BMP); + + return $imagick->getImagesBlob(); + } + + /** + * Processes and returns encoded image as ICO string + * + * @return string + */ + protected function processIco() + { + $format = 'ico'; + $compression = \Imagick::COMPRESSION_UNDEFINED; + + $imagick = $this->image->getCore(); + $imagick->setFormat($format); + $imagick->setImageFormat($format); + $imagick->setCompression($compression); + $imagick->setImageCompression($compression); + + $this->image->mime = image_type_to_mime_type(IMAGETYPE_ICO); + + return $imagick->getImagesBlob(); + } + + /** + * Processes and returns encoded image as PSD string + * + * @return string + */ + protected function processPsd() + { + $format = 'psd'; + $compression = \Imagick::COMPRESSION_UNDEFINED; + + $imagick = $this->image->getCore(); + $imagick->setFormat($format); + $imagick->setImageFormat($format); + $imagick->setCompression($compression); + $imagick->setImageCompression($compression); + + $this->image->mime = image_type_to_mime_type(IMAGETYPE_PSD); + + return $imagick->getImagesBlob(); + } + + /** + * Processes and returns encoded image as AVIF string + * + * @return string + */ + protected function processAvif() + { + if ( ! \Imagick::queryFormats('AVIF')) { + throw new NotSupportedException( + "AVIF format is not supported by Imagick installation." + ); + } + + $format = 'avif'; + $compression = \Imagick::COMPRESSION_UNDEFINED; + + $imagick = $this->image->getCore(); + $imagick->setFormat($format); + $imagick->setImageFormat($format); + $imagick->setCompression($compression); + $imagick->setImageCompression($compression); + $imagick->setCompressionQuality($this->quality); + $imagick->setImageCompressionQuality($this->quality); + + return $imagick->getImagesBlob(); + } + + /** + * Processes and returns encoded image as HEIC string + * + * @return string + */ + protected function processHeic() + { + if ( ! \Imagick::queryFormats('HEIC')) { + throw new NotSupportedException( + "HEIC format is not supported by Imagick installation." + ); + } + + $format = 'heic'; + $compression = \Imagick::COMPRESSION_UNDEFINED; + + $imagick = $this->image->getCore(); + $imagick->setFormat($format); + $imagick->setImageFormat($format); + $imagick->setCompression($compression); + $imagick->setImageCompression($compression); + $imagick->setCompressionQuality($this->quality); + $imagick->setImageCompressionQuality($this->quality); + + return $imagick->getImagesBlob(); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Font.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Font.php new file mode 100644 index 0000000..9869d06 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Font.php @@ -0,0 +1,122 @@ +setStrokeAntialias(true); + $draw->setTextAntialias(true); + + // set font file + if ($this->hasApplicableFontFile()) { + $draw->setFont($this->file); + } else { + throw new RuntimeException( + "Font file must be provided to apply text to image." + ); + } + + // parse text color + $color = new Color($this->color); + + $draw->setFontSize($this->size); + $draw->setFillColor($color->getPixel()); + $draw->setTextKerning($this->kerning); + + // align horizontal + switch (strtolower($this->align)) { + case 'center': + $align = \Imagick::ALIGN_CENTER; + break; + + case 'right': + $align = \Imagick::ALIGN_RIGHT; + break; + + default: + $align = \Imagick::ALIGN_LEFT; + break; + } + + $draw->setTextAlignment($align); + + // align vertical + if (strtolower($this->valign) != 'bottom') { + + // corrections on y-position + switch (strtolower($this->valign)) { + case 'center': + case 'middle': + // calculate box size + $dimensions = $image->getCore()->queryFontMetrics($draw, $this->text); + $posy = $posy + $dimensions['textHeight'] * 0.65 / 2; + break; + + case 'top': + // calculate box size + $dimensions = $image->getCore()->queryFontMetrics($draw, $this->text, false); + $posy = $posy + $dimensions['characterHeight']; + break; + } + } + + // apply to image + $image->getCore()->annotateImage($draw, $posx, $posy, $this->angle * (-1), $this->text); + } + + /** + * Calculates bounding box of current font setting + * + * @return array + */ + public function getBoxSize() + { + $box = []; + + // build draw object + $draw = new \ImagickDraw(); + $draw->setStrokeAntialias(true); + $draw->setTextAntialias(true); + + // set font file + if ($this->hasApplicableFontFile()) { + $draw->setFont($this->file); + } else { + throw new RuntimeException( + "Font file must be provided to apply text to image." + ); + } + + $draw->setFontSize($this->size); + + $dimensions = (new \Imagick())->queryFontMetrics($draw, $this->text); + + if (strlen($this->text) == 0) { + // no text -> no boxsize + $box['width'] = 0; + $box['height'] = 0; + } else { + // get boxsize + $box['width'] = intval(abs($dimensions['textWidth'])); + $box['height'] = intval(abs($dimensions['textHeight'])); + } + + return $box; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Shapes/CircleShape.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Shapes/CircleShape.php new file mode 100644 index 0000000..24172ea --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Shapes/CircleShape.php @@ -0,0 +1,40 @@ +width = is_numeric($diameter) ? intval($diameter) : $this->diameter; + $this->height = is_numeric($diameter) ? intval($diameter) : $this->diameter; + $this->diameter = is_numeric($diameter) ? intval($diameter) : $this->diameter; + } + + /** + * Draw current circle on given image + * + * @param Image $image + * @param int $x + * @param int $y + * @return boolean + */ + public function applyToImage(Image $image, $x = 0, $y = 0) + { + return parent::applyToImage($image, $x, $y); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Shapes/EllipseShape.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Shapes/EllipseShape.php new file mode 100644 index 0000000..99694b9 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Shapes/EllipseShape.php @@ -0,0 +1,66 @@ +width = is_numeric($width) ? intval($width) : $this->width; + $this->height = is_numeric($height) ? intval($height) : $this->height; + } + + /** + * Draw ellipse instance on given image + * + * @param Image $image + * @param int $x + * @param int $y + * @return boolean + */ + public function applyToImage(Image $image, $x = 0, $y = 0) + { + $circle = new \ImagickDraw; + + // set background + $bgcolor = new Color($this->background); + $circle->setFillColor($bgcolor->getPixel()); + + // set border + if ($this->hasBorder()) { + $border_color = new Color($this->border_color); + $circle->setStrokeWidth($this->border_width); + $circle->setStrokeColor($border_color->getPixel()); + } + + $circle->ellipse($x, $y, $this->width / 2, $this->height / 2, 0, 360); + + $image->getCore()->drawImage($circle); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Shapes/LineShape.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Shapes/LineShape.php new file mode 100644 index 0000000..0d6b329 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Shapes/LineShape.php @@ -0,0 +1,94 @@ +x = is_numeric($x) ? intval($x) : $this->x; + $this->y = is_numeric($y) ? intval($y) : $this->y; + } + + /** + * Set current line color + * + * @param string $color + * @return void + */ + public function color($color) + { + $this->color = $color; + } + + /** + * Set current line width in pixels + * + * @param int $width + * @return void + */ + public function width($width) + { + $this->width = $width; + } + + /** + * Draw current instance of line to given endpoint on given image + * + * @param Image $image + * @param int $x + * @param int $y + * @return boolean + */ + public function applyToImage(Image $image, $x = 0, $y = 0) + { + $line = new \ImagickDraw; + + $color = new Color($this->color); + $line->setStrokeColor($color->getPixel()); + $line->setStrokeWidth($this->width); + + $line->line($this->x, $this->y, $x, $y); + $image->getCore()->drawImage($line); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Shapes/PolygonShape.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Shapes/PolygonShape.php new file mode 100644 index 0000000..45f44ad --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Shapes/PolygonShape.php @@ -0,0 +1,81 @@ +points = $this->formatPoints($points); + } + + /** + * Draw polygon on given image + * + * @param Image $image + * @param int $x + * @param int $y + * @return boolean + */ + public function applyToImage(Image $image, $x = 0, $y = 0) + { + $polygon = new \ImagickDraw; + + // set background + $bgcolor = new Color($this->background); + $polygon->setFillColor($bgcolor->getPixel()); + + // set border + if ($this->hasBorder()) { + $border_color = new Color($this->border_color); + $polygon->setStrokeWidth($this->border_width); + $polygon->setStrokeColor($border_color->getPixel()); + } + + $polygon->polygon($this->points); + + $image->getCore()->drawImage($polygon); + + return true; + } + + /** + * Format polygon points to Imagick format + * + * @param Array $points + * @return Array + */ + private function formatPoints($points) + { + $ipoints = []; + $count = 1; + + foreach ($points as $key => $value) { + if ($count%2 === 0) { + $y = $value; + $ipoints[] = ['x' => $x, 'y' => $y]; + } else { + $x = $value; + } + $count++; + } + + return $ipoints; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Shapes/RectangleShape.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Shapes/RectangleShape.php new file mode 100644 index 0000000..1166d77 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Shapes/RectangleShape.php @@ -0,0 +1,84 @@ +x1 = is_numeric($x1) ? intval($x1) : $this->x1; + $this->y1 = is_numeric($y1) ? intval($y1) : $this->y1; + $this->x2 = is_numeric($x2) ? intval($x2) : $this->x2; + $this->y2 = is_numeric($y2) ? intval($y2) : $this->y2; + } + + /** + * Draw rectangle to given image at certain position + * + * @param Image $image + * @param int $x + * @param int $y + * @return boolean + */ + public function applyToImage(Image $image, $x = 0, $y = 0) + { + $rectangle = new \ImagickDraw; + + // set background + $bgcolor = new Color($this->background); + $rectangle->setFillColor($bgcolor->getPixel()); + + // set border + if ($this->hasBorder()) { + $border_color = new Color($this->border_color); + $rectangle->setStrokeWidth($this->border_width); + $rectangle->setStrokeColor($border_color->getPixel()); + } + + $rectangle->rectangle($this->x1, $this->y1, $this->x2, $this->y2); + + $image->getCore()->drawImage($rectangle); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Point.php b/vendor/intervention/image/src/Intervention/Image/Point.php new file mode 100644 index 0000000..d51452e --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Point.php @@ -0,0 +1,64 @@ +x = is_numeric($x) ? intval($x) : 0; + $this->y = is_numeric($y) ? intval($y) : 0; + } + + /** + * Sets X coordinate + * + * @param int $x + */ + public function setX($x) + { + $this->x = intval($x); + } + + /** + * Sets Y coordinate + * + * @param int $y + */ + public function setY($y) + { + $this->y = intval($y); + } + + /** + * Sets both X and Y coordinate + * + * @param int $x + * @param int $y + */ + public function setPosition($x, $y) + { + $this->setX($x); + $this->setY($y); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Response.php b/vendor/intervention/image/src/Intervention/Image/Response.php new file mode 100644 index 0000000..59a51e9 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Response.php @@ -0,0 +1,78 @@ +image = $image; + $this->format = $format ? $format : $image->mime; + $this->quality = $quality ? $quality : 90; + } + + /** + * Builds response according to settings + * + * @return mixed + */ + public function make() + { + $this->image->encode($this->format, $this->quality); + $data = $this->image->getEncoded(); + $mime = finfo_buffer(finfo_open(FILEINFO_MIME_TYPE), $data); + $length = strlen($data); + + if (function_exists('app') && is_a($app = app(), 'Illuminate\Foundation\Application')) { + + $response = IlluminateResponse::make($data); + $response->header('Content-Type', $mime); + $response->header('Content-Length', $length); + + } elseif (class_exists('\Symfony\Component\HttpFoundation\Response')) { + + $response = new SymfonyResponse($data); + $response->headers->set('Content-Type', $mime); + $response->headers->set('Content-Length', $length); + + } else { + + header('Content-Type: ' . $mime); + header('Content-Length: ' . $length); + $response = $data; + } + + return $response; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Size.php b/vendor/intervention/image/src/Intervention/Image/Size.php new file mode 100644 index 0000000..b1f26e4 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Size.php @@ -0,0 +1,374 @@ +width = is_numeric($width) ? intval($width) : 1; + $this->height = is_numeric($height) ? intval($height) : 1; + $this->pivot = $pivot ? $pivot : new Point; + } + + /** + * Set the width and height absolutely + * + * @param int $width + * @param int $height + */ + public function set($width, $height) + { + $this->width = $width; + $this->height = $height; + } + + /** + * Set current pivot point + * + * @param Point $point + */ + public function setPivot(Point $point) + { + $this->pivot = $point; + } + + /** + * Get the current width + * + * @return int + */ + public function getWidth() + { + return intval($this->width); + } + + /** + * Get the current height + * + * @return int + */ + public function getHeight() + { + return intval($this->height); + } + + /** + * Calculate the current aspect ratio + * + * @return float + */ + public function getRatio() + { + return $this->width / $this->height; + } + + /** + * Resize to desired width and/or height + * + * @param int $width + * @param int $height + * @param Closure $callback + * @return Size + */ + public function resize($width, $height, Closure $callback = null) + { + if (is_null($width) && is_null($height)) { + throw new InvalidArgumentException( + "Width or height needs to be defined." + ); + } + + // new size with dominant width + $dominant_w_size = clone $this; + $dominant_w_size->resizeHeight($height, $callback); + $dominant_w_size->resizeWidth($width, $callback); + + // new size with dominant height + $dominant_h_size = clone $this; + $dominant_h_size->resizeWidth($width, $callback); + $dominant_h_size->resizeHeight($height, $callback); + + // decide which size to use + if ($dominant_h_size->fitsInto(new self($width, $height))) { + $this->set($dominant_h_size->width, $dominant_h_size->height); + } else { + $this->set($dominant_w_size->width, $dominant_w_size->height); + } + + return $this; + } + + /** + * Scale size according to given constraints + * + * @param int $width + * @param Closure $callback + * @return Size + */ + private function resizeWidth($width, Closure $callback = null) + { + $constraint = $this->getConstraint($callback); + + if ($constraint->isFixed(Constraint::UPSIZE)) { + $max_width = $constraint->getSize()->getWidth(); + $max_height = $constraint->getSize()->getHeight(); + } + + if (is_numeric($width)) { + + if ($constraint->isFixed(Constraint::UPSIZE)) { + $this->width = ($width > $max_width) ? $max_width : $width; + } else { + $this->width = $width; + } + + if ($constraint->isFixed(Constraint::ASPECTRATIO)) { + $h = max(1, intval(round($this->width / $constraint->getSize()->getRatio()))); + + if ($constraint->isFixed(Constraint::UPSIZE)) { + $this->height = ($h > $max_height) ? $max_height : $h; + } else { + $this->height = $h; + } + } + } + } + + /** + * Scale size according to given constraints + * + * @param int $height + * @param Closure $callback + * @return Size + */ + private function resizeHeight($height, Closure $callback = null) + { + $constraint = $this->getConstraint($callback); + + if ($constraint->isFixed(Constraint::UPSIZE)) { + $max_width = $constraint->getSize()->getWidth(); + $max_height = $constraint->getSize()->getHeight(); + } + + if (is_numeric($height)) { + + if ($constraint->isFixed(Constraint::UPSIZE)) { + $this->height = ($height > $max_height) ? $max_height : $height; + } else { + $this->height = $height; + } + + if ($constraint->isFixed(Constraint::ASPECTRATIO)) { + $w = max(1, intval(round($this->height * $constraint->getSize()->getRatio()))); + + if ($constraint->isFixed(Constraint::UPSIZE)) { + $this->width = ($w > $max_width) ? $max_width : $w; + } else { + $this->width = $w; + } + } + } + } + + /** + * Calculate the relative position to another Size + * based on the pivot point settings of both sizes. + * + * @param Size $size + * @return \Intervention\Image\Point + */ + public function relativePosition(Size $size) + { + $x = $this->pivot->x - $size->pivot->x; + $y = $this->pivot->y - $size->pivot->y; + + return new Point($x, $y); + } + + /** + * Resize given Size to best fitting size of current size. + * + * @param Size $size + * @return \Intervention\Image\Size + */ + public function fit(Size $size, $position = 'center') + { + // create size with auto height + $auto_height = clone $size; + + $auto_height->resize($this->width, null, function ($constraint) { + $constraint->aspectRatio(); + }); + + // decide which version to use + if ($auto_height->fitsInto($this)) { + + $size = $auto_height; + + } else { + + // create size with auto width + $auto_width = clone $size; + + $auto_width->resize(null, $this->height, function ($constraint) { + $constraint->aspectRatio(); + }); + + $size = $auto_width; + } + + $this->align($position); + $size->align($position); + $size->setPivot($this->relativePosition($size)); + + return $size; + } + + /** + * Checks if given size fits into current size + * + * @param Size $size + * @return boolean + */ + public function fitsInto(Size $size) + { + return ($this->width <= $size->width) && ($this->height <= $size->height); + } + + /** + * Aligns current size's pivot point to given position + * and moves point automatically by offset. + * + * @param string $position + * @param int $offset_x + * @param int $offset_y + * @return \Intervention\Image\Size + */ + public function align($position, $offset_x = 0, $offset_y = 0) + { + switch (strtolower($position)) { + + case 'top': + case 'top-center': + case 'top-middle': + case 'center-top': + case 'middle-top': + $x = intval($this->width / 2); + $y = 0 + $offset_y; + break; + + case 'top-right': + case 'right-top': + $x = $this->width - $offset_x; + $y = 0 + $offset_y; + break; + + case 'left': + case 'left-center': + case 'left-middle': + case 'center-left': + case 'middle-left': + $x = 0 + $offset_x; + $y = intval($this->height / 2); + break; + + case 'right': + case 'right-center': + case 'right-middle': + case 'center-right': + case 'middle-right': + $x = $this->width - $offset_x; + $y = intval($this->height / 2); + break; + + case 'bottom-left': + case 'left-bottom': + $x = 0 + $offset_x; + $y = $this->height - $offset_y; + break; + + case 'bottom': + case 'bottom-center': + case 'bottom-middle': + case 'center-bottom': + case 'middle-bottom': + $x = intval($this->width / 2); + $y = $this->height - $offset_y; + break; + + case 'bottom-right': + case 'right-bottom': + $x = $this->width - $offset_x; + $y = $this->height - $offset_y; + break; + + case 'center': + case 'middle': + case 'center-center': + case 'middle-middle': + $x = intval($this->width / 2) + $offset_x; + $y = intval($this->height / 2) + $offset_y; + break; + + default: + case 'top-left': + case 'left-top': + $x = 0 + $offset_x; + $y = 0 + $offset_y; + break; + } + + $this->pivot->setPosition($x, $y); + + return $this; + } + + /** + * Runs constraints on current size + * + * @param Closure $callback + * @return \Intervention\Image\Constraint + */ + private function getConstraint(Closure $callback = null) + { + $constraint = new Constraint(clone $this); + + if (is_callable($callback)) { + $callback($constraint); + } + + return $constraint; + } +} diff --git a/vendor/intervention/image/src/config/config.php b/vendor/intervention/image/src/config/config.php new file mode 100644 index 0000000..2b1d2c3 --- /dev/null +++ b/vendor/intervention/image/src/config/config.php @@ -0,0 +1,20 @@ + 'gd' + +]; diff --git a/vendor/monolog/monolog/CHANGELOG.md b/vendor/monolog/monolog/CHANGELOG.md new file mode 100644 index 0000000..8a8c651 --- /dev/null +++ b/vendor/monolog/monolog/CHANGELOG.md @@ -0,0 +1,623 @@ +### 2.9.1 (2023-02-06) + + * Fixed Logger not being serializable anymore (#1792) + +### 2.9.0 (2023-02-05) + + * Deprecated FlowdockHandler & Formatter as the flowdock service was shutdown (#1748) + * Added support for enum context values in PsrLogMessageProcessor (#1773) + * Added graylog2/gelf-php 2.x support (#1747) + * Improved `BrowserConsoleHandler` logging to use more appropriate methods than just console.log in the browser (#1739) + * Fixed `WhatFailureGroupHandler` not catching errors happening inside `close()` (#1791) + * Fixed datetime field in `GoogleCloudLoggingFormatter` (#1758) + * Fixed infinite loop detection within Fibers (#1753) + * Fixed `AmqpHandler->setExtraAttributes` not working with buffering handler wrappers (#1781) + +### 2.8.0 (2022-07-24) + + * Deprecated `CubeHandler` and `PHPConsoleHandler` as both projects are abandoned and those should not be used anymore (#1734) + * Added RFC 5424 level (`7` to `0`) support to `Logger::log` and `Logger::addRecord` to increase interoperability (#1723) + * Added support for `__toString` for objects which are not json serializable in `JsonFormatter` (#1733) + * Added `GoogleCloudLoggingFormatter` (#1719) + * Added support for Predis 2.x (#1732) + * Added `AmqpHandler->setExtraAttributes` to allow configuring attributes when using an AMQPExchange (#1724) + * Fixed serialization/unserialization of handlers to make sure private properties are included (#1727) + * Fixed allowInlineLineBreaks in LineFormatter causing issues with windows paths containing `\n` or `\r` sequences (#1720) + * Fixed max normalization depth not being taken into account when formatting exceptions with a deep chain of previous exceptions (#1726) + * Fixed PHP 8.2 deprecation warnings (#1722) + * Fixed rare race condition or filesystem issue where StreamHandler is unable to create the directory the log should go into yet it exists already (#1678) + +### 2.7.0 (2022-06-09) + + * Added `$datetime` parameter to `Logger::addRecord` as low level API to allow logging into the past or future (#1682) + * Added `Logger::useLoggingLoopDetection` to allow disabling cyclic logging detection in concurrent frameworks (#1681) + * Fixed handling of fatal errors if callPrevious is disabled in ErrorHandler (#1670) + * Marked the reusable `Monolog\Test\TestCase` class as `@internal` to make sure PHPStorm does not show it above PHPUnit, you may still use it to test your own handlers/etc though (#1677) + * Fixed RotatingFileHandler issue when the date format contained slashes (#1671) + +### 2.6.0 (2022-05-10) + + * Deprecated `SwiftMailerHandler`, use `SymfonyMailerHandler` instead + * Added `SymfonyMailerHandler` (#1663) + * Added ElasticSearch 8.x support to the ElasticsearchHandler (#1662) + * Added a way to filter/modify stack traces in LineFormatter (#1665) + * Fixed UdpSocket not being able to reopen/reconnect after close() + * Fixed infinite loops if a Handler is triggering logging while handling log records + +### 2.5.0 (2022-04-08) + + * Added `callType` to IntrospectionProcessor (#1612) + * Fixed AsMonologProcessor syntax to be compatible with PHP 7.2 (#1651) + +### 2.4.0 (2022-03-14) + + * Added [`Monolog\LogRecord`](src/Monolog/LogRecord.php) interface that can be used to type-hint records like `array|\Monolog\LogRecord $record` to be forward compatible with the upcoming Monolog 3 changes + * Added `includeStacktraces` constructor params to LineFormatter & JsonFormatter (#1603) + * Added `persistent`, `timeout`, `writingTimeout`, `connectionTimeout`, `chunkSize` constructor params to SocketHandler and derivatives (#1600) + * Added `AsMonologProcessor` PHP attribute which can help autowiring / autoconfiguration of processors if frameworks / integrations decide to make use of it. This is useless when used purely with Monolog (#1637) + * Added support for keeping native BSON types as is in MongoDBFormatter (#1620) + * Added support for a `user_agent` key in WebProcessor, disabled by default but you can use it by configuring the $extraFields you want (#1613) + * Added support for username/userIcon in SlackWebhookHandler (#1617) + * Added extension points to BrowserConsoleHandler (#1593) + * Added record message/context/extra info to exceptions thrown when a StreamHandler cannot open its stream to avoid completely losing the data logged (#1630) + * Fixed error handler signature to accept a null $context which happens with internal PHP errors (#1614) + * Fixed a few setter methods not returning `self` (#1609) + * Fixed handling of records going over the max Telegram message length (#1616) + +### 2.3.5 (2021-10-01) + + * Fixed regression in StreamHandler since 2.3.3 on systems with the memory_limit set to >=20GB (#1592) + +### 2.3.4 (2021-09-15) + + * Fixed support for psr/log 3.x (#1589) + +### 2.3.3 (2021-09-14) + + * Fixed memory usage when using StreamHandler and calling stream_get_contents on the resource you passed to it (#1578, #1577) + * Fixed support for psr/log 2.x (#1587) + * Fixed some type annotations + +### 2.3.2 (2021-07-23) + + * Fixed compatibility with PHP 7.2 - 7.4 when experiencing PCRE errors (#1568) + +### 2.3.1 (2021-07-14) + + * Fixed Utils::getClass handling of anonymous classes not being fully compatible with PHP 8 (#1563) + * Fixed some `@inheritDoc` annotations having the wrong case + +### 2.3.0 (2021-07-05) + + * Added a ton of PHPStan type annotations as well as type aliases on Monolog\Logger for Record, Level and LevelName that you can import (#1557) + * Added ability to customize date format when using JsonFormatter (#1561) + * Fixed FilterHandler not calling reset on its internal handler when reset() is called on it (#1531) + * Fixed SyslogUdpHandler not setting the timezone correctly on DateTimeImmutable instances (#1540) + * Fixed StreamHandler thread safety - chunk size set to 2GB now to avoid interlacing when doing concurrent writes (#1553) + +### 2.2.0 (2020-12-14) + + * Added JSON_PARTIAL_OUTPUT_ON_ERROR to default json encoding flags, to avoid dropping entire context data or even records due to an invalid subset of it somewhere + * Added setDateFormat to NormalizerFormatter (and Line/Json formatters by extension) to allow changing this after object creation + * Added RedisPubSubHandler to log records to a Redis channel using PUBLISH + * Added support for Elastica 7, and deprecated the $type argument of ElasticaFormatter which is not in use anymore as of Elastica 7 + * Added support for millisecond write timeouts in SocketHandler, you can now pass floats to setWritingTimeout, e.g. 0.2 is 200ms + * Added support for unix sockets in SyslogUdpHandler (set $port to 0 to make the $host a unix socket) + * Added handleBatch support for TelegramBotHandler + * Added RFC5424e extended date format including milliseconds to SyslogUdpHandler + * Added support for configuring handlers with numeric level values in strings (coming from e.g. env vars) + * Fixed Wildfire/FirePHP/ChromePHP handling of unicode characters + * Fixed PHP 8 issues in SyslogUdpHandler + * Fixed internal type error when mbstring is missing + +### 2.1.1 (2020-07-23) + + * Fixed removing of json encoding options + * Fixed type hint of $level not accepting strings in SendGridHandler and OverflowHandler + * Fixed SwiftMailerHandler not accepting email templates with an empty subject + * Fixed array access on null in RavenHandler + * Fixed unique_id in WebProcessor not being disableable + +### 2.1.0 (2020-05-22) + + * Added `JSON_INVALID_UTF8_SUBSTITUTE` to default json flags, so that invalid UTF8 characters now get converted to [�](https://en.wikipedia.org/wiki/Specials_(Unicode_block)#Replacement_character) instead of being converted from ISO-8859-15 to UTF8 as it was before, which was hardly a comprehensive solution + * Added `$ignoreEmptyContextAndExtra` option to JsonFormatter to skip empty context/extra entirely from the output + * Added `$parseMode`, `$disableWebPagePreview` and `$disableNotification` options to TelegramBotHandler + * Added tentative support for PHP 8 + * NormalizerFormatter::addJsonEncodeOption and removeJsonEncodeOption are now public to allow modifying default json flags + * Fixed GitProcessor type error when there is no git repo present + * Fixed normalization of SoapFault objects containing deeply nested objects as "detail" + * Fixed support for relative paths in RotatingFileHandler + +### 2.0.2 (2019-12-20) + + * Fixed ElasticsearchHandler swallowing exceptions details when failing to index log records + * Fixed normalization of SoapFault objects containing non-strings as "detail" in LineFormatter + * Fixed formatting of resources in JsonFormatter + * Fixed RedisHandler failing to use MULTI properly when passed a proxied Redis instance (e.g. in Symfony with lazy services) + * Fixed FilterHandler triggering a notice when handleBatch was filtering all records passed to it + * Fixed Turkish locale messing up the conversion of level names to their constant values + +### 2.0.1 (2019-11-13) + + * Fixed normalization of Traversables to avoid traversing them as not all of them are rewindable + * Fixed setFormatter/getFormatter to forward to the nested handler in FilterHandler, FingersCrossedHandler, BufferHandler, OverflowHandler and SamplingHandler + * Fixed BrowserConsoleHandler formatting when using multiple styles + * Fixed normalization of exception codes to be always integers even for PDOException which have them as numeric strings + * Fixed normalization of SoapFault objects containing non-strings as "detail" + * Fixed json encoding across all handlers to always attempt recovery of non-UTF-8 strings instead of failing the whole encoding + * Fixed ChromePHPHandler to avoid sending more data than latest Chrome versions allow in headers (4KB down from 256KB). + * Fixed type error in BrowserConsoleHandler when the context array of log records was not associative. + +### 2.0.0 (2019-08-30) + + * BC Break: This is a major release, see [UPGRADE.md](UPGRADE.md) for details if you are coming from a 1.x release + * BC Break: Logger methods log/debug/info/notice/warning/error/critical/alert/emergency now have explicit void return types + * Added FallbackGroupHandler which works like the WhatFailureGroupHandler but stops dispatching log records as soon as one handler accepted it + * Fixed support for UTF-8 when cutting strings to avoid cutting a multibyte-character in half + * Fixed normalizers handling of exception backtraces to avoid serializing arguments in some cases + * Fixed date timezone handling in SyslogUdpHandler + +### 2.0.0-beta2 (2019-07-06) + + * BC Break: This is a major release, see [UPGRADE.md](UPGRADE.md) for details if you are coming from a 1.x release + * BC Break: PHP 7.2 is now the minimum required PHP version. + * BC Break: Removed SlackbotHandler, RavenHandler and HipChatHandler, see [UPGRADE.md](UPGRADE.md) for details + * Added OverflowHandler which will only flush log records to its nested handler when reaching a certain amount of logs (i.e. only pass through when things go really bad) + * Added TelegramBotHandler to log records to a [Telegram](https://core.telegram.org/bots/api) bot account + * Added support for JsonSerializable when normalizing exceptions + * Added support for RFC3164 (outdated BSD syslog protocol) to SyslogUdpHandler + * Added SoapFault details to formatted exceptions + * Fixed DeduplicationHandler silently failing to start when file could not be opened + * Fixed issue in GroupHandler and WhatFailureGroupHandler where setting multiple processors would duplicate records + * Fixed GelfFormatter losing some data when one attachment was too long + * Fixed issue in SignalHandler restarting syscalls functionality + * Improved performance of LogglyHandler when sending multiple logs in a single request + +### 2.0.0-beta1 (2018-12-08) + + * BC Break: This is a major release, see [UPGRADE.md](UPGRADE.md) for details if you are coming from a 1.x release + * BC Break: PHP 7.1 is now the minimum required PHP version. + * BC Break: Quite a few interface changes, only relevant if you implemented your own handlers/processors/formatters + * BC Break: Removed non-PSR-3 methods to add records, all the `add*` (e.g. `addWarning`) methods as well as `emerg`, `crit`, `err` and `warn` + * BC Break: The record timezone is now set per Logger instance and not statically anymore + * BC Break: There is no more default handler configured on empty Logger instances + * BC Break: ElasticSearchHandler renamed to ElasticaHandler + * BC Break: Various handler-specific breaks, see [UPGRADE.md](UPGRADE.md) for details + * Added scalar type hints and return hints in all the places it was possible. Switched strict_types on for more reliability. + * Added DateTimeImmutable support, all record datetime are now immutable, and will toString/json serialize with the correct date format, including microseconds (unless disabled) + * Added timezone and microseconds to the default date format + * Added SendGridHandler to use the SendGrid API to send emails + * Added LogmaticHandler to use the Logmatic.io API to store log records + * Added SqsHandler to send log records to an AWS SQS queue + * Added ElasticsearchHandler to send records via the official ES library. Elastica users should now use ElasticaHandler instead of ElasticSearchHandler + * Added NoopHandler which is similar to the NullHandle but does not prevent the bubbling of log records to handlers further down the configuration, useful for temporarily disabling a handler in configuration files + * Added ProcessHandler to write log output to the STDIN of a given process + * Added HostnameProcessor that adds the machine's hostname to log records + * Added a `$dateFormat` option to the PsrLogMessageProcessor which lets you format DateTime instances nicely + * Added support for the PHP 7.x `mongodb` extension in the MongoDBHandler + * Fixed many minor issues in various handlers, and probably added a few regressions too + +### 1.26.1 (2021-05-28) + + * Fixed PHP 8.1 deprecation warning + +### 1.26.0 (2020-12-14) + + * Added $dateFormat and $removeUsedContextFields arguments to PsrLogMessageProcessor (backport from 2.x) + +### 1.25.5 (2020-07-23) + + * Fixed array access on null in RavenHandler + * Fixed unique_id in WebProcessor not being disableable + +### 1.25.4 (2020-05-22) + + * Fixed GitProcessor type error when there is no git repo present + * Fixed normalization of SoapFault objects containing deeply nested objects as "detail" + * Fixed support for relative paths in RotatingFileHandler + +### 1.25.3 (2019-12-20) + + * Fixed formatting of resources in JsonFormatter + * Fixed RedisHandler failing to use MULTI properly when passed a proxied Redis instance (e.g. in Symfony with lazy services) + * Fixed FilterHandler triggering a notice when handleBatch was filtering all records passed to it + * Fixed Turkish locale messing up the conversion of level names to their constant values + +### 1.25.2 (2019-11-13) + + * Fixed normalization of Traversables to avoid traversing them as not all of them are rewindable + * Fixed setFormatter/getFormatter to forward to the nested handler in FilterHandler, FingersCrossedHandler, BufferHandler and SamplingHandler + * Fixed BrowserConsoleHandler formatting when using multiple styles + * Fixed normalization of exception codes to be always integers even for PDOException which have them as numeric strings + * Fixed normalization of SoapFault objects containing non-strings as "detail" + * Fixed json encoding across all handlers to always attempt recovery of non-UTF-8 strings instead of failing the whole encoding + +### 1.25.1 (2019-09-06) + + * Fixed forward-compatible interfaces to be compatible with Monolog 1.x too. + +### 1.25.0 (2019-09-06) + + * Deprecated SlackbotHandler, use SlackWebhookHandler or SlackHandler instead + * Deprecated RavenHandler, use sentry/sentry 2.x and their Sentry\Monolog\Handler instead + * Deprecated HipChatHandler, migrate to Slack and use SlackWebhookHandler or SlackHandler instead + * Added forward-compatible interfaces and traits FormattableHandlerInterface, FormattableHandlerTrait, ProcessableHandlerInterface, ProcessableHandlerTrait. If you use modern PHP and want to make code compatible with Monolog 1 and 2 this can help. You will have to require at least Monolog 1.25 though. + * Added support for RFC3164 (outdated BSD syslog protocol) to SyslogUdpHandler + * Fixed issue in GroupHandler and WhatFailureGroupHandler where setting multiple processors would duplicate records + * Fixed issue in SignalHandler restarting syscalls functionality + * Fixed normalizers handling of exception backtraces to avoid serializing arguments in some cases + * Fixed ZendMonitorHandler to work with the latest Zend Server versions + * Fixed ChromePHPHandler to avoid sending more data than latest Chrome versions allow in headers (4KB down from 256KB). + +### 1.24.0 (2018-11-05) + + * BC Notice: If you are extending any of the Monolog's Formatters' `normalize` method, make sure you add the new `$depth = 0` argument to your function signature to avoid strict PHP warnings. + * Added a `ResettableInterface` in order to reset/reset/clear/flush handlers and processors + * Added a `ProcessorInterface` as an optional way to label a class as being a processor (mostly useful for autowiring dependency containers) + * Added a way to log signals being received using Monolog\SignalHandler + * Added ability to customize error handling at the Logger level using Logger::setExceptionHandler + * Added InsightOpsHandler to migrate users of the LogEntriesHandler + * Added protection to NormalizerFormatter against circular and very deep structures, it now stops normalizing at a depth of 9 + * Added capture of stack traces to ErrorHandler when logging PHP errors + * Added RavenHandler support for a `contexts` context or extra key to forward that to Sentry's contexts + * Added forwarding of context info to FluentdFormatter + * Added SocketHandler::setChunkSize to override the default chunk size in case you must send large log lines to rsyslog for example + * Added ability to extend/override BrowserConsoleHandler + * Added SlackWebhookHandler::getWebhookUrl and SlackHandler::getToken to enable class extensibility + * Added SwiftMailerHandler::getSubjectFormatter to enable class extensibility + * Dropped official support for HHVM in test builds + * Fixed normalization of exception traces when call_user_func is used to avoid serializing objects and the data they contain + * Fixed naming of fields in Slack handler, all field names are now capitalized in all cases + * Fixed HipChatHandler bug where slack dropped messages randomly + * Fixed normalization of objects in Slack handlers + * Fixed support for PHP7's Throwable in NewRelicHandler + * Fixed race bug when StreamHandler sometimes incorrectly reported it failed to create a directory + * Fixed table row styling issues in HtmlFormatter + * Fixed RavenHandler dropping the message when logging exception + * Fixed WhatFailureGroupHandler skipping processors when using handleBatch + and implement it where possible + * Fixed display of anonymous class names + +### 1.23.0 (2017-06-19) + + * Improved SyslogUdpHandler's support for RFC5424 and added optional `$ident` argument + * Fixed GelfHandler truncation to be per field and not per message + * Fixed compatibility issue with PHP <5.3.6 + * Fixed support for headless Chrome in ChromePHPHandler + * Fixed support for latest Aws SDK in DynamoDbHandler + * Fixed support for SwiftMailer 6.0+ in SwiftMailerHandler + +### 1.22.1 (2017-03-13) + + * Fixed lots of minor issues in the new Slack integrations + * Fixed support for allowInlineLineBreaks in LineFormatter when formatting exception backtraces + +### 1.22.0 (2016-11-26) + + * Added SlackbotHandler and SlackWebhookHandler to set up Slack integration more easily + * Added MercurialProcessor to add mercurial revision and branch names to log records + * Added support for AWS SDK v3 in DynamoDbHandler + * Fixed fatal errors occurring when normalizing generators that have been fully consumed + * Fixed RollbarHandler to include a level (rollbar level), monolog_level (original name), channel and datetime (unix) + * Fixed RollbarHandler not flushing records automatically, calling close() explicitly is not necessary anymore + * Fixed SyslogUdpHandler to avoid sending empty frames + * Fixed a few PHP 7.0 and 7.1 compatibility issues + +### 1.21.0 (2016-07-29) + + * Break: Reverted the addition of $context when the ErrorHandler handles regular php errors from 1.20.0 as it was causing issues + * Added support for more formats in RotatingFileHandler::setFilenameFormat as long as they have Y, m and d in order + * Added ability to format the main line of text the SlackHandler sends by explicitly setting a formatter on the handler + * Added information about SoapFault instances in NormalizerFormatter + * Added $handleOnlyReportedErrors option on ErrorHandler::registerErrorHandler (default true) to allow logging of all errors no matter the error_reporting level + +### 1.20.0 (2016-07-02) + + * Added FingersCrossedHandler::activate() to manually trigger the handler regardless of the activation policy + * Added StreamHandler::getUrl to retrieve the stream's URL + * Added ability to override addRow/addTitle in HtmlFormatter + * Added the $context to context information when the ErrorHandler handles a regular php error + * Deprecated RotatingFileHandler::setFilenameFormat to only support 3 formats: Y, Y-m and Y-m-d + * Fixed WhatFailureGroupHandler to work with PHP7 throwables + * Fixed a few minor bugs + +### 1.19.0 (2016-04-12) + + * Break: StreamHandler will not close streams automatically that it does not own. If you pass in a stream (not a path/url), then it will not close it for you. You can retrieve those using getStream() if needed + * Added DeduplicationHandler to remove duplicate records from notifications across multiple requests, useful for email or other notifications on errors + * Added ability to use `%message%` and other LineFormatter replacements in the subject line of emails sent with NativeMailHandler and SwiftMailerHandler + * Fixed HipChatHandler handling of long messages + +### 1.18.2 (2016-04-02) + + * Fixed ElasticaFormatter to use more precise dates + * Fixed GelfMessageFormatter sending too long messages + +### 1.18.1 (2016-03-13) + + * Fixed SlackHandler bug where slack dropped messages randomly + * Fixed RedisHandler issue when using with the PHPRedis extension + * Fixed AmqpHandler content-type being incorrectly set when using with the AMQP extension + * Fixed BrowserConsoleHandler regression + +### 1.18.0 (2016-03-01) + + * Added optional reduction of timestamp precision via `Logger->useMicrosecondTimestamps(false)`, disabling it gets you a bit of performance boost but reduces the precision to the second instead of microsecond + * Added possibility to skip some extra stack frames in IntrospectionProcessor if you have some library wrapping Monolog that is always adding frames + * Added `Logger->withName` to clone a logger (keeping all handlers) with a new name + * Added FluentdFormatter for the Fluentd unix socket protocol + * Added HandlerWrapper base class to ease the creation of handler wrappers, just extend it and override as needed + * Added support for replacing context sub-keys using `%context.*%` in LineFormatter + * Added support for `payload` context value in RollbarHandler + * Added setRelease to RavenHandler to describe the application version, sent with every log + * Added support for `fingerprint` context value in RavenHandler + * Fixed JSON encoding errors that would gobble up the whole log record, we now handle those more gracefully by dropping chars as needed + * Fixed write timeouts in SocketHandler and derivatives, set to 10sec by default, lower it with `setWritingTimeout()` + * Fixed PHP7 compatibility with regard to Exception/Throwable handling in a few places + +### 1.17.2 (2015-10-14) + + * Fixed ErrorHandler compatibility with non-Monolog PSR-3 loggers + * Fixed SlackHandler handling to use slack functionalities better + * Fixed SwiftMailerHandler bug when sending multiple emails they all had the same id + * Fixed 5.3 compatibility regression + +### 1.17.1 (2015-08-31) + + * Fixed RollbarHandler triggering PHP notices + +### 1.17.0 (2015-08-30) + + * Added support for `checksum` and `release` context/extra values in RavenHandler + * Added better support for exceptions in RollbarHandler + * Added UidProcessor::getUid + * Added support for showing the resource type in NormalizedFormatter + * Fixed IntrospectionProcessor triggering PHP notices + +### 1.16.0 (2015-08-09) + + * Added IFTTTHandler to notify ifttt.com triggers + * Added Logger::setHandlers() to allow setting/replacing all handlers + * Added $capSize in RedisHandler to cap the log size + * Fixed StreamHandler creation of directory to only trigger when the first log write happens + * Fixed bug in the handling of curl failures + * Fixed duplicate logging of fatal errors when both error and fatal error handlers are registered in monolog's ErrorHandler + * Fixed missing fatal errors records with handlers that need to be closed to flush log records + * Fixed TagProcessor::addTags support for associative arrays + +### 1.15.0 (2015-07-12) + + * Added addTags and setTags methods to change a TagProcessor + * Added automatic creation of directories if they are missing for a StreamHandler to open a log file + * Added retry functionality to Loggly, Cube and Mandrill handlers so they retry up to 5 times in case of network failure + * Fixed process exit code being incorrectly reset to 0 if ErrorHandler::registerExceptionHandler was used + * Fixed HTML/JS escaping in BrowserConsoleHandler + * Fixed JSON encoding errors being silently suppressed (PHP 5.5+ only) + +### 1.14.0 (2015-06-19) + + * Added PHPConsoleHandler to send record to Chrome's PHP Console extension and library + * Added support for objects implementing __toString in the NormalizerFormatter + * Added support for HipChat's v2 API in HipChatHandler + * Added Logger::setTimezone() to initialize the timezone monolog should use in case date.timezone isn't correct for your app + * Added an option to send formatted message instead of the raw record on PushoverHandler via ->useFormattedMessage(true) + * Fixed curl errors being silently suppressed + +### 1.13.1 (2015-03-09) + + * Fixed regression in HipChat requiring a new token to be created + +### 1.13.0 (2015-03-05) + + * Added Registry::hasLogger to check for the presence of a logger instance + * Added context.user support to RavenHandler + * Added HipChat API v2 support in the HipChatHandler + * Added NativeMailerHandler::addParameter to pass params to the mail() process + * Added context data to SlackHandler when $includeContextAndExtra is true + * Added ability to customize the Swift_Message per-email in SwiftMailerHandler + * Fixed SwiftMailerHandler to lazily create message instances if a callback is provided + * Fixed serialization of INF and NaN values in Normalizer and LineFormatter + +### 1.12.0 (2014-12-29) + + * Break: HandlerInterface::isHandling now receives a partial record containing only a level key. This was always the intent and does not break any Monolog handler but is strictly speaking a BC break and you should check if you relied on any other field in your own handlers. + * Added PsrHandler to forward records to another PSR-3 logger + * Added SamplingHandler to wrap around a handler and include only every Nth record + * Added MongoDBFormatter to support better storage with MongoDBHandler (it must be enabled manually for now) + * Added exception codes in the output of most formatters + * Added LineFormatter::includeStacktraces to enable exception stack traces in logs (uses more than one line) + * Added $useShortAttachment to SlackHandler to minify attachment size and $includeExtra to append extra data + * Added $host to HipChatHandler for users of private instances + * Added $transactionName to NewRelicHandler and support for a transaction_name context value + * Fixed MandrillHandler to avoid outputting API call responses + * Fixed some non-standard behaviors in SyslogUdpHandler + +### 1.11.0 (2014-09-30) + + * Break: The NewRelicHandler extra and context data are now prefixed with extra_ and context_ to avoid clashes. Watch out if you have scripts reading those from the API and rely on names + * Added WhatFailureGroupHandler to suppress any exception coming from the wrapped handlers and avoid chain failures if a logging service fails + * Added MandrillHandler to send emails via the Mandrillapp.com API + * Added SlackHandler to log records to a Slack.com account + * Added FleepHookHandler to log records to a Fleep.io account + * Added LogglyHandler::addTag to allow adding tags to an existing handler + * Added $ignoreEmptyContextAndExtra to LineFormatter to avoid empty [] at the end + * Added $useLocking to StreamHandler and RotatingFileHandler to enable flock() while writing + * Added support for PhpAmqpLib in the AmqpHandler + * Added FingersCrossedHandler::clear and BufferHandler::clear to reset them between batches in long running jobs + * Added support for adding extra fields from $_SERVER in the WebProcessor + * Fixed support for non-string values in PrsLogMessageProcessor + * Fixed SwiftMailer messages being sent with the wrong date in long running scripts + * Fixed minor PHP 5.6 compatibility issues + * Fixed BufferHandler::close being called twice + +### 1.10.0 (2014-06-04) + + * Added Logger::getHandlers() and Logger::getProcessors() methods + * Added $passthruLevel argument to FingersCrossedHandler to let it always pass some records through even if the trigger level is not reached + * Added support for extra data in NewRelicHandler + * Added $expandNewlines flag to the ErrorLogHandler to create multiple log entries when a message has multiple lines + +### 1.9.1 (2014-04-24) + + * Fixed regression in RotatingFileHandler file permissions + * Fixed initialization of the BufferHandler to make sure it gets flushed after receiving records + * Fixed ChromePHPHandler and FirePHPHandler's activation strategies to be more conservative + +### 1.9.0 (2014-04-20) + + * Added LogEntriesHandler to send logs to a LogEntries account + * Added $filePermissions to tweak file mode on StreamHandler and RotatingFileHandler + * Added $useFormatting flag to MemoryProcessor to make it send raw data in bytes + * Added support for table formatting in FirePHPHandler via the table context key + * Added a TagProcessor to add tags to records, and support for tags in RavenHandler + * Added $appendNewline flag to the JsonFormatter to enable using it when logging to files + * Added sound support to the PushoverHandler + * Fixed multi-threading support in StreamHandler + * Fixed empty headers issue when ChromePHPHandler received no records + * Fixed default format of the ErrorLogHandler + +### 1.8.0 (2014-03-23) + + * Break: the LineFormatter now strips newlines by default because this was a bug, set $allowInlineLineBreaks to true if you need them + * Added BrowserConsoleHandler to send logs to any browser's console via console.log() injection in the output + * Added FilterHandler to filter records and only allow those of a given list of levels through to the wrapped handler + * Added FlowdockHandler to send logs to a Flowdock account + * Added RollbarHandler to send logs to a Rollbar account + * Added HtmlFormatter to send prettier log emails with colors for each log level + * Added GitProcessor to add the current branch/commit to extra record data + * Added a Monolog\Registry class to allow easier global access to pre-configured loggers + * Added support for the new official graylog2/gelf-php lib for GelfHandler, upgrade if you can by replacing the mlehner/gelf-php requirement + * Added support for HHVM + * Added support for Loggly batch uploads + * Added support for tweaking the content type and encoding in NativeMailerHandler + * Added $skipClassesPartials to tweak the ignored classes in the IntrospectionProcessor + * Fixed batch request support in GelfHandler + +### 1.7.0 (2013-11-14) + + * Added ElasticSearchHandler to send logs to an Elastic Search server + * Added DynamoDbHandler and ScalarFormatter to send logs to Amazon's Dynamo DB + * Added SyslogUdpHandler to send logs to a remote syslogd server + * Added LogglyHandler to send logs to a Loggly account + * Added $level to IntrospectionProcessor so it only adds backtraces when needed + * Added $version to LogstashFormatter to allow using the new v1 Logstash format + * Added $appName to NewRelicHandler + * Added configuration of Pushover notification retries/expiry + * Added $maxColumnWidth to NativeMailerHandler to change the 70 chars default + * Added chainability to most setters for all handlers + * Fixed RavenHandler batch processing so it takes the message from the record with highest priority + * Fixed HipChatHandler batch processing so it sends all messages at once + * Fixed issues with eAccelerator + * Fixed and improved many small things + +### 1.6.0 (2013-07-29) + + * Added HipChatHandler to send logs to a HipChat chat room + * Added ErrorLogHandler to send logs to PHP's error_log function + * Added NewRelicHandler to send logs to NewRelic's service + * Added Monolog\ErrorHandler helper class to register a Logger as exception/error/fatal handler + * Added ChannelLevelActivationStrategy for the FingersCrossedHandler to customize levels by channel + * Added stack traces output when normalizing exceptions (json output & co) + * Added Monolog\Logger::API constant (currently 1) + * Added support for ChromePHP's v4.0 extension + * Added support for message priorities in PushoverHandler, see $highPriorityLevel and $emergencyLevel + * Added support for sending messages to multiple users at once with the PushoverHandler + * Fixed RavenHandler's support for batch sending of messages (when behind a Buffer or FingersCrossedHandler) + * Fixed normalization of Traversables with very large data sets, only the first 1000 items are shown now + * Fixed issue in RotatingFileHandler when an open_basedir restriction is active + * Fixed minor issues in RavenHandler and bumped the API to Raven 0.5.0 + * Fixed SyslogHandler issue when many were used concurrently with different facilities + +### 1.5.0 (2013-04-23) + + * Added ProcessIdProcessor to inject the PID in log records + * Added UidProcessor to inject a unique identifier to all log records of one request/run + * Added support for previous exceptions in the LineFormatter exception serialization + * Added Monolog\Logger::getLevels() to get all available levels + * Fixed ChromePHPHandler so it avoids sending headers larger than Chrome can handle + +### 1.4.1 (2013-04-01) + + * Fixed exception formatting in the LineFormatter to be more minimalistic + * Fixed RavenHandler's handling of context/extra data, requires Raven client >0.1.0 + * Fixed log rotation in RotatingFileHandler to work with long running scripts spanning multiple days + * Fixed WebProcessor array access so it checks for data presence + * Fixed Buffer, Group and FingersCrossed handlers to make use of their processors + +### 1.4.0 (2013-02-13) + + * Added RedisHandler to log to Redis via the Predis library or the phpredis extension + * Added ZendMonitorHandler to log to the Zend Server monitor + * Added the possibility to pass arrays of handlers and processors directly in the Logger constructor + * Added `$useSSL` option to the PushoverHandler which is enabled by default + * Fixed ChromePHPHandler and FirePHPHandler issue when multiple instances are used simultaneously + * Fixed header injection capability in the NativeMailHandler + +### 1.3.1 (2013-01-11) + + * Fixed LogstashFormatter to be usable with stream handlers + * Fixed GelfMessageFormatter levels on Windows + +### 1.3.0 (2013-01-08) + + * Added PSR-3 compliance, the `Monolog\Logger` class is now an instance of `Psr\Log\LoggerInterface` + * Added PsrLogMessageProcessor that you can selectively enable for full PSR-3 compliance + * Added LogstashFormatter (combine with SocketHandler or StreamHandler to send logs to Logstash) + * Added PushoverHandler to send mobile notifications + * Added CouchDBHandler and DoctrineCouchDBHandler + * Added RavenHandler to send data to Sentry servers + * Added support for the new MongoClient class in MongoDBHandler + * Added microsecond precision to log records' timestamps + * Added `$flushOnOverflow` param to BufferHandler to flush by batches instead of losing + the oldest entries + * Fixed normalization of objects with cyclic references + +### 1.2.1 (2012-08-29) + + * Added new $logopts arg to SyslogHandler to provide custom openlog options + * Fixed fatal error in SyslogHandler + +### 1.2.0 (2012-08-18) + + * Added AmqpHandler (for use with AMQP servers) + * Added CubeHandler + * Added NativeMailerHandler::addHeader() to send custom headers in mails + * Added the possibility to specify more than one recipient in NativeMailerHandler + * Added the possibility to specify float timeouts in SocketHandler + * Added NOTICE and EMERGENCY levels to conform with RFC 5424 + * Fixed the log records to use the php default timezone instead of UTC + * Fixed BufferHandler not being flushed properly on PHP fatal errors + * Fixed normalization of exotic resource types + * Fixed the default format of the SyslogHandler to avoid duplicating datetimes in syslog + +### 1.1.0 (2012-04-23) + + * Added Monolog\Logger::isHandling() to check if a handler will + handle the given log level + * Added ChromePHPHandler + * Added MongoDBHandler + * Added GelfHandler (for use with Graylog2 servers) + * Added SocketHandler (for use with syslog-ng for example) + * Added NormalizerFormatter + * Added the possibility to change the activation strategy of the FingersCrossedHandler + * Added possibility to show microseconds in logs + * Added `server` and `referer` to WebProcessor output + +### 1.0.2 (2011-10-24) + + * Fixed bug in IE with large response headers and FirePHPHandler + +### 1.0.1 (2011-08-25) + + * Added MemoryPeakUsageProcessor and MemoryUsageProcessor + * Added Monolog\Logger::getName() to get a logger's channel name + +### 1.0.0 (2011-07-06) + + * Added IntrospectionProcessor to get info from where the logger was called + * Fixed WebProcessor in CLI + +### 1.0.0-RC1 (2011-07-01) + + * Initial release diff --git a/vendor/monolog/monolog/LICENSE b/vendor/monolog/monolog/LICENSE new file mode 100644 index 0000000..aa2a042 --- /dev/null +++ b/vendor/monolog/monolog/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2011-2020 Jordi Boggiano + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/monolog/monolog/README.md b/vendor/monolog/monolog/README.md new file mode 100644 index 0000000..bfcae0c --- /dev/null +++ b/vendor/monolog/monolog/README.md @@ -0,0 +1,112 @@ +# Monolog - Logging for PHP [![Continuous Integration](https://github.com/Seldaek/monolog/workflows/Continuous%20Integration/badge.svg?branch=main)](https://github.com/Seldaek/monolog/actions) + +[![Total Downloads](https://img.shields.io/packagist/dt/monolog/monolog.svg)](https://packagist.org/packages/monolog/monolog) +[![Latest Stable Version](https://img.shields.io/packagist/v/monolog/monolog.svg)](https://packagist.org/packages/monolog/monolog) + + +Monolog sends your logs to files, sockets, inboxes, databases and various +web services. See the complete list of handlers below. Special handlers +allow you to build advanced logging strategies. + +This library implements the [PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md) +interface that you can type-hint against in your own libraries to keep +a maximum of interoperability. You can also use it in your applications to +make sure you can always use another compatible logger at a later time. +As of 1.11.0 Monolog public APIs will also accept PSR-3 log levels. +Internally Monolog still uses its own level scheme since it predates PSR-3. + +## Installation + +Install the latest version with + +```bash +$ composer require monolog/monolog +``` + +## Basic Usage + +```php +pushHandler(new StreamHandler('path/to/your.log', Logger::WARNING)); + +// add records to the log +$log->warning('Foo'); +$log->error('Bar'); +``` + +## Documentation + +- [Usage Instructions](doc/01-usage.md) +- [Handlers, Formatters and Processors](doc/02-handlers-formatters-processors.md) +- [Utility Classes](doc/03-utilities.md) +- [Extending Monolog](doc/04-extending.md) +- [Log Record Structure](doc/message-structure.md) + +## Support Monolog Financially + +Get supported Monolog and help fund the project with the [Tidelift Subscription](https://tidelift.com/subscription/pkg/packagist-monolog-monolog?utm_source=packagist-monolog-monolog&utm_medium=referral&utm_campaign=enterprise) or via [GitHub sponsorship](https://github.com/sponsors/Seldaek). + +Tidelift delivers commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. + +## Third Party Packages + +Third party handlers, formatters and processors are +[listed in the wiki](https://github.com/Seldaek/monolog/wiki/Third-Party-Packages). You +can also add your own there if you publish one. + +## About + +### Requirements + +- Monolog `^2.0` works with PHP 7.2 or above, use Monolog `^1.25` for PHP 5.3+ support. + +### Support + +Monolog 1.x support is somewhat limited at this point and only important fixes will be done. You should migrate to Monolog 2 where possible to benefit from all the latest features and fixes. + +### Submitting bugs and feature requests + +Bugs and feature request are tracked on [GitHub](https://github.com/Seldaek/monolog/issues) + +### Framework Integrations + +- Frameworks and libraries using [PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md) + can be used very easily with Monolog since it implements the interface. +- [Symfony](http://symfony.com) comes out of the box with Monolog. +- [Laravel](http://laravel.com/) comes out of the box with Monolog. +- [Lumen](http://lumen.laravel.com/) comes out of the box with Monolog. +- [PPI](https://github.com/ppi/framework) comes out of the box with Monolog. +- [CakePHP](http://cakephp.org/) is usable with Monolog via the [cakephp-monolog](https://github.com/jadb/cakephp-monolog) plugin. +- [Slim](http://www.slimframework.com/) is usable with Monolog via the [Slim-Monolog](https://github.com/Flynsarmy/Slim-Monolog) log writer. +- [XOOPS 2.6](http://xoops.org/) comes out of the box with Monolog. +- [Aura.Web_Project](https://github.com/auraphp/Aura.Web_Project) comes out of the box with Monolog. +- [Nette Framework](http://nette.org/en/) is usable with Monolog via the [contributte/monolog](https://github.com/contributte/monolog) or [orisai/nette-monolog](https://github.com/orisai/nette-monolog) extensions. +- [Proton Micro Framework](https://github.com/alexbilbie/Proton) comes out of the box with Monolog. +- [FuelPHP](http://fuelphp.com/) comes out of the box with Monolog. +- [Equip Framework](https://github.com/equip/framework) comes out of the box with Monolog. +- [Yii 2](http://www.yiiframework.com/) is usable with Monolog via the [yii2-monolog](https://github.com/merorafael/yii2-monolog) or [yii2-psr-log-target](https://github.com/samdark/yii2-psr-log-target) plugins. +- [Hawkbit Micro Framework](https://github.com/HawkBitPhp/hawkbit) comes out of the box with Monolog. +- [SilverStripe 4](https://www.silverstripe.org/) comes out of the box with Monolog. +- [Drupal](https://www.drupal.org/) is usable with Monolog via the [monolog](https://www.drupal.org/project/monolog) module. +- [Aimeos ecommerce framework](https://aimeos.org/) is usable with Monolog via the [ai-monolog](https://github.com/aimeos/ai-monolog) extension. +- [Magento](https://magento.com/) comes out of the box with Monolog. + +### Author + +Jordi Boggiano - -
+See also the list of [contributors](https://github.com/Seldaek/monolog/contributors) who participated in this project. + +### License + +Monolog is licensed under the MIT License - see the [LICENSE](LICENSE) file for details + +### Acknowledgements + +This library is heavily inspired by Python's [Logbook](https://logbook.readthedocs.io/en/stable/) +library, although most concepts have been adjusted to fit to the PHP world. diff --git a/vendor/monolog/monolog/UPGRADE.md b/vendor/monolog/monolog/UPGRADE.md new file mode 100644 index 0000000..84e15e6 --- /dev/null +++ b/vendor/monolog/monolog/UPGRADE.md @@ -0,0 +1,72 @@ +### 2.0.0 + +- `Monolog\Logger::API` can be used to distinguish between a Monolog `1` and `2` + install of Monolog when writing integration code. + +- Removed non-PSR-3 methods to add records, all the `add*` (e.g. `addWarning`) + methods as well as `emerg`, `crit`, `err` and `warn`. + +- DateTime are now formatted with a timezone and microseconds (unless disabled). + Various formatters and log output might be affected, which may mess with log parsing + in some cases. + +- The `datetime` in every record array is now a DateTimeImmutable, not that you + should have been modifying these anyway. + +- The timezone is now set per Logger instance and not statically, either + via ->setTimezone or passed in the constructor. Calls to Logger::setTimezone + should be converted. + +- `HandlerInterface` has been split off and two new interfaces now exist for + more granular controls: `ProcessableHandlerInterface` and + `FormattableHandlerInterface`. Handlers not extending `AbstractHandler` + should make sure to implement the relevant interfaces. + +- `HandlerInterface` now requires the `close` method to be implemented. This + only impacts you if you implement the interface yourself, but you can extend + the new `Monolog\Handler\Handler` base class too. + +- There is no more default handler configured on empty Logger instances, if + you were relying on that you will not get any output anymore, make sure to + configure the handler you need. + +#### LogglyFormatter + +- The records' `datetime` is not sent anymore. Only `timestamp` is sent to Loggly. + +#### AmqpHandler + +- Log levels are not shortened to 4 characters anymore. e.g. a warning record + will be sent using the `warning.channel` routing key instead of `warn.channel` + as in 1.x. +- The exchange name does not default to 'log' anymore, and it is completely ignored + now for the AMQP extension users. Only PHPAmqpLib uses it if provided. + +#### RotatingFileHandler + +- The file name format must now contain `{date}` and the date format must be set + to one of the predefined FILE_PER_* constants to avoid issues with file rotation. + See `setFilenameFormat`. + +#### LogstashFormatter + +- Removed Logstash V0 support +- Context/extra prefix has been removed in favor of letting users configure the exact key being sent +- Context/extra data are now sent as an object instead of single keys + +#### HipChatHandler + +- Removed deprecated HipChat handler, migrate to Slack and use SlackWebhookHandler or SlackHandler instead + +#### SlackbotHandler + +- Removed deprecated SlackbotHandler handler, use SlackWebhookHandler or SlackHandler instead + +#### RavenHandler + +- Removed deprecated RavenHandler handler, use sentry/sentry 2.x and their Sentry\Monolog\Handler instead + +#### ElasticSearchHandler + +- As support for the official Elasticsearch library was added, the former ElasticSearchHandler has been + renamed to ElasticaHandler and the new one added as ElasticsearchHandler. diff --git a/vendor/monolog/monolog/src/Monolog/Attribute/AsMonologProcessor.php b/vendor/monolog/monolog/src/Monolog/Attribute/AsMonologProcessor.php new file mode 100644 index 0000000..188bbb0 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Attribute/AsMonologProcessor.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Attribute; + +/** + * A reusable attribute to help configure a class or a method as a processor. + * + * Using it offers no guarantee: it needs to be leveraged by a Monolog third-party consumer. + * + * Using it with the Monolog library only has no effect at all: processors should still be turned into a callable if + * needed and manually pushed to the loggers and to the processable handlers. + */ +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class AsMonologProcessor +{ + /** @var string|null */ + public $channel = null; + /** @var string|null */ + public $handler = null; + /** @var string|null */ + public $method = null; + + /** + * @param string|null $channel The logging channel the processor should be pushed to. + * @param string|null $handler The handler the processor should be pushed to. + * @param string|null $method The method that processes the records (if the attribute is used at the class level). + */ + public function __construct( + ?string $channel = null, + ?string $handler = null, + ?string $method = null + ) { + $this->channel = $channel; + $this->handler = $handler; + $this->method = $method; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/DateTimeImmutable.php b/vendor/monolog/monolog/src/Monolog/DateTimeImmutable.php new file mode 100644 index 0000000..6a1ba9b --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/DateTimeImmutable.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; + +use DateTimeZone; + +/** + * Overrides default json encoding of date time objects + * + * @author Menno Holtkamp + * @author Jordi Boggiano + */ +class DateTimeImmutable extends \DateTimeImmutable implements \JsonSerializable +{ + /** + * @var bool + */ + private $useMicroseconds; + + public function __construct(bool $useMicroseconds, ?DateTimeZone $timezone = null) + { + $this->useMicroseconds = $useMicroseconds; + + parent::__construct('now', $timezone); + } + + public function jsonSerialize(): string + { + if ($this->useMicroseconds) { + return $this->format('Y-m-d\TH:i:s.uP'); + } + + return $this->format('Y-m-d\TH:i:sP'); + } + + public function __toString(): string + { + return $this->jsonSerialize(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/ErrorHandler.php b/vendor/monolog/monolog/src/Monolog/ErrorHandler.php new file mode 100644 index 0000000..576f171 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/ErrorHandler.php @@ -0,0 +1,307 @@ + + * + * 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; + +/** + * Monolog error handler + * + * A facility to enable logging of runtime errors, exceptions and fatal errors. + * + * Quick setup: ErrorHandler::register($logger); + * + * @author Jordi Boggiano + */ +class ErrorHandler +{ + /** @var LoggerInterface */ + private $logger; + + /** @var ?callable */ + private $previousExceptionHandler = null; + /** @var array an array of class name to LogLevel::* constant mapping */ + private $uncaughtExceptionLevelMap = []; + + /** @var callable|true|null */ + private $previousErrorHandler = null; + /** @var array an array of E_* constant to LogLevel::* constant mapping */ + private $errorLevelMap = []; + /** @var bool */ + private $handleOnlyReportedErrors = true; + + /** @var bool */ + private $hasFatalErrorHandler = false; + /** @var LogLevel::* */ + private $fatalLevel = LogLevel::ALERT; + /** @var ?string */ + private $reservedMemory = null; + /** @var ?array{type: int, message: string, file: string, line: int, trace: mixed} */ + private $lastFatalData = null; + /** @var int[] */ + private static $fatalErrors = [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR]; + + public function __construct(LoggerInterface $logger) + { + $this->logger = $logger; + } + + /** + * Registers a new ErrorHandler for a given Logger + * + * By default it will handle errors, exceptions and fatal errors + * + * @param LoggerInterface $logger + * @param array|false $errorLevelMap an array of E_* constant to LogLevel::* constant mapping, or false to disable error handling + * @param array|false $exceptionLevelMap an array of class name to LogLevel::* constant mapping, or false to disable exception handling + * @param LogLevel::*|null|false $fatalLevel a LogLevel::* constant, null to use the default LogLevel::ALERT or false to disable fatal error handling + * @return ErrorHandler + */ + public static function register(LoggerInterface $logger, $errorLevelMap = [], $exceptionLevelMap = [], $fatalLevel = null): self + { + /** @phpstan-ignore-next-line */ + $handler = new static($logger); + if ($errorLevelMap !== false) { + $handler->registerErrorHandler($errorLevelMap); + } + if ($exceptionLevelMap !== false) { + $handler->registerExceptionHandler($exceptionLevelMap); + } + if ($fatalLevel !== false) { + $handler->registerFatalHandler($fatalLevel); + } + + return $handler; + } + + /** + * @param array $levelMap an array of class name to LogLevel::* constant mapping + * @return $this + */ + public function registerExceptionHandler(array $levelMap = [], bool $callPrevious = true): self + { + $prev = set_exception_handler(function (\Throwable $e): void { + $this->handleException($e); + }); + $this->uncaughtExceptionLevelMap = $levelMap; + foreach ($this->defaultExceptionLevelMap() as $class => $level) { + if (!isset($this->uncaughtExceptionLevelMap[$class])) { + $this->uncaughtExceptionLevelMap[$class] = $level; + } + } + if ($callPrevious && $prev) { + $this->previousExceptionHandler = $prev; + } + + return $this; + } + + /** + * @param array $levelMap an array of E_* constant to LogLevel::* constant mapping + * @return $this + */ + public function registerErrorHandler(array $levelMap = [], bool $callPrevious = true, int $errorTypes = -1, bool $handleOnlyReportedErrors = true): self + { + $prev = set_error_handler([$this, 'handleError'], $errorTypes); + $this->errorLevelMap = array_replace($this->defaultErrorLevelMap(), $levelMap); + if ($callPrevious) { + $this->previousErrorHandler = $prev ?: true; + } else { + $this->previousErrorHandler = null; + } + + $this->handleOnlyReportedErrors = $handleOnlyReportedErrors; + + return $this; + } + + /** + * @param LogLevel::*|null $level a LogLevel::* constant, null to use the default LogLevel::ALERT + * @param int $reservedMemorySize Amount of KBs to reserve in memory so that it can be freed when handling fatal errors giving Monolog some room in memory to get its job done + */ + public function registerFatalHandler($level = null, int $reservedMemorySize = 20): self + { + register_shutdown_function([$this, 'handleFatalError']); + + $this->reservedMemory = str_repeat(' ', 1024 * $reservedMemorySize); + $this->fatalLevel = null === $level ? LogLevel::ALERT : $level; + $this->hasFatalErrorHandler = true; + + return $this; + } + + /** + * @return array + */ + protected function defaultExceptionLevelMap(): array + { + return [ + 'ParseError' => LogLevel::CRITICAL, + 'Throwable' => LogLevel::ERROR, + ]; + } + + /** + * @return array + */ + protected function defaultErrorLevelMap(): array + { + return [ + E_ERROR => LogLevel::CRITICAL, + E_WARNING => LogLevel::WARNING, + E_PARSE => LogLevel::ALERT, + E_NOTICE => LogLevel::NOTICE, + E_CORE_ERROR => LogLevel::CRITICAL, + E_CORE_WARNING => LogLevel::WARNING, + E_COMPILE_ERROR => LogLevel::ALERT, + E_COMPILE_WARNING => LogLevel::WARNING, + E_USER_ERROR => LogLevel::ERROR, + E_USER_WARNING => LogLevel::WARNING, + E_USER_NOTICE => LogLevel::NOTICE, + E_STRICT => LogLevel::NOTICE, + E_RECOVERABLE_ERROR => LogLevel::ERROR, + E_DEPRECATED => LogLevel::NOTICE, + E_USER_DEPRECATED => LogLevel::NOTICE, + ]; + } + + /** + * @phpstan-return never + */ + private function handleException(\Throwable $e): void + { + $level = LogLevel::ERROR; + foreach ($this->uncaughtExceptionLevelMap as $class => $candidate) { + if ($e instanceof $class) { + $level = $candidate; + break; + } + } + + $this->logger->log( + $level, + sprintf('Uncaught Exception %s: "%s" at %s line %s', Utils::getClass($e), $e->getMessage(), $e->getFile(), $e->getLine()), + ['exception' => $e] + ); + + if ($this->previousExceptionHandler) { + ($this->previousExceptionHandler)($e); + } + + if (!headers_sent() && !ini_get('display_errors')) { + http_response_code(500); + } + + exit(255); + } + + /** + * @private + * + * @param mixed[] $context + */ + public function handleError(int $code, string $message, string $file = '', int $line = 0, ?array $context = []): bool + { + if ($this->handleOnlyReportedErrors && !(error_reporting() & $code)) { + return false; + } + + // fatal error codes are ignored if a fatal error handler is present as well to avoid duplicate log entries + if (!$this->hasFatalErrorHandler || !in_array($code, self::$fatalErrors, true)) { + $level = $this->errorLevelMap[$code] ?? LogLevel::CRITICAL; + $this->logger->log($level, self::codeToString($code).': '.$message, ['code' => $code, 'message' => $message, 'file' => $file, 'line' => $line]); + } else { + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); + array_shift($trace); // Exclude handleError from trace + $this->lastFatalData = ['type' => $code, 'message' => $message, 'file' => $file, 'line' => $line, 'trace' => $trace]; + } + + if ($this->previousErrorHandler === true) { + return false; + } elseif ($this->previousErrorHandler) { + return (bool) ($this->previousErrorHandler)($code, $message, $file, $line, $context); + } + + return true; + } + + /** + * @private + */ + public function handleFatalError(): void + { + $this->reservedMemory = ''; + + if (is_array($this->lastFatalData)) { + $lastError = $this->lastFatalData; + } else { + $lastError = error_get_last(); + } + + if ($lastError && in_array($lastError['type'], self::$fatalErrors, true)) { + $trace = $lastError['trace'] ?? null; + $this->logger->log( + $this->fatalLevel, + 'Fatal Error ('.self::codeToString($lastError['type']).'): '.$lastError['message'], + ['code' => $lastError['type'], 'message' => $lastError['message'], 'file' => $lastError['file'], 'line' => $lastError['line'], 'trace' => $trace] + ); + + if ($this->logger instanceof Logger) { + foreach ($this->logger->getHandlers() as $handler) { + $handler->close(); + } + } + } + } + + /** + * @param int $code + */ + private static function codeToString($code): string + { + switch ($code) { + case E_ERROR: + return 'E_ERROR'; + case E_WARNING: + return 'E_WARNING'; + case E_PARSE: + return 'E_PARSE'; + case E_NOTICE: + return 'E_NOTICE'; + case E_CORE_ERROR: + return 'E_CORE_ERROR'; + case E_CORE_WARNING: + return 'E_CORE_WARNING'; + case E_COMPILE_ERROR: + return 'E_COMPILE_ERROR'; + case E_COMPILE_WARNING: + return 'E_COMPILE_WARNING'; + case E_USER_ERROR: + return 'E_USER_ERROR'; + case E_USER_WARNING: + return 'E_USER_WARNING'; + case E_USER_NOTICE: + return 'E_USER_NOTICE'; + case E_STRICT: + return 'E_STRICT'; + case E_RECOVERABLE_ERROR: + return 'E_RECOVERABLE_ERROR'; + case E_DEPRECATED: + return 'E_DEPRECATED'; + case E_USER_DEPRECATED: + return 'E_USER_DEPRECATED'; + } + + return 'Unknown PHP error'; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php new file mode 100644 index 0000000..aa1884b --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php @@ -0,0 +1,83 @@ + + * + * 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; + +/** + * Formats a log message according to the ChromePHP array format + * + * @author Christophe Coevoet + */ +class ChromePHPFormatter implements FormatterInterface +{ + /** + * Translates Monolog log levels to Wildfire levels. + * + * @var array + */ + private $logLevels = [ + Logger::DEBUG => 'log', + Logger::INFO => 'info', + Logger::NOTICE => 'info', + Logger::WARNING => 'warn', + Logger::ERROR => 'error', + Logger::CRITICAL => 'error', + Logger::ALERT => 'error', + Logger::EMERGENCY => 'error', + ]; + + /** + * {@inheritDoc} + */ + public function format(array $record) + { + // Retrieve the line and file if set and remove them from the formatted extra + $backtrace = 'unknown'; + if (isset($record['extra']['file'], $record['extra']['line'])) { + $backtrace = $record['extra']['file'].' : '.$record['extra']['line']; + unset($record['extra']['file'], $record['extra']['line']); + } + + $message = ['message' => $record['message']]; + if ($record['context']) { + $message['context'] = $record['context']; + } + if ($record['extra']) { + $message['extra'] = $record['extra']; + } + if (count($message) === 1) { + $message = reset($message); + } + + return [ + $record['channel'], + $message, + $backtrace, + $this->logLevels[$record['level']], + ]; + } + + /** + * {@inheritDoc} + */ + public function formatBatch(array $records) + { + $formatted = []; + + foreach ($records as $record) { + $formatted[] = $this->format($record); + } + + return $formatted; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/ElasticaFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/ElasticaFormatter.php new file mode 100644 index 0000000..6c8a9ab --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/ElasticaFormatter.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Elastica\Document; + +/** + * Format a log message into an Elastica Document + * + * @author Jelle Vink + * + * @phpstan-import-type Record from \Monolog\Logger + */ +class ElasticaFormatter extends NormalizerFormatter +{ + /** + * @var string Elastic search index name + */ + protected $index; + + /** + * @var ?string Elastic search document type + */ + protected $type; + + /** + * @param string $index Elastic Search index name + * @param ?string $type Elastic Search document type, deprecated as of Elastica 7 + */ + public function __construct(string $index, ?string $type) + { + // elasticsearch requires a ISO 8601 format date with optional millisecond precision. + parent::__construct('Y-m-d\TH:i:s.uP'); + + $this->index = $index; + $this->type = $type; + } + + /** + * {@inheritDoc} + */ + public function format(array $record) + { + $record = parent::format($record); + + return $this->getDocument($record); + } + + public function getIndex(): string + { + return $this->index; + } + + /** + * @deprecated since Elastica 7 type has no effect + */ + public function getType(): string + { + /** @phpstan-ignore-next-line */ + return $this->type; + } + + /** + * Convert a log message into an Elastica Document + * + * @phpstan-param Record $record + */ + protected function getDocument(array $record): Document + { + $document = new Document(); + $document->setData($record); + if (method_exists($document, 'setType')) { + /** @phpstan-ignore-next-line */ + $document->setType($this->type); + } + $document->setIndex($this->index); + + return $document; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/ElasticsearchFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/ElasticsearchFormatter.php new file mode 100644 index 0000000..b792b81 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/ElasticsearchFormatter.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use DateTimeInterface; + +/** + * Format a log message into an Elasticsearch record + * + * @author Avtandil Kikabidze + */ +class ElasticsearchFormatter extends NormalizerFormatter +{ + /** + * @var string Elasticsearch index name + */ + protected $index; + + /** + * @var string Elasticsearch record type + */ + protected $type; + + /** + * @param string $index Elasticsearch index name + * @param string $type Elasticsearch record type + */ + public function __construct(string $index, string $type) + { + // Elasticsearch requires an ISO 8601 format date with optional millisecond precision. + parent::__construct(DateTimeInterface::ISO8601); + + $this->index = $index; + $this->type = $type; + } + + /** + * {@inheritDoc} + */ + public function format(array $record) + { + $record = parent::format($record); + + return $this->getDocument($record); + } + + /** + * Getter index + * + * @return string + */ + public function getIndex(): string + { + return $this->index; + } + + /** + * Getter type + * + * @return string + */ + public function getType(): string + { + return $this->type; + } + + /** + * Convert a log message into an Elasticsearch record + * + * @param mixed[] $record Log message + * @return mixed[] + */ + protected function getDocument(array $record): array + { + $record['_index'] = $this->index; + $record['_type'] = $this->type; + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/FlowdockFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/FlowdockFormatter.php new file mode 100644 index 0000000..867ae58 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/FlowdockFormatter.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\Formatter; + +/** + * formats the record to be used in the FlowdockHandler + * + * @author Dominik Liebler + * @deprecated Since 2.9.0 and 3.3.0, Flowdock was shutdown we will thus drop this handler in Monolog 4 + */ +class FlowdockFormatter implements FormatterInterface +{ + /** + * @var string + */ + private $source; + + /** + * @var string + */ + private $sourceEmail; + + public function __construct(string $source, string $sourceEmail) + { + $this->source = $source; + $this->sourceEmail = $sourceEmail; + } + + /** + * {@inheritDoc} + * + * @return mixed[] + */ + public function format(array $record): array + { + $tags = [ + '#logs', + '#' . strtolower($record['level_name']), + '#' . $record['channel'], + ]; + + foreach ($record['extra'] as $value) { + $tags[] = '#' . $value; + } + + $subject = sprintf( + 'in %s: %s - %s', + $this->source, + $record['level_name'], + $this->getShortMessage($record['message']) + ); + + $record['flowdock'] = [ + 'source' => $this->source, + 'from_address' => $this->sourceEmail, + 'subject' => $subject, + 'content' => $record['message'], + 'tags' => $tags, + 'project' => $this->source, + ]; + + return $record; + } + + /** + * {@inheritDoc} + * + * @return mixed[][] + */ + public function formatBatch(array $records): array + { + $formatted = []; + + foreach ($records as $record) { + $formatted[] = $this->format($record); + } + + return $formatted; + } + + public function getShortMessage(string $message): string + { + static $hasMbString; + + if (null === $hasMbString) { + $hasMbString = function_exists('mb_strlen'); + } + + $maxLength = 45; + + if ($hasMbString) { + if (mb_strlen($message, 'UTF-8') > $maxLength) { + $message = mb_substr($message, 0, $maxLength - 4, 'UTF-8') . ' ...'; + } + } else { + if (strlen($message) > $maxLength) { + $message = substr($message, 0, $maxLength - 4) . ' ...'; + } + } + + return $message; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/FluentdFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/FluentdFormatter.php new file mode 100644 index 0000000..29b14d3 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/FluentdFormatter.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\Formatter; + +use Monolog\Utils; + +/** + * Class FluentdFormatter + * + * Serializes a log message to Fluentd unix socket protocol + * + * Fluentd config: + * + * + * type unix + * path /var/run/td-agent/td-agent.sock + * + * + * Monolog setup: + * + * $logger = new Monolog\Logger('fluent.tag'); + * $fluentHandler = new Monolog\Handler\SocketHandler('unix:///var/run/td-agent/td-agent.sock'); + * $fluentHandler->setFormatter(new Monolog\Formatter\FluentdFormatter()); + * $logger->pushHandler($fluentHandler); + * + * @author Andrius Putna + */ +class FluentdFormatter implements FormatterInterface +{ + /** + * @var bool $levelTag should message level be a part of the fluentd tag + */ + protected $levelTag = false; + + public function __construct(bool $levelTag = false) + { + if (!function_exists('json_encode')) { + throw new \RuntimeException('PHP\'s json extension is required to use Monolog\'s FluentdUnixFormatter'); + } + + $this->levelTag = $levelTag; + } + + public function isUsingLevelsInTag(): bool + { + return $this->levelTag; + } + + public function format(array $record): string + { + $tag = $record['channel']; + if ($this->levelTag) { + $tag .= '.' . strtolower($record['level_name']); + } + + $message = [ + 'message' => $record['message'], + 'context' => $record['context'], + 'extra' => $record['extra'], + ]; + + if (!$this->levelTag) { + $message['level'] = $record['level']; + $message['level_name'] = $record['level_name']; + } + + return Utils::jsonEncode([$tag, $record['datetime']->getTimestamp(), $message]); + } + + public function formatBatch(array $records): string + { + $message = ''; + foreach ($records as $record) { + $message .= $this->format($record); + } + + return $message; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php b/vendor/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php new file mode 100644 index 0000000..19617ec --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/FormatterInterface.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\Formatter; + +/** + * Interface for formatters + * + * @author Jordi Boggiano + * + * @phpstan-import-type Record from \Monolog\Logger + */ +interface FormatterInterface +{ + /** + * Formats a log record. + * + * @param array $record A record to format + * @return mixed The formatted record + * + * @phpstan-param Record $record + */ + public function format(array $record); + + /** + * Formats a set of log records. + * + * @param array $records A set of records to format + * @return mixed The formatted set of records + * + * @phpstan-param Record[] $records + */ + public function formatBatch(array $records); +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php new file mode 100644 index 0000000..3b3e1e7 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php @@ -0,0 +1,175 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; +use Gelf\Message; +use Monolog\Utils; + +/** + * Serializes a log message to GELF + * @see http://docs.graylog.org/en/latest/pages/gelf.html + * + * @author Matt Lehner + * + * @phpstan-import-type Level from \Monolog\Logger + */ +class GelfMessageFormatter extends NormalizerFormatter +{ + protected const DEFAULT_MAX_LENGTH = 32766; + + /** + * @var string the name of the system for the Gelf log message + */ + protected $systemName; + + /** + * @var string a prefix for 'extra' fields from the Monolog record (optional) + */ + protected $extraPrefix; + + /** + * @var string a prefix for 'context' fields from the Monolog record (optional) + */ + protected $contextPrefix; + + /** + * @var int max length per field + */ + protected $maxLength; + + /** + * @var int + */ + private $gelfVersion = 2; + + /** + * Translates Monolog log levels to Graylog2 log priorities. + * + * @var array + * + * @phpstan-var array + */ + private $logLevels = [ + Logger::DEBUG => 7, + Logger::INFO => 6, + Logger::NOTICE => 5, + Logger::WARNING => 4, + Logger::ERROR => 3, + Logger::CRITICAL => 2, + Logger::ALERT => 1, + Logger::EMERGENCY => 0, + ]; + + public function __construct(?string $systemName = null, ?string $extraPrefix = null, string $contextPrefix = 'ctxt_', ?int $maxLength = null) + { + if (!class_exists(Message::class)) { + throw new \RuntimeException('Composer package graylog2/gelf-php is required to use Monolog\'s GelfMessageFormatter'); + } + + parent::__construct('U.u'); + + $this->systemName = (is_null($systemName) || $systemName === '') ? (string) gethostname() : $systemName; + + $this->extraPrefix = is_null($extraPrefix) ? '' : $extraPrefix; + $this->contextPrefix = $contextPrefix; + $this->maxLength = is_null($maxLength) ? self::DEFAULT_MAX_LENGTH : $maxLength; + + if (method_exists(Message::class, 'setFacility')) { + $this->gelfVersion = 1; + } + } + + /** + * {@inheritDoc} + */ + public function format(array $record): Message + { + $context = $extra = []; + if (isset($record['context'])) { + /** @var mixed[] $context */ + $context = parent::normalize($record['context']); + } + if (isset($record['extra'])) { + /** @var mixed[] $extra */ + $extra = parent::normalize($record['extra']); + } + + if (!isset($record['datetime'], $record['message'], $record['level'])) { + throw new \InvalidArgumentException('The record should at least contain datetime, message and level keys, '.var_export($record, true).' given'); + } + + $message = new Message(); + $message + ->setTimestamp($record['datetime']) + ->setShortMessage((string) $record['message']) + ->setHost($this->systemName) + ->setLevel($this->logLevels[$record['level']]); + + // message length + system name length + 200 for padding / metadata + $len = 200 + strlen((string) $record['message']) + strlen($this->systemName); + + if ($len > $this->maxLength) { + $message->setShortMessage(Utils::substr($record['message'], 0, $this->maxLength)); + } + + if ($this->gelfVersion === 1) { + if (isset($record['channel'])) { + $message->setFacility($record['channel']); + } + if (isset($extra['line'])) { + $message->setLine($extra['line']); + unset($extra['line']); + } + if (isset($extra['file'])) { + $message->setFile($extra['file']); + unset($extra['file']); + } + } else { + $message->setAdditional('facility', $record['channel']); + } + + foreach ($extra as $key => $val) { + $val = is_scalar($val) || null === $val ? $val : $this->toJson($val); + $len = strlen($this->extraPrefix . $key . $val); + if ($len > $this->maxLength) { + $message->setAdditional($this->extraPrefix . $key, Utils::substr((string) $val, 0, $this->maxLength)); + + continue; + } + $message->setAdditional($this->extraPrefix . $key, $val); + } + + foreach ($context as $key => $val) { + $val = is_scalar($val) || null === $val ? $val : $this->toJson($val); + $len = strlen($this->contextPrefix . $key . $val); + if ($len > $this->maxLength) { + $message->setAdditional($this->contextPrefix . $key, Utils::substr((string) $val, 0, $this->maxLength)); + + continue; + } + $message->setAdditional($this->contextPrefix . $key, $val); + } + + if ($this->gelfVersion === 1) { + /** @phpstan-ignore-next-line */ + if (null === $message->getFile() && isset($context['exception']['file'])) { + if (preg_match("/^(.+):([0-9]+)$/", $context['exception']['file'], $matches)) { + $message->setFile($matches[1]); + $message->setLine($matches[2]); + } + } + } + + return $message; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/GoogleCloudLoggingFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/GoogleCloudLoggingFormatter.php new file mode 100644 index 0000000..ca52ebf --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/GoogleCloudLoggingFormatter.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 DateTimeInterface; +use Monolog\LogRecord; + +/** + * Encodes message information into JSON in a format compatible with Cloud logging. + * + * @see https://cloud.google.com/logging/docs/structured-logging + * @see https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry + * + * @author Luís Cobucci + */ +final class GoogleCloudLoggingFormatter extends JsonFormatter +{ + /** {@inheritdoc} **/ + public function format(array $record): string + { + // Re-key level for GCP logging + $record['severity'] = $record['level_name']; + $record['time'] = $record['datetime']->format(DateTimeInterface::RFC3339_EXTENDED); + + // Remove keys that are not used by GCP + unset($record['level'], $record['level_name'], $record['datetime']); + + return parent::format($record); + } +} + diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/HtmlFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/HtmlFormatter.php new file mode 100644 index 0000000..10a4311 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/HtmlFormatter.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; +use Monolog\Utils; + +/** + * Formats incoming records into an HTML table + * + * This is especially useful for html email logging + * + * @author Tiago Brito + */ +class HtmlFormatter extends NormalizerFormatter +{ + /** + * Translates Monolog log levels to html color priorities. + * + * @var array + */ + protected $logLevels = [ + Logger::DEBUG => '#CCCCCC', + Logger::INFO => '#28A745', + Logger::NOTICE => '#17A2B8', + Logger::WARNING => '#FFC107', + Logger::ERROR => '#FD7E14', + Logger::CRITICAL => '#DC3545', + Logger::ALERT => '#821722', + Logger::EMERGENCY => '#000000', + ]; + + /** + * @param string|null $dateFormat The format of the timestamp: one supported by DateTime::format + */ + public function __construct(?string $dateFormat = null) + { + parent::__construct($dateFormat); + } + + /** + * Creates an HTML table row + * + * @param string $th Row header content + * @param string $td Row standard cell content + * @param bool $escapeTd false if td content must not be html escaped + */ + protected function addRow(string $th, string $td = ' ', bool $escapeTd = true): string + { + $th = htmlspecialchars($th, ENT_NOQUOTES, 'UTF-8'); + if ($escapeTd) { + $td = '
'.htmlspecialchars($td, ENT_NOQUOTES, 'UTF-8').'
'; + } + + return "\n$th:\n".$td."\n"; + } + + /** + * Create a HTML h1 tag + * + * @param string $title Text to be in the h1 + * @param int $level Error level + * @return string + */ + protected function addTitle(string $title, int $level): string + { + $title = htmlspecialchars($title, ENT_NOQUOTES, 'UTF-8'); + + return '

'.$title.'

'; + } + + /** + * Formats a log record. + * + * @return string The formatted record + */ + public function format(array $record): string + { + $output = $this->addTitle($record['level_name'], $record['level']); + $output .= ''; + + $output .= $this->addRow('Message', (string) $record['message']); + $output .= $this->addRow('Time', $this->formatDate($record['datetime'])); + $output .= $this->addRow('Channel', $record['channel']); + if ($record['context']) { + $embeddedTable = '
'; + foreach ($record['context'] as $key => $value) { + $embeddedTable .= $this->addRow((string) $key, $this->convertToString($value)); + } + $embeddedTable .= '
'; + $output .= $this->addRow('Context', $embeddedTable, false); + } + if ($record['extra']) { + $embeddedTable = ''; + foreach ($record['extra'] as $key => $value) { + $embeddedTable .= $this->addRow((string) $key, $this->convertToString($value)); + } + $embeddedTable .= '
'; + $output .= $this->addRow('Extra', $embeddedTable, false); + } + + return $output.''; + } + + /** + * Formats a set of log records. + * + * @return string The formatted set of records + */ + public function formatBatch(array $records): string + { + $message = ''; + foreach ($records as $record) { + $message .= $this->format($record); + } + + return $message; + } + + /** + * @param mixed $data + */ + protected function convertToString($data): string + { + if (null === $data || is_scalar($data)) { + return (string) $data; + } + + $data = $this->normalize($data); + + return Utils::jsonEncode($data, JSON_PRETTY_PRINT | Utils::DEFAULT_JSON_FLAGS, true); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php new file mode 100644 index 0000000..b737d82 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php @@ -0,0 +1,224 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Throwable; + +/** + * Encodes whatever record data is passed to it as json + * + * This can be useful to log to databases or remote APIs + * + * @author Jordi Boggiano + * + * @phpstan-import-type Record from \Monolog\Logger + */ +class JsonFormatter extends NormalizerFormatter +{ + public const BATCH_MODE_JSON = 1; + public const BATCH_MODE_NEWLINES = 2; + + /** @var self::BATCH_MODE_* */ + protected $batchMode; + /** @var bool */ + protected $appendNewline; + /** @var bool */ + protected $ignoreEmptyContextAndExtra; + /** @var bool */ + protected $includeStacktraces = false; + + /** + * @param self::BATCH_MODE_* $batchMode + */ + public function __construct(int $batchMode = self::BATCH_MODE_JSON, bool $appendNewline = true, bool $ignoreEmptyContextAndExtra = false, bool $includeStacktraces = false) + { + $this->batchMode = $batchMode; + $this->appendNewline = $appendNewline; + $this->ignoreEmptyContextAndExtra = $ignoreEmptyContextAndExtra; + $this->includeStacktraces = $includeStacktraces; + + parent::__construct(); + } + + /** + * The batch mode option configures the formatting style for + * multiple records. By default, multiple records will be + * formatted as a JSON-encoded array. However, for + * compatibility with some API endpoints, alternative styles + * are available. + */ + public function getBatchMode(): int + { + return $this->batchMode; + } + + /** + * True if newlines are appended to every formatted record + */ + public function isAppendingNewlines(): bool + { + return $this->appendNewline; + } + + /** + * {@inheritDoc} + */ + public function format(array $record): string + { + $normalized = $this->normalize($record); + + if (isset($normalized['context']) && $normalized['context'] === []) { + if ($this->ignoreEmptyContextAndExtra) { + unset($normalized['context']); + } else { + $normalized['context'] = new \stdClass; + } + } + if (isset($normalized['extra']) && $normalized['extra'] === []) { + if ($this->ignoreEmptyContextAndExtra) { + unset($normalized['extra']); + } else { + $normalized['extra'] = new \stdClass; + } + } + + return $this->toJson($normalized, true) . ($this->appendNewline ? "\n" : ''); + } + + /** + * {@inheritDoc} + */ + public function formatBatch(array $records): string + { + switch ($this->batchMode) { + case static::BATCH_MODE_NEWLINES: + return $this->formatBatchNewlines($records); + + case static::BATCH_MODE_JSON: + default: + return $this->formatBatchJson($records); + } + } + + /** + * @return self + */ + public function includeStacktraces(bool $include = true): self + { + $this->includeStacktraces = $include; + + return $this; + } + + /** + * Return a JSON-encoded array of records. + * + * @phpstan-param Record[] $records + */ + protected function formatBatchJson(array $records): string + { + return $this->toJson($this->normalize($records), true); + } + + /** + * Use new lines to separate records instead of a + * JSON-encoded array. + * + * @phpstan-param Record[] $records + */ + protected function formatBatchNewlines(array $records): string + { + $instance = $this; + + $oldNewline = $this->appendNewline; + $this->appendNewline = false; + array_walk($records, function (&$value, $key) use ($instance) { + $value = $instance->format($value); + }); + $this->appendNewline = $oldNewline; + + return implode("\n", $records); + } + + /** + * Normalizes given $data. + * + * @param mixed $data + * + * @return mixed + */ + protected function normalize($data, int $depth = 0) + { + if ($depth > $this->maxNormalizeDepth) { + return 'Over '.$this->maxNormalizeDepth.' levels deep, aborting normalization'; + } + + if (is_array($data)) { + $normalized = []; + + $count = 1; + foreach ($data as $key => $value) { + if ($count++ > $this->maxNormalizeItemCount) { + $normalized['...'] = 'Over '.$this->maxNormalizeItemCount.' items ('.count($data).' total), aborting normalization'; + break; + } + + $normalized[$key] = $this->normalize($value, $depth + 1); + } + + return $normalized; + } + + if (is_object($data)) { + if ($data instanceof \DateTimeInterface) { + return $this->formatDate($data); + } + + if ($data instanceof Throwable) { + return $this->normalizeException($data, $depth); + } + + // if the object has specific json serializability we want to make sure we skip the __toString treatment below + if ($data instanceof \JsonSerializable) { + return $data; + } + + if (method_exists($data, '__toString')) { + return $data->__toString(); + } + + return $data; + } + + if (is_resource($data)) { + return parent::normalize($data); + } + + return $data; + } + + /** + * Normalizes given exception with or without its own stack trace based on + * `includeStacktraces` property. + * + * {@inheritDoc} + */ + protected function normalizeException(Throwable $e, int $depth = 0): array + { + $data = parent::normalizeException($e, $depth); + if (!$this->includeStacktraces) { + unset($data['trace']); + } + + return $data; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php new file mode 100644 index 0000000..b31b297 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php @@ -0,0 +1,246 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Utils; + +/** + * Formats incoming records into a one-line string + * + * This is especially useful for logging to files + * + * @author Jordi Boggiano + * @author Christophe Coevoet + */ +class LineFormatter extends NormalizerFormatter +{ + public const SIMPLE_FORMAT = "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n"; + + /** @var string */ + protected $format; + /** @var bool */ + protected $allowInlineLineBreaks; + /** @var bool */ + protected $ignoreEmptyContextAndExtra; + /** @var bool */ + protected $includeStacktraces; + /** @var ?callable */ + protected $stacktracesParser; + + /** + * @param string|null $format The format of the message + * @param string|null $dateFormat The format of the timestamp: one supported by DateTime::format + * @param bool $allowInlineLineBreaks Whether to allow inline line breaks in log entries + * @param bool $ignoreEmptyContextAndExtra + */ + public function __construct(?string $format = null, ?string $dateFormat = null, bool $allowInlineLineBreaks = false, bool $ignoreEmptyContextAndExtra = false, bool $includeStacktraces = false) + { + $this->format = $format === null ? static::SIMPLE_FORMAT : $format; + $this->allowInlineLineBreaks = $allowInlineLineBreaks; + $this->ignoreEmptyContextAndExtra = $ignoreEmptyContextAndExtra; + $this->includeStacktraces($includeStacktraces); + parent::__construct($dateFormat); + } + + public function includeStacktraces(bool $include = true, ?callable $parser = null): self + { + $this->includeStacktraces = $include; + if ($this->includeStacktraces) { + $this->allowInlineLineBreaks = true; + $this->stacktracesParser = $parser; + } + + return $this; + } + + public function allowInlineLineBreaks(bool $allow = true): self + { + $this->allowInlineLineBreaks = $allow; + + return $this; + } + + public function ignoreEmptyContextAndExtra(bool $ignore = true): self + { + $this->ignoreEmptyContextAndExtra = $ignore; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function format(array $record): string + { + $vars = parent::format($record); + + $output = $this->format; + + foreach ($vars['extra'] as $var => $val) { + if (false !== strpos($output, '%extra.'.$var.'%')) { + $output = str_replace('%extra.'.$var.'%', $this->stringify($val), $output); + unset($vars['extra'][$var]); + } + } + + foreach ($vars['context'] as $var => $val) { + if (false !== strpos($output, '%context.'.$var.'%')) { + $output = str_replace('%context.'.$var.'%', $this->stringify($val), $output); + unset($vars['context'][$var]); + } + } + + if ($this->ignoreEmptyContextAndExtra) { + if (empty($vars['context'])) { + unset($vars['context']); + $output = str_replace('%context%', '', $output); + } + + if (empty($vars['extra'])) { + unset($vars['extra']); + $output = str_replace('%extra%', '', $output); + } + } + + foreach ($vars as $var => $val) { + if (false !== strpos($output, '%'.$var.'%')) { + $output = str_replace('%'.$var.'%', $this->stringify($val), $output); + } + } + + // remove leftover %extra.xxx% and %context.xxx% if any + if (false !== strpos($output, '%')) { + $output = preg_replace('/%(?:extra|context)\..+?%/', '', $output); + if (null === $output) { + $pcreErrorCode = preg_last_error(); + throw new \RuntimeException('Failed to run preg_replace: ' . $pcreErrorCode . ' / ' . Utils::pcreLastErrorMessage($pcreErrorCode)); + } + } + + return $output; + } + + public function formatBatch(array $records): string + { + $message = ''; + foreach ($records as $record) { + $message .= $this->format($record); + } + + return $message; + } + + /** + * @param mixed $value + */ + public function stringify($value): string + { + return $this->replaceNewlines($this->convertToString($value)); + } + + protected function normalizeException(\Throwable $e, int $depth = 0): string + { + $str = $this->formatException($e); + + if ($previous = $e->getPrevious()) { + do { + $depth++; + if ($depth > $this->maxNormalizeDepth) { + $str .= '\n[previous exception] Over ' . $this->maxNormalizeDepth . ' levels deep, aborting normalization'; + break; + } + + $str .= "\n[previous exception] " . $this->formatException($previous); + } while ($previous = $previous->getPrevious()); + } + + return $str; + } + + /** + * @param mixed $data + */ + protected function convertToString($data): string + { + if (null === $data || is_bool($data)) { + return var_export($data, true); + } + + if (is_scalar($data)) { + return (string) $data; + } + + return $this->toJson($data, true); + } + + protected function replaceNewlines(string $str): string + { + if ($this->allowInlineLineBreaks) { + if (0 === strpos($str, '{')) { + $str = preg_replace('/(?getCode(); + if ($e instanceof \SoapFault) { + if (isset($e->faultcode)) { + $str .= ' faultcode: ' . $e->faultcode; + } + + if (isset($e->faultactor)) { + $str .= ' faultactor: ' . $e->faultactor; + } + + if (isset($e->detail)) { + if (is_string($e->detail)) { + $str .= ' detail: ' . $e->detail; + } elseif (is_object($e->detail) || is_array($e->detail)) { + $str .= ' detail: ' . $this->toJson($e->detail, true); + } + } + } + $str .= '): ' . $e->getMessage() . ' at ' . $e->getFile() . ':' . $e->getLine() . ')'; + + if ($this->includeStacktraces) { + $str .= $this->stacktracesParser($e); + } + + return $str; + } + + private function stacktracesParser(\Throwable $e): string + { + $trace = $e->getTraceAsString(); + + if ($this->stacktracesParser) { + $trace = $this->stacktracesParserCustom($trace); + } + + return "\n[stacktrace]\n" . $trace . "\n"; + } + + private function stacktracesParserCustom(string $trace): string + { + return implode("\n", array_filter(array_map($this->stacktracesParser, explode("\n", $trace)))); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/LogglyFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/LogglyFormatter.php new file mode 100644 index 0000000..29841aa --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/LogglyFormatter.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +/** + * Encodes message information into JSON in a format compatible with Loggly. + * + * @author Adam Pancutt + */ +class LogglyFormatter extends JsonFormatter +{ + /** + * Overrides the default batch mode to new lines for compatibility with the + * Loggly bulk API. + */ + public function __construct(int $batchMode = self::BATCH_MODE_NEWLINES, bool $appendNewline = false) + { + parent::__construct($batchMode, $appendNewline); + } + + /** + * Appends the 'timestamp' parameter for indexing by Loggly. + * + * @see https://www.loggly.com/docs/automated-parsing/#json + * @see \Monolog\Formatter\JsonFormatter::format() + */ + public function format(array $record): string + { + if (isset($record["datetime"]) && ($record["datetime"] instanceof \DateTimeInterface)) { + $record["timestamp"] = $record["datetime"]->format("Y-m-d\TH:i:s.uO"); + unset($record["datetime"]); + } + + return parent::format($record); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/LogmaticFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/LogmaticFormatter.php new file mode 100644 index 0000000..b0451ab --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/LogmaticFormatter.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\Formatter; + +/** + * Encodes message information into JSON in a format compatible with Logmatic. + * + * @author Julien Breux + */ +class LogmaticFormatter extends JsonFormatter +{ + protected const MARKERS = ["sourcecode", "php"]; + + /** + * @var string + */ + protected $hostname = ''; + + /** + * @var string + */ + protected $appname = ''; + + public function setHostname(string $hostname): self + { + $this->hostname = $hostname; + + return $this; + } + + public function setAppname(string $appname): self + { + $this->appname = $appname; + + return $this; + } + + /** + * Appends the 'hostname' and 'appname' parameter for indexing by Logmatic. + * + * @see http://doc.logmatic.io/docs/basics-to-send-data + * @see \Monolog\Formatter\JsonFormatter::format() + */ + public function format(array $record): string + { + if (!empty($this->hostname)) { + $record["hostname"] = $this->hostname; + } + if (!empty($this->appname)) { + $record["appname"] = $this->appname; + } + + $record["@marker"] = static::MARKERS; + + return parent::format($record); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php new file mode 100644 index 0000000..f8de0d3 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +/** + * Serializes a log message to Logstash Event Format + * + * @see https://www.elastic.co/products/logstash + * @see https://github.com/elastic/logstash/blob/master/logstash-core/src/main/java/org/logstash/Event.java + * + * @author Tim Mower + */ +class LogstashFormatter extends NormalizerFormatter +{ + /** + * @var string the name of the system for the Logstash log message, used to fill the @source field + */ + protected $systemName; + + /** + * @var string an application name for the Logstash log message, used to fill the @type field + */ + protected $applicationName; + + /** + * @var string the key for 'extra' fields from the Monolog record + */ + protected $extraKey; + + /** + * @var string the key for 'context' fields from the Monolog record + */ + protected $contextKey; + + /** + * @param string $applicationName The application that sends the data, used as the "type" field of logstash + * @param string|null $systemName The system/machine name, used as the "source" field of logstash, defaults to the hostname of the machine + * @param string $extraKey The key for extra keys inside logstash "fields", defaults to extra + * @param string $contextKey The key for context keys inside logstash "fields", defaults to context + */ + public function __construct(string $applicationName, ?string $systemName = null, string $extraKey = 'extra', string $contextKey = 'context') + { + // logstash requires a ISO 8601 format date with optional millisecond precision. + parent::__construct('Y-m-d\TH:i:s.uP'); + + $this->systemName = $systemName === null ? (string) gethostname() : $systemName; + $this->applicationName = $applicationName; + $this->extraKey = $extraKey; + $this->contextKey = $contextKey; + } + + /** + * {@inheritDoc} + */ + public function format(array $record): string + { + $record = parent::format($record); + + if (empty($record['datetime'])) { + $record['datetime'] = gmdate('c'); + } + $message = [ + '@timestamp' => $record['datetime'], + '@version' => 1, + 'host' => $this->systemName, + ]; + if (isset($record['message'])) { + $message['message'] = $record['message']; + } + if (isset($record['channel'])) { + $message['type'] = $record['channel']; + $message['channel'] = $record['channel']; + } + if (isset($record['level_name'])) { + $message['level'] = $record['level_name']; + } + if (isset($record['level'])) { + $message['monolog_level'] = $record['level']; + } + if ($this->applicationName) { + $message['type'] = $this->applicationName; + } + if (!empty($record['extra'])) { + $message[$this->extraKey] = $record['extra']; + } + if (!empty($record['context'])) { + $message[$this->contextKey] = $record['context']; + } + + return $this->toJson($message) . "\n"; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php new file mode 100644 index 0000000..fca69a8 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php @@ -0,0 +1,162 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use MongoDB\BSON\Type; +use MongoDB\BSON\UTCDateTime; +use Monolog\Utils; + +/** + * Formats a record for use with the MongoDBHandler. + * + * @author Florian Plattner + */ +class MongoDBFormatter implements FormatterInterface +{ + /** @var bool */ + private $exceptionTraceAsString; + /** @var int */ + private $maxNestingLevel; + /** @var bool */ + private $isLegacyMongoExt; + + /** + * @param int $maxNestingLevel 0 means infinite nesting, the $record itself is level 1, $record['context'] is 2 + * @param bool $exceptionTraceAsString set to false to log exception traces as a sub documents instead of strings + */ + public function __construct(int $maxNestingLevel = 3, bool $exceptionTraceAsString = true) + { + $this->maxNestingLevel = max($maxNestingLevel, 0); + $this->exceptionTraceAsString = $exceptionTraceAsString; + + $this->isLegacyMongoExt = extension_loaded('mongodb') && version_compare((string) phpversion('mongodb'), '1.1.9', '<='); + } + + /** + * {@inheritDoc} + * + * @return mixed[] + */ + public function format(array $record): array + { + /** @var mixed[] $res */ + $res = $this->formatArray($record); + + return $res; + } + + /** + * {@inheritDoc} + * + * @return array + */ + public function formatBatch(array $records): array + { + $formatted = []; + foreach ($records as $key => $record) { + $formatted[$key] = $this->format($record); + } + + return $formatted; + } + + /** + * @param mixed[] $array + * @return mixed[]|string Array except when max nesting level is reached then a string "[...]" + */ + protected function formatArray(array $array, int $nestingLevel = 0) + { + if ($this->maxNestingLevel > 0 && $nestingLevel > $this->maxNestingLevel) { + return '[...]'; + } + + foreach ($array as $name => $value) { + if ($value instanceof \DateTimeInterface) { + $array[$name] = $this->formatDate($value, $nestingLevel + 1); + } elseif ($value instanceof \Throwable) { + $array[$name] = $this->formatException($value, $nestingLevel + 1); + } elseif (is_array($value)) { + $array[$name] = $this->formatArray($value, $nestingLevel + 1); + } elseif (is_object($value) && !$value instanceof Type) { + $array[$name] = $this->formatObject($value, $nestingLevel + 1); + } + } + + return $array; + } + + /** + * @param mixed $value + * @return mixed[]|string + */ + protected function formatObject($value, int $nestingLevel) + { + $objectVars = get_object_vars($value); + $objectVars['class'] = Utils::getClass($value); + + return $this->formatArray($objectVars, $nestingLevel); + } + + /** + * @return mixed[]|string + */ + protected function formatException(\Throwable $exception, int $nestingLevel) + { + $formattedException = [ + 'class' => Utils::getClass($exception), + 'message' => $exception->getMessage(), + 'code' => (int) $exception->getCode(), + 'file' => $exception->getFile() . ':' . $exception->getLine(), + ]; + + if ($this->exceptionTraceAsString === true) { + $formattedException['trace'] = $exception->getTraceAsString(); + } else { + $formattedException['trace'] = $exception->getTrace(); + } + + return $this->formatArray($formattedException, $nestingLevel); + } + + protected function formatDate(\DateTimeInterface $value, int $nestingLevel): UTCDateTime + { + if ($this->isLegacyMongoExt) { + return $this->legacyGetMongoDbDateTime($value); + } + + return $this->getMongoDbDateTime($value); + } + + private function getMongoDbDateTime(\DateTimeInterface $value): UTCDateTime + { + return new UTCDateTime((int) floor(((float) $value->format('U.u')) * 1000)); + } + + /** + * This is needed to support MongoDB Driver v1.19 and below + * + * See https://github.com/mongodb/mongo-php-driver/issues/426 + * + * It can probably be removed in 2.1 or later once MongoDB's 1.2 is released and widely adopted + */ + private function legacyGetMongoDbDateTime(\DateTimeInterface $value): UTCDateTime + { + $milliseconds = floor(((float) $value->format('U.u')) * 1000); + + $milliseconds = (PHP_INT_SIZE == 8) //64-bit OS? + ? (int) $milliseconds + : (string) $milliseconds; + + // @phpstan-ignore-next-line + return new UTCDateTime($milliseconds); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php new file mode 100644 index 0000000..5441bc0 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.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\Formatter; + +use Monolog\DateTimeImmutable; +use Monolog\Utils; +use Throwable; + +/** + * Normalizes incoming records to remove objects/resources so it's easier to dump to various targets + * + * @author Jordi Boggiano + */ +class NormalizerFormatter implements FormatterInterface +{ + public const SIMPLE_DATE = "Y-m-d\TH:i:sP"; + + /** @var string */ + protected $dateFormat; + /** @var int */ + protected $maxNormalizeDepth = 9; + /** @var int */ + protected $maxNormalizeItemCount = 1000; + + /** @var int */ + private $jsonEncodeOptions = Utils::DEFAULT_JSON_FLAGS; + + /** + * @param string|null $dateFormat The format of the timestamp: one supported by DateTime::format + */ + public function __construct(?string $dateFormat = null) + { + $this->dateFormat = null === $dateFormat ? static::SIMPLE_DATE : $dateFormat; + if (!function_exists('json_encode')) { + throw new \RuntimeException('PHP\'s json extension is required to use Monolog\'s NormalizerFormatter'); + } + } + + /** + * {@inheritDoc} + * + * @param mixed[] $record + */ + public function format(array $record) + { + return $this->normalize($record); + } + + /** + * {@inheritDoc} + */ + public function formatBatch(array $records) + { + foreach ($records as $key => $record) { + $records[$key] = $this->format($record); + } + + return $records; + } + + public function getDateFormat(): string + { + return $this->dateFormat; + } + + public function setDateFormat(string $dateFormat): self + { + $this->dateFormat = $dateFormat; + + return $this; + } + + /** + * The maximum number of normalization levels to go through + */ + public function getMaxNormalizeDepth(): int + { + return $this->maxNormalizeDepth; + } + + public function setMaxNormalizeDepth(int $maxNormalizeDepth): self + { + $this->maxNormalizeDepth = $maxNormalizeDepth; + + return $this; + } + + /** + * The maximum number of items to normalize per level + */ + public function getMaxNormalizeItemCount(): int + { + return $this->maxNormalizeItemCount; + } + + public function setMaxNormalizeItemCount(int $maxNormalizeItemCount): self + { + $this->maxNormalizeItemCount = $maxNormalizeItemCount; + + return $this; + } + + /** + * Enables `json_encode` pretty print. + */ + public function setJsonPrettyPrint(bool $enable): self + { + if ($enable) { + $this->jsonEncodeOptions |= JSON_PRETTY_PRINT; + } else { + $this->jsonEncodeOptions &= ~JSON_PRETTY_PRINT; + } + + return $this; + } + + /** + * @param mixed $data + * @return null|scalar|array + */ + protected function normalize($data, int $depth = 0) + { + if ($depth > $this->maxNormalizeDepth) { + return 'Over ' . $this->maxNormalizeDepth . ' levels deep, aborting normalization'; + } + + if (null === $data || is_scalar($data)) { + if (is_float($data)) { + if (is_infinite($data)) { + return ($data > 0 ? '' : '-') . 'INF'; + } + if (is_nan($data)) { + return 'NaN'; + } + } + + return $data; + } + + if (is_array($data)) { + $normalized = []; + + $count = 1; + foreach ($data as $key => $value) { + if ($count++ > $this->maxNormalizeItemCount) { + $normalized['...'] = 'Over ' . $this->maxNormalizeItemCount . ' items ('.count($data).' total), aborting normalization'; + break; + } + + $normalized[$key] = $this->normalize($value, $depth + 1); + } + + return $normalized; + } + + if ($data instanceof \DateTimeInterface) { + return $this->formatDate($data); + } + + if (is_object($data)) { + if ($data instanceof Throwable) { + return $this->normalizeException($data, $depth); + } + + if ($data instanceof \JsonSerializable) { + /** @var null|scalar|array $value */ + $value = $data->jsonSerialize(); + } elseif (method_exists($data, '__toString')) { + /** @var string $value */ + $value = $data->__toString(); + } else { + // the rest is normalized by json encoding and decoding it + /** @var null|scalar|array $value */ + $value = json_decode($this->toJson($data, true), true); + } + + return [Utils::getClass($data) => $value]; + } + + if (is_resource($data)) { + return sprintf('[resource(%s)]', get_resource_type($data)); + } + + return '[unknown('.gettype($data).')]'; + } + + /** + * @return mixed[] + */ + protected function normalizeException(Throwable $e, int $depth = 0) + { + if ($depth > $this->maxNormalizeDepth) { + return ['Over ' . $this->maxNormalizeDepth . ' levels deep, aborting normalization']; + } + + if ($e instanceof \JsonSerializable) { + return (array) $e->jsonSerialize(); + } + + $data = [ + 'class' => Utils::getClass($e), + 'message' => $e->getMessage(), + 'code' => (int) $e->getCode(), + 'file' => $e->getFile().':'.$e->getLine(), + ]; + + if ($e instanceof \SoapFault) { + if (isset($e->faultcode)) { + $data['faultcode'] = $e->faultcode; + } + + if (isset($e->faultactor)) { + $data['faultactor'] = $e->faultactor; + } + + if (isset($e->detail)) { + if (is_string($e->detail)) { + $data['detail'] = $e->detail; + } elseif (is_object($e->detail) || is_array($e->detail)) { + $data['detail'] = $this->toJson($e->detail, true); + } + } + } + + $trace = $e->getTrace(); + foreach ($trace as $frame) { + if (isset($frame['file'])) { + $data['trace'][] = $frame['file'].':'.$frame['line']; + } + } + + if ($previous = $e->getPrevious()) { + $data['previous'] = $this->normalizeException($previous, $depth + 1); + } + + return $data; + } + + /** + * Return the JSON representation of a value + * + * @param mixed $data + * @throws \RuntimeException if encoding fails and errors are not ignored + * @return string if encoding fails and ignoreErrors is true 'null' is returned + */ + protected function toJson($data, bool $ignoreErrors = false): string + { + return Utils::jsonEncode($data, $this->jsonEncodeOptions, $ignoreErrors); + } + + /** + * @return string + */ + protected function formatDate(\DateTimeInterface $date) + { + // in case the date format isn't custom then we defer to the custom DateTimeImmutable + // formatting logic, which will pick the right format based on whether useMicroseconds is on + if ($this->dateFormat === self::SIMPLE_DATE && $date instanceof DateTimeImmutable) { + return (string) $date; + } + + return $date->format($this->dateFormat); + } + + public function addJsonEncodeOption(int $option): self + { + $this->jsonEncodeOptions |= $option; + + return $this; + } + + public function removeJsonEncodeOption(int $option): self + { + $this->jsonEncodeOptions &= ~$option; + + return $this; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/ScalarFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/ScalarFormatter.php new file mode 100644 index 0000000..187bc55 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/ScalarFormatter.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +/** + * Formats data into an associative array of scalar values. + * Objects and arrays will be JSON encoded. + * + * @author Andrew Lawson + */ +class ScalarFormatter extends NormalizerFormatter +{ + /** + * {@inheritDoc} + * + * @phpstan-return array $record + */ + public function format(array $record): array + { + $result = []; + foreach ($record as $key => $value) { + $result[$key] = $this->normalizeValue($value); + } + + return $result; + } + + /** + * @param mixed $value + * @return scalar|null + */ + protected function normalizeValue($value) + { + $normalized = $this->normalize($value); + + if (is_array($normalized)) { + return $this->toJson($normalized, true); + } + + return $normalized; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php new file mode 100644 index 0000000..6539b34 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php @@ -0,0 +1,139 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; + +/** + * Serializes a log message according to Wildfire's header requirements + * + * @author Eric Clemmons (@ericclemmons) + * @author Christophe Coevoet + * @author Kirill chEbba Chebunin + * + * @phpstan-import-type Level from \Monolog\Logger + */ +class WildfireFormatter extends NormalizerFormatter +{ + /** + * Translates Monolog log levels to Wildfire levels. + * + * @var array + */ + private $logLevels = [ + Logger::DEBUG => 'LOG', + Logger::INFO => 'INFO', + Logger::NOTICE => 'INFO', + Logger::WARNING => 'WARN', + Logger::ERROR => 'ERROR', + Logger::CRITICAL => 'ERROR', + Logger::ALERT => 'ERROR', + Logger::EMERGENCY => 'ERROR', + ]; + + /** + * @param string|null $dateFormat The format of the timestamp: one supported by DateTime::format + */ + public function __construct(?string $dateFormat = null) + { + parent::__construct($dateFormat); + + // http headers do not like non-ISO-8559-1 characters + $this->removeJsonEncodeOption(JSON_UNESCAPED_UNICODE); + } + + /** + * {@inheritDoc} + * + * @return string + */ + public function format(array $record): string + { + // Retrieve the line and file if set and remove them from the formatted extra + $file = $line = ''; + if (isset($record['extra']['file'])) { + $file = $record['extra']['file']; + unset($record['extra']['file']); + } + if (isset($record['extra']['line'])) { + $line = $record['extra']['line']; + unset($record['extra']['line']); + } + + /** @var mixed[] $record */ + $record = $this->normalize($record); + $message = ['message' => $record['message']]; + $handleError = false; + if ($record['context']) { + $message['context'] = $record['context']; + $handleError = true; + } + if ($record['extra']) { + $message['extra'] = $record['extra']; + $handleError = true; + } + if (count($message) === 1) { + $message = reset($message); + } + + if (isset($record['context']['table'])) { + $type = 'TABLE'; + $label = $record['channel'] .': '. $record['message']; + $message = $record['context']['table']; + } else { + $type = $this->logLevels[$record['level']]; + $label = $record['channel']; + } + + // Create JSON object describing the appearance of the message in the console + $json = $this->toJson([ + [ + 'Type' => $type, + 'File' => $file, + 'Line' => $line, + 'Label' => $label, + ], + $message, + ], $handleError); + + // The message itself is a serialization of the above JSON object + it's length + return sprintf( + '%d|%s|', + strlen($json), + $json + ); + } + + /** + * {@inheritDoc} + * + * @phpstan-return never + */ + public function formatBatch(array $records) + { + throw new \BadMethodCallException('Batch formatting does not make sense for the WildfireFormatter'); + } + + /** + * {@inheritDoc} + * + * @return null|scalar|array|object + */ + protected function normalize($data, int $depth = 0) + { + if (is_object($data) && !$data instanceof \DateTimeInterface) { + return $data; + } + + return parent::normalize($data, $depth); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/AbstractHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/AbstractHandler.php new file mode 100644 index 0000000..a5cdaa7 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/AbstractHandler.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\Logger; +use Monolog\ResettableInterface; +use Psr\Log\LogLevel; + +/** + * Base Handler class providing basic level/bubble support + * + * @author Jordi Boggiano + * + * @phpstan-import-type Level from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger + */ +abstract class AbstractHandler extends Handler implements ResettableInterface +{ + /** + * @var int + * @phpstan-var Level + */ + protected $level = Logger::DEBUG; + /** @var bool */ + protected $bubble = true; + + /** + * @param int|string $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * + * @phpstan-param Level|LevelName|LogLevel::* $level + */ + public function __construct($level = Logger::DEBUG, bool $bubble = true) + { + $this->setLevel($level); + $this->bubble = $bubble; + } + + /** + * {@inheritDoc} + */ + public function isHandling(array $record): bool + { + return $record['level'] >= $this->level; + } + + /** + * Sets minimum logging level at which this handler will be triggered. + * + * @param Level|LevelName|LogLevel::* $level Level or level name + * @return self + */ + public function setLevel($level): self + { + $this->level = Logger::toMonologLevel($level); + + return $this; + } + + /** + * Gets minimum logging level at which this handler will be triggered. + * + * @return int + * + * @phpstan-return Level + */ + public function getLevel(): int + { + return $this->level; + } + + /** + * Sets the bubbling behavior. + * + * @param bool $bubble true means that this handler allows bubbling. + * false means that bubbling is not permitted. + * @return self + */ + public function setBubble(bool $bubble): self + { + $this->bubble = $bubble; + + return $this; + } + + /** + * Gets the bubbling behavior. + * + * @return bool true means that this handler allows bubbling. + * false means that bubbling is not permitted. + */ + public function getBubble(): bool + { + return $this->bubble; + } + + /** + * {@inheritDoc} + */ + public function reset() + { + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php new file mode 100644 index 0000000..77e533f --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.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; + +/** + * Base Handler class providing the Handler structure, including processors and formatters + * + * Classes extending it should (in most cases) only implement write($record) + * + * @author Jordi Boggiano + * @author Christophe Coevoet + * + * @phpstan-import-type LevelName from \Monolog\Logger + * @phpstan-import-type Level from \Monolog\Logger + * @phpstan-import-type Record from \Monolog\Logger + * @phpstan-type FormattedRecord array{message: string, context: mixed[], level: Level, level_name: LevelName, channel: string, datetime: \DateTimeImmutable, extra: mixed[], formatted: mixed} + */ +abstract class AbstractProcessingHandler extends AbstractHandler implements ProcessableHandlerInterface, FormattableHandlerInterface +{ + use ProcessableHandlerTrait; + use FormattableHandlerTrait; + + /** + * {@inheritDoc} + */ + public function handle(array $record): bool + { + if (!$this->isHandling($record)) { + return false; + } + + if ($this->processors) { + /** @var Record $record */ + $record = $this->processRecord($record); + } + + $record['formatted'] = $this->getFormatter()->format($record); + + $this->write($record); + + return false === $this->bubble; + } + + /** + * Writes the record down to the log of the implementing handler + * + * @phpstan-param FormattedRecord $record + */ + abstract protected function write(array $record): void; + + /** + * @return void + */ + public function reset() + { + parent::reset(); + + $this->resetProcessors(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/AbstractSyslogHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/AbstractSyslogHandler.php new file mode 100644 index 0000000..5e5ad1c --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/AbstractSyslogHandler.php @@ -0,0 +1,106 @@ + + * + * 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\Formatter\FormatterInterface; +use Monolog\Formatter\LineFormatter; + +/** + * Common syslog functionality + * + * @phpstan-import-type Level from \Monolog\Logger + */ +abstract class AbstractSyslogHandler extends AbstractProcessingHandler +{ + /** @var int */ + protected $facility; + + /** + * Translates Monolog log levels to syslog log priorities. + * @var array + * @phpstan-var array + */ + protected $logLevels = [ + Logger::DEBUG => LOG_DEBUG, + Logger::INFO => LOG_INFO, + Logger::NOTICE => LOG_NOTICE, + Logger::WARNING => LOG_WARNING, + Logger::ERROR => LOG_ERR, + Logger::CRITICAL => LOG_CRIT, + Logger::ALERT => LOG_ALERT, + Logger::EMERGENCY => LOG_EMERG, + ]; + + /** + * List of valid log facility names. + * @var array + */ + protected $facilities = [ + 'auth' => LOG_AUTH, + 'authpriv' => LOG_AUTHPRIV, + 'cron' => LOG_CRON, + 'daemon' => LOG_DAEMON, + 'kern' => LOG_KERN, + 'lpr' => LOG_LPR, + 'mail' => LOG_MAIL, + 'news' => LOG_NEWS, + 'syslog' => LOG_SYSLOG, + 'user' => LOG_USER, + 'uucp' => LOG_UUCP, + ]; + + /** + * @param string|int $facility Either one of the names of the keys in $this->facilities, or a LOG_* facility constant + */ + public function __construct($facility = LOG_USER, $level = Logger::DEBUG, bool $bubble = true) + { + parent::__construct($level, $bubble); + + if (!defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->facilities['local0'] = LOG_LOCAL0; + $this->facilities['local1'] = LOG_LOCAL1; + $this->facilities['local2'] = LOG_LOCAL2; + $this->facilities['local3'] = LOG_LOCAL3; + $this->facilities['local4'] = LOG_LOCAL4; + $this->facilities['local5'] = LOG_LOCAL5; + $this->facilities['local6'] = LOG_LOCAL6; + $this->facilities['local7'] = LOG_LOCAL7; + } else { + $this->facilities['local0'] = 128; // LOG_LOCAL0 + $this->facilities['local1'] = 136; // LOG_LOCAL1 + $this->facilities['local2'] = 144; // LOG_LOCAL2 + $this->facilities['local3'] = 152; // LOG_LOCAL3 + $this->facilities['local4'] = 160; // LOG_LOCAL4 + $this->facilities['local5'] = 168; // LOG_LOCAL5 + $this->facilities['local6'] = 176; // LOG_LOCAL6 + $this->facilities['local7'] = 184; // LOG_LOCAL7 + } + + // convert textual description of facility to syslog constant + if (is_string($facility) && array_key_exists(strtolower($facility), $this->facilities)) { + $facility = $this->facilities[strtolower($facility)]; + } elseif (!in_array($facility, array_values($this->facilities), true)) { + throw new \UnexpectedValueException('Unknown facility value "'.$facility.'" given'); + } + + $this->facility = $facility; + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new LineFormatter('%channel%.%level_name%: %message% %context% %extra%'); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/AmqpHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/AmqpHandler.php new file mode 100644 index 0000000..994872c --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/AmqpHandler.php @@ -0,0 +1,171 @@ + + * + * 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\Formatter\FormatterInterface; +use Monolog\Formatter\JsonFormatter; +use PhpAmqpLib\Message\AMQPMessage; +use PhpAmqpLib\Channel\AMQPChannel; +use AMQPExchange; + +/** + * @phpstan-import-type Record from \Monolog\Logger + */ +class AmqpHandler extends AbstractProcessingHandler +{ + /** + * @var AMQPExchange|AMQPChannel $exchange + */ + protected $exchange; + /** @var array */ + private $extraAttributes = []; + + /** + * @return array + */ + public function getExtraAttributes(): array + { + return $this->extraAttributes; + } + + /** + * Configure extra attributes to pass to the AMQPExchange (if you are using the amqp extension) + * + * @param array $extraAttributes One of content_type, content_encoding, + * message_id, user_id, app_id, delivery_mode, + * priority, timestamp, expiration, type + * or reply_to, headers. + * @return AmqpHandler + */ + public function setExtraAttributes(array $extraAttributes): self + { + $this->extraAttributes = $extraAttributes; + return $this; + } + + /** + * @var string + */ + protected $exchangeName; + + /** + * @param AMQPExchange|AMQPChannel $exchange AMQPExchange (php AMQP ext) or PHP AMQP lib channel, ready for use + * @param string|null $exchangeName Optional exchange name, for AMQPChannel (PhpAmqpLib) only + */ + public function __construct($exchange, ?string $exchangeName = null, $level = Logger::DEBUG, bool $bubble = true) + { + if ($exchange instanceof AMQPChannel) { + $this->exchangeName = (string) $exchangeName; + } elseif (!$exchange instanceof AMQPExchange) { + throw new \InvalidArgumentException('PhpAmqpLib\Channel\AMQPChannel or AMQPExchange instance required'); + } elseif ($exchangeName) { + @trigger_error('The $exchangeName parameter can only be passed when using PhpAmqpLib, if using an AMQPExchange instance configure it beforehand', E_USER_DEPRECATED); + } + $this->exchange = $exchange; + + parent::__construct($level, $bubble); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void + { + $data = $record["formatted"]; + $routingKey = $this->getRoutingKey($record); + + if ($this->exchange instanceof AMQPExchange) { + $attributes = [ + 'delivery_mode' => 2, + 'content_type' => 'application/json', + ]; + if ($this->extraAttributes) { + $attributes = array_merge($attributes, $this->extraAttributes); + } + $this->exchange->publish( + $data, + $routingKey, + 0, + $attributes + ); + } else { + $this->exchange->basic_publish( + $this->createAmqpMessage($data), + $this->exchangeName, + $routingKey + ); + } + } + + /** + * {@inheritDoc} + */ + public function handleBatch(array $records): void + { + if ($this->exchange instanceof AMQPExchange) { + parent::handleBatch($records); + + return; + } + + foreach ($records as $record) { + if (!$this->isHandling($record)) { + continue; + } + + /** @var Record $record */ + $record = $this->processRecord($record); + $data = $this->getFormatter()->format($record); + + $this->exchange->batch_basic_publish( + $this->createAmqpMessage($data), + $this->exchangeName, + $this->getRoutingKey($record) + ); + } + + $this->exchange->publish_batch(); + } + + /** + * Gets the routing key for the AMQP exchange + * + * @phpstan-param Record $record + */ + protected function getRoutingKey(array $record): string + { + $routingKey = sprintf('%s.%s', $record['level_name'], $record['channel']); + + return strtolower($routingKey); + } + + private function createAmqpMessage(string $data): AMQPMessage + { + $attributes = [ + 'delivery_mode' => 2, + 'content_type' => 'application/json', + ]; + if ($this->extraAttributes) { + $attributes = array_merge($attributes, $this->extraAttributes); + } + return new AMQPMessage($data, $attributes); + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, false); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.php new file mode 100644 index 0000000..95bbfed --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.php @@ -0,0 +1,308 @@ + + * + * 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\FormatterInterface; +use Monolog\Formatter\LineFormatter; +use Monolog\Utils; +use Monolog\Logger; + +use function count; +use function headers_list; +use function stripos; +use function trigger_error; + +use const E_USER_DEPRECATED; + +/** + * Handler sending logs to browser's javascript console with no browser extension required + * + * @author Olivier Poitrey + * + * @phpstan-import-type FormattedRecord from AbstractProcessingHandler + */ +class BrowserConsoleHandler extends AbstractProcessingHandler +{ + /** @var bool */ + protected static $initialized = false; + /** @var FormattedRecord[] */ + protected static $records = []; + + protected const FORMAT_HTML = 'html'; + protected const FORMAT_JS = 'js'; + protected const FORMAT_UNKNOWN = 'unknown'; + + /** + * {@inheritDoc} + * + * Formatted output may contain some formatting markers to be transferred to `console.log` using the %c format. + * + * Example of formatted string: + * + * You can do [[blue text]]{color: blue} or [[green background]]{background-color: green; color: white} + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new LineFormatter('[[%channel%]]{macro: autolabel} [[%level_name%]]{font-weight: bold} %message%'); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void + { + // Accumulate records + static::$records[] = $record; + + // Register shutdown handler if not already done + if (!static::$initialized) { + static::$initialized = true; + $this->registerShutdownFunction(); + } + } + + /** + * Convert records to javascript console commands and send it to the browser. + * This method is automatically called on PHP shutdown if output is HTML or Javascript. + */ + public static function send(): void + { + $format = static::getResponseFormat(); + if ($format === self::FORMAT_UNKNOWN) { + return; + } + + if (count(static::$records)) { + if ($format === self::FORMAT_HTML) { + static::writeOutput(''); + } elseif ($format === self::FORMAT_JS) { + static::writeOutput(static::generateScript()); + } + static::resetStatic(); + } + } + + public function close(): void + { + self::resetStatic(); + } + + public function reset() + { + parent::reset(); + + self::resetStatic(); + } + + /** + * Forget all logged records + */ + public static function resetStatic(): void + { + static::$records = []; + } + + /** + * Wrapper for register_shutdown_function to allow overriding + */ + protected function registerShutdownFunction(): void + { + if (PHP_SAPI !== 'cli') { + register_shutdown_function(['Monolog\Handler\BrowserConsoleHandler', 'send']); + } + } + + /** + * Wrapper for echo to allow overriding + */ + protected static function writeOutput(string $str): void + { + echo $str; + } + + /** + * Checks the format of the response + * + * If Content-Type is set to application/javascript or text/javascript -> js + * If Content-Type is set to text/html, or is unset -> html + * If Content-Type is anything else -> unknown + * + * @return string One of 'js', 'html' or 'unknown' + * @phpstan-return self::FORMAT_* + */ + protected static function getResponseFormat(): string + { + // Check content type + foreach (headers_list() as $header) { + if (stripos($header, 'content-type:') === 0) { + return static::getResponseFormatFromContentType($header); + } + } + + return self::FORMAT_HTML; + } + + /** + * @return string One of 'js', 'html' or 'unknown' + * @phpstan-return self::FORMAT_* + */ + protected static function getResponseFormatFromContentType(string $contentType): string + { + // This handler only works with HTML and javascript outputs + // text/javascript is obsolete in favour of application/javascript, but still used + if (stripos($contentType, 'application/javascript') !== false || stripos($contentType, 'text/javascript') !== false) { + return self::FORMAT_JS; + } + + if (stripos($contentType, 'text/html') !== false) { + return self::FORMAT_HTML; + } + + return self::FORMAT_UNKNOWN; + } + + private static function generateScript(): string + { + $script = []; + foreach (static::$records as $record) { + $context = static::dump('Context', $record['context']); + $extra = static::dump('Extra', $record['extra']); + + if (empty($context) && empty($extra)) { + $script[] = static::call_array(static::getConsoleMethodForLevel($record['level']), static::handleStyles($record['formatted'])); + } else { + $script = array_merge( + $script, + [static::call_array('groupCollapsed', static::handleStyles($record['formatted']))], + $context, + $extra, + [static::call('groupEnd')] + ); + } + } + + return "(function (c) {if (c && c.groupCollapsed) {\n" . implode("\n", $script) . "\n}})(console);"; + } + + private static function getConsoleMethodForLevel(int $level): string + { + return [ + Logger::DEBUG => 'debug', + Logger::INFO => 'info', + Logger::NOTICE => 'info', + Logger::WARNING => 'warn', + Logger::ERROR => 'error', + Logger::CRITICAL => 'error', + Logger::ALERT => 'error', + Logger::EMERGENCY => 'error', + ][$level] ?? 'log'; + } + + /** + * @return string[] + */ + private static function handleStyles(string $formatted): array + { + $args = []; + $format = '%c' . $formatted; + preg_match_all('/\[\[(.*?)\]\]\{([^}]*)\}/s', $format, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER); + + foreach (array_reverse($matches) as $match) { + $args[] = '"font-weight: normal"'; + $args[] = static::quote(static::handleCustomStyles($match[2][0], $match[1][0])); + + $pos = $match[0][1]; + $format = Utils::substr($format, 0, $pos) . '%c' . $match[1][0] . '%c' . Utils::substr($format, $pos + strlen($match[0][0])); + } + + $args[] = static::quote('font-weight: normal'); + $args[] = static::quote($format); + + return array_reverse($args); + } + + private static function handleCustomStyles(string $style, string $string): string + { + static $colors = ['blue', 'green', 'red', 'magenta', 'orange', 'black', 'grey']; + static $labels = []; + + $style = preg_replace_callback('/macro\s*:(.*?)(?:;|$)/', function (array $m) use ($string, &$colors, &$labels) { + if (trim($m[1]) === 'autolabel') { + // Format the string as a label with consistent auto assigned background color + if (!isset($labels[$string])) { + $labels[$string] = $colors[count($labels) % count($colors)]; + } + $color = $labels[$string]; + + return "background-color: $color; color: white; border-radius: 3px; padding: 0 2px 0 2px"; + } + + return $m[1]; + }, $style); + + if (null === $style) { + $pcreErrorCode = preg_last_error(); + throw new \RuntimeException('Failed to run preg_replace_callback: ' . $pcreErrorCode . ' / ' . Utils::pcreLastErrorMessage($pcreErrorCode)); + } + + return $style; + } + + /** + * @param mixed[] $dict + * @return mixed[] + */ + private static function dump(string $title, array $dict): array + { + $script = []; + $dict = array_filter($dict); + if (empty($dict)) { + return $script; + } + $script[] = static::call('log', static::quote('%c%s'), static::quote('font-weight: bold'), static::quote($title)); + foreach ($dict as $key => $value) { + $value = json_encode($value); + if (empty($value)) { + $value = static::quote(''); + } + $script[] = static::call('log', static::quote('%s: %o'), static::quote((string) $key), $value); + } + + return $script; + } + + private static function quote(string $arg): string + { + return '"' . addcslashes($arg, "\"\n\\") . '"'; + } + + /** + * @param mixed $args + */ + private static function call(...$args): string + { + $method = array_shift($args); + if (!is_string($method)) { + throw new \UnexpectedValueException('Expected the first arg to be a string, got: '.var_export($method, true)); + } + + return static::call_array($method, $args); + } + + /** + * @param mixed[] $args + */ + private static function call_array(string $method, array $args): string + { + return 'c.' . $method . '(' . implode(', ', $args) . ');'; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/BufferHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/BufferHandler.php new file mode 100644 index 0000000..fcce5d6 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/BufferHandler.php @@ -0,0 +1,167 @@ + + * + * 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\ResettableInterface; +use Monolog\Formatter\FormatterInterface; + +/** + * Buffers all records until closing the handler and then pass them as batch. + * + * This is useful for a MailHandler to send only one mail per request instead of + * sending one per log message. + * + * @author Christophe Coevoet + * + * @phpstan-import-type Record from \Monolog\Logger + */ +class BufferHandler extends AbstractHandler implements ProcessableHandlerInterface, FormattableHandlerInterface +{ + use ProcessableHandlerTrait; + + /** @var HandlerInterface */ + protected $handler; + /** @var int */ + protected $bufferSize = 0; + /** @var int */ + protected $bufferLimit; + /** @var bool */ + protected $flushOnOverflow; + /** @var Record[] */ + protected $buffer = []; + /** @var bool */ + protected $initialized = false; + + /** + * @param HandlerInterface $handler Handler. + * @param int $bufferLimit How many entries should be buffered at most, beyond that the oldest items are removed from the buffer. + * @param bool $flushOnOverflow If true, the buffer is flushed when the max size has been reached, by default oldest entries are discarded + */ + public function __construct(HandlerInterface $handler, int $bufferLimit = 0, $level = Logger::DEBUG, bool $bubble = true, bool $flushOnOverflow = false) + { + parent::__construct($level, $bubble); + $this->handler = $handler; + $this->bufferLimit = $bufferLimit; + $this->flushOnOverflow = $flushOnOverflow; + } + + /** + * {@inheritDoc} + */ + public function handle(array $record): bool + { + if ($record['level'] < $this->level) { + return false; + } + + if (!$this->initialized) { + // __destructor() doesn't get called on Fatal errors + register_shutdown_function([$this, 'close']); + $this->initialized = true; + } + + if ($this->bufferLimit > 0 && $this->bufferSize === $this->bufferLimit) { + if ($this->flushOnOverflow) { + $this->flush(); + } else { + array_shift($this->buffer); + $this->bufferSize--; + } + } + + if ($this->processors) { + /** @var Record $record */ + $record = $this->processRecord($record); + } + + $this->buffer[] = $record; + $this->bufferSize++; + + return false === $this->bubble; + } + + public function flush(): void + { + if ($this->bufferSize === 0) { + return; + } + + $this->handler->handleBatch($this->buffer); + $this->clear(); + } + + public function __destruct() + { + // suppress the parent behavior since we already have register_shutdown_function() + // to call close(), and the reference contained there will prevent this from being + // GC'd until the end of the request + } + + /** + * {@inheritDoc} + */ + public function close(): void + { + $this->flush(); + + $this->handler->close(); + } + + /** + * Clears the buffer without flushing any messages down to the wrapped handler. + */ + public function clear(): void + { + $this->bufferSize = 0; + $this->buffer = []; + } + + public function reset() + { + $this->flush(); + + parent::reset(); + + $this->resetProcessors(); + + if ($this->handler instanceof ResettableInterface) { + $this->handler->reset(); + } + } + + /** + * {@inheritDoc} + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + if ($this->handler instanceof FormattableHandlerInterface) { + $this->handler->setFormatter($formatter); + + return $this; + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($this->handler).' does not support formatters.'); + } + + /** + * {@inheritDoc} + */ + public function getFormatter(): FormatterInterface + { + if ($this->handler instanceof FormattableHandlerInterface) { + return $this->handler->getFormatter(); + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($this->handler).' does not support formatters.'); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php new file mode 100644 index 0000000..234ecf6 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php @@ -0,0 +1,196 @@ + + * + * 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\ChromePHPFormatter; +use Monolog\Formatter\FormatterInterface; +use Monolog\Logger; +use Monolog\Utils; + +/** + * Handler sending logs to the ChromePHP extension (http://www.chromephp.com/) + * + * This also works out of the box with Firefox 43+ + * + * @author Christophe Coevoet + * + * @phpstan-import-type Record from \Monolog\Logger + */ +class ChromePHPHandler extends AbstractProcessingHandler +{ + use WebRequestRecognizerTrait; + + /** + * Version of the extension + */ + protected const VERSION = '4.0'; + + /** + * Header name + */ + protected const HEADER_NAME = 'X-ChromeLogger-Data'; + + /** + * Regular expression to detect supported browsers (matches any Chrome, or Firefox 43+) + */ + protected const USER_AGENT_REGEX = '{\b(?:Chrome/\d+(?:\.\d+)*|HeadlessChrome|Firefox/(?:4[3-9]|[5-9]\d|\d{3,})(?:\.\d)*)\b}'; + + /** @var bool */ + protected static $initialized = false; + + /** + * Tracks whether we sent too much data + * + * Chrome limits the headers to 4KB, so when we sent 3KB we stop sending + * + * @var bool + */ + protected static $overflowed = false; + + /** @var mixed[] */ + protected static $json = [ + 'version' => self::VERSION, + 'columns' => ['label', 'log', 'backtrace', 'type'], + 'rows' => [], + ]; + + /** @var bool */ + protected static $sendHeaders = true; + + public function __construct($level = Logger::DEBUG, bool $bubble = true) + { + parent::__construct($level, $bubble); + if (!function_exists('json_encode')) { + throw new \RuntimeException('PHP\'s json extension is required to use Monolog\'s ChromePHPHandler'); + } + } + + /** + * {@inheritDoc} + */ + public function handleBatch(array $records): void + { + if (!$this->isWebRequest()) { + return; + } + + $messages = []; + + foreach ($records as $record) { + if ($record['level'] < $this->level) { + continue; + } + /** @var Record $message */ + $message = $this->processRecord($record); + $messages[] = $message; + } + + if (!empty($messages)) { + $messages = $this->getFormatter()->formatBatch($messages); + self::$json['rows'] = array_merge(self::$json['rows'], $messages); + $this->send(); + } + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new ChromePHPFormatter(); + } + + /** + * Creates & sends header for a record + * + * @see sendHeader() + * @see send() + */ + protected function write(array $record): void + { + if (!$this->isWebRequest()) { + return; + } + + self::$json['rows'][] = $record['formatted']; + + $this->send(); + } + + /** + * Sends the log header + * + * @see sendHeader() + */ + protected function send(): void + { + if (self::$overflowed || !self::$sendHeaders) { + return; + } + + if (!self::$initialized) { + self::$initialized = true; + + self::$sendHeaders = $this->headersAccepted(); + if (!self::$sendHeaders) { + return; + } + + self::$json['request_uri'] = $_SERVER['REQUEST_URI'] ?? ''; + } + + $json = Utils::jsonEncode(self::$json, Utils::DEFAULT_JSON_FLAGS & ~JSON_UNESCAPED_UNICODE, true); + $data = base64_encode($json); + if (strlen($data) > 3 * 1024) { + self::$overflowed = true; + + $record = [ + 'message' => 'Incomplete logs, chrome header size limit reached', + 'context' => [], + 'level' => Logger::WARNING, + 'level_name' => Logger::getLevelName(Logger::WARNING), + 'channel' => 'monolog', + 'datetime' => new \DateTimeImmutable(), + 'extra' => [], + ]; + self::$json['rows'][count(self::$json['rows']) - 1] = $this->getFormatter()->format($record); + $json = Utils::jsonEncode(self::$json, Utils::DEFAULT_JSON_FLAGS & ~JSON_UNESCAPED_UNICODE, true); + $data = base64_encode($json); + } + + if (trim($data) !== '') { + $this->sendHeader(static::HEADER_NAME, $data); + } + } + + /** + * Send header string to the client + */ + protected function sendHeader(string $header, string $content): void + { + if (!headers_sent() && self::$sendHeaders) { + header(sprintf('%s: %s', $header, $content)); + } + } + + /** + * Verifies if the headers are accepted by the current user agent + */ + protected function headersAccepted(): bool + { + if (empty($_SERVER['HTTP_USER_AGENT'])) { + return false; + } + + return preg_match(static::USER_AGENT_REGEX, $_SERVER['HTTP_USER_AGENT']) === 1; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php new file mode 100644 index 0000000..5265761 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\JsonFormatter; +use Monolog\Logger; + +/** + * CouchDB handler + * + * @author Markus Bachmann + */ +class CouchDBHandler extends AbstractProcessingHandler +{ + /** @var mixed[] */ + private $options; + + /** + * @param mixed[] $options + */ + public function __construct(array $options = [], $level = Logger::DEBUG, bool $bubble = true) + { + $this->options = array_merge([ + 'host' => 'localhost', + 'port' => 5984, + 'dbname' => 'logger', + 'username' => null, + 'password' => null, + ], $options); + + parent::__construct($level, $bubble); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void + { + $basicAuth = null; + if ($this->options['username']) { + $basicAuth = sprintf('%s:%s@', $this->options['username'], $this->options['password']); + } + + $url = 'http://'.$basicAuth.$this->options['host'].':'.$this->options['port'].'/'.$this->options['dbname']; + $context = stream_context_create([ + 'http' => [ + 'method' => 'POST', + 'content' => $record['formatted'], + 'ignore_errors' => true, + 'max_redirects' => 0, + 'header' => 'Content-type: application/json', + ], + ]); + + if (false === @file_get_contents($url, false, $context)) { + throw new \RuntimeException(sprintf('Could not connect to %s', $url)); + } + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, false); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/CubeHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/CubeHandler.php new file mode 100644 index 0000000..3535a4f --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/CubeHandler.php @@ -0,0 +1,167 @@ + + * + * 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\Utils; + +/** + * Logs to Cube. + * + * @link https://github.com/square/cube/wiki + * @author Wan Chen + * @deprecated Since 2.8.0 and 3.2.0, Cube appears abandoned and thus we will drop this handler in Monolog 4 + */ +class CubeHandler extends AbstractProcessingHandler +{ + /** @var resource|\Socket|null */ + private $udpConnection = null; + /** @var resource|\CurlHandle|null */ + private $httpConnection = null; + /** @var string */ + private $scheme; + /** @var string */ + private $host; + /** @var int */ + private $port; + /** @var string[] */ + private $acceptedSchemes = ['http', 'udp']; + + /** + * Create a Cube handler + * + * @throws \UnexpectedValueException when given url is not a valid url. + * A valid url must consist of three parts : protocol://host:port + * Only valid protocols used by Cube are http and udp + */ + public function __construct(string $url, $level = Logger::DEBUG, bool $bubble = true) + { + $urlInfo = parse_url($url); + + if ($urlInfo === false || !isset($urlInfo['scheme'], $urlInfo['host'], $urlInfo['port'])) { + throw new \UnexpectedValueException('URL "'.$url.'" is not valid'); + } + + if (!in_array($urlInfo['scheme'], $this->acceptedSchemes)) { + throw new \UnexpectedValueException( + 'Invalid protocol (' . $urlInfo['scheme'] . ').' + . ' Valid options are ' . implode(', ', $this->acceptedSchemes) + ); + } + + $this->scheme = $urlInfo['scheme']; + $this->host = $urlInfo['host']; + $this->port = (int) $urlInfo['port']; + + parent::__construct($level, $bubble); + } + + /** + * Establish a connection to an UDP socket + * + * @throws \LogicException when unable to connect to the socket + * @throws MissingExtensionException when there is no socket extension + */ + protected function connectUdp(): void + { + if (!extension_loaded('sockets')) { + throw new MissingExtensionException('The sockets extension is required to use udp URLs with the CubeHandler'); + } + + $udpConnection = socket_create(AF_INET, SOCK_DGRAM, 0); + if (false === $udpConnection) { + throw new \LogicException('Unable to create a socket'); + } + + $this->udpConnection = $udpConnection; + if (!socket_connect($this->udpConnection, $this->host, $this->port)) { + throw new \LogicException('Unable to connect to the socket at ' . $this->host . ':' . $this->port); + } + } + + /** + * Establish a connection to an http server + * + * @throws \LogicException when unable to connect to the socket + * @throws MissingExtensionException when no curl extension + */ + protected function connectHttp(): void + { + if (!extension_loaded('curl')) { + throw new MissingExtensionException('The curl extension is required to use http URLs with the CubeHandler'); + } + + $httpConnection = curl_init('http://'.$this->host.':'.$this->port.'/1.0/event/put'); + if (false === $httpConnection) { + throw new \LogicException('Unable to connect to ' . $this->host . ':' . $this->port); + } + + $this->httpConnection = $httpConnection; + curl_setopt($this->httpConnection, CURLOPT_CUSTOMREQUEST, "POST"); + curl_setopt($this->httpConnection, CURLOPT_RETURNTRANSFER, true); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void + { + $date = $record['datetime']; + + $data = ['time' => $date->format('Y-m-d\TH:i:s.uO')]; + unset($record['datetime']); + + if (isset($record['context']['type'])) { + $data['type'] = $record['context']['type']; + unset($record['context']['type']); + } else { + $data['type'] = $record['channel']; + } + + $data['data'] = $record['context']; + $data['data']['level'] = $record['level']; + + if ($this->scheme === 'http') { + $this->writeHttp(Utils::jsonEncode($data)); + } else { + $this->writeUdp(Utils::jsonEncode($data)); + } + } + + private function writeUdp(string $data): void + { + if (!$this->udpConnection) { + $this->connectUdp(); + } + + socket_send($this->udpConnection, $data, strlen($data), 0); + } + + private function writeHttp(string $data): void + { + if (!$this->httpConnection) { + $this->connectHttp(); + } + + if (null === $this->httpConnection) { + throw new \LogicException('No connection could be established'); + } + + curl_setopt($this->httpConnection, CURLOPT_POSTFIELDS, '['.$data.']'); + curl_setopt($this->httpConnection, CURLOPT_HTTPHEADER, [ + 'Content-Type: application/json', + 'Content-Length: ' . strlen('['.$data.']'), + ]); + + Curl\Util::execute($this->httpConnection, 5, false); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/Curl/Util.php b/vendor/monolog/monolog/src/Monolog/Handler/Curl/Util.php new file mode 100644 index 0000000..7213e8e --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/Curl/Util.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler\Curl; + +use CurlHandle; + +/** + * This class is marked as internal and it is not under the BC promise of the package. + * + * @internal + */ +final class Util +{ + /** @var array */ + private static $retriableErrorCodes = [ + CURLE_COULDNT_RESOLVE_HOST, + CURLE_COULDNT_CONNECT, + CURLE_HTTP_NOT_FOUND, + CURLE_READ_ERROR, + CURLE_OPERATION_TIMEOUTED, + CURLE_HTTP_POST_ERROR, + CURLE_SSL_CONNECT_ERROR, + ]; + + /** + * Executes a CURL request with optional retries and exception on failure + * + * @param resource|CurlHandle $ch curl handler + * @param int $retries + * @param bool $closeAfterDone + * @return bool|string @see curl_exec + */ + public static function execute($ch, int $retries = 5, bool $closeAfterDone = true) + { + while ($retries--) { + $curlResponse = curl_exec($ch); + if ($curlResponse === false) { + $curlErrno = curl_errno($ch); + + if (false === in_array($curlErrno, self::$retriableErrorCodes, true) || !$retries) { + $curlError = curl_error($ch); + + if ($closeAfterDone) { + curl_close($ch); + } + + throw new \RuntimeException(sprintf('Curl error (code %d): %s', $curlErrno, $curlError)); + } + + continue; + } + + if ($closeAfterDone) { + curl_close($ch); + } + + return $curlResponse; + } + + return false; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php new file mode 100644 index 0000000..9b85ae7 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php @@ -0,0 +1,186 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Psr\Log\LogLevel; + +/** + * Simple handler wrapper that deduplicates log records across multiple requests + * + * It also includes the BufferHandler functionality and will buffer + * all messages until the end of the request or flush() is called. + * + * This works by storing all log records' messages above $deduplicationLevel + * to the file specified by $deduplicationStore. When further logs come in at the end of the + * request (or when flush() is called), all those above $deduplicationLevel are checked + * against the existing stored logs. If they match and the timestamps in the stored log is + * not older than $time seconds, the new log record is discarded. If no log record is new, the + * whole data set is discarded. + * + * This is mainly useful in combination with Mail handlers or things like Slack or HipChat handlers + * that send messages to people, to avoid spamming with the same message over and over in case of + * a major component failure like a database server being down which makes all requests fail in the + * same way. + * + * @author Jordi Boggiano + * + * @phpstan-import-type Record from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger + * @phpstan-import-type Level from \Monolog\Logger + */ +class DeduplicationHandler extends BufferHandler +{ + /** + * @var string + */ + protected $deduplicationStore; + + /** + * @var Level + */ + protected $deduplicationLevel; + + /** + * @var int + */ + protected $time; + + /** + * @var bool + */ + private $gc = false; + + /** + * @param HandlerInterface $handler Handler. + * @param string $deduplicationStore The file/path where the deduplication log should be kept + * @param string|int $deduplicationLevel The minimum logging level for log records to be looked at for deduplication purposes + * @param int $time The period (in seconds) during which duplicate entries should be suppressed after a given log is sent through + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * + * @phpstan-param Level|LevelName|LogLevel::* $deduplicationLevel + */ + public function __construct(HandlerInterface $handler, ?string $deduplicationStore = null, $deduplicationLevel = Logger::ERROR, int $time = 60, bool $bubble = true) + { + parent::__construct($handler, 0, Logger::DEBUG, $bubble, false); + + $this->deduplicationStore = $deduplicationStore === null ? sys_get_temp_dir() . '/monolog-dedup-' . substr(md5(__FILE__), 0, 20) .'.log' : $deduplicationStore; + $this->deduplicationLevel = Logger::toMonologLevel($deduplicationLevel); + $this->time = $time; + } + + public function flush(): void + { + if ($this->bufferSize === 0) { + return; + } + + $passthru = null; + + foreach ($this->buffer as $record) { + if ($record['level'] >= $this->deduplicationLevel) { + $passthru = $passthru || !$this->isDuplicate($record); + if ($passthru) { + $this->appendRecord($record); + } + } + } + + // default of null is valid as well as if no record matches duplicationLevel we just pass through + if ($passthru === true || $passthru === null) { + $this->handler->handleBatch($this->buffer); + } + + $this->clear(); + + if ($this->gc) { + $this->collectLogs(); + } + } + + /** + * @phpstan-param Record $record + */ + private function isDuplicate(array $record): bool + { + if (!file_exists($this->deduplicationStore)) { + return false; + } + + $store = file($this->deduplicationStore, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + if (!is_array($store)) { + return false; + } + + $yesterday = time() - 86400; + $timestampValidity = $record['datetime']->getTimestamp() - $this->time; + $expectedMessage = preg_replace('{[\r\n].*}', '', $record['message']); + + for ($i = count($store) - 1; $i >= 0; $i--) { + list($timestamp, $level, $message) = explode(':', $store[$i], 3); + + if ($level === $record['level_name'] && $message === $expectedMessage && $timestamp > $timestampValidity) { + return true; + } + + if ($timestamp < $yesterday) { + $this->gc = true; + } + } + + return false; + } + + private function collectLogs(): void + { + if (!file_exists($this->deduplicationStore)) { + return; + } + + $handle = fopen($this->deduplicationStore, 'rw+'); + + if (!$handle) { + throw new \RuntimeException('Failed to open file for reading and writing: ' . $this->deduplicationStore); + } + + flock($handle, LOCK_EX); + $validLogs = []; + + $timestampValidity = time() - $this->time; + + while (!feof($handle)) { + $log = fgets($handle); + if ($log && substr($log, 0, 10) >= $timestampValidity) { + $validLogs[] = $log; + } + } + + ftruncate($handle, 0); + rewind($handle); + foreach ($validLogs as $log) { + fwrite($handle, $log); + } + + flock($handle, LOCK_UN); + fclose($handle); + + $this->gc = false; + } + + /** + * @phpstan-param Record $record + */ + private function appendRecord(array $record): void + { + file_put_contents($this->deduplicationStore, $record['datetime']->getTimestamp() . ':' . $record['level_name'] . ':' . preg_replace('{[\r\n].*}', '', $record['message']) . "\n", FILE_APPEND); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php new file mode 100644 index 0000000..ebd52c3 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.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\Logger; +use Monolog\Formatter\NormalizerFormatter; +use Monolog\Formatter\FormatterInterface; +use Doctrine\CouchDB\CouchDBClient; + +/** + * CouchDB handler for Doctrine CouchDB ODM + * + * @author Markus Bachmann + */ +class DoctrineCouchDBHandler extends AbstractProcessingHandler +{ + /** @var CouchDBClient */ + private $client; + + public function __construct(CouchDBClient $client, $level = Logger::DEBUG, bool $bubble = true) + { + $this->client = $client; + parent::__construct($level, $bubble); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void + { + $this->client->postDocument($record['formatted']); + } + + protected function getDefaultFormatter(): FormatterInterface + { + return new NormalizerFormatter; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php new file mode 100644 index 0000000..21840bf --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php @@ -0,0 +1,104 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Aws\Sdk; +use Aws\DynamoDb\DynamoDbClient; +use Monolog\Formatter\FormatterInterface; +use Aws\DynamoDb\Marshaler; +use Monolog\Formatter\ScalarFormatter; +use Monolog\Logger; + +/** + * Amazon DynamoDB handler (http://aws.amazon.com/dynamodb/) + * + * @link https://github.com/aws/aws-sdk-php/ + * @author Andrew Lawson + */ +class DynamoDbHandler extends AbstractProcessingHandler +{ + public const DATE_FORMAT = 'Y-m-d\TH:i:s.uO'; + + /** + * @var DynamoDbClient + */ + protected $client; + + /** + * @var string + */ + protected $table; + + /** + * @var int + */ + protected $version; + + /** + * @var Marshaler + */ + protected $marshaler; + + public function __construct(DynamoDbClient $client, string $table, $level = Logger::DEBUG, bool $bubble = true) + { + /** @phpstan-ignore-next-line */ + if (defined('Aws\Sdk::VERSION') && version_compare(Sdk::VERSION, '3.0', '>=')) { + $this->version = 3; + $this->marshaler = new Marshaler; + } else { + $this->version = 2; + } + + $this->client = $client; + $this->table = $table; + + parent::__construct($level, $bubble); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void + { + $filtered = $this->filterEmptyFields($record['formatted']); + if ($this->version === 3) { + $formatted = $this->marshaler->marshalItem($filtered); + } else { + /** @phpstan-ignore-next-line */ + $formatted = $this->client->formatAttributes($filtered); + } + + $this->client->putItem([ + 'TableName' => $this->table, + 'Item' => $formatted, + ]); + } + + /** + * @param mixed[] $record + * @return mixed[] + */ + protected function filterEmptyFields(array $record): array + { + return array_filter($record, function ($value) { + return !empty($value) || false === $value || 0 === $value; + }); + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new ScalarFormatter(self::DATE_FORMAT); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ElasticaHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ElasticaHandler.php new file mode 100644 index 0000000..fc92ca4 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/ElasticaHandler.php @@ -0,0 +1,129 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Elastica\Document; +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\ElasticaFormatter; +use Monolog\Logger; +use Elastica\Client; +use Elastica\Exception\ExceptionInterface; + +/** + * Elastic Search handler + * + * Usage example: + * + * $client = new \Elastica\Client(); + * $options = array( + * 'index' => 'elastic_index_name', + * 'type' => 'elastic_doc_type', Types have been removed in Elastica 7 + * ); + * $handler = new ElasticaHandler($client, $options); + * $log = new Logger('application'); + * $log->pushHandler($handler); + * + * @author Jelle Vink + */ +class ElasticaHandler extends AbstractProcessingHandler +{ + /** + * @var Client + */ + protected $client; + + /** + * @var mixed[] Handler config options + */ + protected $options = []; + + /** + * @param Client $client Elastica Client object + * @param mixed[] $options Handler configuration + */ + public function __construct(Client $client, array $options = [], $level = Logger::DEBUG, bool $bubble = true) + { + parent::__construct($level, $bubble); + $this->client = $client; + $this->options = array_merge( + [ + 'index' => 'monolog', // Elastic index name + 'type' => 'record', // Elastic document type + 'ignore_error' => false, // Suppress Elastica exceptions + ], + $options + ); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void + { + $this->bulkSend([$record['formatted']]); + } + + /** + * {@inheritDoc} + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + if ($formatter instanceof ElasticaFormatter) { + return parent::setFormatter($formatter); + } + + throw new \InvalidArgumentException('ElasticaHandler is only compatible with ElasticaFormatter'); + } + + /** + * @return mixed[] + */ + public function getOptions(): array + { + return $this->options; + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new ElasticaFormatter($this->options['index'], $this->options['type']); + } + + /** + * {@inheritDoc} + */ + public function handleBatch(array $records): void + { + $documents = $this->getFormatter()->formatBatch($records); + $this->bulkSend($documents); + } + + /** + * Use Elasticsearch bulk API to send list of documents + * + * @param Document[] $documents + * + * @throws \RuntimeException + */ + protected function bulkSend(array $documents): void + { + try { + $this->client->addDocuments($documents); + } catch (ExceptionInterface $e) { + if (!$this->options['ignore_error']) { + throw new \RuntimeException("Error sending messages to Elasticsearch", 0, $e); + } + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ElasticsearchHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ElasticsearchHandler.php new file mode 100644 index 0000000..e88375c --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/ElasticsearchHandler.php @@ -0,0 +1,218 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Elastic\Elasticsearch\Response\Elasticsearch; +use Throwable; +use RuntimeException; +use Monolog\Logger; +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\ElasticsearchFormatter; +use InvalidArgumentException; +use Elasticsearch\Common\Exceptions\RuntimeException as ElasticsearchRuntimeException; +use Elasticsearch\Client; +use Elastic\Elasticsearch\Exception\InvalidArgumentException as ElasticInvalidArgumentException; +use Elastic\Elasticsearch\Client as Client8; + +/** + * Elasticsearch handler + * + * @link https://www.elastic.co/guide/en/elasticsearch/client/php-api/current/index.html + * + * Simple usage example: + * + * $client = \Elasticsearch\ClientBuilder::create() + * ->setHosts($hosts) + * ->build(); + * + * $options = array( + * 'index' => 'elastic_index_name', + * 'type' => 'elastic_doc_type', + * ); + * $handler = new ElasticsearchHandler($client, $options); + * $log = new Logger('application'); + * $log->pushHandler($handler); + * + * @author Avtandil Kikabidze + */ +class ElasticsearchHandler extends AbstractProcessingHandler +{ + /** + * @var Client|Client8 + */ + protected $client; + + /** + * @var mixed[] Handler config options + */ + protected $options = []; + + /** + * @var bool + */ + private $needsType; + + /** + * @param Client|Client8 $client Elasticsearch Client object + * @param mixed[] $options Handler configuration + */ + public function __construct($client, array $options = [], $level = Logger::DEBUG, bool $bubble = true) + { + if (!$client instanceof Client && !$client instanceof Client8) { + throw new \TypeError('Elasticsearch\Client or Elastic\Elasticsearch\Client instance required'); + } + + parent::__construct($level, $bubble); + $this->client = $client; + $this->options = array_merge( + [ + 'index' => 'monolog', // Elastic index name + 'type' => '_doc', // Elastic document type + 'ignore_error' => false, // Suppress Elasticsearch exceptions + ], + $options + ); + + if ($client instanceof Client8 || $client::VERSION[0] === '7') { + $this->needsType = false; + // force the type to _doc for ES8/ES7 + $this->options['type'] = '_doc'; + } else { + $this->needsType = true; + } + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void + { + $this->bulkSend([$record['formatted']]); + } + + /** + * {@inheritDoc} + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + if ($formatter instanceof ElasticsearchFormatter) { + return parent::setFormatter($formatter); + } + + throw new InvalidArgumentException('ElasticsearchHandler is only compatible with ElasticsearchFormatter'); + } + + /** + * Getter options + * + * @return mixed[] + */ + public function getOptions(): array + { + return $this->options; + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new ElasticsearchFormatter($this->options['index'], $this->options['type']); + } + + /** + * {@inheritDoc} + */ + public function handleBatch(array $records): void + { + $documents = $this->getFormatter()->formatBatch($records); + $this->bulkSend($documents); + } + + /** + * Use Elasticsearch bulk API to send list of documents + * + * @param array[] $records Records + _index/_type keys + * @throws \RuntimeException + */ + protected function bulkSend(array $records): void + { + try { + $params = [ + 'body' => [], + ]; + + foreach ($records as $record) { + $params['body'][] = [ + 'index' => $this->needsType ? [ + '_index' => $record['_index'], + '_type' => $record['_type'], + ] : [ + '_index' => $record['_index'], + ], + ]; + unset($record['_index'], $record['_type']); + + $params['body'][] = $record; + } + + /** @var Elasticsearch */ + $responses = $this->client->bulk($params); + + if ($responses['errors'] === true) { + throw $this->createExceptionFromResponses($responses); + } + } catch (Throwable $e) { + if (! $this->options['ignore_error']) { + throw new RuntimeException('Error sending messages to Elasticsearch', 0, $e); + } + } + } + + /** + * Creates elasticsearch exception from responses array + * + * Only the first error is converted into an exception. + * + * @param mixed[]|Elasticsearch $responses returned by $this->client->bulk() + */ + protected function createExceptionFromResponses($responses): Throwable + { + foreach ($responses['items'] ?? [] as $item) { + if (isset($item['index']['error'])) { + return $this->createExceptionFromError($item['index']['error']); + } + } + + if (class_exists(ElasticInvalidArgumentException::class)) { + return new ElasticInvalidArgumentException('Elasticsearch failed to index one or more records.'); + } + + return new ElasticsearchRuntimeException('Elasticsearch failed to index one or more records.'); + } + + /** + * Creates elasticsearch exception from error array + * + * @param mixed[] $error + */ + protected function createExceptionFromError(array $error): Throwable + { + $previous = isset($error['caused_by']) ? $this->createExceptionFromError($error['caused_by']) : null; + + if (class_exists(ElasticInvalidArgumentException::class)) { + return new ElasticInvalidArgumentException($error['type'] . ': ' . $error['reason'], 0, $previous); + } + + return new ElasticsearchRuntimeException($error['type'] . ': ' . $error['reason'], 0, $previous); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php new file mode 100644 index 0000000..f2e2203 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\LineFormatter; +use Monolog\Formatter\FormatterInterface; +use Monolog\Logger; +use Monolog\Utils; + +/** + * Stores to PHP error_log() handler. + * + * @author Elan Ruusamäe + */ +class ErrorLogHandler extends AbstractProcessingHandler +{ + public const OPERATING_SYSTEM = 0; + public const SAPI = 4; + + /** @var int */ + protected $messageType; + /** @var bool */ + protected $expandNewlines; + + /** + * @param int $messageType Says where the error should go. + * @param bool $expandNewlines If set to true, newlines in the message will be expanded to be take multiple log entries + */ + public function __construct(int $messageType = self::OPERATING_SYSTEM, $level = Logger::DEBUG, bool $bubble = true, bool $expandNewlines = false) + { + parent::__construct($level, $bubble); + + if (false === in_array($messageType, self::getAvailableTypes(), true)) { + $message = sprintf('The given message type "%s" is not supported', print_r($messageType, true)); + + throw new \InvalidArgumentException($message); + } + + $this->messageType = $messageType; + $this->expandNewlines = $expandNewlines; + } + + /** + * @return int[] With all available types + */ + public static function getAvailableTypes(): array + { + return [ + self::OPERATING_SYSTEM, + self::SAPI, + ]; + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new LineFormatter('[%datetime%] %channel%.%level_name%: %message% %context% %extra%'); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void + { + if (!$this->expandNewlines) { + error_log((string) $record['formatted'], $this->messageType); + + return; + } + + $lines = preg_split('{[\r\n]+}', (string) $record['formatted']); + if ($lines === false) { + $pcreErrorCode = preg_last_error(); + throw new \RuntimeException('Failed to preg_split formatted string: ' . $pcreErrorCode . ' / '. Utils::pcreLastErrorMessage($pcreErrorCode)); + } + foreach ($lines as $line) { + error_log($line, $this->messageType); + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FallbackGroupHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FallbackGroupHandler.php new file mode 100644 index 0000000..d4e234c --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FallbackGroupHandler.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Throwable; + +/** + * Forwards records to at most one handler + * + * If a handler fails, the exception is suppressed and the record is forwarded to the next handler. + * + * As soon as one handler handles a record successfully, the handling stops there. + * + * @phpstan-import-type Record from \Monolog\Logger + */ +class FallbackGroupHandler extends GroupHandler +{ + /** + * {@inheritDoc} + */ + public function handle(array $record): bool + { + if ($this->processors) { + /** @var Record $record */ + $record = $this->processRecord($record); + } + foreach ($this->handlers as $handler) { + try { + $handler->handle($record); + break; + } catch (Throwable $e) { + // What throwable? + } + } + + return false === $this->bubble; + } + + /** + * {@inheritDoc} + */ + public function handleBatch(array $records): void + { + if ($this->processors) { + $processed = []; + foreach ($records as $record) { + $processed[] = $this->processRecord($record); + } + /** @var Record[] $records */ + $records = $processed; + } + + foreach ($this->handlers as $handler) { + try { + $handler->handleBatch($records); + break; + } catch (Throwable $e) { + // What throwable? + } + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FilterHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FilterHandler.php new file mode 100644 index 0000000..718f17e --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FilterHandler.php @@ -0,0 +1,212 @@ + + * + * 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\ResettableInterface; +use Monolog\Formatter\FormatterInterface; +use Psr\Log\LogLevel; + +/** + * Simple handler wrapper that filters records based on a list of levels + * + * It can be configured with an exact list of levels to allow, or a min/max level. + * + * @author Hennadiy Verkh + * @author Jordi Boggiano + * + * @phpstan-import-type Record from \Monolog\Logger + * @phpstan-import-type Level from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger + */ +class FilterHandler extends Handler implements ProcessableHandlerInterface, ResettableInterface, FormattableHandlerInterface +{ + use ProcessableHandlerTrait; + + /** + * Handler or factory callable($record, $this) + * + * @var callable|HandlerInterface + * @phpstan-var callable(?Record, HandlerInterface): HandlerInterface|HandlerInterface + */ + protected $handler; + + /** + * Minimum level for logs that are passed to handler + * + * @var int[] + * @phpstan-var array + */ + protected $acceptedLevels; + + /** + * Whether the messages that are handled can bubble up the stack or not + * + * @var bool + */ + protected $bubble; + + /** + * @psalm-param HandlerInterface|callable(?Record, HandlerInterface): HandlerInterface $handler + * + * @param callable|HandlerInterface $handler Handler or factory callable($record|null, $filterHandler). + * @param int|array $minLevelOrList A list of levels to accept or a minimum level if maxLevel is provided + * @param int|string $maxLevel Maximum level to accept, only used if $minLevelOrList is not an array + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * + * @phpstan-param Level|LevelName|LogLevel::*|array $minLevelOrList + * @phpstan-param Level|LevelName|LogLevel::* $maxLevel + */ + public function __construct($handler, $minLevelOrList = Logger::DEBUG, $maxLevel = Logger::EMERGENCY, bool $bubble = true) + { + $this->handler = $handler; + $this->bubble = $bubble; + $this->setAcceptedLevels($minLevelOrList, $maxLevel); + + if (!$this->handler instanceof HandlerInterface && !is_callable($this->handler)) { + throw new \RuntimeException("The given handler (".json_encode($this->handler).") is not a callable nor a Monolog\Handler\HandlerInterface object"); + } + } + + /** + * @phpstan-return array + */ + public function getAcceptedLevels(): array + { + return array_flip($this->acceptedLevels); + } + + /** + * @param int|string|array $minLevelOrList A list of levels to accept or a minimum level or level name if maxLevel is provided + * @param int|string $maxLevel Maximum level or level name to accept, only used if $minLevelOrList is not an array + * + * @phpstan-param Level|LevelName|LogLevel::*|array $minLevelOrList + * @phpstan-param Level|LevelName|LogLevel::* $maxLevel + */ + public function setAcceptedLevels($minLevelOrList = Logger::DEBUG, $maxLevel = Logger::EMERGENCY): self + { + if (is_array($minLevelOrList)) { + $acceptedLevels = array_map('Monolog\Logger::toMonologLevel', $minLevelOrList); + } else { + $minLevelOrList = Logger::toMonologLevel($minLevelOrList); + $maxLevel = Logger::toMonologLevel($maxLevel); + $acceptedLevels = array_values(array_filter(Logger::getLevels(), function ($level) use ($minLevelOrList, $maxLevel) { + return $level >= $minLevelOrList && $level <= $maxLevel; + })); + } + $this->acceptedLevels = array_flip($acceptedLevels); + + return $this; + } + + /** + * {@inheritDoc} + */ + public function isHandling(array $record): bool + { + return isset($this->acceptedLevels[$record['level']]); + } + + /** + * {@inheritDoc} + */ + public function handle(array $record): bool + { + if (!$this->isHandling($record)) { + return false; + } + + if ($this->processors) { + /** @var Record $record */ + $record = $this->processRecord($record); + } + + $this->getHandler($record)->handle($record); + + return false === $this->bubble; + } + + /** + * {@inheritDoc} + */ + public function handleBatch(array $records): void + { + $filtered = []; + foreach ($records as $record) { + if ($this->isHandling($record)) { + $filtered[] = $record; + } + } + + if (count($filtered) > 0) { + $this->getHandler($filtered[count($filtered) - 1])->handleBatch($filtered); + } + } + + /** + * Return the nested handler + * + * If the handler was provided as a factory callable, this will trigger the handler's instantiation. + * + * @return HandlerInterface + * + * @phpstan-param Record $record + */ + public function getHandler(array $record = null) + { + if (!$this->handler instanceof HandlerInterface) { + $this->handler = ($this->handler)($record, $this); + if (!$this->handler instanceof HandlerInterface) { + throw new \RuntimeException("The factory callable should return a HandlerInterface"); + } + } + + return $this->handler; + } + + /** + * {@inheritDoc} + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + $handler = $this->getHandler(); + if ($handler instanceof FormattableHandlerInterface) { + $handler->setFormatter($formatter); + + return $this; + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($handler).' does not support formatters.'); + } + + /** + * {@inheritDoc} + */ + public function getFormatter(): FormatterInterface + { + $handler = $this->getHandler(); + if ($handler instanceof FormattableHandlerInterface) { + return $handler->getFormatter(); + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($handler).' does not support formatters.'); + } + + public function reset() + { + $this->resetProcessors(); + + if ($this->getHandler() instanceof ResettableInterface) { + $this->getHandler()->reset(); + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php new file mode 100644 index 0000000..0aa5607 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.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\Handler\FingersCrossed; + +/** + * Interface for activation strategies for the FingersCrossedHandler. + * + * @author Johannes M. Schmitt + * + * @phpstan-import-type Record from \Monolog\Logger + */ +interface ActivationStrategyInterface +{ + /** + * Returns whether the given record activates the handler. + * + * @phpstan-param Record $record + */ + public function isHandlerActivated(array $record): bool; +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php new file mode 100644 index 0000000..7b9abb5 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler\FingersCrossed; + +use Monolog\Logger; +use Psr\Log\LogLevel; + +/** + * Channel and Error level based monolog activation strategy. Allows to trigger activation + * based on level per channel. e.g. trigger activation on level 'ERROR' by default, except + * for records of the 'sql' channel; those should trigger activation on level 'WARN'. + * + * Example: + * + * + * $activationStrategy = new ChannelLevelActivationStrategy( + * Logger::CRITICAL, + * array( + * 'request' => Logger::ALERT, + * 'sensitive' => Logger::ERROR, + * ) + * ); + * $handler = new FingersCrossedHandler(new StreamHandler('php://stderr'), $activationStrategy); + * + * + * @author Mike Meessen + * + * @phpstan-import-type Record from \Monolog\Logger + * @phpstan-import-type Level from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger + */ +class ChannelLevelActivationStrategy implements ActivationStrategyInterface +{ + /** + * @var Level + */ + private $defaultActionLevel; + + /** + * @var array + */ + private $channelToActionLevel; + + /** + * @param int|string $defaultActionLevel The default action level to be used if the record's category doesn't match any + * @param array $channelToActionLevel An array that maps channel names to action levels. + * + * @phpstan-param array $channelToActionLevel + * @phpstan-param Level|LevelName|LogLevel::* $defaultActionLevel + */ + public function __construct($defaultActionLevel, array $channelToActionLevel = []) + { + $this->defaultActionLevel = Logger::toMonologLevel($defaultActionLevel); + $this->channelToActionLevel = array_map('Monolog\Logger::toMonologLevel', $channelToActionLevel); + } + + /** + * @phpstan-param Record $record + */ + public function isHandlerActivated(array $record): bool + { + if (isset($this->channelToActionLevel[$record['channel']])) { + return $record['level'] >= $this->channelToActionLevel[$record['channel']]; + } + + return $record['level'] >= $this->defaultActionLevel; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php new file mode 100644 index 0000000..5ec88ea --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler\FingersCrossed; + +use Monolog\Logger; +use Psr\Log\LogLevel; + +/** + * Error level based activation strategy. + * + * @author Johannes M. Schmitt + * + * @phpstan-import-type Level from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger + */ +class ErrorLevelActivationStrategy implements ActivationStrategyInterface +{ + /** + * @var Level + */ + private $actionLevel; + + /** + * @param int|string $actionLevel Level or name or value + * + * @phpstan-param Level|LevelName|LogLevel::* $actionLevel + */ + public function __construct($actionLevel) + { + $this->actionLevel = Logger::toMonologLevel($actionLevel); + } + + public function isHandlerActivated(array $record): bool + { + return $record['level'] >= $this->actionLevel; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php new file mode 100644 index 0000000..0627b44 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php @@ -0,0 +1,252 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy; +use Monolog\Handler\FingersCrossed\ActivationStrategyInterface; +use Monolog\Logger; +use Monolog\ResettableInterface; +use Monolog\Formatter\FormatterInterface; +use Psr\Log\LogLevel; + +/** + * Buffers all records until a certain level is reached + * + * The advantage of this approach is that you don't get any clutter in your log files. + * Only requests which actually trigger an error (or whatever your actionLevel is) will be + * in the logs, but they will contain all records, not only those above the level threshold. + * + * You can then have a passthruLevel as well which means that at the end of the request, + * even if it did not get activated, it will still send through log records of e.g. at least a + * warning level. + * + * You can find the various activation strategies in the + * Monolog\Handler\FingersCrossed\ namespace. + * + * @author Jordi Boggiano + * + * @phpstan-import-type Record from \Monolog\Logger + * @phpstan-import-type Level from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger + */ +class FingersCrossedHandler extends Handler implements ProcessableHandlerInterface, ResettableInterface, FormattableHandlerInterface +{ + use ProcessableHandlerTrait; + + /** + * @var callable|HandlerInterface + * @phpstan-var callable(?Record, HandlerInterface): HandlerInterface|HandlerInterface + */ + protected $handler; + /** @var ActivationStrategyInterface */ + protected $activationStrategy; + /** @var bool */ + protected $buffering = true; + /** @var int */ + protected $bufferSize; + /** @var Record[] */ + protected $buffer = []; + /** @var bool */ + protected $stopBuffering; + /** + * @var ?int + * @phpstan-var ?Level + */ + protected $passthruLevel; + /** @var bool */ + protected $bubble; + + /** + * @psalm-param HandlerInterface|callable(?Record, HandlerInterface): HandlerInterface $handler + * + * @param callable|HandlerInterface $handler Handler or factory callable($record|null, $fingersCrossedHandler). + * @param int|string|ActivationStrategyInterface $activationStrategy Strategy which determines when this handler takes action, or a level name/value at which the handler is activated + * @param int $bufferSize How many entries should be buffered at most, beyond that the oldest items are removed from the buffer. + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param bool $stopBuffering Whether the handler should stop buffering after being triggered (default true) + * @param int|string $passthruLevel Minimum level to always flush to handler on close, even if strategy not triggered + * + * @phpstan-param Level|LevelName|LogLevel::* $passthruLevel + * @phpstan-param Level|LevelName|LogLevel::*|ActivationStrategyInterface $activationStrategy + */ + public function __construct($handler, $activationStrategy = null, int $bufferSize = 0, bool $bubble = true, bool $stopBuffering = true, $passthruLevel = null) + { + if (null === $activationStrategy) { + $activationStrategy = new ErrorLevelActivationStrategy(Logger::WARNING); + } + + // convert simple int activationStrategy to an object + if (!$activationStrategy instanceof ActivationStrategyInterface) { + $activationStrategy = new ErrorLevelActivationStrategy($activationStrategy); + } + + $this->handler = $handler; + $this->activationStrategy = $activationStrategy; + $this->bufferSize = $bufferSize; + $this->bubble = $bubble; + $this->stopBuffering = $stopBuffering; + + if ($passthruLevel !== null) { + $this->passthruLevel = Logger::toMonologLevel($passthruLevel); + } + + if (!$this->handler instanceof HandlerInterface && !is_callable($this->handler)) { + throw new \RuntimeException("The given handler (".json_encode($this->handler).") is not a callable nor a Monolog\Handler\HandlerInterface object"); + } + } + + /** + * {@inheritDoc} + */ + public function isHandling(array $record): bool + { + return true; + } + + /** + * Manually activate this logger regardless of the activation strategy + */ + public function activate(): void + { + if ($this->stopBuffering) { + $this->buffering = false; + } + + $this->getHandler(end($this->buffer) ?: null)->handleBatch($this->buffer); + $this->buffer = []; + } + + /** + * {@inheritDoc} + */ + public function handle(array $record): bool + { + if ($this->processors) { + /** @var Record $record */ + $record = $this->processRecord($record); + } + + if ($this->buffering) { + $this->buffer[] = $record; + if ($this->bufferSize > 0 && count($this->buffer) > $this->bufferSize) { + array_shift($this->buffer); + } + if ($this->activationStrategy->isHandlerActivated($record)) { + $this->activate(); + } + } else { + $this->getHandler($record)->handle($record); + } + + return false === $this->bubble; + } + + /** + * {@inheritDoc} + */ + public function close(): void + { + $this->flushBuffer(); + + $this->getHandler()->close(); + } + + public function reset() + { + $this->flushBuffer(); + + $this->resetProcessors(); + + if ($this->getHandler() instanceof ResettableInterface) { + $this->getHandler()->reset(); + } + } + + /** + * Clears the buffer without flushing any messages down to the wrapped handler. + * + * It also resets the handler to its initial buffering state. + */ + public function clear(): void + { + $this->buffer = []; + $this->reset(); + } + + /** + * Resets the state of the handler. Stops forwarding records to the wrapped handler. + */ + private function flushBuffer(): void + { + if (null !== $this->passthruLevel) { + $level = $this->passthruLevel; + $this->buffer = array_filter($this->buffer, function ($record) use ($level) { + return $record['level'] >= $level; + }); + if (count($this->buffer) > 0) { + $this->getHandler(end($this->buffer))->handleBatch($this->buffer); + } + } + + $this->buffer = []; + $this->buffering = true; + } + + /** + * Return the nested handler + * + * If the handler was provided as a factory callable, this will trigger the handler's instantiation. + * + * @return HandlerInterface + * + * @phpstan-param Record $record + */ + public function getHandler(array $record = null) + { + if (!$this->handler instanceof HandlerInterface) { + $this->handler = ($this->handler)($record, $this); + if (!$this->handler instanceof HandlerInterface) { + throw new \RuntimeException("The factory callable should return a HandlerInterface"); + } + } + + return $this->handler; + } + + /** + * {@inheritDoc} + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + $handler = $this->getHandler(); + if ($handler instanceof FormattableHandlerInterface) { + $handler->setFormatter($formatter); + + return $this; + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($handler).' does not support formatters.'); + } + + /** + * {@inheritDoc} + */ + public function getFormatter(): FormatterInterface + { + $handler = $this->getHandler(); + if ($handler instanceof FormattableHandlerInterface) { + return $handler->getFormatter(); + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($handler).' does not support formatters.'); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php new file mode 100644 index 0000000..72718de --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php @@ -0,0 +1,180 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\WildfireFormatter; +use Monolog\Formatter\FormatterInterface; + +/** + * Simple FirePHP Handler (http://www.firephp.org/), which uses the Wildfire protocol. + * + * @author Eric Clemmons (@ericclemmons) + * + * @phpstan-import-type FormattedRecord from AbstractProcessingHandler + */ +class FirePHPHandler extends AbstractProcessingHandler +{ + use WebRequestRecognizerTrait; + + /** + * WildFire JSON header message format + */ + protected const PROTOCOL_URI = 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2'; + + /** + * FirePHP structure for parsing messages & their presentation + */ + protected const STRUCTURE_URI = 'http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1'; + + /** + * Must reference a "known" plugin, otherwise headers won't display in FirePHP + */ + protected const PLUGIN_URI = 'http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/0.3'; + + /** + * Header prefix for Wildfire to recognize & parse headers + */ + protected const HEADER_PREFIX = 'X-Wf'; + + /** + * Whether or not Wildfire vendor-specific headers have been generated & sent yet + * @var bool + */ + protected static $initialized = false; + + /** + * Shared static message index between potentially multiple handlers + * @var int + */ + protected static $messageIndex = 1; + + /** @var bool */ + protected static $sendHeaders = true; + + /** + * Base header creation function used by init headers & record headers + * + * @param array $meta Wildfire Plugin, Protocol & Structure Indexes + * @param string $message Log message + * + * @return array Complete header string ready for the client as key and message as value + * + * @phpstan-return non-empty-array + */ + protected function createHeader(array $meta, string $message): array + { + $header = sprintf('%s-%s', static::HEADER_PREFIX, join('-', $meta)); + + return [$header => $message]; + } + + /** + * Creates message header from record + * + * @return array + * + * @phpstan-return non-empty-array + * + * @see createHeader() + * + * @phpstan-param FormattedRecord $record + */ + protected function createRecordHeader(array $record): array + { + // Wildfire is extensible to support multiple protocols & plugins in a single request, + // but we're not taking advantage of that (yet), so we're using "1" for simplicity's sake. + return $this->createHeader( + [1, 1, 1, self::$messageIndex++], + $record['formatted'] + ); + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new WildfireFormatter(); + } + + /** + * Wildfire initialization headers to enable message parsing + * + * @see createHeader() + * @see sendHeader() + * + * @return array + */ + protected function getInitHeaders(): array + { + // Initial payload consists of required headers for Wildfire + return array_merge( + $this->createHeader(['Protocol', 1], static::PROTOCOL_URI), + $this->createHeader([1, 'Structure', 1], static::STRUCTURE_URI), + $this->createHeader([1, 'Plugin', 1], static::PLUGIN_URI) + ); + } + + /** + * Send header string to the client + */ + protected function sendHeader(string $header, string $content): void + { + if (!headers_sent() && self::$sendHeaders) { + header(sprintf('%s: %s', $header, $content)); + } + } + + /** + * Creates & sends header for a record, ensuring init headers have been sent prior + * + * @see sendHeader() + * @see sendInitHeaders() + */ + protected function write(array $record): void + { + if (!self::$sendHeaders || !$this->isWebRequest()) { + return; + } + + // WildFire-specific headers must be sent prior to any messages + if (!self::$initialized) { + self::$initialized = true; + + self::$sendHeaders = $this->headersAccepted(); + if (!self::$sendHeaders) { + return; + } + + foreach ($this->getInitHeaders() as $header => $content) { + $this->sendHeader($header, $content); + } + } + + $header = $this->createRecordHeader($record); + if (trim(current($header)) !== '') { + $this->sendHeader(key($header), current($header)); + } + } + + /** + * Verifies if the headers are accepted by the current user agent + */ + protected function headersAccepted(): bool + { + if (!empty($_SERVER['HTTP_USER_AGENT']) && preg_match('{\bFirePHP/\d+\.\d+\b}', $_SERVER['HTTP_USER_AGENT'])) { + return true; + } + + return isset($_SERVER['HTTP_X_FIREPHP_VERSION']); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php new file mode 100644 index 0000000..85c95b9 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php @@ -0,0 +1,135 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\LineFormatter; +use Monolog\Logger; + +/** + * Sends logs to Fleep.io using Webhook integrations + * + * You'll need a Fleep.io account to use this handler. + * + * @see https://fleep.io/integrations/webhooks/ Fleep Webhooks Documentation + * @author Ando Roots + * + * @phpstan-import-type FormattedRecord from AbstractProcessingHandler + */ +class FleepHookHandler extends SocketHandler +{ + protected const FLEEP_HOST = 'fleep.io'; + + protected const FLEEP_HOOK_URI = '/hook/'; + + /** + * @var string Webhook token (specifies the conversation where logs are sent) + */ + protected $token; + + /** + * Construct a new Fleep.io Handler. + * + * For instructions on how to create a new web hook in your conversations + * see https://fleep.io/integrations/webhooks/ + * + * @param string $token Webhook token + * @throws MissingExtensionException + */ + public function __construct( + string $token, + $level = Logger::DEBUG, + bool $bubble = true, + bool $persistent = false, + float $timeout = 0.0, + float $writingTimeout = 10.0, + ?float $connectionTimeout = null, + ?int $chunkSize = null + ) { + if (!extension_loaded('openssl')) { + throw new MissingExtensionException('The OpenSSL PHP extension is required to use the FleepHookHandler'); + } + + $this->token = $token; + + $connectionString = 'ssl://' . static::FLEEP_HOST . ':443'; + parent::__construct( + $connectionString, + $level, + $bubble, + $persistent, + $timeout, + $writingTimeout, + $connectionTimeout, + $chunkSize + ); + } + + /** + * Returns the default formatter to use with this handler + * + * Overloaded to remove empty context and extra arrays from the end of the log message. + * + * @return LineFormatter + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new LineFormatter(null, null, true, true); + } + + /** + * Handles a log record + */ + public function write(array $record): void + { + parent::write($record); + $this->closeSocket(); + } + + /** + * {@inheritDoc} + */ + protected function generateDataStream(array $record): string + { + $content = $this->buildContent($record); + + return $this->buildHeader($content) . $content; + } + + /** + * Builds the header of the API Call + */ + private function buildHeader(string $content): string + { + $header = "POST " . static::FLEEP_HOOK_URI . $this->token . " HTTP/1.1\r\n"; + $header .= "Host: " . static::FLEEP_HOST . "\r\n"; + $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; + $header .= "Content-Length: " . strlen($content) . "\r\n"; + $header .= "\r\n"; + + return $header; + } + + /** + * Builds the body of API call + * + * @phpstan-param FormattedRecord $record + */ + private function buildContent(array $record): string + { + $dataArray = [ + 'message' => $record['formatted'], + ]; + + return http_build_query($dataArray); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php new file mode 100644 index 0000000..5715d58 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php @@ -0,0 +1,133 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Utils; +use Monolog\Formatter\FlowdockFormatter; +use Monolog\Formatter\FormatterInterface; + +/** + * Sends notifications through the Flowdock push API + * + * This must be configured with a FlowdockFormatter instance via setFormatter() + * + * Notes: + * API token - Flowdock API token + * + * @author Dominik Liebler + * @see https://www.flowdock.com/api/push + * + * @phpstan-import-type FormattedRecord from AbstractProcessingHandler + * @deprecated Since 2.9.0 and 3.3.0, Flowdock was shutdown we will thus drop this handler in Monolog 4 + */ +class FlowdockHandler extends SocketHandler +{ + /** + * @var string + */ + protected $apiToken; + + /** + * @throws MissingExtensionException if OpenSSL is missing + */ + public function __construct( + string $apiToken, + $level = Logger::DEBUG, + bool $bubble = true, + bool $persistent = false, + float $timeout = 0.0, + float $writingTimeout = 10.0, + ?float $connectionTimeout = null, + ?int $chunkSize = null + ) { + if (!extension_loaded('openssl')) { + throw new MissingExtensionException('The OpenSSL PHP extension is required to use the FlowdockHandler'); + } + + parent::__construct( + 'ssl://api.flowdock.com:443', + $level, + $bubble, + $persistent, + $timeout, + $writingTimeout, + $connectionTimeout, + $chunkSize + ); + $this->apiToken = $apiToken; + } + + /** + * {@inheritDoc} + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + if (!$formatter instanceof FlowdockFormatter) { + throw new \InvalidArgumentException('The FlowdockHandler requires an instance of Monolog\Formatter\FlowdockFormatter to function correctly'); + } + + return parent::setFormatter($formatter); + } + + /** + * Gets the default formatter. + */ + protected function getDefaultFormatter(): FormatterInterface + { + throw new \InvalidArgumentException('The FlowdockHandler must be configured (via setFormatter) with an instance of Monolog\Formatter\FlowdockFormatter to function correctly'); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void + { + parent::write($record); + + $this->closeSocket(); + } + + /** + * {@inheritDoc} + */ + protected function generateDataStream(array $record): string + { + $content = $this->buildContent($record); + + return $this->buildHeader($content) . $content; + } + + /** + * Builds the body of API call + * + * @phpstan-param FormattedRecord $record + */ + private function buildContent(array $record): string + { + return Utils::jsonEncode($record['formatted']['flowdock']); + } + + /** + * Builds the header of the API Call + */ + private function buildHeader(string $content): string + { + $header = "POST /v1/messages/team_inbox/" . $this->apiToken . " HTTP/1.1\r\n"; + $header .= "Host: api.flowdock.com\r\n"; + $header .= "Content-Type: application/json\r\n"; + $header .= "Content-Length: " . strlen($content) . "\r\n"; + $header .= "\r\n"; + + return $header; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerInterface.php b/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerInterface.php new file mode 100644 index 0000000..fc1693c --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerInterface.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; + +/** + * Interface to describe loggers that have a formatter + * + * @author Jordi Boggiano + */ +interface FormattableHandlerInterface +{ + /** + * Sets the formatter. + * + * @param FormatterInterface $formatter + * @return HandlerInterface self + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface; + + /** + * Gets the formatter. + * + * @return FormatterInterface + */ + public function getFormatter(): FormatterInterface; +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerTrait.php b/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerTrait.php new file mode 100644 index 0000000..b60bdce --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerTrait.php @@ -0,0 +1,60 @@ + + * + * 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\FormatterInterface; +use Monolog\Formatter\LineFormatter; + +/** + * Helper trait for implementing FormattableInterface + * + * @author Jordi Boggiano + */ +trait FormattableHandlerTrait +{ + /** + * @var ?FormatterInterface + */ + protected $formatter; + + /** + * {@inheritDoc} + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + $this->formatter = $formatter; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function getFormatter(): FormatterInterface + { + if (!$this->formatter) { + $this->formatter = $this->getDefaultFormatter(); + } + + return $this->formatter; + } + + /** + * Gets the default formatter. + * + * Overwrite this if the LineFormatter is not a good default for your handler. + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new LineFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/GelfHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/GelfHandler.php new file mode 100644 index 0000000..4ff26c4 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/GelfHandler.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Gelf\PublisherInterface; +use Monolog\Logger; +use Monolog\Formatter\GelfMessageFormatter; +use Monolog\Formatter\FormatterInterface; + +/** + * Handler to send messages to a Graylog2 (http://www.graylog2.org) server + * + * @author Matt Lehner + * @author Benjamin Zikarsky + */ +class GelfHandler extends AbstractProcessingHandler +{ + /** + * @var PublisherInterface the publisher object that sends the message to the server + */ + protected $publisher; + + /** + * @param PublisherInterface $publisher a gelf publisher object + */ + public function __construct(PublisherInterface $publisher, $level = Logger::DEBUG, bool $bubble = true) + { + parent::__construct($level, $bubble); + + $this->publisher = $publisher; + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void + { + $this->publisher->publish($record['formatted']); + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new GelfMessageFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/GroupHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/GroupHandler.php new file mode 100644 index 0000000..3c9dc4b --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/GroupHandler.php @@ -0,0 +1,132 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; +use Monolog\ResettableInterface; + +/** + * Forwards records to multiple handlers + * + * @author Lenar Lõhmus + * + * @phpstan-import-type Record from \Monolog\Logger + */ +class GroupHandler extends Handler implements ProcessableHandlerInterface, ResettableInterface +{ + use ProcessableHandlerTrait; + + /** @var HandlerInterface[] */ + protected $handlers; + /** @var bool */ + protected $bubble; + + /** + * @param HandlerInterface[] $handlers Array of Handlers. + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct(array $handlers, bool $bubble = true) + { + foreach ($handlers as $handler) { + if (!$handler instanceof HandlerInterface) { + throw new \InvalidArgumentException('The first argument of the GroupHandler must be an array of HandlerInterface instances.'); + } + } + + $this->handlers = $handlers; + $this->bubble = $bubble; + } + + /** + * {@inheritDoc} + */ + public function isHandling(array $record): bool + { + foreach ($this->handlers as $handler) { + if ($handler->isHandling($record)) { + return true; + } + } + + return false; + } + + /** + * {@inheritDoc} + */ + public function handle(array $record): bool + { + if ($this->processors) { + /** @var Record $record */ + $record = $this->processRecord($record); + } + + foreach ($this->handlers as $handler) { + $handler->handle($record); + } + + return false === $this->bubble; + } + + /** + * {@inheritDoc} + */ + public function handleBatch(array $records): void + { + if ($this->processors) { + $processed = []; + foreach ($records as $record) { + $processed[] = $this->processRecord($record); + } + /** @var Record[] $records */ + $records = $processed; + } + + foreach ($this->handlers as $handler) { + $handler->handleBatch($records); + } + } + + public function reset() + { + $this->resetProcessors(); + + foreach ($this->handlers as $handler) { + if ($handler instanceof ResettableInterface) { + $handler->reset(); + } + } + } + + public function close(): void + { + parent::close(); + + foreach ($this->handlers as $handler) { + $handler->close(); + } + } + + /** + * {@inheritDoc} + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + foreach ($this->handlers as $handler) { + if ($handler instanceof FormattableHandlerInterface) { + $handler->setFormatter($formatter); + } + } + + return $this; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/Handler.php b/vendor/monolog/monolog/src/Monolog/Handler/Handler.php new file mode 100644 index 0000000..34b4935 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/Handler.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\Handler; + +/** + * Base Handler class providing basic close() support as well as handleBatch + * + * @author Jordi Boggiano + */ +abstract class Handler implements HandlerInterface +{ + /** + * {@inheritDoc} + */ + public function handleBatch(array $records): void + { + foreach ($records as $record) { + $this->handle($record); + } + } + + /** + * {@inheritDoc} + */ + public function close(): void + { + } + + public function __destruct() + { + try { + $this->close(); + } catch (\Throwable $e) { + // do nothing + } + } + + public function __sleep() + { + $this->close(); + + $reflClass = new \ReflectionClass($this); + + $keys = []; + foreach ($reflClass->getProperties() as $reflProp) { + if (!$reflProp->isStatic()) { + $keys[] = $reflProp->getName(); + } + } + + return $keys; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php b/vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php new file mode 100644 index 0000000..affcc51 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.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; + +/** + * Interface that all Monolog Handlers must implement + * + * @author Jordi Boggiano + * + * @phpstan-import-type Record from \Monolog\Logger + * @phpstan-import-type Level from \Monolog\Logger + */ +interface HandlerInterface +{ + /** + * Checks whether the given record will be handled by this handler. + * + * This is mostly done for performance reasons, to avoid calling processors for nothing. + * + * Handlers should still check the record levels within handle(), returning false in isHandling() + * is no guarantee that handle() will not be called, and isHandling() might not be called + * for a given record. + * + * @param array $record Partial log record containing only a level key + * + * @return bool + * + * @phpstan-param array{level: Level} $record + */ + public function isHandling(array $record): bool; + + /** + * Handles a record. + * + * All records may be passed to this method, and the handler should discard + * those that it does not want to handle. + * + * The return value of this function controls the bubbling process of the handler stack. + * Unless the bubbling is interrupted (by returning true), the Logger class will keep on + * calling further handlers in the stack with a given log record. + * + * @param array $record The record to handle + * @return bool true means that this handler handled the record, and that bubbling is not permitted. + * false means the record was either not processed or that this handler allows bubbling. + * + * @phpstan-param Record $record + */ + public function handle(array $record): bool; + + /** + * Handles a set of records at once. + * + * @param array $records The records to handle (an array of record arrays) + * + * @phpstan-param Record[] $records + */ + public function handleBatch(array $records): void; + + /** + * Closes the handler. + * + * Ends a log cycle and frees all resources used by the handler. + * + * Closing a Handler means flushing all buffers and freeing any open resources/handles. + * + * Implementations have to be idempotent (i.e. it should be possible to call close several times without breakage) + * and ideally handlers should be able to reopen themselves on handle() after they have been closed. + * + * This is useful at the end of a request and will be called automatically when the object + * is destroyed if you extend Monolog\Handler\Handler. + * + * If you are thinking of calling this method yourself, most likely you should be + * calling ResettableInterface::reset instead. Have a look. + */ + public function close(): void; +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php b/vendor/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php new file mode 100644 index 0000000..d4351b9 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/HandlerWrapper.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\ResettableInterface; +use Monolog\Formatter\FormatterInterface; + +/** + * This simple wrapper class can be used to extend handlers functionality. + * + * Example: A custom filtering that can be applied to any handler. + * + * Inherit from this class and override handle() like this: + * + * public function handle(array $record) + * { + * if ($record meets certain conditions) { + * return false; + * } + * return $this->handler->handle($record); + * } + * + * @author Alexey Karapetov + */ +class HandlerWrapper implements HandlerInterface, ProcessableHandlerInterface, FormattableHandlerInterface, ResettableInterface +{ + /** + * @var HandlerInterface + */ + protected $handler; + + public function __construct(HandlerInterface $handler) + { + $this->handler = $handler; + } + + /** + * {@inheritDoc} + */ + public function isHandling(array $record): bool + { + return $this->handler->isHandling($record); + } + + /** + * {@inheritDoc} + */ + public function handle(array $record): bool + { + return $this->handler->handle($record); + } + + /** + * {@inheritDoc} + */ + public function handleBatch(array $records): void + { + $this->handler->handleBatch($records); + } + + /** + * {@inheritDoc} + */ + public function close(): void + { + $this->handler->close(); + } + + /** + * {@inheritDoc} + */ + public function pushProcessor(callable $callback): HandlerInterface + { + if ($this->handler instanceof ProcessableHandlerInterface) { + $this->handler->pushProcessor($callback); + + return $this; + } + + throw new \LogicException('The wrapped handler does not implement ' . ProcessableHandlerInterface::class); + } + + /** + * {@inheritDoc} + */ + public function popProcessor(): callable + { + if ($this->handler instanceof ProcessableHandlerInterface) { + return $this->handler->popProcessor(); + } + + throw new \LogicException('The wrapped handler does not implement ' . ProcessableHandlerInterface::class); + } + + /** + * {@inheritDoc} + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + if ($this->handler instanceof FormattableHandlerInterface) { + $this->handler->setFormatter($formatter); + + return $this; + } + + throw new \LogicException('The wrapped handler does not implement ' . FormattableHandlerInterface::class); + } + + /** + * {@inheritDoc} + */ + public function getFormatter(): FormatterInterface + { + if ($this->handler instanceof FormattableHandlerInterface) { + return $this->handler->getFormatter(); + } + + throw new \LogicException('The wrapped handler does not implement ' . FormattableHandlerInterface::class); + } + + public function reset() + { + if ($this->handler instanceof ResettableInterface) { + $this->handler->reset(); + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php new file mode 100644 index 0000000..000ccea --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Utils; + +/** + * IFTTTHandler uses cURL to trigger IFTTT Maker actions + * + * Register a secret key and trigger/event name at https://ifttt.com/maker + * + * value1 will be the channel from monolog's Logger constructor, + * value2 will be the level name (ERROR, WARNING, ..) + * value3 will be the log record's message + * + * @author Nehal Patel + */ +class IFTTTHandler extends AbstractProcessingHandler +{ + /** @var string */ + private $eventName; + /** @var string */ + private $secretKey; + + /** + * @param string $eventName The name of the IFTTT Maker event that should be triggered + * @param string $secretKey A valid IFTTT secret key + */ + public function __construct(string $eventName, string $secretKey, $level = Logger::ERROR, bool $bubble = true) + { + if (!extension_loaded('curl')) { + throw new MissingExtensionException('The curl extension is needed to use the IFTTTHandler'); + } + + $this->eventName = $eventName; + $this->secretKey = $secretKey; + + parent::__construct($level, $bubble); + } + + /** + * {@inheritDoc} + */ + public function write(array $record): void + { + $postData = [ + "value1" => $record["channel"], + "value2" => $record["level_name"], + "value3" => $record["message"], + ]; + $postString = Utils::jsonEncode($postData); + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, "https://maker.ifttt.com/trigger/" . $this->eventName . "/with/key/" . $this->secretKey); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $postString); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + "Content-Type: application/json", + ]); + + Curl\Util::execute($ch); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/InsightOpsHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/InsightOpsHandler.php new file mode 100644 index 0000000..71f64a2 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/InsightOpsHandler.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\Logger; + +/** + * Inspired on LogEntriesHandler. + * + * @author Robert Kaufmann III + * @author Gabriel Machado + */ +class InsightOpsHandler extends SocketHandler +{ + /** + * @var string + */ + protected $logToken; + + /** + * @param string $token Log token supplied by InsightOps + * @param string $region Region where InsightOps account is hosted. Could be 'us' or 'eu'. + * @param bool $useSSL Whether or not SSL encryption should be used + * + * @throws MissingExtensionException If SSL encryption is set to true and OpenSSL is missing + */ + public function __construct( + string $token, + string $region = 'us', + bool $useSSL = true, + $level = Logger::DEBUG, + bool $bubble = true, + bool $persistent = false, + float $timeout = 0.0, + float $writingTimeout = 10.0, + ?float $connectionTimeout = null, + ?int $chunkSize = null + ) { + if ($useSSL && !extension_loaded('openssl')) { + throw new MissingExtensionException('The OpenSSL PHP plugin is required to use SSL encrypted connection for InsightOpsHandler'); + } + + $endpoint = $useSSL + ? 'ssl://' . $region . '.data.logs.insight.rapid7.com:443' + : $region . '.data.logs.insight.rapid7.com:80'; + + parent::__construct( + $endpoint, + $level, + $bubble, + $persistent, + $timeout, + $writingTimeout, + $connectionTimeout, + $chunkSize + ); + $this->logToken = $token; + } + + /** + * {@inheritDoc} + */ + protected function generateDataStream(array $record): string + { + return $this->logToken . ' ' . $record['formatted']; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php new file mode 100644 index 0000000..25fcd15 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * @author Robert Kaufmann III + */ +class LogEntriesHandler extends SocketHandler +{ + /** + * @var string + */ + protected $logToken; + + /** + * @param string $token Log token supplied by LogEntries + * @param bool $useSSL Whether or not SSL encryption should be used. + * @param string $host Custom hostname to send the data to if needed + * + * @throws MissingExtensionException If SSL encryption is set to true and OpenSSL is missing + */ + public function __construct( + string $token, + bool $useSSL = true, + $level = Logger::DEBUG, + bool $bubble = true, + string $host = 'data.logentries.com', + bool $persistent = false, + float $timeout = 0.0, + float $writingTimeout = 10.0, + ?float $connectionTimeout = null, + ?int $chunkSize = null + ) { + if ($useSSL && !extension_loaded('openssl')) { + throw new MissingExtensionException('The OpenSSL PHP plugin is required to use SSL encrypted connection for LogEntriesHandler'); + } + + $endpoint = $useSSL ? 'ssl://' . $host . ':443' : $host . ':80'; + parent::__construct( + $endpoint, + $level, + $bubble, + $persistent, + $timeout, + $writingTimeout, + $connectionTimeout, + $chunkSize + ); + $this->logToken = $token; + } + + /** + * {@inheritDoc} + */ + protected function generateDataStream(array $record): string + { + return $this->logToken . ' ' . $record['formatted']; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/LogglyHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/LogglyHandler.php new file mode 100644 index 0000000..6d13db3 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/LogglyHandler.php @@ -0,0 +1,160 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\LogglyFormatter; +use function array_key_exists; +use CurlHandle; + +/** + * Sends errors to Loggly. + * + * @author Przemek Sobstel + * @author Adam Pancutt + * @author Gregory Barchard + */ +class LogglyHandler extends AbstractProcessingHandler +{ + protected const HOST = 'logs-01.loggly.com'; + protected const ENDPOINT_SINGLE = 'inputs'; + protected const ENDPOINT_BATCH = 'bulk'; + + /** + * Caches the curl handlers for every given endpoint. + * + * @var resource[]|CurlHandle[] + */ + protected $curlHandlers = []; + + /** @var string */ + protected $token; + + /** @var string[] */ + protected $tag = []; + + /** + * @param string $token API token supplied by Loggly + * + * @throws MissingExtensionException If the curl extension is missing + */ + public function __construct(string $token, $level = Logger::DEBUG, bool $bubble = true) + { + if (!extension_loaded('curl')) { + throw new MissingExtensionException('The curl extension is needed to use the LogglyHandler'); + } + + $this->token = $token; + + parent::__construct($level, $bubble); + } + + /** + * Loads and returns the shared curl handler for the given endpoint. + * + * @param string $endpoint + * + * @return resource|CurlHandle + */ + protected function getCurlHandler(string $endpoint) + { + if (!array_key_exists($endpoint, $this->curlHandlers)) { + $this->curlHandlers[$endpoint] = $this->loadCurlHandle($endpoint); + } + + return $this->curlHandlers[$endpoint]; + } + + /** + * Starts a fresh curl session for the given endpoint and returns its handler. + * + * @param string $endpoint + * + * @return resource|CurlHandle + */ + private function loadCurlHandle(string $endpoint) + { + $url = sprintf("https://%s/%s/%s/", static::HOST, $endpoint, $this->token); + + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + + return $ch; + } + + /** + * @param string[]|string $tag + */ + public function setTag($tag): self + { + $tag = !empty($tag) ? $tag : []; + $this->tag = is_array($tag) ? $tag : [$tag]; + + return $this; + } + + /** + * @param string[]|string $tag + */ + public function addTag($tag): self + { + if (!empty($tag)) { + $tag = is_array($tag) ? $tag : [$tag]; + $this->tag = array_unique(array_merge($this->tag, $tag)); + } + + return $this; + } + + protected function write(array $record): void + { + $this->send($record["formatted"], static::ENDPOINT_SINGLE); + } + + public function handleBatch(array $records): void + { + $level = $this->level; + + $records = array_filter($records, function ($record) use ($level) { + return ($record['level'] >= $level); + }); + + if ($records) { + $this->send($this->getFormatter()->formatBatch($records), static::ENDPOINT_BATCH); + } + } + + protected function send(string $data, string $endpoint): void + { + $ch = $this->getCurlHandler($endpoint); + + $headers = ['Content-Type: application/json']; + + if (!empty($this->tag)) { + $headers[] = 'X-LOGGLY-TAG: '.implode(',', $this->tag); + } + + curl_setopt($ch, CURLOPT_POSTFIELDS, $data); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + + Curl\Util::execute($ch, 5, false); + } + + protected function getDefaultFormatter(): FormatterInterface + { + return new LogglyFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/LogmaticHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/LogmaticHandler.php new file mode 100644 index 0000000..859a469 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/LogmaticHandler.php @@ -0,0 +1,106 @@ + + * + * 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\Formatter\FormatterInterface; +use Monolog\Formatter\LogmaticFormatter; + +/** + * @author Julien Breux + */ +class LogmaticHandler extends SocketHandler +{ + /** + * @var string + */ + private $logToken; + + /** + * @var string + */ + private $hostname; + + /** + * @var string + */ + private $appname; + + /** + * @param string $token Log token supplied by Logmatic. + * @param string $hostname Host name supplied by Logmatic. + * @param string $appname Application name supplied by Logmatic. + * @param bool $useSSL Whether or not SSL encryption should be used. + * + * @throws MissingExtensionException If SSL encryption is set to true and OpenSSL is missing + */ + public function __construct( + string $token, + string $hostname = '', + string $appname = '', + bool $useSSL = true, + $level = Logger::DEBUG, + bool $bubble = true, + bool $persistent = false, + float $timeout = 0.0, + float $writingTimeout = 10.0, + ?float $connectionTimeout = null, + ?int $chunkSize = null + ) { + if ($useSSL && !extension_loaded('openssl')) { + throw new MissingExtensionException('The OpenSSL PHP extension is required to use SSL encrypted connection for LogmaticHandler'); + } + + $endpoint = $useSSL ? 'ssl://api.logmatic.io:10515' : 'api.logmatic.io:10514'; + $endpoint .= '/v1/'; + + parent::__construct( + $endpoint, + $level, + $bubble, + $persistent, + $timeout, + $writingTimeout, + $connectionTimeout, + $chunkSize + ); + + $this->logToken = $token; + $this->hostname = $hostname; + $this->appname = $appname; + } + + /** + * {@inheritDoc} + */ + protected function generateDataStream(array $record): string + { + return $this->logToken . ' ' . $record['formatted']; + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter(): FormatterInterface + { + $formatter = new LogmaticFormatter(); + + if (!empty($this->hostname)) { + $formatter->setHostname($this->hostname); + } + if (!empty($this->appname)) { + $formatter->setAppname($this->appname); + } + + return $formatter; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/MailHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/MailHandler.php new file mode 100644 index 0000000..97f3432 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/MailHandler.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 Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\HtmlFormatter; + +/** + * Base class for all mail handlers + * + * @author Gyula Sallai + * + * @phpstan-import-type Record from \Monolog\Logger + */ +abstract class MailHandler extends AbstractProcessingHandler +{ + /** + * {@inheritDoc} + */ + public function handleBatch(array $records): void + { + $messages = []; + + foreach ($records as $record) { + if ($record['level'] < $this->level) { + continue; + } + /** @var Record $message */ + $message = $this->processRecord($record); + $messages[] = $message; + } + + if (!empty($messages)) { + $this->send((string) $this->getFormatter()->formatBatch($messages), $messages); + } + } + + /** + * Send a mail with the given content + * + * @param string $content formatted email body to be sent + * @param array $records the array of log records that formed this content + * + * @phpstan-param Record[] $records + */ + abstract protected function send(string $content, array $records): void; + + /** + * {@inheritDoc} + */ + protected function write(array $record): void + { + $this->send((string) $record['formatted'], [$record]); + } + + /** + * @phpstan-param non-empty-array $records + * @phpstan-return Record + */ + protected function getHighestRecord(array $records): array + { + $highestRecord = null; + foreach ($records as $record) { + if ($highestRecord === null || $highestRecord['level'] < $record['level']) { + $highestRecord = $record; + } + } + + return $highestRecord; + } + + protected function isHtmlBody(string $body): bool + { + return ($body[0] ?? null) === '<'; + } + + /** + * Gets the default formatter. + * + * @return FormatterInterface + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new HtmlFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/MandrillHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/MandrillHandler.php new file mode 100644 index 0000000..3003500 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/MandrillHandler.php @@ -0,0 +1,83 @@ + + * + * 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 Swift; +use Swift_Message; + +/** + * MandrillHandler uses cURL to send the emails to the Mandrill API + * + * @author Adam Nicholson + */ +class MandrillHandler extends MailHandler +{ + /** @var Swift_Message */ + protected $message; + /** @var string */ + protected $apiKey; + + /** + * @psalm-param Swift_Message|callable(): Swift_Message $message + * + * @param string $apiKey A valid Mandrill API key + * @param callable|Swift_Message $message An example message for real messages, only the body will be replaced + */ + public function __construct(string $apiKey, $message, $level = Logger::ERROR, bool $bubble = true) + { + parent::__construct($level, $bubble); + + if (!$message instanceof Swift_Message && is_callable($message)) { + $message = $message(); + } + if (!$message instanceof Swift_Message) { + throw new \InvalidArgumentException('You must provide either a Swift_Message instance or a callable returning it'); + } + $this->message = $message; + $this->apiKey = $apiKey; + } + + /** + * {@inheritDoc} + */ + protected function send(string $content, array $records): void + { + $mime = 'text/plain'; + if ($this->isHtmlBody($content)) { + $mime = 'text/html'; + } + + $message = clone $this->message; + $message->setBody($content, $mime); + /** @phpstan-ignore-next-line */ + if (version_compare(Swift::VERSION, '6.0.0', '>=')) { + $message->setDate(new \DateTimeImmutable()); + } else { + /** @phpstan-ignore-next-line */ + $message->setDate(time()); + } + + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_URL, 'https://mandrillapp.com/api/1.0/messages/send-raw.json'); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([ + 'key' => $this->apiKey, + 'raw_message' => (string) $message, + 'async' => false, + ])); + + Curl\Util::execute($ch); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php b/vendor/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php new file mode 100644 index 0000000..3965aee --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +/** + * Exception can be thrown if an extension for a handler is missing + * + * @author Christian Bergau + */ +class MissingExtensionException extends \Exception +{ +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php new file mode 100644 index 0000000..3063091 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use MongoDB\Driver\BulkWrite; +use MongoDB\Driver\Manager; +use MongoDB\Client; +use Monolog\Logger; +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\MongoDBFormatter; + +/** + * Logs to a MongoDB database. + * + * Usage example: + * + * $log = new \Monolog\Logger('application'); + * $client = new \MongoDB\Client('mongodb://localhost:27017'); + * $mongodb = new \Monolog\Handler\MongoDBHandler($client, 'logs', 'prod'); + * $log->pushHandler($mongodb); + * + * The above examples uses the MongoDB PHP library's client class; however, the + * MongoDB\Driver\Manager class from ext-mongodb is also supported. + */ +class MongoDBHandler extends AbstractProcessingHandler +{ + /** @var \MongoDB\Collection */ + private $collection; + /** @var Client|Manager */ + private $manager; + /** @var string */ + private $namespace; + + /** + * Constructor. + * + * @param Client|Manager $mongodb MongoDB library or driver client + * @param string $database Database name + * @param string $collection Collection name + */ + public function __construct($mongodb, string $database, string $collection, $level = Logger::DEBUG, bool $bubble = true) + { + if (!($mongodb instanceof Client || $mongodb instanceof Manager)) { + throw new \InvalidArgumentException('MongoDB\Client or MongoDB\Driver\Manager instance required'); + } + + if ($mongodb instanceof Client) { + $this->collection = $mongodb->selectCollection($database, $collection); + } else { + $this->manager = $mongodb; + $this->namespace = $database . '.' . $collection; + } + + parent::__construct($level, $bubble); + } + + protected function write(array $record): void + { + if (isset($this->collection)) { + $this->collection->insertOne($record['formatted']); + } + + if (isset($this->manager, $this->namespace)) { + $bulk = new BulkWrite; + $bulk->insert($record["formatted"]); + $this->manager->executeBulkWrite($this->namespace, $bulk); + } + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new MongoDBFormatter; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php new file mode 100644 index 0000000..0c0a3bd --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php @@ -0,0 +1,174 @@ + + * + * 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\Formatter\LineFormatter; + +/** + * NativeMailerHandler uses the mail() function to send the emails + * + * @author Christophe Coevoet + * @author Mark Garrett + */ +class NativeMailerHandler extends MailHandler +{ + /** + * The email addresses to which the message will be sent + * @var string[] + */ + protected $to; + + /** + * The subject of the email + * @var string + */ + protected $subject; + + /** + * Optional headers for the message + * @var string[] + */ + protected $headers = []; + + /** + * Optional parameters for the message + * @var string[] + */ + protected $parameters = []; + + /** + * The wordwrap length for the message + * @var int + */ + protected $maxColumnWidth; + + /** + * The Content-type for the message + * @var string|null + */ + protected $contentType; + + /** + * The encoding for the message + * @var string + */ + protected $encoding = 'utf-8'; + + /** + * @param string|string[] $to The receiver of the mail + * @param string $subject The subject of the mail + * @param string $from The sender of the mail + * @param int $maxColumnWidth The maximum column width that the message lines will have + */ + public function __construct($to, string $subject, string $from, $level = Logger::ERROR, bool $bubble = true, int $maxColumnWidth = 70) + { + parent::__construct($level, $bubble); + $this->to = (array) $to; + $this->subject = $subject; + $this->addHeader(sprintf('From: %s', $from)); + $this->maxColumnWidth = $maxColumnWidth; + } + + /** + * Add headers to the message + * + * @param string|string[] $headers Custom added headers + */ + public function addHeader($headers): self + { + foreach ((array) $headers as $header) { + if (strpos($header, "\n") !== false || strpos($header, "\r") !== false) { + throw new \InvalidArgumentException('Headers can not contain newline characters for security reasons'); + } + $this->headers[] = $header; + } + + return $this; + } + + /** + * Add parameters to the message + * + * @param string|string[] $parameters Custom added parameters + */ + public function addParameter($parameters): self + { + $this->parameters = array_merge($this->parameters, (array) $parameters); + + return $this; + } + + /** + * {@inheritDoc} + */ + protected function send(string $content, array $records): void + { + $contentType = $this->getContentType() ?: ($this->isHtmlBody($content) ? 'text/html' : 'text/plain'); + + if ($contentType !== 'text/html') { + $content = wordwrap($content, $this->maxColumnWidth); + } + + $headers = ltrim(implode("\r\n", $this->headers) . "\r\n", "\r\n"); + $headers .= 'Content-type: ' . $contentType . '; charset=' . $this->getEncoding() . "\r\n"; + if ($contentType === 'text/html' && false === strpos($headers, 'MIME-Version:')) { + $headers .= 'MIME-Version: 1.0' . "\r\n"; + } + + $subject = $this->subject; + if ($records) { + $subjectFormatter = new LineFormatter($this->subject); + $subject = $subjectFormatter->format($this->getHighestRecord($records)); + } + + $parameters = implode(' ', $this->parameters); + foreach ($this->to as $to) { + mail($to, $subject, $content, $headers, $parameters); + } + } + + public function getContentType(): ?string + { + return $this->contentType; + } + + public function getEncoding(): string + { + return $this->encoding; + } + + /** + * @param string $contentType The content type of the email - Defaults to text/plain. Use text/html for HTML messages. + */ + public function setContentType(string $contentType): self + { + if (strpos($contentType, "\n") !== false || strpos($contentType, "\r") !== false) { + throw new \InvalidArgumentException('The content type can not contain newline characters to prevent email header injection'); + } + + $this->contentType = $contentType; + + return $this; + } + + public function setEncoding(string $encoding): self + { + if (strpos($encoding, "\n") !== false || strpos($encoding, "\r") !== false) { + throw new \InvalidArgumentException('The encoding can not contain newline characters to prevent email header injection'); + } + + $this->encoding = $encoding; + + return $this; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php new file mode 100644 index 0000000..114d749 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php @@ -0,0 +1,199 @@ + + * + * 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\Utils; +use Monolog\Formatter\NormalizerFormatter; +use Monolog\Formatter\FormatterInterface; + +/** + * Class to record a log on a NewRelic application. + * Enabling New Relic High Security mode may prevent capture of useful information. + * + * This handler requires a NormalizerFormatter to function and expects an array in $record['formatted'] + * + * @see https://docs.newrelic.com/docs/agents/php-agent + * @see https://docs.newrelic.com/docs/accounts-partnerships/accounts/security/high-security + */ +class NewRelicHandler extends AbstractProcessingHandler +{ + /** + * Name of the New Relic application that will receive logs from this handler. + * + * @var ?string + */ + protected $appName; + + /** + * Name of the current transaction + * + * @var ?string + */ + protected $transactionName; + + /** + * Some context and extra data is passed into the handler as arrays of values. Do we send them as is + * (useful if we are using the API), or explode them for display on the NewRelic RPM website? + * + * @var bool + */ + protected $explodeArrays; + + /** + * {@inheritDoc} + * + * @param string|null $appName + * @param bool $explodeArrays + * @param string|null $transactionName + */ + public function __construct( + $level = Logger::ERROR, + bool $bubble = true, + ?string $appName = null, + bool $explodeArrays = false, + ?string $transactionName = null + ) { + parent::__construct($level, $bubble); + + $this->appName = $appName; + $this->explodeArrays = $explodeArrays; + $this->transactionName = $transactionName; + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void + { + if (!$this->isNewRelicEnabled()) { + throw new MissingExtensionException('The newrelic PHP extension is required to use the NewRelicHandler'); + } + + if ($appName = $this->getAppName($record['context'])) { + $this->setNewRelicAppName($appName); + } + + if ($transactionName = $this->getTransactionName($record['context'])) { + $this->setNewRelicTransactionName($transactionName); + unset($record['formatted']['context']['transaction_name']); + } + + if (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Throwable) { + newrelic_notice_error($record['message'], $record['context']['exception']); + unset($record['formatted']['context']['exception']); + } else { + newrelic_notice_error($record['message']); + } + + if (isset($record['formatted']['context']) && is_array($record['formatted']['context'])) { + foreach ($record['formatted']['context'] as $key => $parameter) { + if (is_array($parameter) && $this->explodeArrays) { + foreach ($parameter as $paramKey => $paramValue) { + $this->setNewRelicParameter('context_' . $key . '_' . $paramKey, $paramValue); + } + } else { + $this->setNewRelicParameter('context_' . $key, $parameter); + } + } + } + + if (isset($record['formatted']['extra']) && is_array($record['formatted']['extra'])) { + foreach ($record['formatted']['extra'] as $key => $parameter) { + if (is_array($parameter) && $this->explodeArrays) { + foreach ($parameter as $paramKey => $paramValue) { + $this->setNewRelicParameter('extra_' . $key . '_' . $paramKey, $paramValue); + } + } else { + $this->setNewRelicParameter('extra_' . $key, $parameter); + } + } + } + } + + /** + * Checks whether the NewRelic extension is enabled in the system. + * + * @return bool + */ + protected function isNewRelicEnabled(): bool + { + return extension_loaded('newrelic'); + } + + /** + * Returns the appname where this log should be sent. Each log can override the default appname, set in this + * handler's constructor, by providing the appname in it's context. + * + * @param mixed[] $context + */ + protected function getAppName(array $context): ?string + { + if (isset($context['appname'])) { + return $context['appname']; + } + + return $this->appName; + } + + /** + * Returns the name of the current transaction. Each log can override the default transaction name, set in this + * handler's constructor, by providing the transaction_name in it's context + * + * @param mixed[] $context + */ + protected function getTransactionName(array $context): ?string + { + if (isset($context['transaction_name'])) { + return $context['transaction_name']; + } + + return $this->transactionName; + } + + /** + * Sets the NewRelic application that should receive this log. + */ + protected function setNewRelicAppName(string $appName): void + { + newrelic_set_appname($appName); + } + + /** + * Overwrites the name of the current transaction + */ + protected function setNewRelicTransactionName(string $transactionName): void + { + newrelic_name_transaction($transactionName); + } + + /** + * @param string $key + * @param mixed $value + */ + protected function setNewRelicParameter(string $key, $value): void + { + if (null === $value || is_scalar($value)) { + newrelic_add_custom_parameter($key, $value); + } else { + newrelic_add_custom_parameter($key, Utils::jsonEncode($value, null, true)); + } + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new NormalizerFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/NoopHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/NoopHandler.php new file mode 100644 index 0000000..1ddf0be --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/NoopHandler.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\Handler; + +/** + * No-op + * + * This handler handles anything, but does nothing, and does not stop bubbling to the rest of the stack. + * This can be used for testing, or to disable a handler when overriding a configuration without + * influencing the rest of the stack. + * + * @author Roel Harbers + */ +class NoopHandler extends Handler +{ + /** + * {@inheritDoc} + */ + public function isHandling(array $record): bool + { + return true; + } + + /** + * {@inheritDoc} + */ + public function handle(array $record): bool + { + return false; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/NullHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/NullHandler.php new file mode 100644 index 0000000..e75ee0c --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/NullHandler.php @@ -0,0 +1,60 @@ + + * + * 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 Psr\Log\LogLevel; + +/** + * Blackhole + * + * Any record it can handle will be thrown away. This can be used + * to put on top of an existing stack to override it temporarily. + * + * @author Jordi Boggiano + * + * @phpstan-import-type Level from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger + */ +class NullHandler extends Handler +{ + /** + * @var int + */ + private $level; + + /** + * @param string|int $level The minimum logging level at which this handler will be triggered + * + * @phpstan-param Level|LevelName|LogLevel::* $level + */ + public function __construct($level = Logger::DEBUG) + { + $this->level = Logger::toMonologLevel($level); + } + + /** + * {@inheritDoc} + */ + public function isHandling(array $record): bool + { + return $record['level'] >= $this->level; + } + + /** + * {@inheritDoc} + */ + public function handle(array $record): bool + { + return $record['level'] >= $this->level; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/OverflowHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/OverflowHandler.php new file mode 100644 index 0000000..22068c9 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/OverflowHandler.php @@ -0,0 +1,149 @@ + + * + * 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\Formatter\FormatterInterface; + +/** + * Handler to only pass log messages when a certain threshold of number of messages is reached. + * + * This can be useful in cases of processing a batch of data, but you're for example only interested + * in case it fails catastrophically instead of a warning for 1 or 2 events. Worse things can happen, right? + * + * Usage example: + * + * ``` + * $log = new Logger('application'); + * $handler = new SomeHandler(...) + * + * // Pass all warnings to the handler when more than 10 & all error messages when more then 5 + * $overflow = new OverflowHandler($handler, [Logger::WARNING => 10, Logger::ERROR => 5]); + * + * $log->pushHandler($overflow); + *``` + * + * @author Kris Buist + */ +class OverflowHandler extends AbstractHandler implements FormattableHandlerInterface +{ + /** @var HandlerInterface */ + private $handler; + + /** @var int[] */ + private $thresholdMap = [ + Logger::DEBUG => 0, + Logger::INFO => 0, + Logger::NOTICE => 0, + Logger::WARNING => 0, + Logger::ERROR => 0, + Logger::CRITICAL => 0, + Logger::ALERT => 0, + Logger::EMERGENCY => 0, + ]; + + /** + * Buffer of all messages passed to the handler before the threshold was reached + * + * @var mixed[][] + */ + private $buffer = []; + + /** + * @param HandlerInterface $handler + * @param int[] $thresholdMap Dictionary of logger level => threshold + */ + public function __construct( + HandlerInterface $handler, + array $thresholdMap = [], + $level = Logger::DEBUG, + bool $bubble = true + ) { + $this->handler = $handler; + foreach ($thresholdMap as $thresholdLevel => $threshold) { + $this->thresholdMap[$thresholdLevel] = $threshold; + } + parent::__construct($level, $bubble); + } + + /** + * Handles a record. + * + * All records may be passed to this method, and the handler should discard + * those that it does not want to handle. + * + * The return value of this function controls the bubbling process of the handler stack. + * Unless the bubbling is interrupted (by returning true), the Logger class will keep on + * calling further handlers in the stack with a given log record. + * + * {@inheritDoc} + */ + public function handle(array $record): bool + { + if ($record['level'] < $this->level) { + return false; + } + + $level = $record['level']; + + if (!isset($this->thresholdMap[$level])) { + $this->thresholdMap[$level] = 0; + } + + if ($this->thresholdMap[$level] > 0) { + // The overflow threshold is not yet reached, so we're buffering the record and lowering the threshold by 1 + $this->thresholdMap[$level]--; + $this->buffer[$level][] = $record; + + return false === $this->bubble; + } + + if ($this->thresholdMap[$level] == 0) { + // This current message is breaking the threshold. Flush the buffer and continue handling the current record + foreach ($this->buffer[$level] ?? [] as $buffered) { + $this->handler->handle($buffered); + } + $this->thresholdMap[$level]--; + unset($this->buffer[$level]); + } + + $this->handler->handle($record); + + return false === $this->bubble; + } + + /** + * {@inheritDoc} + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + if ($this->handler instanceof FormattableHandlerInterface) { + $this->handler->setFormatter($formatter); + + return $this; + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($this->handler).' does not support formatters.'); + } + + /** + * {@inheritDoc} + */ + public function getFormatter(): FormatterInterface + { + if ($this->handler instanceof FormattableHandlerInterface) { + return $this->handler->getFormatter(); + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($this->handler).' does not support formatters.'); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php new file mode 100644 index 0000000..23a1d11 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php @@ -0,0 +1,263 @@ + + * + * 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\Formatter\FormatterInterface; +use Monolog\Logger; +use Monolog\Utils; +use PhpConsole\Connector; +use PhpConsole\Handler as VendorPhpConsoleHandler; +use PhpConsole\Helper; + +/** + * Monolog handler for Google Chrome extension "PHP Console" + * + * Display PHP error/debug log messages in Google Chrome console and notification popups, executes PHP code remotely + * + * Usage: + * 1. Install Google Chrome extension [now dead and removed from the chrome store] + * 2. See overview https://github.com/barbushin/php-console#overview + * 3. Install PHP Console library https://github.com/barbushin/php-console#installation + * 4. Example (result will looks like http://i.hizliresim.com/vg3Pz4.png) + * + * $logger = new \Monolog\Logger('all', array(new \Monolog\Handler\PHPConsoleHandler())); + * \Monolog\ErrorHandler::register($logger); + * echo $undefinedVar; + * $logger->debug('SELECT * FROM users', array('db', 'time' => 0.012)); + * PC::debug($_SERVER); // PHP Console debugger for any type of vars + * + * @author Sergey Barbushin https://www.linkedin.com/in/barbushin + * + * @phpstan-import-type Record from \Monolog\Logger + * @deprecated Since 2.8.0 and 3.2.0, PHPConsole is abandoned and thus we will drop this handler in Monolog 4 + */ +class PHPConsoleHandler extends AbstractProcessingHandler +{ + /** @var array */ + private $options = [ + 'enabled' => true, // bool Is PHP Console server enabled + 'classesPartialsTraceIgnore' => ['Monolog\\'], // array Hide calls of classes started with... + 'debugTagsKeysInContext' => [0, 'tag'], // bool Is PHP Console server enabled + 'useOwnErrorsHandler' => false, // bool Enable errors handling + 'useOwnExceptionsHandler' => false, // bool Enable exceptions handling + 'sourcesBasePath' => null, // string Base path of all project sources to strip in errors source paths + 'registerHelper' => true, // bool Register PhpConsole\Helper that allows short debug calls like PC::debug($var, 'ta.g.s') + 'serverEncoding' => null, // string|null Server internal encoding + 'headersLimit' => null, // int|null Set headers size limit for your web-server + 'password' => null, // string|null Protect PHP Console connection by password + 'enableSslOnlyMode' => false, // bool Force connection by SSL for clients with PHP Console installed + 'ipMasks' => [], // array Set IP masks of clients that will be allowed to connect to PHP Console: array('192.168.*.*', '127.0.0.1') + 'enableEvalListener' => false, // bool Enable eval request to be handled by eval dispatcher(if enabled, 'password' option is also required) + 'dumperDetectCallbacks' => false, // bool Convert callback items in dumper vars to (callback SomeClass::someMethod) strings + 'dumperLevelLimit' => 5, // int Maximum dumped vars array or object nested dump level + 'dumperItemsCountLimit' => 100, // int Maximum dumped var same level array items or object properties number + 'dumperItemSizeLimit' => 5000, // int Maximum length of any string or dumped array item + 'dumperDumpSizeLimit' => 500000, // int Maximum approximate size of dumped vars result formatted in JSON + 'detectDumpTraceAndSource' => false, // bool Autodetect and append trace data to debug + 'dataStorage' => null, // \PhpConsole\Storage|null Fixes problem with custom $_SESSION handler(see http://goo.gl/Ne8juJ) + ]; + + /** @var Connector */ + private $connector; + + /** + * @param array $options See \Monolog\Handler\PHPConsoleHandler::$options for more details + * @param Connector|null $connector Instance of \PhpConsole\Connector class (optional) + * @throws \RuntimeException + */ + public function __construct(array $options = [], ?Connector $connector = null, $level = Logger::DEBUG, bool $bubble = true) + { + if (!class_exists('PhpConsole\Connector')) { + throw new \RuntimeException('PHP Console library not found. See https://github.com/barbushin/php-console#installation'); + } + parent::__construct($level, $bubble); + $this->options = $this->initOptions($options); + $this->connector = $this->initConnector($connector); + } + + /** + * @param array $options + * + * @return array + */ + private function initOptions(array $options): array + { + $wrongOptions = array_diff(array_keys($options), array_keys($this->options)); + if ($wrongOptions) { + throw new \RuntimeException('Unknown options: ' . implode(', ', $wrongOptions)); + } + + return array_replace($this->options, $options); + } + + private function initConnector(?Connector $connector = null): Connector + { + if (!$connector) { + if ($this->options['dataStorage']) { + Connector::setPostponeStorage($this->options['dataStorage']); + } + $connector = Connector::getInstance(); + } + + if ($this->options['registerHelper'] && !Helper::isRegistered()) { + Helper::register(); + } + + if ($this->options['enabled'] && $connector->isActiveClient()) { + if ($this->options['useOwnErrorsHandler'] || $this->options['useOwnExceptionsHandler']) { + $handler = VendorPhpConsoleHandler::getInstance(); + $handler->setHandleErrors($this->options['useOwnErrorsHandler']); + $handler->setHandleExceptions($this->options['useOwnExceptionsHandler']); + $handler->start(); + } + if ($this->options['sourcesBasePath']) { + $connector->setSourcesBasePath($this->options['sourcesBasePath']); + } + if ($this->options['serverEncoding']) { + $connector->setServerEncoding($this->options['serverEncoding']); + } + if ($this->options['password']) { + $connector->setPassword($this->options['password']); + } + if ($this->options['enableSslOnlyMode']) { + $connector->enableSslOnlyMode(); + } + if ($this->options['ipMasks']) { + $connector->setAllowedIpMasks($this->options['ipMasks']); + } + if ($this->options['headersLimit']) { + $connector->setHeadersLimit($this->options['headersLimit']); + } + if ($this->options['detectDumpTraceAndSource']) { + $connector->getDebugDispatcher()->detectTraceAndSource = true; + } + $dumper = $connector->getDumper(); + $dumper->levelLimit = $this->options['dumperLevelLimit']; + $dumper->itemsCountLimit = $this->options['dumperItemsCountLimit']; + $dumper->itemSizeLimit = $this->options['dumperItemSizeLimit']; + $dumper->dumpSizeLimit = $this->options['dumperDumpSizeLimit']; + $dumper->detectCallbacks = $this->options['dumperDetectCallbacks']; + if ($this->options['enableEvalListener']) { + $connector->startEvalRequestsListener(); + } + } + + return $connector; + } + + public function getConnector(): Connector + { + return $this->connector; + } + + /** + * @return array + */ + public function getOptions(): array + { + return $this->options; + } + + public function handle(array $record): bool + { + if ($this->options['enabled'] && $this->connector->isActiveClient()) { + return parent::handle($record); + } + + return !$this->bubble; + } + + /** + * Writes the record down to the log of the implementing handler + */ + protected function write(array $record): void + { + if ($record['level'] < Logger::NOTICE) { + $this->handleDebugRecord($record); + } elseif (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Throwable) { + $this->handleExceptionRecord($record); + } else { + $this->handleErrorRecord($record); + } + } + + /** + * @phpstan-param Record $record + */ + private function handleDebugRecord(array $record): void + { + $tags = $this->getRecordTags($record); + $message = $record['message']; + if ($record['context']) { + $message .= ' ' . Utils::jsonEncode($this->connector->getDumper()->dump(array_filter($record['context'])), null, true); + } + $this->connector->getDebugDispatcher()->dispatchDebug($message, $tags, $this->options['classesPartialsTraceIgnore']); + } + + /** + * @phpstan-param Record $record + */ + private function handleExceptionRecord(array $record): void + { + $this->connector->getErrorsDispatcher()->dispatchException($record['context']['exception']); + } + + /** + * @phpstan-param Record $record + */ + private function handleErrorRecord(array $record): void + { + $context = $record['context']; + + $this->connector->getErrorsDispatcher()->dispatchError( + $context['code'] ?? null, + $context['message'] ?? $record['message'], + $context['file'] ?? null, + $context['line'] ?? null, + $this->options['classesPartialsTraceIgnore'] + ); + } + + /** + * @phpstan-param Record $record + * @return string + */ + private function getRecordTags(array &$record) + { + $tags = null; + if (!empty($record['context'])) { + $context = & $record['context']; + foreach ($this->options['debugTagsKeysInContext'] as $key) { + if (!empty($context[$key])) { + $tags = $context[$key]; + if ($key === 0) { + array_shift($context); + } else { + unset($context[$key]); + } + break; + } + } + } + + return $tags ?: strtolower($record['level_name']); + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new LineFormatter('%message%'); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ProcessHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ProcessHandler.php new file mode 100644 index 0000000..8a8cf1b --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/ProcessHandler.php @@ -0,0 +1,191 @@ + + * + * 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; + +/** + * Stores to STDIN of any process, specified by a command. + * + * Usage example: + *
+ * $log = new Logger('myLogger');
+ * $log->pushHandler(new ProcessHandler('/usr/bin/php /var/www/monolog/someScript.php'));
+ * 
+ * + * @author Kolja Zuelsdorf + */ +class ProcessHandler extends AbstractProcessingHandler +{ + /** + * Holds the process to receive data on its STDIN. + * + * @var resource|bool|null + */ + private $process; + + /** + * @var string + */ + private $command; + + /** + * @var string|null + */ + private $cwd; + + /** + * @var resource[] + */ + private $pipes = []; + + /** + * @var array + */ + protected const DESCRIPTOR_SPEC = [ + 0 => ['pipe', 'r'], // STDIN is a pipe that the child will read from + 1 => ['pipe', 'w'], // STDOUT is a pipe that the child will write to + 2 => ['pipe', 'w'], // STDERR is a pipe to catch the any errors + ]; + + /** + * @param string $command Command for the process to start. Absolute paths are recommended, + * especially if you do not use the $cwd parameter. + * @param string|null $cwd "Current working directory" (CWD) for the process to be executed in. + * @throws \InvalidArgumentException + */ + public function __construct(string $command, $level = Logger::DEBUG, bool $bubble = true, ?string $cwd = null) + { + if ($command === '') { + throw new \InvalidArgumentException('The command argument must be a non-empty string.'); + } + if ($cwd === '') { + throw new \InvalidArgumentException('The optional CWD argument must be a non-empty string or null.'); + } + + parent::__construct($level, $bubble); + + $this->command = $command; + $this->cwd = $cwd; + } + + /** + * Writes the record down to the log of the implementing handler + * + * @throws \UnexpectedValueException + */ + protected function write(array $record): void + { + $this->ensureProcessIsStarted(); + + $this->writeProcessInput($record['formatted']); + + $errors = $this->readProcessErrors(); + if (empty($errors) === false) { + throw new \UnexpectedValueException(sprintf('Errors while writing to process: %s', $errors)); + } + } + + /** + * Makes sure that the process is actually started, and if not, starts it, + * assigns the stream pipes, and handles startup errors, if any. + */ + private function ensureProcessIsStarted(): void + { + if (is_resource($this->process) === false) { + $this->startProcess(); + + $this->handleStartupErrors(); + } + } + + /** + * Starts the actual process and sets all streams to non-blocking. + */ + private function startProcess(): void + { + $this->process = proc_open($this->command, static::DESCRIPTOR_SPEC, $this->pipes, $this->cwd); + + foreach ($this->pipes as $pipe) { + stream_set_blocking($pipe, false); + } + } + + /** + * Selects the STDERR stream, handles upcoming startup errors, and throws an exception, if any. + * + * @throws \UnexpectedValueException + */ + private function handleStartupErrors(): void + { + $selected = $this->selectErrorStream(); + if (false === $selected) { + throw new \UnexpectedValueException('Something went wrong while selecting a stream.'); + } + + $errors = $this->readProcessErrors(); + + if (is_resource($this->process) === false || empty($errors) === false) { + throw new \UnexpectedValueException( + sprintf('The process "%s" could not be opened: ' . $errors, $this->command) + ); + } + } + + /** + * Selects the STDERR stream. + * + * @return int|bool + */ + protected function selectErrorStream() + { + $empty = []; + $errorPipes = [$this->pipes[2]]; + + return stream_select($errorPipes, $empty, $empty, 1); + } + + /** + * Reads the errors of the process, if there are any. + * + * @codeCoverageIgnore + * @return string Empty string if there are no errors. + */ + protected function readProcessErrors(): string + { + return (string) stream_get_contents($this->pipes[2]); + } + + /** + * Writes to the input stream of the opened process. + * + * @codeCoverageIgnore + */ + protected function writeProcessInput(string $string): void + { + fwrite($this->pipes[0], $string); + } + + /** + * {@inheritDoc} + */ + public function close(): void + { + if (is_resource($this->process)) { + foreach ($this->pipes as $pipe) { + fclose($pipe); + } + proc_close($this->process); + $this->process = null; + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerInterface.php b/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerInterface.php new file mode 100644 index 0000000..3adec7a --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerInterface.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\Processor\ProcessorInterface; + +/** + * Interface to describe loggers that have processors + * + * @author Jordi Boggiano + * + * @phpstan-import-type Record from \Monolog\Logger + */ +interface ProcessableHandlerInterface +{ + /** + * Adds a processor in the stack. + * + * @psalm-param ProcessorInterface|callable(Record): Record $callback + * + * @param ProcessorInterface|callable $callback + * @return HandlerInterface self + */ + public function pushProcessor(callable $callback): HandlerInterface; + + /** + * Removes the processor on top of the stack and returns it. + * + * @psalm-return ProcessorInterface|callable(Record): Record $callback + * + * @throws \LogicException In case the processor stack is empty + * @return callable|ProcessorInterface + */ + public function popProcessor(): callable; +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerTrait.php b/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerTrait.php new file mode 100644 index 0000000..9ef6e30 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerTrait.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\ResettableInterface; +use Monolog\Processor\ProcessorInterface; + +/** + * Helper trait for implementing ProcessableInterface + * + * @author Jordi Boggiano + * + * @phpstan-import-type Record from \Monolog\Logger + */ +trait ProcessableHandlerTrait +{ + /** + * @var callable[] + * @phpstan-var array + */ + protected $processors = []; + + /** + * {@inheritDoc} + */ + public function pushProcessor(callable $callback): HandlerInterface + { + array_unshift($this->processors, $callback); + + return $this; + } + + /** + * {@inheritDoc} + */ + public function popProcessor(): callable + { + if (!$this->processors) { + throw new \LogicException('You tried to pop from an empty processor stack.'); + } + + return array_shift($this->processors); + } + + /** + * Processes a record. + * + * @phpstan-param Record $record + * @phpstan-return Record + */ + protected function processRecord(array $record): array + { + foreach ($this->processors as $processor) { + $record = $processor($record); + } + + return $record; + } + + protected function resetProcessors(): void + { + foreach ($this->processors as $processor) { + if ($processor instanceof ResettableInterface) { + $processor->reset(); + } + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/PsrHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/PsrHandler.php new file mode 100644 index 0000000..36e19cc --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/PsrHandler.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 Monolog\Logger; +use Psr\Log\LoggerInterface; +use Monolog\Formatter\FormatterInterface; + +/** + * Proxies log messages to an existing PSR-3 compliant logger. + * + * If a formatter is configured, the formatter's output MUST be a string and the + * formatted message will be fed to the wrapped PSR logger instead of the original + * log record's message. + * + * @author Michael Moussa + */ +class PsrHandler extends AbstractHandler implements FormattableHandlerInterface +{ + /** + * PSR-3 compliant logger + * + * @var LoggerInterface + */ + protected $logger; + + /** + * @var FormatterInterface|null + */ + protected $formatter; + + /** + * @param LoggerInterface $logger The underlying PSR-3 compliant logger to which messages will be proxied + */ + public function __construct(LoggerInterface $logger, $level = Logger::DEBUG, bool $bubble = true) + { + parent::__construct($level, $bubble); + + $this->logger = $logger; + } + + /** + * {@inheritDoc} + */ + public function handle(array $record): bool + { + if (!$this->isHandling($record)) { + return false; + } + + if ($this->formatter) { + $formatted = $this->formatter->format($record); + $this->logger->log(strtolower($record['level_name']), (string) $formatted, $record['context']); + } else { + $this->logger->log(strtolower($record['level_name']), $record['message'], $record['context']); + } + + return false === $this->bubble; + } + + /** + * Sets the formatter. + * + * @param FormatterInterface $formatter + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + $this->formatter = $formatter; + + return $this; + } + + /** + * Gets the formatter. + * + * @return FormatterInterface + */ + public function getFormatter(): FormatterInterface + { + if (!$this->formatter) { + throw new \LogicException('No formatter has been set and this handler does not have a default formatter'); + } + + return $this->formatter; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/PushoverHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/PushoverHandler.php new file mode 100644 index 0000000..fed2303 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/PushoverHandler.php @@ -0,0 +1,246 @@ + + * + * 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\Utils; +use Psr\Log\LogLevel; + +/** + * Sends notifications through the pushover api to mobile phones + * + * @author Sebastian Göttschkes + * @see https://www.pushover.net/api + * + * @phpstan-import-type FormattedRecord from AbstractProcessingHandler + * @phpstan-import-type Level from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger + */ +class PushoverHandler extends SocketHandler +{ + /** @var string */ + private $token; + /** @var array */ + private $users; + /** @var string */ + private $title; + /** @var string|int|null */ + private $user = null; + /** @var int */ + private $retry; + /** @var int */ + private $expire; + + /** @var int */ + private $highPriorityLevel; + /** @var int */ + private $emergencyLevel; + /** @var bool */ + private $useFormattedMessage = false; + + /** + * All parameters that can be sent to Pushover + * @see https://pushover.net/api + * @var array + */ + private $parameterNames = [ + 'token' => true, + 'user' => true, + 'message' => true, + 'device' => true, + 'title' => true, + 'url' => true, + 'url_title' => true, + 'priority' => true, + 'timestamp' => true, + 'sound' => true, + 'retry' => true, + 'expire' => true, + 'callback' => true, + ]; + + /** + * Sounds the api supports by default + * @see https://pushover.net/api#sounds + * @var string[] + */ + private $sounds = [ + 'pushover', 'bike', 'bugle', 'cashregister', 'classical', 'cosmic', 'falling', 'gamelan', 'incoming', + 'intermission', 'magic', 'mechanical', 'pianobar', 'siren', 'spacealarm', 'tugboat', 'alien', 'climb', + 'persistent', 'echo', 'updown', 'none', + ]; + + /** + * @param string $token Pushover api token + * @param string|array $users Pushover user id or array of ids the message will be sent to + * @param string|null $title Title sent to the Pushover API + * @param bool $useSSL Whether to connect via SSL. Required when pushing messages to users that are not + * the pushover.net app owner. OpenSSL is required for this option. + * @param string|int $highPriorityLevel The minimum logging level at which this handler will start + * sending "high priority" requests to the Pushover API + * @param string|int $emergencyLevel The minimum logging level at which this handler will start + * sending "emergency" requests to the Pushover API + * @param int $retry The retry parameter specifies how often (in seconds) the Pushover servers will + * send the same notification to the user. + * @param int $expire The expire parameter specifies how many seconds your notification will continue + * to be retried for (every retry seconds). + * + * @phpstan-param string|array $users + * @phpstan-param Level|LevelName|LogLevel::* $highPriorityLevel + * @phpstan-param Level|LevelName|LogLevel::* $emergencyLevel + */ + public function __construct( + string $token, + $users, + ?string $title = null, + $level = Logger::CRITICAL, + bool $bubble = true, + bool $useSSL = true, + $highPriorityLevel = Logger::CRITICAL, + $emergencyLevel = Logger::EMERGENCY, + int $retry = 30, + int $expire = 25200, + bool $persistent = false, + float $timeout = 0.0, + float $writingTimeout = 10.0, + ?float $connectionTimeout = null, + ?int $chunkSize = null + ) { + $connectionString = $useSSL ? 'ssl://api.pushover.net:443' : 'api.pushover.net:80'; + parent::__construct( + $connectionString, + $level, + $bubble, + $persistent, + $timeout, + $writingTimeout, + $connectionTimeout, + $chunkSize + ); + + $this->token = $token; + $this->users = (array) $users; + $this->title = $title ?: (string) gethostname(); + $this->highPriorityLevel = Logger::toMonologLevel($highPriorityLevel); + $this->emergencyLevel = Logger::toMonologLevel($emergencyLevel); + $this->retry = $retry; + $this->expire = $expire; + } + + protected function generateDataStream(array $record): string + { + $content = $this->buildContent($record); + + return $this->buildHeader($content) . $content; + } + + /** + * @phpstan-param FormattedRecord $record + */ + private function buildContent(array $record): string + { + // Pushover has a limit of 512 characters on title and message combined. + $maxMessageLength = 512 - strlen($this->title); + + $message = ($this->useFormattedMessage) ? $record['formatted'] : $record['message']; + $message = Utils::substr($message, 0, $maxMessageLength); + + $timestamp = $record['datetime']->getTimestamp(); + + $dataArray = [ + 'token' => $this->token, + 'user' => $this->user, + 'message' => $message, + 'title' => $this->title, + 'timestamp' => $timestamp, + ]; + + if (isset($record['level']) && $record['level'] >= $this->emergencyLevel) { + $dataArray['priority'] = 2; + $dataArray['retry'] = $this->retry; + $dataArray['expire'] = $this->expire; + } elseif (isset($record['level']) && $record['level'] >= $this->highPriorityLevel) { + $dataArray['priority'] = 1; + } + + // First determine the available parameters + $context = array_intersect_key($record['context'], $this->parameterNames); + $extra = array_intersect_key($record['extra'], $this->parameterNames); + + // Least important info should be merged with subsequent info + $dataArray = array_merge($extra, $context, $dataArray); + + // Only pass sounds that are supported by the API + if (isset($dataArray['sound']) && !in_array($dataArray['sound'], $this->sounds)) { + unset($dataArray['sound']); + } + + return http_build_query($dataArray); + } + + private function buildHeader(string $content): string + { + $header = "POST /1/messages.json HTTP/1.1\r\n"; + $header .= "Host: api.pushover.net\r\n"; + $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; + $header .= "Content-Length: " . strlen($content) . "\r\n"; + $header .= "\r\n"; + + return $header; + } + + protected function write(array $record): void + { + foreach ($this->users as $user) { + $this->user = $user; + + parent::write($record); + $this->closeSocket(); + } + + $this->user = null; + } + + /** + * @param int|string $value + * + * @phpstan-param Level|LevelName|LogLevel::* $value + */ + public function setHighPriorityLevel($value): self + { + $this->highPriorityLevel = Logger::toMonologLevel($value); + + return $this; + } + + /** + * @param int|string $value + * + * @phpstan-param Level|LevelName|LogLevel::* $value + */ + public function setEmergencyLevel($value): self + { + $this->emergencyLevel = Logger::toMonologLevel($value); + + return $this; + } + + /** + * Use the formatted message? + */ + public function useFormattedMessage(bool $value): self + { + $this->useFormattedMessage = $value; + + return $this; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/RedisHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/RedisHandler.php new file mode 100644 index 0000000..91d16ea --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/RedisHandler.php @@ -0,0 +1,101 @@ + + * + * 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\Formatter\FormatterInterface; +use Monolog\Logger; + +/** + * Logs to a Redis key using rpush + * + * usage example: + * + * $log = new Logger('application'); + * $redis = new RedisHandler(new Predis\Client("tcp://localhost:6379"), "logs", "prod"); + * $log->pushHandler($redis); + * + * @author Thomas Tourlourat + * + * @phpstan-import-type FormattedRecord from AbstractProcessingHandler + */ +class RedisHandler extends AbstractProcessingHandler +{ + /** @var \Predis\Client<\Predis\Client>|\Redis */ + private $redisClient; + /** @var string */ + private $redisKey; + /** @var int */ + protected $capSize; + + /** + * @param \Predis\Client<\Predis\Client>|\Redis $redis The redis instance + * @param string $key The key name to push records to + * @param int $capSize Number of entries to limit list size to, 0 = unlimited + */ + public function __construct($redis, string $key, $level = Logger::DEBUG, bool $bubble = true, int $capSize = 0) + { + if (!(($redis instanceof \Predis\Client) || ($redis instanceof \Redis))) { + throw new \InvalidArgumentException('Predis\Client or Redis instance required'); + } + + $this->redisClient = $redis; + $this->redisKey = $key; + $this->capSize = $capSize; + + parent::__construct($level, $bubble); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void + { + if ($this->capSize) { + $this->writeCapped($record); + } else { + $this->redisClient->rpush($this->redisKey, $record["formatted"]); + } + } + + /** + * Write and cap the collection + * Writes the record to the redis list and caps its + * + * @phpstan-param FormattedRecord $record + */ + protected function writeCapped(array $record): void + { + if ($this->redisClient instanceof \Redis) { + $mode = defined('\Redis::MULTI') ? \Redis::MULTI : 1; + $this->redisClient->multi($mode) + ->rpush($this->redisKey, $record["formatted"]) + ->ltrim($this->redisKey, -$this->capSize, -1) + ->exec(); + } else { + $redisKey = $this->redisKey; + $capSize = $this->capSize; + $this->redisClient->transaction(function ($tx) use ($record, $redisKey, $capSize) { + $tx->rpush($redisKey, $record["formatted"]); + $tx->ltrim($redisKey, -$capSize, -1); + }); + } + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new LineFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/RedisPubSubHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/RedisPubSubHandler.php new file mode 100644 index 0000000..7789309 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/RedisPubSubHandler.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 Monolog\Handler; + +use Monolog\Formatter\LineFormatter; +use Monolog\Formatter\FormatterInterface; +use Monolog\Logger; + +/** + * Sends the message to a Redis Pub/Sub channel using PUBLISH + * + * usage example: + * + * $log = new Logger('application'); + * $redis = new RedisPubSubHandler(new Predis\Client("tcp://localhost:6379"), "logs", Logger::WARNING); + * $log->pushHandler($redis); + * + * @author Gaëtan Faugère + */ +class RedisPubSubHandler extends AbstractProcessingHandler +{ + /** @var \Predis\Client<\Predis\Client>|\Redis */ + private $redisClient; + /** @var string */ + private $channelKey; + + /** + * @param \Predis\Client<\Predis\Client>|\Redis $redis The redis instance + * @param string $key The channel key to publish records to + */ + public function __construct($redis, string $key, $level = Logger::DEBUG, bool $bubble = true) + { + if (!(($redis instanceof \Predis\Client) || ($redis instanceof \Redis))) { + throw new \InvalidArgumentException('Predis\Client or Redis instance required'); + } + + $this->redisClient = $redis; + $this->channelKey = $key; + + parent::__construct($level, $bubble); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void + { + $this->redisClient->publish($this->channelKey, $record["formatted"]); + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new LineFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/RollbarHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/RollbarHandler.php new file mode 100644 index 0000000..adcc939 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/RollbarHandler.php @@ -0,0 +1,131 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Rollbar\RollbarLogger; +use Throwable; +use Monolog\Logger; + +/** + * Sends errors to Rollbar + * + * If the context data contains a `payload` key, that is used as an array + * of payload options to RollbarLogger's log method. + * + * Rollbar's context info will contain the context + extra keys from the log record + * merged, and then on top of that a few keys: + * + * - level (rollbar level name) + * - monolog_level (monolog level name, raw level, as rollbar only has 5 but monolog 8) + * - channel + * - datetime (unix timestamp) + * + * @author Paul Statezny + */ +class RollbarHandler extends AbstractProcessingHandler +{ + /** + * @var RollbarLogger + */ + protected $rollbarLogger; + + /** @var string[] */ + protected $levelMap = [ + Logger::DEBUG => 'debug', + Logger::INFO => 'info', + Logger::NOTICE => 'info', + Logger::WARNING => 'warning', + Logger::ERROR => 'error', + Logger::CRITICAL => 'critical', + Logger::ALERT => 'critical', + Logger::EMERGENCY => 'critical', + ]; + + /** + * Records whether any log records have been added since the last flush of the rollbar notifier + * + * @var bool + */ + private $hasRecords = false; + + /** @var bool */ + protected $initialized = false; + + /** + * @param RollbarLogger $rollbarLogger RollbarLogger object constructed with valid token + */ + public function __construct(RollbarLogger $rollbarLogger, $level = Logger::ERROR, bool $bubble = true) + { + $this->rollbarLogger = $rollbarLogger; + + parent::__construct($level, $bubble); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void + { + if (!$this->initialized) { + // __destructor() doesn't get called on Fatal errors + register_shutdown_function(array($this, 'close')); + $this->initialized = true; + } + + $context = $record['context']; + $context = array_merge($context, $record['extra'], [ + 'level' => $this->levelMap[$record['level']], + 'monolog_level' => $record['level_name'], + 'channel' => $record['channel'], + 'datetime' => $record['datetime']->format('U'), + ]); + + if (isset($context['exception']) && $context['exception'] instanceof Throwable) { + $exception = $context['exception']; + unset($context['exception']); + $toLog = $exception; + } else { + $toLog = $record['message']; + } + + // @phpstan-ignore-next-line + $this->rollbarLogger->log($context['level'], $toLog, $context); + + $this->hasRecords = true; + } + + public function flush(): void + { + if ($this->hasRecords) { + $this->rollbarLogger->flush(); + $this->hasRecords = false; + } + } + + /** + * {@inheritDoc} + */ + public function close(): void + { + $this->flush(); + } + + /** + * {@inheritDoc} + */ + public function reset() + { + $this->flush(); + + parent::reset(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php new file mode 100644 index 0000000..17745d2 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php @@ -0,0 +1,207 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use InvalidArgumentException; +use Monolog\Logger; +use Monolog\Utils; + +/** + * Stores logs to files that are rotated every day and a limited number of files are kept. + * + * This rotation is only intended to be used as a workaround. Using logrotate to + * handle the rotation is strongly encouraged when you can use it. + * + * @author Christophe Coevoet + * @author Jordi Boggiano + */ +class RotatingFileHandler extends StreamHandler +{ + public const FILE_PER_DAY = 'Y-m-d'; + public const FILE_PER_MONTH = 'Y-m'; + public const FILE_PER_YEAR = 'Y'; + + /** @var string */ + protected $filename; + /** @var int */ + protected $maxFiles; + /** @var bool */ + protected $mustRotate; + /** @var \DateTimeImmutable */ + protected $nextRotation; + /** @var string */ + protected $filenameFormat; + /** @var string */ + protected $dateFormat; + + /** + * @param string $filename + * @param int $maxFiles The maximal amount of files to keep (0 means unlimited) + * @param int|null $filePermission Optional file permissions (default (0644) are only for owner read/write) + * @param bool $useLocking Try to lock log file before doing any writes + */ + public function __construct(string $filename, int $maxFiles = 0, $level = Logger::DEBUG, bool $bubble = true, ?int $filePermission = null, bool $useLocking = false) + { + $this->filename = Utils::canonicalizePath($filename); + $this->maxFiles = $maxFiles; + $this->nextRotation = new \DateTimeImmutable('tomorrow'); + $this->filenameFormat = '{filename}-{date}'; + $this->dateFormat = static::FILE_PER_DAY; + + parent::__construct($this->getTimedFilename(), $level, $bubble, $filePermission, $useLocking); + } + + /** + * {@inheritDoc} + */ + public function close(): void + { + parent::close(); + + if (true === $this->mustRotate) { + $this->rotate(); + } + } + + /** + * {@inheritDoc} + */ + public function reset() + { + parent::reset(); + + if (true === $this->mustRotate) { + $this->rotate(); + } + } + + public function setFilenameFormat(string $filenameFormat, string $dateFormat): self + { + if (!preg_match('{^[Yy](([/_.-]?m)([/_.-]?d)?)?$}', $dateFormat)) { + throw new InvalidArgumentException( + '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.' + ); + } + if (substr_count($filenameFormat, '{date}') === 0) { + throw new InvalidArgumentException( + 'Invalid filename format - format must contain at least `{date}`, because otherwise rotating is impossible.' + ); + } + $this->filenameFormat = $filenameFormat; + $this->dateFormat = $dateFormat; + $this->url = $this->getTimedFilename(); + $this->close(); + + return $this; + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void + { + // on the first record written, if the log is new, we should rotate (once per day) + if (null === $this->mustRotate) { + $this->mustRotate = null === $this->url || !file_exists($this->url); + } + + if ($this->nextRotation <= $record['datetime']) { + $this->mustRotate = true; + $this->close(); + } + + parent::write($record); + } + + /** + * Rotates the files. + */ + protected function rotate(): void + { + // update filename + $this->url = $this->getTimedFilename(); + $this->nextRotation = new \DateTimeImmutable('tomorrow'); + + // skip GC of old logs if files are unlimited + if (0 === $this->maxFiles) { + return; + } + + $logFiles = glob($this->getGlobPattern()); + if (false === $logFiles) { + // failed to glob + return; + } + + if ($this->maxFiles >= count($logFiles)) { + // no files to remove + return; + } + + // Sorting the files by name to remove the older ones + usort($logFiles, function ($a, $b) { + return strcmp($b, $a); + }); + + foreach (array_slice($logFiles, $this->maxFiles) as $file) { + if (is_writable($file)) { + // suppress errors here as unlink() might fail if two processes + // are cleaning up/rotating at the same time + set_error_handler(function (int $errno, string $errstr, string $errfile, int $errline): bool { + return false; + }); + unlink($file); + restore_error_handler(); + } + } + + $this->mustRotate = false; + } + + protected function getTimedFilename(): string + { + $fileInfo = pathinfo($this->filename); + $timedFilename = str_replace( + ['{filename}', '{date}'], + [$fileInfo['filename'], date($this->dateFormat)], + $fileInfo['dirname'] . '/' . $this->filenameFormat + ); + + if (isset($fileInfo['extension'])) { + $timedFilename .= '.'.$fileInfo['extension']; + } + + return $timedFilename; + } + + protected function getGlobPattern(): string + { + $fileInfo = pathinfo($this->filename); + $glob = str_replace( + ['{filename}', '{date}'], + [$fileInfo['filename'], str_replace( + ['Y', 'y', 'm', 'd'], + ['[0-9][0-9][0-9][0-9]', '[0-9][0-9]', '[0-9][0-9]', '[0-9][0-9]'], + $this->dateFormat) + ], + $fileInfo['dirname'] . '/' . $this->filenameFormat + ); + if (isset($fileInfo['extension'])) { + $glob .= '.'.$fileInfo['extension']; + } + + return $glob; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SamplingHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SamplingHandler.php new file mode 100644 index 0000000..c128a32 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SamplingHandler.php @@ -0,0 +1,132 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; + +/** + * Sampling handler + * + * A sampled event stream can be useful for logging high frequency events in + * a production environment where you only need an idea of what is happening + * and are not concerned with capturing every occurrence. Since the decision to + * handle or not handle a particular event is determined randomly, the + * resulting sampled log is not guaranteed to contain 1/N of the events that + * occurred in the application, but based on the Law of large numbers, it will + * tend to be close to this ratio with a large number of attempts. + * + * @author Bryan Davis + * @author Kunal Mehta + * + * @phpstan-import-type Record from \Monolog\Logger + * @phpstan-import-type Level from \Monolog\Logger + */ +class SamplingHandler extends AbstractHandler implements ProcessableHandlerInterface, FormattableHandlerInterface +{ + use ProcessableHandlerTrait; + + /** + * @var HandlerInterface|callable + * @phpstan-var HandlerInterface|callable(Record|array{level: Level}|null, HandlerInterface): HandlerInterface + */ + protected $handler; + + /** + * @var int $factor + */ + protected $factor; + + /** + * @psalm-param HandlerInterface|callable(Record|array{level: Level}|null, HandlerInterface): HandlerInterface $handler + * + * @param callable|HandlerInterface $handler Handler or factory callable($record|null, $samplingHandler). + * @param int $factor Sample factor (e.g. 10 means every ~10th record is sampled) + */ + public function __construct($handler, int $factor) + { + parent::__construct(); + $this->handler = $handler; + $this->factor = $factor; + + if (!$this->handler instanceof HandlerInterface && !is_callable($this->handler)) { + throw new \RuntimeException("The given handler (".json_encode($this->handler).") is not a callable nor a Monolog\Handler\HandlerInterface object"); + } + } + + public function isHandling(array $record): bool + { + return $this->getHandler($record)->isHandling($record); + } + + public function handle(array $record): bool + { + if ($this->isHandling($record) && mt_rand(1, $this->factor) === 1) { + if ($this->processors) { + /** @var Record $record */ + $record = $this->processRecord($record); + } + + $this->getHandler($record)->handle($record); + } + + return false === $this->bubble; + } + + /** + * Return the nested handler + * + * If the handler was provided as a factory callable, this will trigger the handler's instantiation. + * + * @phpstan-param Record|array{level: Level}|null $record + * + * @return HandlerInterface + */ + public function getHandler(array $record = null) + { + if (!$this->handler instanceof HandlerInterface) { + $this->handler = ($this->handler)($record, $this); + if (!$this->handler instanceof HandlerInterface) { + throw new \RuntimeException("The factory callable should return a HandlerInterface"); + } + } + + return $this->handler; + } + + /** + * {@inheritDoc} + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + $handler = $this->getHandler(); + if ($handler instanceof FormattableHandlerInterface) { + $handler->setFormatter($formatter); + + return $this; + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($handler).' does not support formatters.'); + } + + /** + * {@inheritDoc} + */ + public function getFormatter(): FormatterInterface + { + $handler = $this->getHandler(); + if ($handler instanceof FormattableHandlerInterface) { + return $handler->getFormatter(); + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($handler).' does not support formatters.'); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SendGridHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SendGridHandler.php new file mode 100644 index 0000000..1280ee7 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SendGridHandler.php @@ -0,0 +1,102 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * SendGridrHandler uses the SendGrid API v2 function to send Log emails, more information in https://sendgrid.com/docs/API_Reference/Web_API/mail.html + * + * @author Ricardo Fontanelli + */ +class SendGridHandler extends MailHandler +{ + /** + * The SendGrid API User + * @var string + */ + protected $apiUser; + + /** + * The SendGrid API Key + * @var string + */ + protected $apiKey; + + /** + * The email addresses to which the message will be sent + * @var string + */ + protected $from; + + /** + * The email addresses to which the message will be sent + * @var string[] + */ + protected $to; + + /** + * The subject of the email + * @var string + */ + protected $subject; + + /** + * @param string $apiUser The SendGrid API User + * @param string $apiKey The SendGrid API Key + * @param string $from The sender of the email + * @param string|string[] $to The recipients of the email + * @param string $subject The subject of the mail + */ + public function __construct(string $apiUser, string $apiKey, string $from, $to, string $subject, $level = Logger::ERROR, bool $bubble = true) + { + if (!extension_loaded('curl')) { + throw new MissingExtensionException('The curl extension is needed to use the SendGridHandler'); + } + + parent::__construct($level, $bubble); + $this->apiUser = $apiUser; + $this->apiKey = $apiKey; + $this->from = $from; + $this->to = (array) $to; + $this->subject = $subject; + } + + /** + * {@inheritDoc} + */ + protected function send(string $content, array $records): void + { + $message = []; + $message['api_user'] = $this->apiUser; + $message['api_key'] = $this->apiKey; + $message['from'] = $this->from; + foreach ($this->to as $recipient) { + $message['to[]'] = $recipient; + } + $message['subject'] = $this->subject; + $message['date'] = date('r'); + + if ($this->isHtmlBody($content)) { + $message['html'] = $content; + } else { + $message['text'] = $content; + } + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, 'https://api.sendgrid.com/api/mail.send.json'); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($message)); + Curl\Util::execute($ch, 2); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php b/vendor/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php new file mode 100644 index 0000000..71a4109 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php @@ -0,0 +1,387 @@ + + * + * 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\Utils; +use Monolog\Formatter\NormalizerFormatter; +use Monolog\Formatter\FormatterInterface; + +/** + * Slack record utility helping to log to Slack webhooks or API. + * + * @author Greg Kedzierski + * @author Haralan Dobrev + * @see https://api.slack.com/incoming-webhooks + * @see https://api.slack.com/docs/message-attachments + * + * @phpstan-import-type FormattedRecord from \Monolog\Handler\AbstractProcessingHandler + * @phpstan-import-type Record from \Monolog\Logger + */ +class SlackRecord +{ + public const COLOR_DANGER = 'danger'; + + public const COLOR_WARNING = 'warning'; + + public const COLOR_GOOD = 'good'; + + public const COLOR_DEFAULT = '#e3e4e6'; + + /** + * Slack channel (encoded ID or name) + * @var string|null + */ + private $channel; + + /** + * Name of a bot + * @var string|null + */ + private $username; + + /** + * User icon e.g. 'ghost', 'http://example.com/user.png' + * @var string|null + */ + private $userIcon; + + /** + * Whether the message should be added to Slack as attachment (plain text otherwise) + * @var bool + */ + private $useAttachment; + + /** + * Whether the the context/extra messages added to Slack as attachments are in a short style + * @var bool + */ + private $useShortAttachment; + + /** + * Whether the attachment should include context and extra data + * @var bool + */ + private $includeContextAndExtra; + + /** + * Dot separated list of fields to exclude from slack message. E.g. ['context.field1', 'extra.field2'] + * @var string[] + */ + private $excludeFields; + + /** + * @var ?FormatterInterface + */ + private $formatter; + + /** + * @var NormalizerFormatter + */ + private $normalizerFormatter; + + /** + * @param string[] $excludeFields + */ + public function __construct( + ?string $channel = null, + ?string $username = null, + bool $useAttachment = true, + ?string $userIcon = null, + bool $useShortAttachment = false, + bool $includeContextAndExtra = false, + array $excludeFields = array(), + FormatterInterface $formatter = null + ) { + $this + ->setChannel($channel) + ->setUsername($username) + ->useAttachment($useAttachment) + ->setUserIcon($userIcon) + ->useShortAttachment($useShortAttachment) + ->includeContextAndExtra($includeContextAndExtra) + ->excludeFields($excludeFields) + ->setFormatter($formatter); + + if ($this->includeContextAndExtra) { + $this->normalizerFormatter = new NormalizerFormatter(); + } + } + + /** + * Returns required data in format that Slack + * is expecting. + * + * @phpstan-param FormattedRecord $record + * @phpstan-return mixed[] + */ + public function getSlackData(array $record): array + { + $dataArray = array(); + $record = $this->removeExcludedFields($record); + + if ($this->username) { + $dataArray['username'] = $this->username; + } + + if ($this->channel) { + $dataArray['channel'] = $this->channel; + } + + if ($this->formatter && !$this->useAttachment) { + /** @phpstan-ignore-next-line */ + $message = $this->formatter->format($record); + } else { + $message = $record['message']; + } + + if ($this->useAttachment) { + $attachment = array( + 'fallback' => $message, + 'text' => $message, + 'color' => $this->getAttachmentColor($record['level']), + 'fields' => array(), + 'mrkdwn_in' => array('fields'), + 'ts' => $record['datetime']->getTimestamp(), + 'footer' => $this->username, + 'footer_icon' => $this->userIcon, + ); + + if ($this->useShortAttachment) { + $attachment['title'] = $record['level_name']; + } else { + $attachment['title'] = 'Message'; + $attachment['fields'][] = $this->generateAttachmentField('Level', $record['level_name']); + } + + if ($this->includeContextAndExtra) { + foreach (array('extra', 'context') as $key) { + if (empty($record[$key])) { + continue; + } + + if ($this->useShortAttachment) { + $attachment['fields'][] = $this->generateAttachmentField( + (string) $key, + $record[$key] + ); + } else { + // Add all extra fields as individual fields in attachment + $attachment['fields'] = array_merge( + $attachment['fields'], + $this->generateAttachmentFields($record[$key]) + ); + } + } + } + + $dataArray['attachments'] = array($attachment); + } else { + $dataArray['text'] = $message; + } + + if ($this->userIcon) { + if (filter_var($this->userIcon, FILTER_VALIDATE_URL)) { + $dataArray['icon_url'] = $this->userIcon; + } else { + $dataArray['icon_emoji'] = ":{$this->userIcon}:"; + } + } + + return $dataArray; + } + + /** + * Returns a Slack message attachment color associated with + * provided level. + */ + public function getAttachmentColor(int $level): string + { + switch (true) { + case $level >= Logger::ERROR: + return static::COLOR_DANGER; + case $level >= Logger::WARNING: + return static::COLOR_WARNING; + case $level >= Logger::INFO: + return static::COLOR_GOOD; + default: + return static::COLOR_DEFAULT; + } + } + + /** + * Stringifies an array of key/value pairs to be used in attachment fields + * + * @param mixed[] $fields + */ + public function stringify(array $fields): string + { + /** @var Record $fields */ + $normalized = $this->normalizerFormatter->format($fields); + + $hasSecondDimension = count(array_filter($normalized, 'is_array')); + $hasNonNumericKeys = !count(array_filter(array_keys($normalized), 'is_numeric')); + + return $hasSecondDimension || $hasNonNumericKeys + ? Utils::jsonEncode($normalized, JSON_PRETTY_PRINT|Utils::DEFAULT_JSON_FLAGS) + : Utils::jsonEncode($normalized, Utils::DEFAULT_JSON_FLAGS); + } + + /** + * Channel used by the bot when posting + * + * @param ?string $channel + * + * @return static + */ + public function setChannel(?string $channel = null): self + { + $this->channel = $channel; + + return $this; + } + + /** + * Username used by the bot when posting + * + * @param ?string $username + * + * @return static + */ + public function setUsername(?string $username = null): self + { + $this->username = $username; + + return $this; + } + + public function useAttachment(bool $useAttachment = true): self + { + $this->useAttachment = $useAttachment; + + return $this; + } + + public function setUserIcon(?string $userIcon = null): self + { + $this->userIcon = $userIcon; + + if (\is_string($userIcon)) { + $this->userIcon = trim($userIcon, ':'); + } + + return $this; + } + + public function useShortAttachment(bool $useShortAttachment = false): self + { + $this->useShortAttachment = $useShortAttachment; + + return $this; + } + + public function includeContextAndExtra(bool $includeContextAndExtra = false): self + { + $this->includeContextAndExtra = $includeContextAndExtra; + + if ($this->includeContextAndExtra) { + $this->normalizerFormatter = new NormalizerFormatter(); + } + + return $this; + } + + /** + * @param string[] $excludeFields + */ + public function excludeFields(array $excludeFields = []): self + { + $this->excludeFields = $excludeFields; + + return $this; + } + + public function setFormatter(?FormatterInterface $formatter = null): self + { + $this->formatter = $formatter; + + return $this; + } + + /** + * Generates attachment field + * + * @param string|mixed[] $value + * + * @return array{title: string, value: string, short: false} + */ + private function generateAttachmentField(string $title, $value): array + { + $value = is_array($value) + ? sprintf('```%s```', substr($this->stringify($value), 0, 1990)) + : $value; + + return array( + 'title' => ucfirst($title), + 'value' => $value, + 'short' => false, + ); + } + + /** + * Generates a collection of attachment fields from array + * + * @param mixed[] $data + * + * @return array + */ + private function generateAttachmentFields(array $data): array + { + /** @var Record $data */ + $normalized = $this->normalizerFormatter->format($data); + + $fields = array(); + foreach ($normalized as $key => $value) { + $fields[] = $this->generateAttachmentField((string) $key, $value); + } + + return $fields; + } + + /** + * Get a copy of record with fields excluded according to $this->excludeFields + * + * @phpstan-param FormattedRecord $record + * + * @return mixed[] + */ + private function removeExcludedFields(array $record): array + { + foreach ($this->excludeFields as $field) { + $keys = explode('.', $field); + $node = &$record; + $lastKey = end($keys); + foreach ($keys as $key) { + if (!isset($node[$key])) { + break; + } + if ($lastKey === $key) { + unset($node[$key]); + break; + } + $node = &$node[$key]; + } + } + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SlackHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SlackHandler.php new file mode 100644 index 0000000..a648513 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SlackHandler.php @@ -0,0 +1,256 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; +use Monolog\Logger; +use Monolog\Utils; +use Monolog\Handler\Slack\SlackRecord; + +/** + * Sends notifications through Slack API + * + * @author Greg Kedzierski + * @see https://api.slack.com/ + * + * @phpstan-import-type FormattedRecord from AbstractProcessingHandler + */ +class SlackHandler extends SocketHandler +{ + /** + * Slack API token + * @var string + */ + private $token; + + /** + * Instance of the SlackRecord util class preparing data for Slack API. + * @var SlackRecord + */ + private $slackRecord; + + /** + * @param string $token Slack API token + * @param string $channel Slack channel (encoded ID or name) + * @param string|null $username Name of a bot + * @param bool $useAttachment Whether the message should be added to Slack as attachment (plain text otherwise) + * @param string|null $iconEmoji The emoji name to use (or null) + * @param bool $useShortAttachment Whether the context/extra messages added to Slack as attachments are in a short style + * @param bool $includeContextAndExtra Whether the attachment should include context and extra data + * @param string[] $excludeFields Dot separated list of fields to exclude from slack message. E.g. ['context.field1', 'extra.field2'] + * @throws MissingExtensionException If no OpenSSL PHP extension configured + */ + public function __construct( + string $token, + string $channel, + ?string $username = null, + bool $useAttachment = true, + ?string $iconEmoji = null, + $level = Logger::CRITICAL, + bool $bubble = true, + bool $useShortAttachment = false, + bool $includeContextAndExtra = false, + array $excludeFields = array(), + bool $persistent = false, + float $timeout = 0.0, + float $writingTimeout = 10.0, + ?float $connectionTimeout = null, + ?int $chunkSize = null + ) { + if (!extension_loaded('openssl')) { + throw new MissingExtensionException('The OpenSSL PHP extension is required to use the SlackHandler'); + } + + parent::__construct( + 'ssl://slack.com:443', + $level, + $bubble, + $persistent, + $timeout, + $writingTimeout, + $connectionTimeout, + $chunkSize + ); + + $this->slackRecord = new SlackRecord( + $channel, + $username, + $useAttachment, + $iconEmoji, + $useShortAttachment, + $includeContextAndExtra, + $excludeFields + ); + + $this->token = $token; + } + + public function getSlackRecord(): SlackRecord + { + return $this->slackRecord; + } + + public function getToken(): string + { + return $this->token; + } + + /** + * {@inheritDoc} + */ + protected function generateDataStream(array $record): string + { + $content = $this->buildContent($record); + + return $this->buildHeader($content) . $content; + } + + /** + * Builds the body of API call + * + * @phpstan-param FormattedRecord $record + */ + private function buildContent(array $record): string + { + $dataArray = $this->prepareContentData($record); + + return http_build_query($dataArray); + } + + /** + * @phpstan-param FormattedRecord $record + * @return string[] + */ + protected function prepareContentData(array $record): array + { + $dataArray = $this->slackRecord->getSlackData($record); + $dataArray['token'] = $this->token; + + if (!empty($dataArray['attachments'])) { + $dataArray['attachments'] = Utils::jsonEncode($dataArray['attachments']); + } + + return $dataArray; + } + + /** + * Builds the header of the API Call + */ + private function buildHeader(string $content): string + { + $header = "POST /api/chat.postMessage HTTP/1.1\r\n"; + $header .= "Host: slack.com\r\n"; + $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; + $header .= "Content-Length: " . strlen($content) . "\r\n"; + $header .= "\r\n"; + + return $header; + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void + { + parent::write($record); + $this->finalizeWrite(); + } + + /** + * Finalizes the request by reading some bytes and then closing the socket + * + * If we do not read some but close the socket too early, slack sometimes + * drops the request entirely. + */ + protected function finalizeWrite(): void + { + $res = $this->getResource(); + if (is_resource($res)) { + @fread($res, 2048); + } + $this->closeSocket(); + } + + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + parent::setFormatter($formatter); + $this->slackRecord->setFormatter($formatter); + + return $this; + } + + public function getFormatter(): FormatterInterface + { + $formatter = parent::getFormatter(); + $this->slackRecord->setFormatter($formatter); + + return $formatter; + } + + /** + * Channel used by the bot when posting + */ + public function setChannel(string $channel): self + { + $this->slackRecord->setChannel($channel); + + return $this; + } + + /** + * Username used by the bot when posting + */ + public function setUsername(string $username): self + { + $this->slackRecord->setUsername($username); + + return $this; + } + + public function useAttachment(bool $useAttachment): self + { + $this->slackRecord->useAttachment($useAttachment); + + return $this; + } + + public function setIconEmoji(string $iconEmoji): self + { + $this->slackRecord->setUserIcon($iconEmoji); + + return $this; + } + + public function useShortAttachment(bool $useShortAttachment): self + { + $this->slackRecord->useShortAttachment($useShortAttachment); + + return $this; + } + + public function includeContextAndExtra(bool $includeContextAndExtra): self + { + $this->slackRecord->includeContextAndExtra($includeContextAndExtra); + + return $this; + } + + /** + * @param string[] $excludeFields + */ + public function excludeFields(array $excludeFields): self + { + $this->slackRecord->excludeFields($excludeFields); + + return $this; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php new file mode 100644 index 0000000..8ae3c78 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.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\Formatter\FormatterInterface; +use Monolog\Logger; +use Monolog\Utils; +use Monolog\Handler\Slack\SlackRecord; + +/** + * Sends notifications through Slack Webhooks + * + * @author Haralan Dobrev + * @see https://api.slack.com/incoming-webhooks + */ +class SlackWebhookHandler extends AbstractProcessingHandler +{ + /** + * Slack Webhook token + * @var string + */ + private $webhookUrl; + + /** + * Instance of the SlackRecord util class preparing data for Slack API. + * @var SlackRecord + */ + private $slackRecord; + + /** + * @param string $webhookUrl Slack Webhook URL + * @param string|null $channel Slack channel (encoded ID or name) + * @param string|null $username Name of a bot + * @param bool $useAttachment Whether the message should be added to Slack as attachment (plain text otherwise) + * @param string|null $iconEmoji The emoji name to use (or null) + * @param bool $useShortAttachment Whether the the context/extra messages added to Slack as attachments are in a short style + * @param bool $includeContextAndExtra Whether the attachment should include context and extra data + * @param string[] $excludeFields Dot separated list of fields to exclude from slack message. E.g. ['context.field1', 'extra.field2'] + */ + public function __construct( + string $webhookUrl, + ?string $channel = null, + ?string $username = null, + bool $useAttachment = true, + ?string $iconEmoji = null, + bool $useShortAttachment = false, + bool $includeContextAndExtra = false, + $level = Logger::CRITICAL, + bool $bubble = true, + array $excludeFields = array() + ) { + if (!extension_loaded('curl')) { + throw new MissingExtensionException('The curl extension is needed to use the SlackWebhookHandler'); + } + + parent::__construct($level, $bubble); + + $this->webhookUrl = $webhookUrl; + + $this->slackRecord = new SlackRecord( + $channel, + $username, + $useAttachment, + $iconEmoji, + $useShortAttachment, + $includeContextAndExtra, + $excludeFields + ); + } + + public function getSlackRecord(): SlackRecord + { + return $this->slackRecord; + } + + public function getWebhookUrl(): string + { + return $this->webhookUrl; + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void + { + $postData = $this->slackRecord->getSlackData($record); + $postString = Utils::jsonEncode($postData); + + $ch = curl_init(); + $options = array( + CURLOPT_URL => $this->webhookUrl, + CURLOPT_POST => true, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HTTPHEADER => array('Content-type: application/json'), + CURLOPT_POSTFIELDS => $postString, + ); + if (defined('CURLOPT_SAFE_UPLOAD')) { + $options[CURLOPT_SAFE_UPLOAD] = true; + } + + curl_setopt_array($ch, $options); + + Curl\Util::execute($ch); + } + + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + parent::setFormatter($formatter); + $this->slackRecord->setFormatter($formatter); + + return $this; + } + + public function getFormatter(): FormatterInterface + { + $formatter = parent::getFormatter(); + $this->slackRecord->setFormatter($formatter); + + return $formatter; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php new file mode 100644 index 0000000..21701af --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php @@ -0,0 +1,448 @@ + + * + * 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; + +/** + * Stores to any socket - uses fsockopen() or pfsockopen(). + * + * @author Pablo de Leon Belloc + * @see http://php.net/manual/en/function.fsockopen.php + * + * @phpstan-import-type Record from \Monolog\Logger + * @phpstan-import-type FormattedRecord from AbstractProcessingHandler + */ +class SocketHandler extends AbstractProcessingHandler +{ + /** @var string */ + private $connectionString; + /** @var float */ + private $connectionTimeout; + /** @var resource|null */ + private $resource; + /** @var float */ + private $timeout; + /** @var float */ + private $writingTimeout; + /** @var ?int */ + private $lastSentBytes = null; + /** @var ?int */ + private $chunkSize; + /** @var bool */ + private $persistent; + /** @var ?int */ + private $errno = null; + /** @var ?string */ + private $errstr = null; + /** @var ?float */ + private $lastWritingAt = null; + + /** + * @param string $connectionString Socket connection string + * @param bool $persistent Flag to enable/disable persistent connections + * @param float $timeout Socket timeout to wait until the request is being aborted + * @param float $writingTimeout Socket timeout to wait until the request should've been sent/written + * @param float|null $connectionTimeout Socket connect timeout to wait until the connection should've been + * established + * @param int|null $chunkSize Sets the chunk size. Only has effect during connection in the writing cycle + * + * @throws \InvalidArgumentException If an invalid timeout value (less than 0) is passed. + */ + public function __construct( + string $connectionString, + $level = Logger::DEBUG, + bool $bubble = true, + bool $persistent = false, + float $timeout = 0.0, + float $writingTimeout = 10.0, + ?float $connectionTimeout = null, + ?int $chunkSize = null + ) { + parent::__construct($level, $bubble); + $this->connectionString = $connectionString; + + if ($connectionTimeout !== null) { + $this->validateTimeout($connectionTimeout); + } + + $this->connectionTimeout = $connectionTimeout ?? (float) ini_get('default_socket_timeout'); + $this->persistent = $persistent; + $this->validateTimeout($timeout); + $this->timeout = $timeout; + $this->validateTimeout($writingTimeout); + $this->writingTimeout = $writingTimeout; + $this->chunkSize = $chunkSize; + } + + /** + * Connect (if necessary) and write to the socket + * + * {@inheritDoc} + * + * @throws \UnexpectedValueException + * @throws \RuntimeException + */ + protected function write(array $record): void + { + $this->connectIfNotConnected(); + $data = $this->generateDataStream($record); + $this->writeToSocket($data); + } + + /** + * We will not close a PersistentSocket instance so it can be reused in other requests. + */ + public function close(): void + { + if (!$this->isPersistent()) { + $this->closeSocket(); + } + } + + /** + * Close socket, if open + */ + public function closeSocket(): void + { + if (is_resource($this->resource)) { + fclose($this->resource); + $this->resource = null; + } + } + + /** + * Set socket connection to be persistent. It only has effect before the connection is initiated. + */ + public function setPersistent(bool $persistent): self + { + $this->persistent = $persistent; + + return $this; + } + + /** + * Set connection timeout. Only has effect before we connect. + * + * @see http://php.net/manual/en/function.fsockopen.php + */ + public function setConnectionTimeout(float $seconds): self + { + $this->validateTimeout($seconds); + $this->connectionTimeout = $seconds; + + return $this; + } + + /** + * Set write timeout. Only has effect before we connect. + * + * @see http://php.net/manual/en/function.stream-set-timeout.php + */ + public function setTimeout(float $seconds): self + { + $this->validateTimeout($seconds); + $this->timeout = $seconds; + + return $this; + } + + /** + * Set writing timeout. Only has effect during connection in the writing cycle. + * + * @param float $seconds 0 for no timeout + */ + public function setWritingTimeout(float $seconds): self + { + $this->validateTimeout($seconds); + $this->writingTimeout = $seconds; + + return $this; + } + + /** + * Set chunk size. Only has effect during connection in the writing cycle. + */ + public function setChunkSize(int $bytes): self + { + $this->chunkSize = $bytes; + + return $this; + } + + /** + * Get current connection string + */ + public function getConnectionString(): string + { + return $this->connectionString; + } + + /** + * Get persistent setting + */ + public function isPersistent(): bool + { + return $this->persistent; + } + + /** + * Get current connection timeout setting + */ + public function getConnectionTimeout(): float + { + return $this->connectionTimeout; + } + + /** + * Get current in-transfer timeout + */ + public function getTimeout(): float + { + return $this->timeout; + } + + /** + * Get current local writing timeout + * + * @return float + */ + public function getWritingTimeout(): float + { + return $this->writingTimeout; + } + + /** + * Get current chunk size + */ + public function getChunkSize(): ?int + { + return $this->chunkSize; + } + + /** + * Check to see if the socket is currently available. + * + * UDP might appear to be connected but might fail when writing. See http://php.net/fsockopen for details. + */ + public function isConnected(): bool + { + return is_resource($this->resource) + && !feof($this->resource); // on TCP - other party can close connection. + } + + /** + * Wrapper to allow mocking + * + * @return resource|false + */ + protected function pfsockopen() + { + return @pfsockopen($this->connectionString, -1, $this->errno, $this->errstr, $this->connectionTimeout); + } + + /** + * Wrapper to allow mocking + * + * @return resource|false + */ + protected function fsockopen() + { + return @fsockopen($this->connectionString, -1, $this->errno, $this->errstr, $this->connectionTimeout); + } + + /** + * Wrapper to allow mocking + * + * @see http://php.net/manual/en/function.stream-set-timeout.php + * + * @return bool + */ + protected function streamSetTimeout() + { + $seconds = floor($this->timeout); + $microseconds = round(($this->timeout - $seconds) * 1e6); + + if (!is_resource($this->resource)) { + throw new \LogicException('streamSetTimeout called but $this->resource is not a resource'); + } + + return stream_set_timeout($this->resource, (int) $seconds, (int) $microseconds); + } + + /** + * Wrapper to allow mocking + * + * @see http://php.net/manual/en/function.stream-set-chunk-size.php + * + * @return int|bool + */ + protected function streamSetChunkSize() + { + if (!is_resource($this->resource)) { + throw new \LogicException('streamSetChunkSize called but $this->resource is not a resource'); + } + + if (null === $this->chunkSize) { + throw new \LogicException('streamSetChunkSize called but $this->chunkSize is not set'); + } + + return stream_set_chunk_size($this->resource, $this->chunkSize); + } + + /** + * Wrapper to allow mocking + * + * @return int|bool + */ + protected function fwrite(string $data) + { + if (!is_resource($this->resource)) { + throw new \LogicException('fwrite called but $this->resource is not a resource'); + } + + return @fwrite($this->resource, $data); + } + + /** + * Wrapper to allow mocking + * + * @return mixed[]|bool + */ + protected function streamGetMetadata() + { + if (!is_resource($this->resource)) { + throw new \LogicException('streamGetMetadata called but $this->resource is not a resource'); + } + + return stream_get_meta_data($this->resource); + } + + private function validateTimeout(float $value): void + { + if ($value < 0) { + throw new \InvalidArgumentException("Timeout must be 0 or a positive float (got $value)"); + } + } + + private function connectIfNotConnected(): void + { + if ($this->isConnected()) { + return; + } + $this->connect(); + } + + /** + * @phpstan-param FormattedRecord $record + */ + protected function generateDataStream(array $record): string + { + return (string) $record['formatted']; + } + + /** + * @return resource|null + */ + protected function getResource() + { + return $this->resource; + } + + private function connect(): void + { + $this->createSocketResource(); + $this->setSocketTimeout(); + $this->setStreamChunkSize(); + } + + private function createSocketResource(): void + { + if ($this->isPersistent()) { + $resource = $this->pfsockopen(); + } else { + $resource = $this->fsockopen(); + } + if (is_bool($resource)) { + throw new \UnexpectedValueException("Failed connecting to $this->connectionString ($this->errno: $this->errstr)"); + } + $this->resource = $resource; + } + + private function setSocketTimeout(): void + { + if (!$this->streamSetTimeout()) { + throw new \UnexpectedValueException("Failed setting timeout with stream_set_timeout()"); + } + } + + private function setStreamChunkSize(): void + { + if ($this->chunkSize && !$this->streamSetChunkSize()) { + throw new \UnexpectedValueException("Failed setting chunk size with stream_set_chunk_size()"); + } + } + + private function writeToSocket(string $data): void + { + $length = strlen($data); + $sent = 0; + $this->lastSentBytes = $sent; + while ($this->isConnected() && $sent < $length) { + if (0 == $sent) { + $chunk = $this->fwrite($data); + } else { + $chunk = $this->fwrite(substr($data, $sent)); + } + if ($chunk === false) { + throw new \RuntimeException("Could not write to socket"); + } + $sent += $chunk; + $socketInfo = $this->streamGetMetadata(); + if (is_array($socketInfo) && $socketInfo['timed_out']) { + throw new \RuntimeException("Write timed-out"); + } + + if ($this->writingIsTimedOut($sent)) { + throw new \RuntimeException("Write timed-out, no data sent for `{$this->writingTimeout}` seconds, probably we got disconnected (sent $sent of $length)"); + } + } + if (!$this->isConnected() && $sent < $length) { + throw new \RuntimeException("End-of-file reached, probably we got disconnected (sent $sent of $length)"); + } + } + + private function writingIsTimedOut(int $sent): bool + { + // convert to ms + if (0.0 == $this->writingTimeout) { + return false; + } + + if ($sent !== $this->lastSentBytes) { + $this->lastWritingAt = microtime(true); + $this->lastSentBytes = $sent; + + return false; + } else { + usleep(100); + } + + if ((microtime(true) - $this->lastWritingAt) >= $this->writingTimeout) { + $this->closeSocket(); + + return true; + } + + return false; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SqsHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SqsHandler.php new file mode 100644 index 0000000..dcf282b --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SqsHandler.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\Handler; + +use Aws\Sqs\SqsClient; +use Monolog\Logger; +use Monolog\Utils; + +/** + * Writes to any sqs queue. + * + * @author Martijn van Calker + */ +class SqsHandler extends AbstractProcessingHandler +{ + /** 256 KB in bytes - maximum message size in SQS */ + protected const MAX_MESSAGE_SIZE = 262144; + /** 100 KB in bytes - head message size for new error log */ + protected const HEAD_MESSAGE_SIZE = 102400; + + /** @var SqsClient */ + private $client; + /** @var string */ + private $queueUrl; + + public function __construct(SqsClient $sqsClient, string $queueUrl, $level = Logger::DEBUG, bool $bubble = true) + { + parent::__construct($level, $bubble); + + $this->client = $sqsClient; + $this->queueUrl = $queueUrl; + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void + { + if (!isset($record['formatted']) || 'string' !== gettype($record['formatted'])) { + throw new \InvalidArgumentException('SqsHandler accepts only formatted records as a string' . Utils::getRecordMessageForException($record)); + } + + $messageBody = $record['formatted']; + if (strlen($messageBody) >= static::MAX_MESSAGE_SIZE) { + $messageBody = Utils::substr($messageBody, 0, static::HEAD_MESSAGE_SIZE); + } + + $this->client->sendMessage([ + 'QueueUrl' => $this->queueUrl, + 'MessageBody' => $messageBody, + ]); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php new file mode 100644 index 0000000..6518351 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php @@ -0,0 +1,221 @@ + + * + * 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\Utils; + +/** + * Stores to any stream resource + * + * Can be used to store into php://stderr, remote and local files, etc. + * + * @author Jordi Boggiano + * + * @phpstan-import-type FormattedRecord from AbstractProcessingHandler + */ +class StreamHandler extends AbstractProcessingHandler +{ + /** @const int */ + protected const MAX_CHUNK_SIZE = 2147483647; + /** @const int 10MB */ + protected const DEFAULT_CHUNK_SIZE = 10 * 1024 * 1024; + /** @var int */ + protected $streamChunkSize; + /** @var resource|null */ + protected $stream; + /** @var ?string */ + protected $url = null; + /** @var ?string */ + private $errorMessage = null; + /** @var ?int */ + protected $filePermission; + /** @var bool */ + protected $useLocking; + /** @var true|null */ + private $dirCreated = null; + + /** + * @param resource|string $stream If a missing path can't be created, an UnexpectedValueException will be thrown on first write + * @param int|null $filePermission Optional file permissions (default (0644) are only for owner read/write) + * @param bool $useLocking Try to lock log file before doing any writes + * + * @throws \InvalidArgumentException If stream is not a resource or string + */ + public function __construct($stream, $level = Logger::DEBUG, bool $bubble = true, ?int $filePermission = null, bool $useLocking = false) + { + parent::__construct($level, $bubble); + + if (($phpMemoryLimit = Utils::expandIniShorthandBytes(ini_get('memory_limit'))) !== false) { + if ($phpMemoryLimit > 0) { + // use max 10% of allowed memory for the chunk size, and at least 100KB + $this->streamChunkSize = min(static::MAX_CHUNK_SIZE, max((int) ($phpMemoryLimit / 10), 100 * 1024)); + } else { + // memory is unlimited, set to the default 10MB + $this->streamChunkSize = static::DEFAULT_CHUNK_SIZE; + } + } else { + // no memory limit information, set to the default 10MB + $this->streamChunkSize = static::DEFAULT_CHUNK_SIZE; + } + + if (is_resource($stream)) { + $this->stream = $stream; + + stream_set_chunk_size($this->stream, $this->streamChunkSize); + } elseif (is_string($stream)) { + $this->url = Utils::canonicalizePath($stream); + } else { + throw new \InvalidArgumentException('A stream must either be a resource or a string.'); + } + + $this->filePermission = $filePermission; + $this->useLocking = $useLocking; + } + + /** + * {@inheritDoc} + */ + public function close(): void + { + if ($this->url && is_resource($this->stream)) { + fclose($this->stream); + } + $this->stream = null; + $this->dirCreated = null; + } + + /** + * Return the currently active stream if it is open + * + * @return resource|null + */ + public function getStream() + { + return $this->stream; + } + + /** + * Return the stream URL if it was configured with a URL and not an active resource + * + * @return string|null + */ + public function getUrl(): ?string + { + return $this->url; + } + + /** + * @return int + */ + public function getStreamChunkSize(): int + { + return $this->streamChunkSize; + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void + { + if (!is_resource($this->stream)) { + $url = $this->url; + if (null === $url || '' === $url) { + throw new \LogicException('Missing stream url, the stream can not be opened. This may be caused by a premature call to close().' . Utils::getRecordMessageForException($record)); + } + $this->createDir($url); + $this->errorMessage = null; + set_error_handler([$this, 'customErrorHandler']); + $stream = fopen($url, 'a'); + if ($this->filePermission !== null) { + @chmod($url, $this->filePermission); + } + restore_error_handler(); + if (!is_resource($stream)) { + $this->stream = null; + + throw new \UnexpectedValueException(sprintf('The stream or file "%s" could not be opened in append mode: '.$this->errorMessage, $url) . Utils::getRecordMessageForException($record)); + } + stream_set_chunk_size($stream, $this->streamChunkSize); + $this->stream = $stream; + } + + $stream = $this->stream; + if (!is_resource($stream)) { + throw new \LogicException('No stream was opened yet' . Utils::getRecordMessageForException($record)); + } + + if ($this->useLocking) { + // ignoring errors here, there's not much we can do about them + flock($stream, LOCK_EX); + } + + $this->streamWrite($stream, $record); + + if ($this->useLocking) { + flock($stream, LOCK_UN); + } + } + + /** + * Write to stream + * @param resource $stream + * @param array $record + * + * @phpstan-param FormattedRecord $record + */ + protected function streamWrite($stream, array $record): void + { + fwrite($stream, (string) $record['formatted']); + } + + private function customErrorHandler(int $code, string $msg): bool + { + $this->errorMessage = preg_replace('{^(fopen|mkdir)\(.*?\): }', '', $msg); + + return true; + } + + private function getDirFromStream(string $stream): ?string + { + $pos = strpos($stream, '://'); + if ($pos === false) { + return dirname($stream); + } + + if ('file://' === substr($stream, 0, 7)) { + return dirname(substr($stream, 7)); + } + + return null; + } + + private function createDir(string $url): void + { + // Do not try to create dir if it has already been tried. + if ($this->dirCreated) { + return; + } + + $dir = $this->getDirFromStream($url); + if (null !== $dir && !is_dir($dir)) { + $this->errorMessage = null; + set_error_handler([$this, 'customErrorHandler']); + $status = mkdir($dir, 0777, true); + restore_error_handler(); + if (false === $status && !is_dir($dir) && strpos((string) $this->errorMessage, 'File exists') === false) { + throw new \UnexpectedValueException(sprintf('There is no existing directory at "%s" and it could not be created: '.$this->errorMessage, $dir)); + } + } + $this->dirCreated = true; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.php new file mode 100644 index 0000000..fae9251 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.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\Logger; +use Monolog\Utils; +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\LineFormatter; +use Swift_Message; +use Swift; + +/** + * SwiftMailerHandler uses Swift_Mailer to send the emails + * + * @author Gyula Sallai + * + * @phpstan-import-type Record from \Monolog\Logger + * @deprecated Since Monolog 2.6. Use SymfonyMailerHandler instead. + */ +class SwiftMailerHandler extends MailHandler +{ + /** @var \Swift_Mailer */ + protected $mailer; + /** @var Swift_Message|callable(string, Record[]): Swift_Message */ + private $messageTemplate; + + /** + * @psalm-param Swift_Message|callable(string, Record[]): Swift_Message $message + * + * @param \Swift_Mailer $mailer The mailer to use + * @param callable|Swift_Message $message An example message for real messages, only the body will be replaced + */ + public function __construct(\Swift_Mailer $mailer, $message, $level = Logger::ERROR, bool $bubble = true) + { + parent::__construct($level, $bubble); + + @trigger_error('The SwiftMailerHandler is deprecated since Monolog 2.6. Use SymfonyMailerHandler instead.', E_USER_DEPRECATED); + + $this->mailer = $mailer; + $this->messageTemplate = $message; + } + + /** + * {@inheritDoc} + */ + protected function send(string $content, array $records): void + { + $this->mailer->send($this->buildMessage($content, $records)); + } + + /** + * Gets the formatter for the Swift_Message subject. + * + * @param string|null $format The format of the subject + */ + protected function getSubjectFormatter(?string $format): FormatterInterface + { + return new LineFormatter($format); + } + + /** + * Creates instance of Swift_Message to be sent + * + * @param string $content formatted email body to be sent + * @param array $records Log records that formed the content + * @return Swift_Message + * + * @phpstan-param Record[] $records + */ + protected function buildMessage(string $content, array $records): Swift_Message + { + $message = null; + if ($this->messageTemplate instanceof Swift_Message) { + $message = clone $this->messageTemplate; + $message->generateId(); + } elseif (is_callable($this->messageTemplate)) { + $message = ($this->messageTemplate)($content, $records); + } + + if (!$message instanceof Swift_Message) { + $record = reset($records); + throw new \InvalidArgumentException('Could not resolve message as instance of Swift_Message or a callable returning it' . ($record ? Utils::getRecordMessageForException($record) : '')); + } + + if ($records) { + $subjectFormatter = $this->getSubjectFormatter($message->getSubject()); + $message->setSubject($subjectFormatter->format($this->getHighestRecord($records))); + } + + $mime = 'text/plain'; + if ($this->isHtmlBody($content)) { + $mime = 'text/html'; + } + + $message->setBody($content, $mime); + /** @phpstan-ignore-next-line */ + if (version_compare(Swift::VERSION, '6.0.0', '>=')) { + $message->setDate(new \DateTimeImmutable()); + } else { + /** @phpstan-ignore-next-line */ + $message->setDate(time()); + } + + return $message; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SymfonyMailerHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SymfonyMailerHandler.php new file mode 100644 index 0000000..130e6f1 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SymfonyMailerHandler.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\Logger; +use Monolog\Utils; +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\LineFormatter; +use Symfony\Component\Mailer\MailerInterface; +use Symfony\Component\Mailer\Transport\TransportInterface; +use Symfony\Component\Mime\Email; + +/** + * SymfonyMailerHandler uses Symfony's Mailer component to send the emails + * + * @author Jordi Boggiano + * + * @phpstan-import-type Record from \Monolog\Logger + */ +class SymfonyMailerHandler extends MailHandler +{ + /** @var MailerInterface|TransportInterface */ + protected $mailer; + /** @var Email|callable(string, Record[]): Email */ + private $emailTemplate; + + /** + * @psalm-param Email|callable(string, Record[]): Email $email + * + * @param MailerInterface|TransportInterface $mailer The mailer to use + * @param callable|Email $email An email template, the subject/body will be replaced + */ + public function __construct($mailer, $email, $level = Logger::ERROR, bool $bubble = true) + { + parent::__construct($level, $bubble); + + $this->mailer = $mailer; + $this->emailTemplate = $email; + } + + /** + * {@inheritDoc} + */ + protected function send(string $content, array $records): void + { + $this->mailer->send($this->buildMessage($content, $records)); + } + + /** + * Gets the formatter for the Swift_Message subject. + * + * @param string|null $format The format of the subject + */ + protected function getSubjectFormatter(?string $format): FormatterInterface + { + return new LineFormatter($format); + } + + /** + * Creates instance of Email to be sent + * + * @param string $content formatted email body to be sent + * @param array $records Log records that formed the content + * + * @phpstan-param Record[] $records + */ + protected function buildMessage(string $content, array $records): Email + { + $message = null; + if ($this->emailTemplate instanceof Email) { + $message = clone $this->emailTemplate; + } elseif (is_callable($this->emailTemplate)) { + $message = ($this->emailTemplate)($content, $records); + } + + if (!$message instanceof Email) { + $record = reset($records); + throw new \InvalidArgumentException('Could not resolve message as instance of Email or a callable returning it' . ($record ? Utils::getRecordMessageForException($record) : '')); + } + + if ($records) { + $subjectFormatter = $this->getSubjectFormatter($message->getSubject()); + $message->subject($subjectFormatter->format($this->getHighestRecord($records))); + } + + if ($this->isHtmlBody($content)) { + if (null !== ($charset = $message->getHtmlCharset())) { + $message->html($content, $charset); + } else { + $message->html($content); + } + } else { + if (null !== ($charset = $message->getTextCharset())) { + $message->text($content, $charset); + } else { + $message->text($content); + } + } + + return $message->date(new \DateTimeImmutable()); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SyslogHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SyslogHandler.php new file mode 100644 index 0000000..1d543b7 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SyslogHandler.php @@ -0,0 +1,68 @@ + + * + * 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\Utils; + +/** + * Logs to syslog service. + * + * usage example: + * + * $log = new Logger('application'); + * $syslog = new SyslogHandler('myfacility', 'local6'); + * $formatter = new LineFormatter("%channel%.%level_name%: %message% %extra%"); + * $syslog->setFormatter($formatter); + * $log->pushHandler($syslog); + * + * @author Sven Paulus + */ +class SyslogHandler extends AbstractSyslogHandler +{ + /** @var string */ + protected $ident; + /** @var int */ + protected $logopts; + + /** + * @param string $ident + * @param string|int $facility Either one of the names of the keys in $this->facilities, or a LOG_* facility constant + * @param int $logopts Option flags for the openlog() call, defaults to LOG_PID + */ + public function __construct(string $ident, $facility = LOG_USER, $level = Logger::DEBUG, bool $bubble = true, int $logopts = LOG_PID) + { + parent::__construct($facility, $level, $bubble); + + $this->ident = $ident; + $this->logopts = $logopts; + } + + /** + * {@inheritDoc} + */ + public function close(): void + { + closelog(); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void + { + if (!openlog($this->ident, $this->logopts, $this->facility)) { + throw new \LogicException('Can\'t open syslog for ident "'.$this->ident.'" and facility "'.$this->facility.'"' . Utils::getRecordMessageForException($record)); + } + syslog($this->logLevels[$record['level']], (string) $record['formatted']); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.php b/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.php new file mode 100644 index 0000000..dbd8ef6 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.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\SyslogUdp; + +use Monolog\Utils; +use Socket; + +class UdpSocket +{ + protected const DATAGRAM_MAX_LENGTH = 65023; + + /** @var string */ + protected $ip; + /** @var int */ + protected $port; + /** @var resource|Socket|null */ + protected $socket = null; + + public function __construct(string $ip, int $port = 514) + { + $this->ip = $ip; + $this->port = $port; + } + + /** + * @param string $line + * @param string $header + * @return void + */ + public function write($line, $header = "") + { + $this->send($this->assembleMessage($line, $header)); + } + + public function close(): void + { + if (is_resource($this->socket) || $this->socket instanceof Socket) { + socket_close($this->socket); + $this->socket = null; + } + } + + /** + * @return resource|Socket + */ + protected function getSocket() + { + if (null !== $this->socket) { + return $this->socket; + } + + $domain = AF_INET; + $protocol = SOL_UDP; + // Check if we are using unix sockets. + if ($this->port === 0) { + $domain = AF_UNIX; + $protocol = IPPROTO_IP; + } + + $this->socket = socket_create($domain, SOCK_DGRAM, $protocol) ?: null; + if (null === $this->socket) { + throw new \RuntimeException('The UdpSocket to '.$this->ip.':'.$this->port.' could not be opened via socket_create'); + } + + return $this->socket; + } + + protected function send(string $chunk): void + { + socket_sendto($this->getSocket(), $chunk, strlen($chunk), $flags = 0, $this->ip, $this->port); + } + + protected function assembleMessage(string $line, string $header): string + { + $chunkSize = static::DATAGRAM_MAX_LENGTH - strlen($header); + + return $header . Utils::substr($line, 0, $chunkSize); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php new file mode 100644 index 0000000..deaa19f --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php @@ -0,0 +1,150 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use DateTimeInterface; +use Monolog\Logger; +use Monolog\Handler\SyslogUdp\UdpSocket; +use Monolog\Utils; + +/** + * A Handler for logging to a remote syslogd server. + * + * @author Jesper Skovgaard Nielsen + * @author Dominik Kukacka + */ +class SyslogUdpHandler extends AbstractSyslogHandler +{ + const RFC3164 = 0; + const RFC5424 = 1; + const RFC5424e = 2; + + /** @var array */ + private $dateFormats = array( + self::RFC3164 => 'M d H:i:s', + self::RFC5424 => \DateTime::RFC3339, + self::RFC5424e => \DateTime::RFC3339_EXTENDED, + ); + + /** @var UdpSocket */ + protected $socket; + /** @var string */ + protected $ident; + /** @var self::RFC* */ + protected $rfc; + + /** + * @param string $host Either IP/hostname or a path to a unix socket (port must be 0 then) + * @param int $port Port number, or 0 if $host is a unix socket + * @param string|int $facility Either one of the names of the keys in $this->facilities, or a LOG_* facility constant + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param string $ident Program name or tag for each log message. + * @param int $rfc RFC to format the message for. + * @throws MissingExtensionException + * + * @phpstan-param self::RFC* $rfc + */ + public function __construct(string $host, int $port = 514, $facility = LOG_USER, $level = Logger::DEBUG, bool $bubble = true, string $ident = 'php', int $rfc = self::RFC5424) + { + if (!extension_loaded('sockets')) { + throw new MissingExtensionException('The sockets extension is required to use the SyslogUdpHandler'); + } + + parent::__construct($facility, $level, $bubble); + + $this->ident = $ident; + $this->rfc = $rfc; + + $this->socket = new UdpSocket($host, $port); + } + + protected function write(array $record): void + { + $lines = $this->splitMessageIntoLines($record['formatted']); + + $header = $this->makeCommonSyslogHeader($this->logLevels[$record['level']], $record['datetime']); + + foreach ($lines as $line) { + $this->socket->write($line, $header); + } + } + + public function close(): void + { + $this->socket->close(); + } + + /** + * @param string|string[] $message + * @return string[] + */ + private function splitMessageIntoLines($message): array + { + if (is_array($message)) { + $message = implode("\n", $message); + } + + $lines = preg_split('/$\R?^/m', (string) $message, -1, PREG_SPLIT_NO_EMPTY); + if (false === $lines) { + $pcreErrorCode = preg_last_error(); + throw new \RuntimeException('Could not preg_split: ' . $pcreErrorCode . ' / ' . Utils::pcreLastErrorMessage($pcreErrorCode)); + } + + return $lines; + } + + /** + * Make common syslog header (see rfc5424 or rfc3164) + */ + protected function makeCommonSyslogHeader(int $severity, DateTimeInterface $datetime): string + { + $priority = $severity + $this->facility; + + if (!$pid = getmypid()) { + $pid = '-'; + } + + if (!$hostname = gethostname()) { + $hostname = '-'; + } + + if ($this->rfc === self::RFC3164) { + // see https://github.com/phpstan/phpstan/issues/5348 + // @phpstan-ignore-next-line + $dateNew = $datetime->setTimezone(new \DateTimeZone('UTC')); + $date = $dateNew->format($this->dateFormats[$this->rfc]); + + return "<$priority>" . + $date . " " . + $hostname . " " . + $this->ident . "[" . $pid . "]: "; + } + + $date = $datetime->format($this->dateFormats[$this->rfc]); + + return "<$priority>1 " . + $date . " " . + $hostname . " " . + $this->ident . " " . + $pid . " - - "; + } + + /** + * Inject your own socket, mainly used for testing + */ + public function setSocket(UdpSocket $socket): self + { + $this->socket = $socket; + + return $this; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/TelegramBotHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/TelegramBotHandler.php new file mode 100644 index 0000000..8912eba --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/TelegramBotHandler.php @@ -0,0 +1,274 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use RuntimeException; +use Monolog\Logger; +use Monolog\Utils; + +/** + * Handler send logs to Telegram using Telegram Bot API. + * + * How to use: + * 1) Create telegram bot with https://telegram.me/BotFather + * 2) Create a telegram channel where logs will be recorded. + * 3) Add created bot from step 1 to the created channel from step 2. + * + * Use telegram bot API key from step 1 and channel name with '@' prefix from step 2 to create instance of TelegramBotHandler + * + * @link https://core.telegram.org/bots/api + * + * @author Mazur Alexandr + * + * @phpstan-import-type Record from \Monolog\Logger + */ +class TelegramBotHandler extends AbstractProcessingHandler +{ + private const BOT_API = 'https://api.telegram.org/bot'; + + /** + * The available values of parseMode according to the Telegram api documentation + */ + private const AVAILABLE_PARSE_MODES = [ + 'HTML', + 'MarkdownV2', + 'Markdown', // legacy mode without underline and strikethrough, use MarkdownV2 instead + ]; + + /** + * The maximum number of characters allowed in a message according to the Telegram api documentation + */ + private const MAX_MESSAGE_LENGTH = 4096; + + /** + * Telegram bot access token provided by BotFather. + * Create telegram bot with https://telegram.me/BotFather and use access token from it. + * @var string + */ + private $apiKey; + + /** + * Telegram channel name. + * Since to start with '@' symbol as prefix. + * @var string + */ + private $channel; + + /** + * The kind of formatting that is used for the message. + * See available options at https://core.telegram.org/bots/api#formatting-options + * or in AVAILABLE_PARSE_MODES + * @var ?string + */ + private $parseMode; + + /** + * Disables link previews for links in the message. + * @var ?bool + */ + private $disableWebPagePreview; + + /** + * Sends the message silently. Users will receive a notification with no sound. + * @var ?bool + */ + private $disableNotification; + + /** + * True - split a message longer than MAX_MESSAGE_LENGTH into parts and send in multiple messages. + * False - truncates a message that is too long. + * @var bool + */ + private $splitLongMessages; + + /** + * Adds 1-second delay between sending a split message (according to Telegram API to avoid 429 Too Many Requests). + * @var bool + */ + private $delayBetweenMessages; + + /** + * @param string $apiKey Telegram bot access token provided by BotFather + * @param string $channel Telegram channel name + * @param bool $splitLongMessages Split a message longer than MAX_MESSAGE_LENGTH into parts and send in multiple messages + * @param bool $delayBetweenMessages Adds delay between sending a split message according to Telegram API + * @throws MissingExtensionException + */ + public function __construct( + string $apiKey, + string $channel, + $level = Logger::DEBUG, + bool $bubble = true, + string $parseMode = null, + bool $disableWebPagePreview = null, + bool $disableNotification = null, + bool $splitLongMessages = false, + bool $delayBetweenMessages = false + ) + { + if (!extension_loaded('curl')) { + throw new MissingExtensionException('The curl extension is needed to use the TelegramBotHandler'); + } + + parent::__construct($level, $bubble); + + $this->apiKey = $apiKey; + $this->channel = $channel; + $this->setParseMode($parseMode); + $this->disableWebPagePreview($disableWebPagePreview); + $this->disableNotification($disableNotification); + $this->splitLongMessages($splitLongMessages); + $this->delayBetweenMessages($delayBetweenMessages); + } + + public function setParseMode(string $parseMode = null): self + { + if ($parseMode !== null && !in_array($parseMode, self::AVAILABLE_PARSE_MODES)) { + throw new \InvalidArgumentException('Unknown parseMode, use one of these: ' . implode(', ', self::AVAILABLE_PARSE_MODES) . '.'); + } + + $this->parseMode = $parseMode; + + return $this; + } + + public function disableWebPagePreview(bool $disableWebPagePreview = null): self + { + $this->disableWebPagePreview = $disableWebPagePreview; + + return $this; + } + + public function disableNotification(bool $disableNotification = null): self + { + $this->disableNotification = $disableNotification; + + return $this; + } + + /** + * True - split a message longer than MAX_MESSAGE_LENGTH into parts and send in multiple messages. + * False - truncates a message that is too long. + * @param bool $splitLongMessages + * @return $this + */ + public function splitLongMessages(bool $splitLongMessages = false): self + { + $this->splitLongMessages = $splitLongMessages; + + return $this; + } + + /** + * Adds 1-second delay between sending a split message (according to Telegram API to avoid 429 Too Many Requests). + * @param bool $delayBetweenMessages + * @return $this + */ + public function delayBetweenMessages(bool $delayBetweenMessages = false): self + { + $this->delayBetweenMessages = $delayBetweenMessages; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function handleBatch(array $records): void + { + /** @var Record[] $messages */ + $messages = []; + + foreach ($records as $record) { + if (!$this->isHandling($record)) { + continue; + } + + if ($this->processors) { + /** @var Record $record */ + $record = $this->processRecord($record); + } + + $messages[] = $record; + } + + if (!empty($messages)) { + $this->send((string)$this->getFormatter()->formatBatch($messages)); + } + } + + /** + * @inheritDoc + */ + protected function write(array $record): void + { + $this->send($record['formatted']); + } + + /** + * Send request to @link https://api.telegram.org/bot on SendMessage action. + * @param string $message + */ + protected function send(string $message): void + { + $messages = $this->handleMessageLength($message); + + foreach ($messages as $key => $msg) { + if ($this->delayBetweenMessages && $key > 0) { + sleep(1); + } + + $this->sendCurl($msg); + } + } + + protected function sendCurl(string $message): void + { + $ch = curl_init(); + $url = self::BOT_API . $this->apiKey . '/SendMessage'; + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([ + 'text' => $message, + 'chat_id' => $this->channel, + 'parse_mode' => $this->parseMode, + 'disable_web_page_preview' => $this->disableWebPagePreview, + 'disable_notification' => $this->disableNotification, + ])); + + $result = Curl\Util::execute($ch); + if (!is_string($result)) { + throw new RuntimeException('Telegram API error. Description: No response'); + } + $result = json_decode($result, true); + + if ($result['ok'] === false) { + throw new RuntimeException('Telegram API error. Description: ' . $result['description']); + } + } + + /** + * Handle a message that is too long: truncates or splits into several + * @param string $message + * @return string[] + */ + private function handleMessageLength(string $message): array + { + $truncatedMarker = ' (...truncated)'; + if (!$this->splitLongMessages && strlen($message) > self::MAX_MESSAGE_LENGTH) { + return [Utils::substr($message, 0, self::MAX_MESSAGE_LENGTH - strlen($truncatedMarker)) . $truncatedMarker]; + } + + return str_split($message, self::MAX_MESSAGE_LENGTH); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/TestHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/TestHandler.php new file mode 100644 index 0000000..0986da2 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/TestHandler.php @@ -0,0 +1,231 @@ + + * + * 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 Psr\Log\LogLevel; + +/** + * Used for testing purposes. + * + * It records all records and gives you access to them for verification. + * + * @author Jordi Boggiano + * + * @method bool hasEmergency($record) + * @method bool hasAlert($record) + * @method bool hasCritical($record) + * @method bool hasError($record) + * @method bool hasWarning($record) + * @method bool hasNotice($record) + * @method bool hasInfo($record) + * @method bool hasDebug($record) + * + * @method bool hasEmergencyRecords() + * @method bool hasAlertRecords() + * @method bool hasCriticalRecords() + * @method bool hasErrorRecords() + * @method bool hasWarningRecords() + * @method bool hasNoticeRecords() + * @method bool hasInfoRecords() + * @method bool hasDebugRecords() + * + * @method bool hasEmergencyThatContains($message) + * @method bool hasAlertThatContains($message) + * @method bool hasCriticalThatContains($message) + * @method bool hasErrorThatContains($message) + * @method bool hasWarningThatContains($message) + * @method bool hasNoticeThatContains($message) + * @method bool hasInfoThatContains($message) + * @method bool hasDebugThatContains($message) + * + * @method bool hasEmergencyThatMatches($message) + * @method bool hasAlertThatMatches($message) + * @method bool hasCriticalThatMatches($message) + * @method bool hasErrorThatMatches($message) + * @method bool hasWarningThatMatches($message) + * @method bool hasNoticeThatMatches($message) + * @method bool hasInfoThatMatches($message) + * @method bool hasDebugThatMatches($message) + * + * @method bool hasEmergencyThatPasses($message) + * @method bool hasAlertThatPasses($message) + * @method bool hasCriticalThatPasses($message) + * @method bool hasErrorThatPasses($message) + * @method bool hasWarningThatPasses($message) + * @method bool hasNoticeThatPasses($message) + * @method bool hasInfoThatPasses($message) + * @method bool hasDebugThatPasses($message) + * + * @phpstan-import-type Record from \Monolog\Logger + * @phpstan-import-type Level from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger + */ +class TestHandler extends AbstractProcessingHandler +{ + /** @var Record[] */ + protected $records = []; + /** @var array */ + protected $recordsByLevel = []; + /** @var bool */ + private $skipReset = false; + + /** + * @return array + * + * @phpstan-return Record[] + */ + public function getRecords() + { + return $this->records; + } + + /** + * @return void + */ + public function clear() + { + $this->records = []; + $this->recordsByLevel = []; + } + + /** + * @return void + */ + public function reset() + { + if (!$this->skipReset) { + $this->clear(); + } + } + + /** + * @return void + */ + public function setSkipReset(bool $skipReset) + { + $this->skipReset = $skipReset; + } + + /** + * @param string|int $level Logging level value or name + * + * @phpstan-param Level|LevelName|LogLevel::* $level + */ + public function hasRecords($level): bool + { + return isset($this->recordsByLevel[Logger::toMonologLevel($level)]); + } + + /** + * @param string|array $record Either a message string or an array containing message and optionally context keys that will be checked against all records + * @param string|int $level Logging level value or name + * + * @phpstan-param array{message: string, context?: mixed[]}|string $record + * @phpstan-param Level|LevelName|LogLevel::* $level + */ + public function hasRecord($record, $level): bool + { + if (is_string($record)) { + $record = array('message' => $record); + } + + return $this->hasRecordThatPasses(function ($rec) use ($record) { + if ($rec['message'] !== $record['message']) { + return false; + } + if (isset($record['context']) && $rec['context'] !== $record['context']) { + return false; + } + + return true; + }, $level); + } + + /** + * @param string|int $level Logging level value or name + * + * @phpstan-param Level|LevelName|LogLevel::* $level + */ + public function hasRecordThatContains(string $message, $level): bool + { + return $this->hasRecordThatPasses(function ($rec) use ($message) { + return strpos($rec['message'], $message) !== false; + }, $level); + } + + /** + * @param string|int $level Logging level value or name + * + * @phpstan-param Level|LevelName|LogLevel::* $level + */ + public function hasRecordThatMatches(string $regex, $level): bool + { + return $this->hasRecordThatPasses(function (array $rec) use ($regex): bool { + return preg_match($regex, $rec['message']) > 0; + }, $level); + } + + /** + * @param string|int $level Logging level value or name + * @return bool + * + * @psalm-param callable(Record, int): mixed $predicate + * @phpstan-param Level|LevelName|LogLevel::* $level + */ + public function hasRecordThatPasses(callable $predicate, $level) + { + $level = Logger::toMonologLevel($level); + + if (!isset($this->recordsByLevel[$level])) { + return false; + } + + foreach ($this->recordsByLevel[$level] as $i => $rec) { + if ($predicate($rec, $i)) { + return true; + } + } + + return false; + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void + { + $this->recordsByLevel[$record['level']][] = $record; + $this->records[] = $record; + } + + /** + * @param string $method + * @param mixed[] $args + * @return bool + */ + public function __call($method, $args) + { + if (preg_match('/(.*)(Debug|Info|Notice|Warning|Error|Critical|Alert|Emergency)(.*)/', $method, $matches) > 0) { + $genericMethod = $matches[1] . ('Records' !== $matches[3] ? 'Record' : '') . $matches[3]; + $level = constant('Monolog\Logger::' . strtoupper($matches[2])); + $callback = [$this, $genericMethod]; + if (is_callable($callback)) { + $args[] = $level; + + return call_user_func_array($callback, $args); + } + } + + throw new \BadMethodCallException('Call to undefined method ' . get_class($this) . '::' . $method . '()'); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/WebRequestRecognizerTrait.php b/vendor/monolog/monolog/src/Monolog/Handler/WebRequestRecognizerTrait.php new file mode 100644 index 0000000..c818352 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/WebRequestRecognizerTrait.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +trait WebRequestRecognizerTrait +{ + /** + * Checks if PHP's serving a web request + * @return bool + */ + protected function isWebRequest(): bool + { + return 'cli' !== \PHP_SAPI && 'phpdbg' !== \PHP_SAPI; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php new file mode 100644 index 0000000..b6d3d3b --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +/** + * Forwards records to multiple handlers suppressing failures of each handler + * and continuing through to give every handler a chance to succeed. + * + * @author Craig D'Amelio + * + * @phpstan-import-type Record from \Monolog\Logger + */ +class WhatFailureGroupHandler extends GroupHandler +{ + /** + * {@inheritDoc} + */ + public function handle(array $record): bool + { + if ($this->processors) { + /** @var Record $record */ + $record = $this->processRecord($record); + } + + foreach ($this->handlers as $handler) { + try { + $handler->handle($record); + } catch (\Throwable $e) { + // What failure? + } + } + + return false === $this->bubble; + } + + /** + * {@inheritDoc} + */ + public function handleBatch(array $records): void + { + if ($this->processors) { + $processed = array(); + foreach ($records as $record) { + $processed[] = $this->processRecord($record); + } + /** @var Record[] $records */ + $records = $processed; + } + + foreach ($this->handlers as $handler) { + try { + $handler->handleBatch($records); + } catch (\Throwable $e) { + // What failure? + } + } + } + + /** + * {@inheritDoc} + */ + public function close(): void + { + foreach ($this->handlers as $handler) { + try { + $handler->close(); + } catch (\Throwable $e) { + // What failure? + } + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php new file mode 100644 index 0000000..ddd46d8 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php @@ -0,0 +1,101 @@ + + * + * 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\FormatterInterface; +use Monolog\Formatter\NormalizerFormatter; +use Monolog\Logger; + +/** + * Handler sending logs to Zend Monitor + * + * @author Christian Bergau + * @author Jason Davis + * + * @phpstan-import-type FormattedRecord from AbstractProcessingHandler + */ +class ZendMonitorHandler extends AbstractProcessingHandler +{ + /** + * Monolog level / ZendMonitor Custom Event priority map + * + * @var array + */ + protected $levelMap = []; + + /** + * @throws MissingExtensionException + */ + public function __construct($level = Logger::DEBUG, bool $bubble = true) + { + if (!function_exists('zend_monitor_custom_event')) { + throw new MissingExtensionException( + 'You must have Zend Server installed with Zend Monitor enabled in order to use this handler' + ); + } + //zend monitor constants are not defined if zend monitor is not enabled. + $this->levelMap = [ + Logger::DEBUG => \ZEND_MONITOR_EVENT_SEVERITY_INFO, + Logger::INFO => \ZEND_MONITOR_EVENT_SEVERITY_INFO, + Logger::NOTICE => \ZEND_MONITOR_EVENT_SEVERITY_INFO, + Logger::WARNING => \ZEND_MONITOR_EVENT_SEVERITY_WARNING, + Logger::ERROR => \ZEND_MONITOR_EVENT_SEVERITY_ERROR, + Logger::CRITICAL => \ZEND_MONITOR_EVENT_SEVERITY_ERROR, + Logger::ALERT => \ZEND_MONITOR_EVENT_SEVERITY_ERROR, + Logger::EMERGENCY => \ZEND_MONITOR_EVENT_SEVERITY_ERROR, + ]; + parent::__construct($level, $bubble); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void + { + $this->writeZendMonitorCustomEvent( + Logger::getLevelName($record['level']), + $record['message'], + $record['formatted'], + $this->levelMap[$record['level']] + ); + } + + /** + * Write to Zend Monitor Events + * @param string $type Text displayed in "Class Name (custom)" field + * @param string $message Text displayed in "Error String" + * @param array $formatted Displayed in Custom Variables tab + * @param int $severity Set the event severity level (-1,0,1) + * + * @phpstan-param FormattedRecord $formatted + */ + protected function writeZendMonitorCustomEvent(string $type, string $message, array $formatted, int $severity): void + { + zend_monitor_custom_event($type, $message, $formatted, $severity); + } + + /** + * {@inheritDoc} + */ + public function getDefaultFormatter(): FormatterInterface + { + return new NormalizerFormatter(); + } + + /** + * @return array + */ + public function getLevelMap(): array + { + return $this->levelMap; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/LogRecord.php b/vendor/monolog/monolog/src/Monolog/LogRecord.php new file mode 100644 index 0000000..702807d --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/LogRecord.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use ArrayAccess; + +/** + * Monolog log record interface for forward compatibility with Monolog 3.0 + * + * This is just present in Monolog 2.4+ to allow interoperable code to be written against + * both versions by type-hinting arguments as `array|\Monolog\LogRecord $record` + * + * Do not rely on this interface for other purposes, and do not implement it. + * + * @author Jordi Boggiano + * @template-extends \ArrayAccess<'message'|'level'|'context'|'level_name'|'channel'|'datetime'|'extra'|'formatted', mixed> + * @phpstan-import-type Record from Logger + */ +interface LogRecord extends \ArrayAccess +{ + /** + * @phpstan-return Record + */ + public function toArray(): array; +} diff --git a/vendor/monolog/monolog/src/Monolog/Logger.php b/vendor/monolog/monolog/src/Monolog/Logger.php new file mode 100644 index 0000000..84a2f55 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Logger.php @@ -0,0 +1,761 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use DateTimeZone; +use Monolog\Handler\HandlerInterface; +use Psr\Log\LoggerInterface; +use Psr\Log\InvalidArgumentException; +use Psr\Log\LogLevel; +use Throwable; +use Stringable; + +/** + * Monolog log channel + * + * It contains a stack of Handlers and a stack of Processors, + * and uses them to store records that are added to it. + * + * @author Jordi Boggiano + * + * @phpstan-type Level Logger::DEBUG|Logger::INFO|Logger::NOTICE|Logger::WARNING|Logger::ERROR|Logger::CRITICAL|Logger::ALERT|Logger::EMERGENCY + * @phpstan-type LevelName 'DEBUG'|'INFO'|'NOTICE'|'WARNING'|'ERROR'|'CRITICAL'|'ALERT'|'EMERGENCY' + * @phpstan-type Record array{message: string, context: mixed[], level: Level, level_name: LevelName, channel: string, datetime: \DateTimeImmutable, extra: mixed[]} + */ +class Logger implements LoggerInterface, ResettableInterface +{ + /** + * Detailed debug information + */ + public const DEBUG = 100; + + /** + * Interesting events + * + * Examples: User logs in, SQL logs. + */ + public const INFO = 200; + + /** + * Uncommon events + */ + public const NOTICE = 250; + + /** + * Exceptional occurrences that are not errors + * + * Examples: Use of deprecated APIs, poor use of an API, + * undesirable things that are not necessarily wrong. + */ + public const WARNING = 300; + + /** + * Runtime errors + */ + public const ERROR = 400; + + /** + * Critical conditions + * + * Example: Application component unavailable, unexpected exception. + */ + public const CRITICAL = 500; + + /** + * Action must be taken immediately + * + * Example: Entire website down, database unavailable, etc. + * This should trigger the SMS alerts and wake you up. + */ + public const ALERT = 550; + + /** + * Urgent alert. + */ + public const EMERGENCY = 600; + + /** + * Monolog API version + * + * This is only bumped when API breaks are done and should + * follow the major version of the library + * + * @var int + */ + public const API = 2; + + /** + * This is a static variable and not a constant to serve as an extension point for custom levels + * + * @var array $levels Logging levels with the levels as key + * + * @phpstan-var array $levels Logging levels with the levels as key + */ + protected static $levels = [ + self::DEBUG => 'DEBUG', + self::INFO => 'INFO', + self::NOTICE => 'NOTICE', + self::WARNING => 'WARNING', + self::ERROR => 'ERROR', + self::CRITICAL => 'CRITICAL', + self::ALERT => 'ALERT', + self::EMERGENCY => 'EMERGENCY', + ]; + + /** + * Mapping between levels numbers defined in RFC 5424 and Monolog ones + * + * @phpstan-var array $rfc_5424_levels + */ + private const RFC_5424_LEVELS = [ + 7 => self::DEBUG, + 6 => self::INFO, + 5 => self::NOTICE, + 4 => self::WARNING, + 3 => self::ERROR, + 2 => self::CRITICAL, + 1 => self::ALERT, + 0 => self::EMERGENCY, + ]; + + /** + * @var string + */ + protected $name; + + /** + * The handler stack + * + * @var HandlerInterface[] + */ + protected $handlers; + + /** + * Processors that will process all log records + * + * To process records of a single handler instead, add the processor on that specific handler + * + * @var callable[] + */ + protected $processors; + + /** + * @var bool + */ + protected $microsecondTimestamps = true; + + /** + * @var DateTimeZone + */ + protected $timezone; + + /** + * @var callable|null + */ + protected $exceptionHandler; + + /** + * @var int Keeps track of depth to prevent infinite logging loops + */ + private $logDepth = 0; + + /** + * @var \WeakMap<\Fiber, int>|null Keeps track of depth inside fibers to prevent infinite logging loops + */ + private $fiberLogDepth; + + /** + * @var bool Whether to detect infinite logging loops + * + * This can be disabled via {@see useLoggingLoopDetection} if you have async handlers that do not play well with this + */ + private $detectCycles = true; + + /** + * @psalm-param array $processors + * + * @param string $name The logging channel, a simple descriptive name that is attached to all log records + * @param HandlerInterface[] $handlers Optional stack of handlers, the first one in the array is called first, etc. + * @param callable[] $processors Optional array of processors + * @param DateTimeZone|null $timezone Optional timezone, if not provided date_default_timezone_get() will be used + */ + public function __construct(string $name, array $handlers = [], array $processors = [], ?DateTimeZone $timezone = null) + { + $this->name = $name; + $this->setHandlers($handlers); + $this->processors = $processors; + $this->timezone = $timezone ?: new DateTimeZone(date_default_timezone_get() ?: 'UTC'); + + if (\PHP_VERSION_ID >= 80100) { + // Local variable for phpstan, see https://github.com/phpstan/phpstan/issues/6732#issuecomment-1111118412 + /** @var \WeakMap<\Fiber, int> $fiberLogDepth */ + $fiberLogDepth = new \WeakMap(); + $this->fiberLogDepth = $fiberLogDepth; + } + } + + public function getName(): string + { + return $this->name; + } + + /** + * Return a new cloned instance with the name changed + */ + public function withName(string $name): self + { + $new = clone $this; + $new->name = $name; + + return $new; + } + + /** + * Pushes a handler on to the stack. + */ + public function pushHandler(HandlerInterface $handler): self + { + array_unshift($this->handlers, $handler); + + return $this; + } + + /** + * Pops a handler from the stack + * + * @throws \LogicException If empty handler stack + */ + public function popHandler(): HandlerInterface + { + if (!$this->handlers) { + throw new \LogicException('You tried to pop from an empty handler stack.'); + } + + return array_shift($this->handlers); + } + + /** + * Set handlers, replacing all existing ones. + * + * If a map is passed, keys will be ignored. + * + * @param HandlerInterface[] $handlers + */ + public function setHandlers(array $handlers): self + { + $this->handlers = []; + foreach (array_reverse($handlers) as $handler) { + $this->pushHandler($handler); + } + + return $this; + } + + /** + * @return HandlerInterface[] + */ + public function getHandlers(): array + { + return $this->handlers; + } + + /** + * Adds a processor on to the stack. + */ + public function pushProcessor(callable $callback): self + { + array_unshift($this->processors, $callback); + + return $this; + } + + /** + * Removes the processor on top of the stack and returns it. + * + * @throws \LogicException If empty processor stack + * @return callable + */ + public function popProcessor(): callable + { + if (!$this->processors) { + throw new \LogicException('You tried to pop from an empty processor stack.'); + } + + return array_shift($this->processors); + } + + /** + * @return callable[] + */ + public function getProcessors(): array + { + return $this->processors; + } + + /** + * Control the use of microsecond resolution timestamps in the 'datetime' + * member of new records. + * + * As of PHP7.1 microseconds are always included by the engine, so + * there is no performance penalty and Monolog 2 enabled microseconds + * by default. This function lets you disable them though in case you want + * to suppress microseconds from the output. + * + * @param bool $micro True to use microtime() to create timestamps + */ + public function useMicrosecondTimestamps(bool $micro): self + { + $this->microsecondTimestamps = $micro; + + return $this; + } + + public function useLoggingLoopDetection(bool $detectCycles): self + { + $this->detectCycles = $detectCycles; + + return $this; + } + + /** + * Adds a log record. + * + * @param int $level The logging level (a Monolog or RFC 5424 level) + * @param string $message The log message + * @param mixed[] $context The log context + * @param DateTimeImmutable $datetime Optional log date to log into the past or future + * @return bool Whether the record has been processed + * + * @phpstan-param Level $level + */ + public function addRecord(int $level, string $message, array $context = [], DateTimeImmutable $datetime = null): bool + { + if (isset(self::RFC_5424_LEVELS[$level])) { + $level = self::RFC_5424_LEVELS[$level]; + } + + if ($this->detectCycles) { + if (\PHP_VERSION_ID >= 80100 && $fiber = \Fiber::getCurrent()) { + $this->fiberLogDepth[$fiber] = $this->fiberLogDepth[$fiber] ?? 0; + $logDepth = ++$this->fiberLogDepth[$fiber]; + } else { + $logDepth = ++$this->logDepth; + } + } else { + $logDepth = 0; + } + + if ($logDepth === 3) { + $this->warning('A possible infinite logging loop was detected and aborted. It appears some of your handler code is triggering logging, see the previous log record for a hint as to what may be the cause.'); + return false; + } elseif ($logDepth >= 5) { // log depth 4 is let through, so we can log the warning above + return false; + } + + try { + $record = null; + + foreach ($this->handlers as $handler) { + if (null === $record) { + // skip creating the record as long as no handler is going to handle it + if (!$handler->isHandling(['level' => $level])) { + continue; + } + + $levelName = static::getLevelName($level); + + $record = [ + 'message' => $message, + 'context' => $context, + 'level' => $level, + 'level_name' => $levelName, + 'channel' => $this->name, + 'datetime' => $datetime ?? new DateTimeImmutable($this->microsecondTimestamps, $this->timezone), + 'extra' => [], + ]; + + try { + foreach ($this->processors as $processor) { + $record = $processor($record); + } + } catch (Throwable $e) { + $this->handleException($e, $record); + + return true; + } + } + + // once the record exists, send it to all handlers as long as the bubbling chain is not interrupted + try { + if (true === $handler->handle($record)) { + break; + } + } catch (Throwable $e) { + $this->handleException($e, $record); + + return true; + } + } + } finally { + if ($this->detectCycles) { + if (isset($fiber)) { + $this->fiberLogDepth[$fiber]--; + } else { + $this->logDepth--; + } + } + } + + return null !== $record; + } + + /** + * Ends a log cycle and frees all resources used by handlers. + * + * Closing a Handler means flushing all buffers and freeing any open resources/handles. + * Handlers that have been closed should be able to accept log records again and re-open + * themselves on demand, but this may not always be possible depending on implementation. + * + * This is useful at the end of a request and will be called automatically on every handler + * when they get destructed. + */ + public function close(): void + { + foreach ($this->handlers as $handler) { + $handler->close(); + } + } + + /** + * Ends a log cycle and resets all handlers and processors 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. + */ + public function reset(): void + { + foreach ($this->handlers as $handler) { + if ($handler instanceof ResettableInterface) { + $handler->reset(); + } + } + + foreach ($this->processors as $processor) { + if ($processor instanceof ResettableInterface) { + $processor->reset(); + } + } + } + + /** + * Gets all supported logging levels. + * + * @return array Assoc array with human-readable level names => level codes. + * @phpstan-return array + */ + public static function getLevels(): array + { + return array_flip(static::$levels); + } + + /** + * Gets the name of the logging level. + * + * @throws \Psr\Log\InvalidArgumentException If level is not defined + * + * @phpstan-param Level $level + * @phpstan-return LevelName + */ + public static function getLevelName(int $level): string + { + if (!isset(static::$levels[$level])) { + throw new InvalidArgumentException('Level "'.$level.'" is not defined, use one of: '.implode(', ', array_keys(static::$levels))); + } + + return static::$levels[$level]; + } + + /** + * Converts PSR-3 levels to Monolog ones if necessary + * + * @param string|int $level Level number (monolog) or name (PSR-3) + * @throws \Psr\Log\InvalidArgumentException If level is not defined + * + * @phpstan-param Level|LevelName|LogLevel::* $level + * @phpstan-return Level + */ + public static function toMonologLevel($level): int + { + if (is_string($level)) { + if (is_numeric($level)) { + /** @phpstan-ignore-next-line */ + return intval($level); + } + + // Contains chars of all log levels and avoids using strtoupper() which may have + // strange results depending on locale (for example, "i" will become "İ" in Turkish locale) + $upper = strtr($level, 'abcdefgilmnortuwy', 'ABCDEFGILMNORTUWY'); + if (defined(__CLASS__.'::'.$upper)) { + return constant(__CLASS__ . '::' . $upper); + } + + throw new InvalidArgumentException('Level "'.$level.'" is not defined, use one of: '.implode(', ', array_keys(static::$levels) + static::$levels)); + } + + if (!is_int($level)) { + throw new InvalidArgumentException('Level "'.var_export($level, true).'" is not defined, use one of: '.implode(', ', array_keys(static::$levels) + static::$levels)); + } + + return $level; + } + + /** + * Checks whether the Logger has a handler that listens on the given level + * + * @phpstan-param Level $level + */ + public function isHandling(int $level): bool + { + $record = [ + 'level' => $level, + ]; + + foreach ($this->handlers as $handler) { + if ($handler->isHandling($record)) { + return true; + } + } + + return false; + } + + /** + * Set a custom exception handler that will be called if adding a new record fails + * + * The callable will receive an exception object and the record that failed to be logged + */ + public function setExceptionHandler(?callable $callback): self + { + $this->exceptionHandler = $callback; + + return $this; + } + + public function getExceptionHandler(): ?callable + { + return $this->exceptionHandler; + } + + /** + * Adds a log record at an arbitrary level. + * + * This method allows for compatibility with common interfaces. + * + * @param mixed $level The log level (a Monolog, PSR-3 or RFC 5424 level) + * @param string|Stringable $message The log message + * @param mixed[] $context The log context + * + * @phpstan-param Level|LevelName|LogLevel::* $level + */ + public function log($level, $message, array $context = []): void + { + if (!is_int($level) && !is_string($level)) { + throw new \InvalidArgumentException('$level is expected to be a string or int'); + } + + if (isset(self::RFC_5424_LEVELS[$level])) { + $level = self::RFC_5424_LEVELS[$level]; + } + + $level = static::toMonologLevel($level); + + $this->addRecord($level, (string) $message, $context); + } + + /** + * Adds a log record at the DEBUG level. + * + * This method allows for compatibility with common interfaces. + * + * @param string|Stringable $message The log message + * @param mixed[] $context The log context + */ + public function debug($message, array $context = []): void + { + $this->addRecord(static::DEBUG, (string) $message, $context); + } + + /** + * Adds a log record at the INFO level. + * + * This method allows for compatibility with common interfaces. + * + * @param string|Stringable $message The log message + * @param mixed[] $context The log context + */ + public function info($message, array $context = []): void + { + $this->addRecord(static::INFO, (string) $message, $context); + } + + /** + * Adds a log record at the NOTICE level. + * + * This method allows for compatibility with common interfaces. + * + * @param string|Stringable $message The log message + * @param mixed[] $context The log context + */ + public function notice($message, array $context = []): void + { + $this->addRecord(static::NOTICE, (string) $message, $context); + } + + /** + * Adds a log record at the WARNING level. + * + * This method allows for compatibility with common interfaces. + * + * @param string|Stringable $message The log message + * @param mixed[] $context The log context + */ + public function warning($message, array $context = []): void + { + $this->addRecord(static::WARNING, (string) $message, $context); + } + + /** + * Adds a log record at the ERROR level. + * + * This method allows for compatibility with common interfaces. + * + * @param string|Stringable $message The log message + * @param mixed[] $context The log context + */ + public function error($message, array $context = []): void + { + $this->addRecord(static::ERROR, (string) $message, $context); + } + + /** + * Adds a log record at the CRITICAL level. + * + * This method allows for compatibility with common interfaces. + * + * @param string|Stringable $message The log message + * @param mixed[] $context The log context + */ + public function critical($message, array $context = []): void + { + $this->addRecord(static::CRITICAL, (string) $message, $context); + } + + /** + * Adds a log record at the ALERT level. + * + * This method allows for compatibility with common interfaces. + * + * @param string|Stringable $message The log message + * @param mixed[] $context The log context + */ + public function alert($message, array $context = []): void + { + $this->addRecord(static::ALERT, (string) $message, $context); + } + + /** + * Adds a log record at the EMERGENCY level. + * + * This method allows for compatibility with common interfaces. + * + * @param string|Stringable $message The log message + * @param mixed[] $context The log context + */ + public function emergency($message, array $context = []): void + { + $this->addRecord(static::EMERGENCY, (string) $message, $context); + } + + /** + * Sets the timezone to be used for the timestamp of log records. + */ + public function setTimezone(DateTimeZone $tz): self + { + $this->timezone = $tz; + + return $this; + } + + /** + * Returns the timezone to be used for the timestamp of log records. + */ + public function getTimezone(): DateTimeZone + { + return $this->timezone; + } + + /** + * Delegates exception management to the custom exception handler, + * or throws the exception if no custom handler is set. + * + * @param array $record + * @phpstan-param Record $record + */ + protected function handleException(Throwable $e, array $record): void + { + if (!$this->exceptionHandler) { + throw $e; + } + + ($this->exceptionHandler)($e, $record); + } + + /** + * @return array + */ + public function __serialize(): array + { + return [ + 'name' => $this->name, + 'handlers' => $this->handlers, + 'processors' => $this->processors, + 'microsecondTimestamps' => $this->microsecondTimestamps, + 'timezone' => $this->timezone, + 'exceptionHandler' => $this->exceptionHandler, + 'logDepth' => $this->logDepth, + 'detectCycles' => $this->detectCycles, + ]; + } + + /** + * @param array $data + */ + public function __unserialize(array $data): void + { + foreach (['name', 'handlers', 'processors', 'microsecondTimestamps', 'timezone', 'exceptionHandler', 'logDepth', 'detectCycles'] as $property) { + if (isset($data[$property])) { + $this->$property = $data[$property]; + } + } + + if (\PHP_VERSION_ID >= 80100) { + // Local variable for phpstan, see https://github.com/phpstan/phpstan/issues/6732#issuecomment-1111118412 + /** @var \WeakMap<\Fiber, int> $fiberLogDepth */ + $fiberLogDepth = new \WeakMap(); + $this->fiberLogDepth = $fiberLogDepth; + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/GitProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/GitProcessor.php new file mode 100644 index 0000000..8166bdc --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/GitProcessor.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\Logger; +use Psr\Log\LogLevel; + +/** + * Injects Git branch and Git commit SHA in all records + * + * @author Nick Otter + * @author Jordi Boggiano + * + * @phpstan-import-type Level from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger + */ +class GitProcessor implements ProcessorInterface +{ + /** @var int */ + private $level; + /** @var array{branch: string, commit: string}|array|null */ + private static $cache = null; + + /** + * @param string|int $level The minimum logging level at which this Processor will be triggered + * + * @phpstan-param Level|LevelName|LogLevel::* $level + */ + public function __construct($level = Logger::DEBUG) + { + $this->level = Logger::toMonologLevel($level); + } + + /** + * {@inheritDoc} + */ + public function __invoke(array $record): array + { + // return if the level is not high enough + if ($record['level'] < $this->level) { + return $record; + } + + $record['extra']['git'] = self::getGitInfo(); + + return $record; + } + + /** + * @return array{branch: string, commit: string}|array + */ + private static function getGitInfo(): array + { + if (self::$cache) { + return self::$cache; + } + + $branches = `git branch -v --no-abbrev`; + if ($branches && preg_match('{^\* (.+?)\s+([a-f0-9]{40})(?:\s|$)}m', $branches, $matches)) { + return self::$cache = [ + 'branch' => $matches[1], + 'commit' => $matches[2], + ]; + } + + return self::$cache = []; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/HostnameProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/HostnameProcessor.php new file mode 100644 index 0000000..91fda7d --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/HostnameProcessor.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Injects value of gethostname in all records + */ +class HostnameProcessor implements ProcessorInterface +{ + /** @var string */ + private static $host; + + public function __construct() + { + self::$host = (string) gethostname(); + } + + /** + * {@inheritDoc} + */ + public function __invoke(array $record): array + { + $record['extra']['hostname'] = self::$host; + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php new file mode 100644 index 0000000..a32e76b --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.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 Monolog\Processor; + +use Monolog\Logger; +use Psr\Log\LogLevel; + +/** + * Injects line/file:class/function where the log message came from + * + * Warning: This only works if the handler processes the logs directly. + * If you put the processor on a handler that is behind a FingersCrossedHandler + * for example, the processor will only be called once the trigger level is reached, + * and all the log records will have the same file/line/.. data from the call that + * triggered the FingersCrossedHandler. + * + * @author Jordi Boggiano + * + * @phpstan-import-type Level from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger + */ +class IntrospectionProcessor implements ProcessorInterface +{ + /** @var int */ + private $level; + /** @var string[] */ + private $skipClassesPartials; + /** @var int */ + private $skipStackFramesCount; + /** @var string[] */ + private $skipFunctions = [ + 'call_user_func', + 'call_user_func_array', + ]; + + /** + * @param string|int $level The minimum logging level at which this Processor will be triggered + * @param string[] $skipClassesPartials + * + * @phpstan-param Level|LevelName|LogLevel::* $level + */ + public function __construct($level = Logger::DEBUG, array $skipClassesPartials = [], int $skipStackFramesCount = 0) + { + $this->level = Logger::toMonologLevel($level); + $this->skipClassesPartials = array_merge(['Monolog\\'], $skipClassesPartials); + $this->skipStackFramesCount = $skipStackFramesCount; + } + + /** + * {@inheritDoc} + */ + public function __invoke(array $record): array + { + // return if the level is not high enough + if ($record['level'] < $this->level) { + return $record; + } + + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); + + // skip first since it's always the current method + array_shift($trace); + // the call_user_func call is also skipped + array_shift($trace); + + $i = 0; + + while ($this->isTraceClassOrSkippedFunction($trace, $i)) { + if (isset($trace[$i]['class'])) { + foreach ($this->skipClassesPartials as $part) { + if (strpos($trace[$i]['class'], $part) !== false) { + $i++; + + continue 2; + } + } + } elseif (in_array($trace[$i]['function'], $this->skipFunctions)) { + $i++; + + continue; + } + + break; + } + + $i += $this->skipStackFramesCount; + + // we should have the call source now + $record['extra'] = array_merge( + $record['extra'], + [ + 'file' => isset($trace[$i - 1]['file']) ? $trace[$i - 1]['file'] : null, + 'line' => isset($trace[$i - 1]['line']) ? $trace[$i - 1]['line'] : null, + 'class' => isset($trace[$i]['class']) ? $trace[$i]['class'] : null, + 'callType' => isset($trace[$i]['type']) ? $trace[$i]['type'] : null, + 'function' => isset($trace[$i]['function']) ? $trace[$i]['function'] : null, + ] + ); + + return $record; + } + + /** + * @param array[] $trace + */ + private function isTraceClassOrSkippedFunction(array $trace, int $index): bool + { + if (!isset($trace[$index])) { + return false; + } + + return isset($trace[$index]['class']) || in_array($trace[$index]['function'], $this->skipFunctions); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php new file mode 100644 index 0000000..37c756f --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Injects memory_get_peak_usage in all records + * + * @see Monolog\Processor\MemoryProcessor::__construct() for options + * @author Rob Jensen + */ +class MemoryPeakUsageProcessor extends MemoryProcessor +{ + /** + * {@inheritDoc} + */ + public function __invoke(array $record): array + { + $usage = memory_get_peak_usage($this->realUsage); + + if ($this->useFormatting) { + $usage = $this->formatBytes($usage); + } + + $record['extra']['memory_peak_usage'] = $usage; + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php new file mode 100644 index 0000000..227deb7 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Some methods that are common for all memory processors + * + * @author Rob Jensen + */ +abstract class MemoryProcessor implements ProcessorInterface +{ + /** + * @var bool If true, get the real size of memory allocated from system. Else, only the memory used by emalloc() is reported. + */ + protected $realUsage; + + /** + * @var bool If true, then format memory size to human readable string (MB, KB, B depending on size) + */ + protected $useFormatting; + + /** + * @param bool $realUsage Set this to true to get the real size of memory allocated from system. + * @param bool $useFormatting If true, then format memory size to human readable string (MB, KB, B depending on size) + */ + public function __construct(bool $realUsage = true, bool $useFormatting = true) + { + $this->realUsage = $realUsage; + $this->useFormatting = $useFormatting; + } + + /** + * Formats bytes into a human readable string if $this->useFormatting is true, otherwise return $bytes as is + * + * @param int $bytes + * @return string|int Formatted string if $this->useFormatting is true, otherwise return $bytes as int + */ + protected function formatBytes(int $bytes) + { + if (!$this->useFormatting) { + return $bytes; + } + + if ($bytes > 1024 * 1024) { + return round($bytes / 1024 / 1024, 2).' MB'; + } elseif ($bytes > 1024) { + return round($bytes / 1024, 2).' KB'; + } + + return $bytes . ' B'; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php new file mode 100644 index 0000000..e141921 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Injects memory_get_usage in all records + * + * @see Monolog\Processor\MemoryProcessor::__construct() for options + * @author Rob Jensen + */ +class MemoryUsageProcessor extends MemoryProcessor +{ + /** + * {@inheritDoc} + */ + public function __invoke(array $record): array + { + $usage = memory_get_usage($this->realUsage); + + if ($this->useFormatting) { + $usage = $this->formatBytes($usage); + } + + $record['extra']['memory_usage'] = $usage; + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php new file mode 100644 index 0000000..d4a628f --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\Logger; +use Psr\Log\LogLevel; + +/** + * Injects Hg branch and Hg revision number in all records + * + * @author Jonathan A. Schweder + * + * @phpstan-import-type LevelName from \Monolog\Logger + * @phpstan-import-type Level from \Monolog\Logger + */ +class MercurialProcessor implements ProcessorInterface +{ + /** @var Level */ + private $level; + /** @var array{branch: string, revision: string}|array|null */ + private static $cache = null; + + /** + * @param int|string $level The minimum logging level at which this Processor will be triggered + * + * @phpstan-param Level|LevelName|LogLevel::* $level + */ + public function __construct($level = Logger::DEBUG) + { + $this->level = Logger::toMonologLevel($level); + } + + /** + * {@inheritDoc} + */ + public function __invoke(array $record): array + { + // return if the level is not high enough + if ($record['level'] < $this->level) { + return $record; + } + + $record['extra']['hg'] = self::getMercurialInfo(); + + return $record; + } + + /** + * @return array{branch: string, revision: string}|array + */ + private static function getMercurialInfo(): array + { + if (self::$cache) { + return self::$cache; + } + + $result = explode(' ', trim(`hg id -nb`)); + + if (count($result) >= 3) { + return self::$cache = [ + 'branch' => $result[1], + 'revision' => $result[2], + ]; + } + + return self::$cache = []; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php new file mode 100644 index 0000000..3b939a9 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.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; + +/** + * Adds value of getmypid into records + * + * @author Andreas Hörnicke + */ +class ProcessIdProcessor implements ProcessorInterface +{ + /** + * {@inheritDoc} + */ + public function __invoke(array $record): array + { + $record['extra']['process_id'] = getmypid(); + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/ProcessorInterface.php b/vendor/monolog/monolog/src/Monolog/Processor/ProcessorInterface.php new file mode 100644 index 0000000..5defb7e --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/ProcessorInterface.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; + +/** + * An optional interface to allow labelling Monolog processors. + * + * @author Nicolas Grekas + * + * @phpstan-import-type Record from \Monolog\Logger + */ +interface ProcessorInterface +{ + /** + * @return array The processed record + * + * @phpstan-param Record $record + * @phpstan-return Record + */ + public function __invoke(array $record); +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php new file mode 100644 index 0000000..e7c1217 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.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\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 +{ + public const SIMPLE_DATE = "Y-m-d\TH:i:s.uP"; + + /** @var string|null */ + private $dateFormat; + + /** @var bool */ + private $removeUsedContextFields; + + /** + * @param string|null $dateFormat The format of the timestamp: one supported by DateTime::format + * @param bool $removeUsedContextFields If set to true the fields interpolated into message gets unset + */ + public function __construct(?string $dateFormat = null, bool $removeUsedContextFields = false) + { + $this->dateFormat = $dateFormat; + $this->removeUsedContextFields = $removeUsedContextFields; + } + + /** + * {@inheritDoc} + */ + public function __invoke(array $record): array + { + if (false === strpos($record['message'], '{')) { + return $record; + } + + $replacements = []; + foreach ($record['context'] as $key => $val) { + $placeholder = '{' . $key . '}'; + if (strpos($record['message'], $placeholder) === false) { + continue; + } + + if (is_null($val) || is_scalar($val) || (is_object($val) && method_exists($val, "__toString"))) { + $replacements[$placeholder] = $val; + } elseif ($val instanceof \DateTimeInterface) { + if (!$this->dateFormat && $val instanceof \Monolog\DateTimeImmutable) { + // handle monolog dates using __toString if no specific dateFormat was asked for + // so that it follows the useMicroseconds flag + $replacements[$placeholder] = (string) $val; + } else { + $replacements[$placeholder] = $val->format($this->dateFormat ?: static::SIMPLE_DATE); + } + } elseif ($val instanceof \UnitEnum) { + $replacements[$placeholder] = $val instanceof \BackedEnum ? $val->value : $val->name; + } elseif (is_object($val)) { + $replacements[$placeholder] = '[object '.Utils::getClass($val).']'; + } elseif (is_array($val)) { + $replacements[$placeholder] = 'array'.Utils::jsonEncode($val, null, true); + } else { + $replacements[$placeholder] = '['.gettype($val).']'; + } + + if ($this->removeUsedContextFields) { + unset($record['context'][$key]); + } + } + + $record['message'] = strtr($record['message'], $replacements); + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/TagProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/TagProcessor.php new file mode 100644 index 0000000..80f1874 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/TagProcessor.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Adds a tags array into record + * + * @author Martijn Riemers + */ +class TagProcessor implements ProcessorInterface +{ + /** @var string[] */ + private $tags; + + /** + * @param string[] $tags + */ + public function __construct(array $tags = []) + { + $this->setTags($tags); + } + + /** + * @param string[] $tags + */ + public function addTags(array $tags = []): self + { + $this->tags = array_merge($this->tags, $tags); + + return $this; + } + + /** + * @param string[] $tags + */ + public function setTags(array $tags = []): self + { + $this->tags = $tags; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function __invoke(array $record): array + { + $record['extra']['tags'] = $this->tags; + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.php new file mode 100644 index 0000000..a27b74d --- /dev/null +++ b/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 +{ + /** @var string */ + private $uid; + + public function __construct(int $length = 7) + { + if ($length > 32 || $length < 1) { + throw new \InvalidArgumentException('The uid length must be an integer between 1 and 32'); + } + + $this->uid = $this->generateUid($length); + } + + /** + * {@inheritDoc} + */ + public function __invoke(array $record): array + { + $record['extra']['uid'] = $this->uid; + + return $record; + } + + public function getUid(): string + { + return $this->uid; + } + + public function reset() + { + $this->uid = $this->generateUid(strlen($this->uid)); + } + + private function generateUid(int $length): string + { + return substr(bin2hex(random_bytes((int) ceil($length / 2))), 0, $length); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php new file mode 100644 index 0000000..51850e1 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.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\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 = [ + 'url' => 'REQUEST_URI', + 'ip' => 'REMOTE_ADDR', + 'http_method' => 'REQUEST_METHOD', + 'server' => 'SERVER_NAME', + 'referrer' => 'HTTP_REFERER', + 'user_agent' => 'HTTP_USER_AGENT', + ]; + + /** + * @param array|\ArrayAccess|null $serverData Array or object w/ ArrayAccess that provides access to the $_SERVER data + * @param array|array|null $extraFields Field names and the related key inside $serverData to be added (or just a list of field names to use the default configured $serverData mapping). If not provided it defaults to: [url, ip, http_method, server, referrer] + unique_id if present in server data + */ + 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.'); + } + + $defaultEnabled = ['url', 'ip', 'http_method', 'server', 'referrer']; + if (isset($this->serverData['UNIQUE_ID'])) { + $this->extraFields['unique_id'] = 'UNIQUE_ID'; + $defaultEnabled[] = 'unique_id'; + } + + if (null === $extraFields) { + $extraFields = $defaultEnabled; + } + if (isset($extraFields[0])) { + foreach (array_keys($this->extraFields) as $fieldName) { + if (!in_array($fieldName, $extraFields)) { + unset($this->extraFields[$fieldName]); + } + } + } else { + $this->extraFields = $extraFields; + } + } + + /** + * {@inheritDoc} + */ + public function __invoke(array $record): array + { + // 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; + } + + public function addExtraField(string $extraName, string $serverName): self + { + $this->extraFields[$extraName] = $serverName; + + return $this; + } + + /** + * @param mixed[] $extra + * @return mixed[] + */ + private function appendExtraFields(array $extra): array + { + foreach ($this->extraFields as $extraName => $serverName) { + $extra[$extraName] = $this->serverData[$serverName] ?? null; + } + + return $extra; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Registry.php b/vendor/monolog/monolog/src/Monolog/Registry.php new file mode 100644 index 0000000..ae94ae6 --- /dev/null +++ b/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()->error('Sent to $api Logger instance'); + * Monolog\Registry::application()->error('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 = []; + + /** + * 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 + * @return void + */ + public static function addLogger(Logger $logger, ?string $name = null, bool $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): bool + { + if ($logger instanceof Logger) { + $index = array_search($logger, self::$loggers, true); + + return false !== $index; + } + + 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): void + { + 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(): void + { + self::$loggers = []; + } + + /** + * 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 + */ + public static function getInstance($name): Logger + { + 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 mixed[] $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/vendor/monolog/monolog/src/Monolog/ResettableInterface.php b/vendor/monolog/monolog/src/Monolog/ResettableInterface.php new file mode 100644 index 0000000..2c5fd78 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/ResettableInterface.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace 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 +{ + /** + * @return void + */ + public function reset(); +} diff --git a/vendor/monolog/monolog/src/Monolog/SignalHandler.php b/vendor/monolog/monolog/src/Monolog/SignalHandler.php new file mode 100644 index 0000000..d730eea --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/SignalHandler.php @@ -0,0 +1,120 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use Psr\Log\LoggerInterface; +use Psr\Log\LogLevel; +use ReflectionExtension; + +/** + * Monolog POSIX signal handler + * + * @author Robert Gust-Bardon + * + * @phpstan-import-type Level from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger + */ +class SignalHandler +{ + /** @var LoggerInterface */ + private $logger; + + /** @var array SIG_DFL, SIG_IGN or previous callable */ + private $previousSignalHandler = []; + /** @var array */ + private $signalLevelMap = []; + /** @var array */ + private $signalRestartSyscalls = []; + + public function __construct(LoggerInterface $logger) + { + $this->logger = $logger; + } + + /** + * @param int|string $level Level or level name + * @param bool $callPrevious + * @param bool $restartSyscalls + * @param bool|null $async + * @return $this + * + * @phpstan-param Level|LevelName|LogLevel::* $level + */ + public function registerSignalHandler(int $signo, $level = LogLevel::CRITICAL, bool $callPrevious = true, bool $restartSyscalls = true, ?bool $async = true): self + { + if (!extension_loaded('pcntl') || !function_exists('pcntl_signal')) { + return $this; + } + + $level = Logger::toMonologLevel($level); + + if ($callPrevious) { + $handler = pcntl_signal_get_handler($signo); + $this->previousSignalHandler[$signo] = $handler; + } else { + unset($this->previousSignalHandler[$signo]); + } + $this->signalLevelMap[$signo] = $level; + $this->signalRestartSyscalls[$signo] = $restartSyscalls; + + if ($async !== null) { + pcntl_async_signals($async); + } + + pcntl_signal($signo, [$this, 'handleSignal'], $restartSyscalls); + + return $this; + } + + /** + * @param mixed $siginfo + */ + public function handleSignal(int $signo, $siginfo = null): void + { + static $signals = []; + + if (!$signals && extension_loaded('pcntl')) { + $pcntl = new ReflectionExtension('pcntl'); + // HHVM 3.24.2 returns an empty array. + foreach ($pcntl->getConstants() ?: get_defined_constants(true)['Core'] as $name => $value) { + if (substr($name, 0, 3) === 'SIG' && $name[3] !== '_' && is_int($value)) { + $signals[$value] = $name; + } + } + } + + $level = $this->signalLevelMap[$signo] ?? LogLevel::CRITICAL; + $signal = $signals[$signo] ?? $signo; + $context = $siginfo ?? []; + $this->logger->log($level, sprintf('Program received signal %s', $signal), $context); + + if (!isset($this->previousSignalHandler[$signo])) { + return; + } + + if ($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 = $this->signalRestartSyscalls[$signo] ?? true; + pcntl_signal($signo, SIG_DFL, $restartSyscalls); + pcntl_sigprocmask(SIG_UNBLOCK, [$signo], $oldset); + posix_kill(posix_getpid(), $signo); + pcntl_signal_dispatch(); + pcntl_sigprocmask(SIG_SETMASK, $oldset); + pcntl_signal($signo, [$this, 'handleSignal'], $restartSyscalls); + } + } elseif (is_callable($this->previousSignalHandler[$signo])) { + $this->previousSignalHandler[$signo]($signo, $siginfo); + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Test/TestCase.php b/vendor/monolog/monolog/src/Monolog/Test/TestCase.php new file mode 100644 index 0000000..bc0b425 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Test/TestCase.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\Test; + +use Monolog\Logger; +use Monolog\DateTimeImmutable; +use Monolog\Formatter\FormatterInterface; + +/** + * Lets you easily generate log records and a dummy formatter for testing purposes + * + * @author Jordi Boggiano + * + * @phpstan-import-type Record from \Monolog\Logger + * @phpstan-import-type Level from \Monolog\Logger + * + * @internal feel free to reuse this to test your own handlers, this is marked internal to avoid issues with PHPStorm https://github.com/Seldaek/monolog/issues/1677 + */ +class TestCase extends \PHPUnit\Framework\TestCase +{ + public function tearDown(): void + { + parent::tearDown(); + + if (isset($this->handler)) { + unset($this->handler); + } + } + + /** + * @param mixed[] $context + * + * @return array Record + * + * @phpstan-param Level $level + * @phpstan-return Record + */ + protected function getRecord(int $level = Logger::WARNING, string $message = 'test', array $context = []): array + { + return [ + 'message' => (string) $message, + 'context' => $context, + 'level' => $level, + 'level_name' => Logger::getLevelName($level), + 'channel' => 'test', + 'datetime' => new DateTimeImmutable(true), + 'extra' => [], + ]; + } + + /** + * @phpstan-return Record[] + */ + protected function getMultipleRecords(): array + { + return [ + $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'), + ]; + } + + protected function getIdentityFormatter(): FormatterInterface + { + $formatter = $this->createMock(FormatterInterface::class); + $formatter->expects($this->any()) + ->method('format') + ->will($this->returnCallback(function ($record) { + return $record['message']; + })); + + return $formatter; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Utils.php b/vendor/monolog/monolog/src/Monolog/Utils.php new file mode 100644 index 0000000..360c421 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Utils.php @@ -0,0 +1,284 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +final class Utils +{ + const DEFAULT_JSON_FLAGS = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRESERVE_ZERO_FRACTION | JSON_INVALID_UTF8_SUBSTITUTE | JSON_PARTIAL_OUTPUT_ON_ERROR; + + public static function getClass(object $object): string + { + $class = \get_class($object); + + if (false === ($pos = \strpos($class, "@anonymous\0"))) { + return $class; + } + + if (false === ($parent = \get_parent_class($class))) { + return \substr($class, 0, $pos + 10); + } + + return $parent . '@anonymous'; + } + + public static function substr(string $string, int $start, ?int $length = null): string + { + if (extension_loaded('mbstring')) { + return mb_strcut($string, $start, $length); + } + + return substr($string, $start, (null === $length) ? strlen($string) : $length); + } + + /** + * Makes sure if a relative path is passed in it is turned into an absolute path + * + * @param string $streamUrl stream URL or path without protocol + */ + public static function canonicalizePath(string $streamUrl): string + { + $prefix = ''; + if ('file://' === substr($streamUrl, 0, 7)) { + $streamUrl = substr($streamUrl, 7); + $prefix = 'file://'; + } + + // other type of stream, not supported + if (false !== strpos($streamUrl, '://')) { + return $streamUrl; + } + + // already absolute + if (substr($streamUrl, 0, 1) === '/' || substr($streamUrl, 1, 1) === ':' || substr($streamUrl, 0, 2) === '\\\\') { + return $prefix.$streamUrl; + } + + $streamUrl = getcwd() . '/' . $streamUrl; + + return $prefix.$streamUrl; + } + + /** + * Return the JSON representation of a value + * + * @param mixed $data + * @param int $encodeFlags flags to pass to json encode, defaults to DEFAULT_JSON_FLAGS + * @param bool $ignoreErrors whether to ignore encoding errors or to throw on error, when ignored and the encoding fails, "null" is returned which is valid json for null + * @throws \RuntimeException if encoding fails and errors are not ignored + * @return string when errors are ignored and the encoding fails, "null" is returned which is valid json for null + */ + public static function jsonEncode($data, ?int $encodeFlags = null, bool $ignoreErrors = false): string + { + if (null === $encodeFlags) { + $encodeFlags = self::DEFAULT_JSON_FLAGS; + } + + if ($ignoreErrors) { + $json = @json_encode($data, $encodeFlags); + if (false === $json) { + return 'null'; + } + + return $json; + } + + $json = json_encode($data, $encodeFlags); + if (false === $json) { + $json = self::handleJsonError(json_last_error(), $data); + } + + return $json; + } + + /** + * Handle a json_encode failure. + * + * If the failure is due to invalid string encoding, try to clean the + * input and encode again. If the second encoding attempt fails, the + * initial error is not encoding related or the input can't be cleaned then + * raise a descriptive exception. + * + * @param int $code return code of json_last_error function + * @param mixed $data data that was meant to be encoded + * @param int $encodeFlags flags to pass to json encode, defaults to JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRESERVE_ZERO_FRACTION + * @throws \RuntimeException if failure can't be corrected + * @return string JSON encoded data after error correction + */ + public static function handleJsonError(int $code, $data, ?int $encodeFlags = null): string + { + if ($code !== JSON_ERROR_UTF8) { + self::throwEncodeError($code, $data); + } + + if (is_string($data)) { + self::detectAndCleanUtf8($data); + } elseif (is_array($data)) { + array_walk_recursive($data, array('Monolog\Utils', 'detectAndCleanUtf8')); + } else { + self::throwEncodeError($code, $data); + } + + if (null === $encodeFlags) { + $encodeFlags = self::DEFAULT_JSON_FLAGS; + } + + $json = json_encode($data, $encodeFlags); + + if ($json === false) { + self::throwEncodeError(json_last_error(), $data); + } + + return $json; + } + + /** + * @internal + */ + public static function pcreLastErrorMessage(int $code): string + { + if (PHP_VERSION_ID >= 80000) { + return preg_last_error_msg(); + } + + $constants = (get_defined_constants(true))['pcre']; + $constants = array_filter($constants, function ($key) { + return substr($key, -6) == '_ERROR'; + }, ARRAY_FILTER_USE_KEY); + + $constants = array_flip($constants); + + return $constants[$code] ?? 'UNDEFINED_ERROR'; + } + + /** + * Throws an exception according to a given code with a customized message + * + * @param int $code return code of json_last_error function + * @param mixed $data data that was meant to be encoded + * @throws \RuntimeException + * + * @return never + */ + private static function throwEncodeError(int $code, $data): void + { + switch ($code) { + case JSON_ERROR_DEPTH: + $msg = 'Maximum stack depth exceeded'; + break; + case JSON_ERROR_STATE_MISMATCH: + $msg = 'Underflow or the modes mismatch'; + break; + case JSON_ERROR_CTRL_CHAR: + $msg = 'Unexpected control character found'; + break; + case JSON_ERROR_UTF8: + $msg = 'Malformed UTF-8 characters, possibly incorrectly encoded'; + break; + default: + $msg = 'Unknown error'; + } + + throw new \RuntimeException('JSON encoding failed: '.$msg.'. Encoding: '.var_export($data, true)); + } + + /** + * Detect invalid UTF-8 string characters and convert to valid UTF-8. + * + * Valid UTF-8 input will be left unmodified, but strings containing + * invalid UTF-8 codepoints will be reencoded as UTF-8 with an assumed + * original encoding of ISO-8859-15. This conversion may result in + * incorrect output if the actual encoding was not ISO-8859-15, but it + * will be clean UTF-8 output and will not rely on expensive and fragile + * detection algorithms. + * + * Function converts the input in place in the passed variable so that it + * can be used as a callback for array_walk_recursive. + * + * @param mixed $data Input to check and convert if needed, passed by ref + */ + private static function detectAndCleanUtf8(&$data): void + { + if (is_string($data) && !preg_match('//u', $data)) { + $data = preg_replace_callback( + '/[\x80-\xFF]+/', + function ($m) { + return function_exists('mb_convert_encoding') ? mb_convert_encoding($m[0], 'UTF-8', 'ISO-8859-1') : utf8_encode($m[0]); + }, + $data + ); + if (!is_string($data)) { + $pcreErrorCode = preg_last_error(); + throw new \RuntimeException('Failed to preg_replace_callback: ' . $pcreErrorCode . ' / ' . self::pcreLastErrorMessage($pcreErrorCode)); + } + $data = str_replace( + ['¤', '¦', '¨', '´', '¸', '¼', '½', '¾'], + ['€', 'Š', 'š', 'Ž', 'ž', 'Œ', 'œ', 'Ÿ'], + $data + ); + } + } + + /** + * Converts a string with a valid 'memory_limit' format, to bytes. + * + * @param string|false $val + * @return int|false Returns an integer representing bytes. Returns FALSE in case of error. + */ + public static function expandIniShorthandBytes($val) + { + if (!is_string($val)) { + return false; + } + + // support -1 + if ((int) $val < 0) { + return (int) $val; + } + + if (!preg_match('/^\s*(?\d+)(?:\.\d+)?\s*(?[gmk]?)\s*$/i', $val, $match)) { + return false; + } + + $val = (int) $match['val']; + switch (strtolower($match['unit'] ?? '')) { + case 'g': + $val *= 1024; + case 'm': + $val *= 1024; + case 'k': + $val *= 1024; + } + + return $val; + } + + /** + * @param array $record + */ + public static function getRecordMessageForException(array $record): string + { + $context = ''; + $extra = ''; + try { + if ($record['context']) { + $context = "\nContext: " . json_encode($record['context']); + } + if ($record['extra']) { + $extra = "\nExtra: " . json_encode($record['extra']); + } + } catch (\Throwable $e) { + // noop + } + + return "\nThe exception occurred while attempting to log: " . $record['message'] . $context . $extra; + } +} diff --git a/vendor/nesbot/carbon/.phpstorm.meta.php b/vendor/nesbot/carbon/.phpstorm.meta.php new file mode 100644 index 0000000..bd7c7e0 --- /dev/null +++ b/vendor/nesbot/carbon/.phpstorm.meta.php @@ -0,0 +1,10 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\MessageFormatter; + +use Symfony\Component\Translation\Formatter\MessageFormatterInterface; + +if (!class_exists(LazyMessageFormatter::class, false)) { + abstract class LazyMessageFormatter implements MessageFormatterInterface + { + public function format(string $message, string $locale, array $parameters = []): string + { + return $this->formatter->format( + $message, + $this->transformLocale($locale), + $parameters + ); + } + } +} diff --git a/vendor/nesbot/carbon/lazy/Carbon/MessageFormatter/MessageFormatterMapperWeakType.php b/vendor/nesbot/carbon/lazy/Carbon/MessageFormatter/MessageFormatterMapperWeakType.php new file mode 100644 index 0000000..cbd890d --- /dev/null +++ b/vendor/nesbot/carbon/lazy/Carbon/MessageFormatter/MessageFormatterMapperWeakType.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\MessageFormatter; + +use Symfony\Component\Translation\Formatter\ChoiceMessageFormatterInterface; +use Symfony\Component\Translation\Formatter\MessageFormatterInterface; + +if (!class_exists(LazyMessageFormatter::class, false)) { + abstract class LazyMessageFormatter implements MessageFormatterInterface, ChoiceMessageFormatterInterface + { + abstract protected function transformLocale(?string $locale): ?string; + + public function format($message, $locale, array $parameters = []) + { + return $this->formatter->format( + $message, + $this->transformLocale($locale), + $parameters + ); + } + + public function choiceFormat($message, $number, $locale, array $parameters = []) + { + return $this->formatter->choiceFormat($message, $number, $locale, $parameters); + } + } +} diff --git a/vendor/nesbot/carbon/lazy/Carbon/PHPStan/AbstractMacroBuiltin.php b/vendor/nesbot/carbon/lazy/Carbon/PHPStan/AbstractMacroBuiltin.php new file mode 100644 index 0000000..ba7cf63 --- /dev/null +++ b/vendor/nesbot/carbon/lazy/Carbon/PHPStan/AbstractMacroBuiltin.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\PHPStan; + +use PHPStan\BetterReflection\Reflection; +use ReflectionMethod; + +if (!class_exists(AbstractReflectionMacro::class, false)) { + abstract class AbstractReflectionMacro extends AbstractMacro + { + /** + * {@inheritdoc} + */ + public function getReflection(): ?ReflectionMethod + { + if ($this->reflectionFunction instanceof Reflection\ReflectionMethod) { + return new Reflection\Adapter\ReflectionMethod($this->reflectionFunction); + } + + return $this->reflectionFunction instanceof ReflectionMethod + ? $this->reflectionFunction + : null; + } + } +} diff --git a/vendor/nesbot/carbon/lazy/Carbon/PHPStan/AbstractMacroStatic.php b/vendor/nesbot/carbon/lazy/Carbon/PHPStan/AbstractMacroStatic.php new file mode 100644 index 0000000..bd4c8e8 --- /dev/null +++ b/vendor/nesbot/carbon/lazy/Carbon/PHPStan/AbstractMacroStatic.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\PHPStan; + +use PHPStan\BetterReflection\Reflection; +use ReflectionMethod; + +if (!class_exists(AbstractReflectionMacro::class, false)) { + abstract class AbstractReflectionMacro extends AbstractMacro + { + /** + * {@inheritdoc} + */ + public function getReflection(): ?Reflection\Adapter\ReflectionMethod + { + if ($this->reflectionFunction instanceof Reflection\Adapter\ReflectionMethod) { + return $this->reflectionFunction; + } + + if ($this->reflectionFunction instanceof Reflection\ReflectionMethod) { + return new Reflection\Adapter\ReflectionMethod($this->reflectionFunction); + } + + return $this->reflectionFunction instanceof ReflectionMethod + ? new Reflection\Adapter\ReflectionMethod( + Reflection\ReflectionMethod::createFromName( + $this->reflectionFunction->getDeclaringClass()->getName(), + $this->reflectionFunction->getName() + ) + ) + : null; + } + } +} diff --git a/vendor/nesbot/carbon/lazy/Carbon/PHPStan/MacroStrongType.php b/vendor/nesbot/carbon/lazy/Carbon/PHPStan/MacroStrongType.php new file mode 100644 index 0000000..f615b3a --- /dev/null +++ b/vendor/nesbot/carbon/lazy/Carbon/PHPStan/MacroStrongType.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\PHPStan; + +if (!class_exists(LazyMacro::class, false)) { + abstract class LazyMacro extends AbstractReflectionMacro + { + /** + * {@inheritdoc} + */ + public function getFileName(): ?string + { + $file = $this->reflectionFunction->getFileName(); + + return (($file ? realpath($file) : null) ?: $file) ?: null; + } + + /** + * {@inheritdoc} + */ + public function getStartLine(): ?int + { + return $this->reflectionFunction->getStartLine(); + } + + /** + * {@inheritdoc} + */ + public function getEndLine(): ?int + { + return $this->reflectionFunction->getEndLine(); + } + } +} diff --git a/vendor/nesbot/carbon/lazy/Carbon/PHPStan/MacroWeakType.php b/vendor/nesbot/carbon/lazy/Carbon/PHPStan/MacroWeakType.php new file mode 100644 index 0000000..bf64c1d --- /dev/null +++ b/vendor/nesbot/carbon/lazy/Carbon/PHPStan/MacroWeakType.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\PHPStan; + +if (!class_exists(LazyMacro::class, false)) { + abstract class LazyMacro extends AbstractReflectionMacro + { + /** + * {@inheritdoc} + * + * @return string|false + */ + public function getFileName() + { + $file = $this->reflectionFunction->getFileName(); + + return (($file ? realpath($file) : null) ?: $file) ?: null; + } + + /** + * {@inheritdoc} + * + * @return int|false + */ + public function getStartLine() + { + return $this->reflectionFunction->getStartLine(); + } + + /** + * {@inheritdoc} + * + * @return int|false + */ + public function getEndLine() + { + return $this->reflectionFunction->getEndLine(); + } + } +} diff --git a/vendor/nesbot/carbon/lazy/Carbon/TranslatorStrongType.php b/vendor/nesbot/carbon/lazy/Carbon/TranslatorStrongType.php new file mode 100644 index 0000000..d35308a --- /dev/null +++ b/vendor/nesbot/carbon/lazy/Carbon/TranslatorStrongType.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 Carbon; + +use Symfony\Component\Translation\MessageCatalogueInterface; + +if (!class_exists(LazyTranslator::class, false)) { + class LazyTranslator extends AbstractTranslator implements TranslatorStrongTypeInterface + { + public function trans(?string $id, array $parameters = [], ?string $domain = null, ?string $locale = null): string + { + return $this->translate($id, $parameters, $domain, $locale); + } + + public function getFromCatalogue(MessageCatalogueInterface $catalogue, string $id, string $domain = 'messages') + { + $messages = $this->getPrivateProperty($catalogue, 'messages'); + + if (isset($messages[$domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX][$id])) { + return $messages[$domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX][$id]; + } + + if (isset($messages[$domain][$id])) { + return $messages[$domain][$id]; + } + + $fallbackCatalogue = $this->getPrivateProperty($catalogue, 'fallbackCatalogue'); + + if ($fallbackCatalogue !== null) { + return $this->getFromCatalogue($fallbackCatalogue, $id, $domain); + } + + return $id; + } + + private function getPrivateProperty($instance, string $field) + { + return (function (string $field) { + return $this->$field; + })->call($instance, $field); + } + } +} diff --git a/vendor/nesbot/carbon/lazy/Carbon/TranslatorWeakType.php b/vendor/nesbot/carbon/lazy/Carbon/TranslatorWeakType.php new file mode 100644 index 0000000..94dbdc3 --- /dev/null +++ b/vendor/nesbot/carbon/lazy/Carbon/TranslatorWeakType.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +if (!class_exists(LazyTranslator::class, false)) { + class LazyTranslator extends AbstractTranslator + { + /** + * Returns the translation. + * + * @param string|null $id + * @param array $parameters + * @param string|null $domain + * @param string|null $locale + * + * @return string + */ + public function trans($id, array $parameters = [], $domain = null, $locale = null) + { + return $this->translate($id, $parameters, $domain, $locale); + } + } +} diff --git a/vendor/nesbot/carbon/readme.md b/vendor/nesbot/carbon/readme.md new file mode 100644 index 0000000..efc5811 --- /dev/null +++ b/vendor/nesbot/carbon/readme.md @@ -0,0 +1,153 @@ +# Carbon + +[![Latest Stable Version](https://img.shields.io/packagist/v/nesbot/carbon.svg?style=flat-square)](https://packagist.org/packages/nesbot/carbon) +[![Total Downloads](https://img.shields.io/packagist/dt/nesbot/carbon.svg?style=flat-square)](https://packagist.org/packages/nesbot/carbon) +[![GitHub Actions](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Factions-badge.atrox.dev%2Fbriannesbitt%2FCarbon%2Fbadge&style=flat-square&label=Build&logo=none)](https://github.com/briannesbitt/Carbon/actions) +[![codecov.io](https://img.shields.io/codecov/c/github/briannesbitt/Carbon.svg?style=flat-square)](https://codecov.io/github/briannesbitt/Carbon?branch=master) +[![Tidelift](https://tidelift.com/badges/github/briannesbitt/Carbon)](https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&utm_medium=referral&utm_campaign=readme) + +An international PHP extension for DateTime. [https://carbon.nesbot.com](https://carbon.nesbot.com) + +```php +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!'; +} +// Over 200 languages (and over 500 regional variants) supported: +echo Carbon::now()->subMinutes(2)->diffForHumans(); // '2 minutes ago' +echo Carbon::now()->subMinutes(2)->locale('zh_CN')->diffForHumans(); // '2分钟前' +echo Carbon::parse('2019-07-23 14:51')->isoFormat('LLLL'); // 'Tuesday, July 23, 2019 2:51 PM' +echo Carbon::parse('2019-07-23 14:51')->locale('fr_FR')->isoFormat('LLLL'); // 'mardi 23 juillet 2019 14:51' + +// ... but also does 'from now', 'after' and 'before' +// rolling up to seconds, minutes, hours, days, months, years + +$daysSinceEpoch = Carbon::createFromTimestamp(0)->diffInDays(); +``` + +[Get supported nesbot/carbon with the Tidelift Subscription](https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&utm_medium=referral&utm_campaign=readme) + +## Installation + +### With Composer + +``` +$ composer require nesbot/carbon +``` + +```json +{ + "require": { + "nesbot/carbon": "^2.16" + } +} +``` + +```php + + +### Translators + +[Thanks to people helping us to translate Carbon in so many languages](https://carbon.nesbot.com/contribute/translators/) + +### Sponsors + +Support this project by becoming a sponsor. Your logo will show up here with a link to your website. + + +CasinoHex Canada +Casino-portugal.pt +Slots City +inkedin +Probukmacher +AzuraCast +Triplebyte +GitHub Sponsors +Salesforce + + +[[Become a sponsor via OpenCollective](https://opencollective.com/Carbon#sponsor)] + + + + +[[Become a sponsor via GitHub](https://github.com/sponsors/kylekatarnls)] + +### Backers + +Thank you to all our backers! 🙏 + + + +[[Become a backer](https://opencollective.com/Carbon#backer)] + +## Carbon for enterprise + +Available as part of the Tidelift Subscription. + +The maintainers of ``Carbon`` and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) diff --git a/vendor/nesbot/carbon/sponsors.php b/vendor/nesbot/carbon/sponsors.php new file mode 100644 index 0000000..8012816 --- /dev/null +++ b/vendor/nesbot/carbon/sponsors.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Carbon\CarbonImmutable; + +require_once __DIR__.'/vendor/autoload.php'; + +function getOpenCollectiveSponsors(): string +{ + $members = json_decode(file_get_contents('https://opencollective.com/carbon/members/all.json'), true); + $sixMonthsAgo = CarbonImmutable::parse('now - 6 months')->format('Y-m-d h:i'); + + $list = array_filter($members, static function ($member) use ($sixMonthsAgo) { + return ($member['lastTransactionAmount'] > 3 || $member['isActive']) && + $member['role'] === 'BACKER' && + $member['type'] !== 'USER' && + ($member['totalAmountDonated'] > 100 || $member['lastTransactionAt'] > $sixMonthsAgo || $member['isActive'] && $member['lastTransactionAmount'] >= 30); + }); + + $list = array_map(static function (array $member) { + $createdAt = CarbonImmutable::parse($member['createdAt']); + $monthlyContribution = (float) ($member['totalAmountDonated'] / (1 + $createdAt->diffInMonths())); + + if ( + CarbonImmutable::parse($member['lastTransactionAt'])->isAfter('last month') && + $member['lastTransactionAmount'] > $monthlyContribution + ) { + $monthlyContribution = (float) $member['lastTransactionAmount']; + } + + $yearlyContribution = (float) ($member['totalAmountDonated'] / max(1, $createdAt->floatDiffInYears())); + $status = null; + + if ($monthlyContribution > 29) { + $status = 'sponsor'; + } elseif ($monthlyContribution > 3 || $yearlyContribution > 20) { + $status = 'backer'; + } elseif ($member['totalAmountDonated'] > 0) { + $status = 'helper'; + } + + return array_merge($member, [ + 'star' => ($monthlyContribution > 98 || $yearlyContribution > 500), + 'status' => $status, + 'monthlyContribution' => $monthlyContribution, + 'yearlyContribution' => $yearlyContribution, + ]); + }, $list); + + usort($list, static function (array $a, array $b) { + return ($b['monthlyContribution'] <=> $a['monthlyContribution']) + ?: ($b['totalAmountDonated'] <=> $a['totalAmountDonated']); + }); + + return implode('', array_map(static function (array $member) { + $href = htmlspecialchars($member['website'] ?? $member['profile']); + $src = $member['image'] ?? (strtr($member['profile'], ['https://opencollective.com/' => 'https://images.opencollective.com/']).'/avatar/256.png'); + [$x, $y] = @getimagesize($src) ?: [0, 0]; + $validImage = ($x && $y); + $src = $validImage ? htmlspecialchars($src) : 'https://opencollective.com/static/images/default-guest-logo.svg'; + $height = 64; + $width = $validImage ? round($x * $height / $y) : $height; + $href .= (strpos($href, '?') === false ? '?' : '&').'utm_source=opencollective&utm_medium=github&utm_campaign=Carbon'; + $title = htmlspecialchars(($member['description'] ?? null) ?: $member['name']); + $alt = htmlspecialchars($member['name']); + + return "\n".''. + ''.$alt.''. + ''; + }, $list))."\n"; +} + +file_put_contents('readme.md', preg_replace_callback( + '/()[\s\S]+()/', + static function (array $match) { + return $match[1].getOpenCollectiveSponsors().$match[2]; + }, + file_get_contents('readme.md') +)); diff --git a/vendor/nesbot/carbon/src/Carbon/AbstractTranslator.php b/vendor/nesbot/carbon/src/Carbon/AbstractTranslator.php new file mode 100644 index 0000000..8b8fe08 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/AbstractTranslator.php @@ -0,0 +1,398 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +use Carbon\MessageFormatter\MessageFormatterMapper; +use Closure; +use ReflectionException; +use ReflectionFunction; +use Symfony\Component\Translation; +use Symfony\Component\Translation\Formatter\MessageFormatterInterface; +use Symfony\Component\Translation\Loader\ArrayLoader; + +abstract class AbstractTranslator extends Translation\Translator +{ + /** + * Translator singletons for each language. + * + * @var array + */ + protected static $singletons = []; + + /** + * List of custom localized messages. + * + * @var array + */ + protected $messages = []; + + /** + * List of custom directories that contain translation files. + * + * @var string[] + */ + protected $directories = []; + + /** + * Set to true while constructing. + * + * @var bool + */ + protected $initializing = false; + + /** + * List of locales aliases. + * + * @var array + */ + protected $aliases = [ + 'me' => 'sr_Latn_ME', + 'scr' => 'sh', + ]; + + /** + * Return a singleton instance of Translator. + * + * @param string|null $locale optional initial locale ("en" - english by default) + * + * @return static + */ + public static function get($locale = null) + { + $locale = $locale ?: 'en'; + $key = static::class === Translator::class ? $locale : static::class.'|'.$locale; + + if (!isset(static::$singletons[$key])) { + static::$singletons[$key] = new static($locale); + } + + return static::$singletons[$key]; + } + + public function __construct($locale, MessageFormatterInterface $formatter = null, $cacheDir = null, $debug = false) + { + parent::setLocale($locale); + $this->initializing = true; + $this->directories = [__DIR__.'/Lang']; + $this->addLoader('array', new ArrayLoader()); + parent::__construct($locale, new MessageFormatterMapper($formatter), $cacheDir, $debug); + $this->initializing = false; + } + + /** + * Returns the list of directories translation files are searched in. + * + * @return array + */ + public function getDirectories(): array + { + return $this->directories; + } + + /** + * Set list of directories translation files are searched in. + * + * @param array $directories new directories list + * + * @return $this + */ + public function setDirectories(array $directories) + { + $this->directories = $directories; + + return $this; + } + + /** + * Add a directory to the list translation files are searched in. + * + * @param string $directory new directory + * + * @return $this + */ + public function addDirectory(string $directory) + { + $this->directories[] = $directory; + + return $this; + } + + /** + * Remove a directory from the list translation files are searched in. + * + * @param string $directory directory path + * + * @return $this + */ + public function removeDirectory(string $directory) + { + $search = rtrim(strtr($directory, '\\', '/'), '/'); + + return $this->setDirectories(array_filter($this->getDirectories(), function ($item) use ($search) { + return rtrim(strtr($item, '\\', '/'), '/') !== $search; + })); + } + + /** + * 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) { + $this->messages = []; + + return true; + } + + foreach ($this->getDirectories() as $directory) { + $data = @include sprintf('%s/%s.php', rtrim($directory, '\\/'), $locale); + + if ($data !== false) { + $this->messages[$locale] = $data; + $this->addResource('array', $this->messages[$locale], $locale); + + return true; + } + } + + return false; + } + + /** + * Returns the list of files matching a given locale prefix (or all if empty). + * + * @param string $prefix prefix required to filter result + * + * @return array + */ + public function getLocalesFiles($prefix = '') + { + $files = []; + + foreach ($this->getDirectories() as $directory) { + $directory = rtrim($directory, '\\/'); + + foreach (glob("$directory/$prefix*.php") as $file) { + $files[] = $file; + } + } + + return array_unique($files); + } + + /** + * Returns the list of internally available locales and already loaded custom locales. + * (It will ignore custom translator dynamic loading.) + * + * @param string $prefix prefix required to filter result + * + * @return array + */ + public function getAvailableLocales($prefix = '') + { + $locales = []; + foreach ($this->getLocalesFiles($prefix) as $file) { + $locales[] = substr($file, strrpos($file, '/') + 1, -4); + } + + return array_unique(array_merge($locales, array_keys($this->messages))); + } + + protected function translate(?string $id, array $parameters = [], ?string $domain = null, ?string $locale = null): string + { + if ($domain === null) { + $domain = 'messages'; + } + + $catalogue = $this->getCatalogue($locale); + $format = $this instanceof TranslatorStrongTypeInterface + ? $this->getFromCatalogue($catalogue, (string) $id, $domain) + : $this->getCatalogue($locale)->get((string) $id, $domain); // @codeCoverageIgnore + + if ($format instanceof Closure) { + // @codeCoverageIgnoreStart + try { + $count = (new ReflectionFunction($format))->getNumberOfRequiredParameters(); + } catch (ReflectionException $exception) { + $count = 0; + } + // @codeCoverageIgnoreEnd + + return $format( + ...array_values($parameters), + ...array_fill(0, max(0, $count - \count($parameters)), null) + ); + } + + return parent::trans($id, $parameters, $domain, $locale); + } + + /** + * Init messages language from matching file in Lang directory. + * + * @param string $locale + * + * @return bool + */ + protected function loadMessagesFromFile($locale) + { + return isset($this->messages[$locale]) || $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); + $this->messages[$locale] = array_merge( + $this->messages[$locale] ?? [], + $messages + ); + + return $this; + } + + /** + * Set messages of the current locale and take file first if present. + * + * @param array $messages + * + * @return $this + */ + public function setTranslations($messages) + { + return $this->setMessages($this->getLocale(), $messages); + } + + /** + * 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 ? $this->messages : $this->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,}|\d{2,})/', function ($matches) { + // _2-letters or YUE is a region, _3+-letters is a variant + $upper = strtoupper($matches[1]); + + if ($upper === 'YUE' || $upper === 'ISO' || \strlen($upper) < 3) { + return "_$upper"; + } + + return '_'.ucfirst($matches[1]); + }, strtolower($locale)); + + $previousLocale = $this->getLocale(); + + if ($previousLocale === $locale && isset($this->messages[$locale])) { + return true; + } + + unset(static::$singletons[$previousLocale]); + + if ($locale === 'auto') { + $completeLocale = setlocale(LC_TIME, '0'); + $locale = preg_replace('/^([^_.-]+).*$/', '$1', $completeLocale); + $locales = $this->getAvailableLocales($locale); + + $completeLocaleChunks = preg_split('/[_.-]+/', $completeLocale); + + $getScore = function ($language) use ($completeLocaleChunks) { + return self::compareChunkLists($completeLocaleChunks, preg_split('/[_.-]+/', $language)); + }; + + usort($locales, function ($first, $second) use ($getScore) { + return $getScore($second) <=> $getScore($first); + }); + + $locale = $locales[0]; + } + + if (isset($this->aliases[$locale])) { + $locale = $this->aliases[$locale]; + } + + // If subtag (ex: en_CA) first load the macro (ex: en) to have a fallback + if (str_contains($locale, '_') && + $this->loadMessagesFromFile($macroLocale = preg_replace('/^([^_]+).*$/', '$1', $locale)) + ) { + parent::setLocale($macroLocale); + } + + if (!$this->loadMessagesFromFile($locale) && !$this->initializing) { + return false; + } + + parent::setLocale($locale); + + return true; + } + + /** + * Show locale on var_dump(). + * + * @return array + */ + public function __debugInfo() + { + return [ + 'locale' => $this->getLocale(), + ]; + } + + private static function compareChunkLists($referenceChunks, $chunks) + { + $score = 0; + + foreach ($referenceChunks as $index => $chunk) { + if (!isset($chunks[$index])) { + $score++; + + continue; + } + + if (strtolower($chunks[$index]) === strtolower($chunk)) { + $score += 10; + } + } + + return $score; + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Carbon.php b/vendor/nesbot/carbon/src/Carbon/Carbon.php new file mode 100644 index 0000000..e32569a --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Carbon.php @@ -0,0 +1,523 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +use Carbon\Traits\Date; +use Carbon\Traits\DeprecatedProperties; +use DateTime; +use DateTimeInterface; +use DateTimeZone; + +/** + * A simple API extension for DateTime. + * + * @mixin DeprecatedProperties + * + * + * + * @property int $year + * @property int $yearIso + * @property int $month + * @property int $day + * @property int $hour + * @property int $minute + * @property int $second + * @property int $micro + * @property int $microsecond + * @property int|float|string $timestamp seconds since the Unix Epoch + * @property string $englishDayOfWeek the day of week in English + * @property string $shortEnglishDayOfWeek the abbreviated day of week in English + * @property string $englishMonth the month in English + * @property string $shortEnglishMonth the abbreviated month in English + * @property int $milliseconds + * @property int $millisecond + * @property int $milli + * @property int $week 1 through 53 + * @property int $isoWeek 1 through 53 + * @property int $weekYear year according to week format + * @property int $isoWeekYear year according to ISO week format + * @property int $dayOfYear 1 through 366 + * @property int $age does a diffInYears() with default parameters + * @property int $offset the timezone offset in seconds from UTC + * @property int $offsetMinutes the timezone offset in minutes from UTC + * @property int $offsetHours the timezone offset in hours from UTC + * @property CarbonTimeZone $timezone the current timezone + * @property CarbonTimeZone $tz alias of $timezone + * @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 $weekOfYear ISO-8601 week number of year, weeks starting on Monday + * @property-read int $daysInMonth number of days in the given month + * @property-read string $latinMeridiem "am"/"pm" (Ante meridiem or Post meridiem latin lowercase mark) + * @property-read string $latinUpperMeridiem "AM"/"PM" (Ante meridiem or Post meridiem latin uppercase mark) + * @property-read string $timezoneAbbreviatedName the current timezone abbreviated name + * @property-read string $tzAbbrName alias of $timezoneAbbreviatedName + * @property-read string $dayName long name of weekday translated according to Carbon locale, in english if no translation available for current language + * @property-read string $shortDayName short name of weekday translated according to Carbon locale, in english if no translation available for current language + * @property-read string $minDayName very short name of weekday translated according to Carbon locale, in english if no translation available for current language + * @property-read string $monthName long name of month translated according to Carbon locale, in english if no translation available for current language + * @property-read string $shortMonthName short name of month translated according to Carbon locale, in english if no translation available for current language + * @property-read string $meridiem lowercase meridiem mark translated according to Carbon locale, in latin if no translation available for current language + * @property-read string $upperMeridiem uppercase meridiem mark translated according to Carbon locale, in latin if no translation available for current language + * @property-read int $noZeroHour current hour from 1 to 24 + * @property-read int $weeksInYear 51 through 53 + * @property-read int $isoWeeksInYear 51 through 53 + * @property-read int $weekOfMonth 1 through 5 + * @property-read int $weekNumberInMonth 1 through 5 + * @property-read int $firstWeekDay 0 through 6 + * @property-read int $lastWeekDay 0 through 6 + * @property-read int $daysInYear 365 or 366 + * @property-read int $quarter the quarter of this instance, 1 - 4 + * @property-read int $decade the decade of this instance + * @property-read int $century the century of this instance + * @property-read int $millennium the millennium of this instance + * @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 the current timezone name + * @property-read string $tzName alias of $timezoneName + * @property-read string $locale locale of the current instance + * + * @method bool isUtc() Check if the current instance has UTC timezone. (Both isUtc and isUTC cases are valid.) + * @method bool isLocal() Check if the current instance has non-UTC timezone. + * @method bool isValid() Check if the current instance is a valid date. + * @method bool isDST() Check if the current instance is in a daylight saving time. + * @method bool isSunday() Checks if the instance day is sunday. + * @method bool isMonday() Checks if the instance day is monday. + * @method bool isTuesday() Checks if the instance day is tuesday. + * @method bool isWednesday() Checks if the instance day is wednesday. + * @method bool isThursday() Checks if the instance day is thursday. + * @method bool isFriday() Checks if the instance day is friday. + * @method bool isSaturday() Checks if the instance day is saturday. + * @method bool isSameYear(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same year as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentYear() Checks if the instance is in the same year as the current moment. + * @method bool isNextYear() Checks if the instance is in the same year as the current moment next year. + * @method bool isLastYear() Checks if the instance is in the same year as the current moment last year. + * @method bool isSameWeek(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same week as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentWeek() Checks if the instance is in the same week as the current moment. + * @method bool isNextWeek() Checks if the instance is in the same week as the current moment next week. + * @method bool isLastWeek() Checks if the instance is in the same week as the current moment last week. + * @method bool isSameDay(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same day as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentDay() Checks if the instance is in the same day as the current moment. + * @method bool isNextDay() Checks if the instance is in the same day as the current moment next day. + * @method bool isLastDay() Checks if the instance is in the same day as the current moment last day. + * @method bool isSameHour(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same hour as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentHour() Checks if the instance is in the same hour as the current moment. + * @method bool isNextHour() Checks if the instance is in the same hour as the current moment next hour. + * @method bool isLastHour() Checks if the instance is in the same hour as the current moment last hour. + * @method bool isSameMinute(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same minute as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMinute() Checks if the instance is in the same minute as the current moment. + * @method bool isNextMinute() Checks if the instance is in the same minute as the current moment next minute. + * @method bool isLastMinute() Checks if the instance is in the same minute as the current moment last minute. + * @method bool isSameSecond(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same second as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentSecond() Checks if the instance is in the same second as the current moment. + * @method bool isNextSecond() Checks if the instance is in the same second as the current moment next second. + * @method bool isLastSecond() Checks if the instance is in the same second as the current moment last second. + * @method bool isSameMicro(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same microsecond as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMicro() Checks if the instance is in the same microsecond as the current moment. + * @method bool isNextMicro() Checks if the instance is in the same microsecond as the current moment next microsecond. + * @method bool isLastMicro() Checks if the instance is in the same microsecond as the current moment last microsecond. + * @method bool isSameMicrosecond(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same microsecond as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMicrosecond() Checks if the instance is in the same microsecond as the current moment. + * @method bool isNextMicrosecond() Checks if the instance is in the same microsecond as the current moment next microsecond. + * @method bool isLastMicrosecond() Checks if the instance is in the same microsecond as the current moment last microsecond. + * @method bool isCurrentMonth() Checks if the instance is in the same month as the current moment. + * @method bool isNextMonth() Checks if the instance is in the same month as the current moment next month. + * @method bool isLastMonth() Checks if the instance is in the same month as the current moment last month. + * @method bool isCurrentQuarter() Checks if the instance is in the same quarter as the current moment. + * @method bool isNextQuarter() Checks if the instance is in the same quarter as the current moment next quarter. + * @method bool isLastQuarter() Checks if the instance is in the same quarter as the current moment last quarter. + * @method bool isSameDecade(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same decade as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentDecade() Checks if the instance is in the same decade as the current moment. + * @method bool isNextDecade() Checks if the instance is in the same decade as the current moment next decade. + * @method bool isLastDecade() Checks if the instance is in the same decade as the current moment last decade. + * @method bool isSameCentury(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same century as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentCentury() Checks if the instance is in the same century as the current moment. + * @method bool isNextCentury() Checks if the instance is in the same century as the current moment next century. + * @method bool isLastCentury() Checks if the instance is in the same century as the current moment last century. + * @method bool isSameMillennium(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same millennium as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMillennium() Checks if the instance is in the same millennium as the current moment. + * @method bool isNextMillennium() Checks if the instance is in the same millennium as the current moment next millennium. + * @method bool isLastMillennium() Checks if the instance is in the same millennium as the current moment last millennium. + * @method $this years(int $value) Set current instance year to the given value. + * @method $this year(int $value) Set current instance year to the given value. + * @method $this setYears(int $value) Set current instance year to the given value. + * @method $this setYear(int $value) Set current instance year to the given value. + * @method $this months(int $value) Set current instance month to the given value. + * @method $this month(int $value) Set current instance month to the given value. + * @method $this setMonths(int $value) Set current instance month to the given value. + * @method $this setMonth(int $value) Set current instance month to the given value. + * @method $this days(int $value) Set current instance day to the given value. + * @method $this day(int $value) Set current instance day to the given value. + * @method $this setDays(int $value) Set current instance day to the given value. + * @method $this setDay(int $value) Set current instance day to the given value. + * @method $this hours(int $value) Set current instance hour to the given value. + * @method $this hour(int $value) Set current instance hour to the given value. + * @method $this setHours(int $value) Set current instance hour to the given value. + * @method $this setHour(int $value) Set current instance hour to the given value. + * @method $this minutes(int $value) Set current instance minute to the given value. + * @method $this minute(int $value) Set current instance minute to the given value. + * @method $this setMinutes(int $value) Set current instance minute to the given value. + * @method $this setMinute(int $value) Set current instance minute to the given value. + * @method $this seconds(int $value) Set current instance second to the given value. + * @method $this second(int $value) Set current instance second to the given value. + * @method $this setSeconds(int $value) Set current instance second to the given value. + * @method $this setSecond(int $value) Set current instance second to the given value. + * @method $this millis(int $value) Set current instance millisecond to the given value. + * @method $this milli(int $value) Set current instance millisecond to the given value. + * @method $this setMillis(int $value) Set current instance millisecond to the given value. + * @method $this setMilli(int $value) Set current instance millisecond to the given value. + * @method $this milliseconds(int $value) Set current instance millisecond to the given value. + * @method $this millisecond(int $value) Set current instance millisecond to the given value. + * @method $this setMilliseconds(int $value) Set current instance millisecond to the given value. + * @method $this setMillisecond(int $value) Set current instance millisecond to the given value. + * @method $this micros(int $value) Set current instance microsecond to the given value. + * @method $this micro(int $value) Set current instance microsecond to the given value. + * @method $this setMicros(int $value) Set current instance microsecond to the given value. + * @method $this setMicro(int $value) Set current instance microsecond to the given value. + * @method $this microseconds(int $value) Set current instance microsecond to the given value. + * @method $this microsecond(int $value) Set current instance microsecond to the given value. + * @method $this setMicroseconds(int $value) Set current instance microsecond to the given value. + * @method $this setMicrosecond(int $value) Set current instance microsecond to the given value. + * @method $this addYears(int $value = 1) Add years (the $value count passed in) to the instance (using date interval). + * @method $this addYear() Add one year to the instance (using date interval). + * @method $this subYears(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval). + * @method $this subYear() Sub one year to the instance (using date interval). + * @method $this addYearsWithOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this addYearWithOverflow() Add one year to the instance (using date interval) with overflow explicitly allowed. + * @method $this subYearsWithOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this subYearWithOverflow() Sub one year to the instance (using date interval) with overflow explicitly allowed. + * @method $this addYearsWithoutOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addYearWithoutOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subYearsWithoutOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subYearWithoutOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addYearsWithNoOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addYearWithNoOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subYearsWithNoOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subYearWithNoOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addYearsNoOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addYearNoOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subYearsNoOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subYearNoOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addMonths(int $value = 1) Add months (the $value count passed in) to the instance (using date interval). + * @method $this addMonth() Add one month to the instance (using date interval). + * @method $this subMonths(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval). + * @method $this subMonth() Sub one month to the instance (using date interval). + * @method $this addMonthsWithOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this addMonthWithOverflow() Add one month to the instance (using date interval) with overflow explicitly allowed. + * @method $this subMonthsWithOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this subMonthWithOverflow() Sub one month to the instance (using date interval) with overflow explicitly allowed. + * @method $this addMonthsWithoutOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addMonthWithoutOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMonthsWithoutOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMonthWithoutOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addMonthsWithNoOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addMonthWithNoOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMonthsWithNoOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMonthWithNoOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addMonthsNoOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addMonthNoOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMonthsNoOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMonthNoOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addDays(int $value = 1) Add days (the $value count passed in) to the instance (using date interval). + * @method $this addDay() Add one day to the instance (using date interval). + * @method $this subDays(int $value = 1) Sub days (the $value count passed in) to the instance (using date interval). + * @method $this subDay() Sub one day to the instance (using date interval). + * @method $this addHours(int $value = 1) Add hours (the $value count passed in) to the instance (using date interval). + * @method $this addHour() Add one hour to the instance (using date interval). + * @method $this subHours(int $value = 1) Sub hours (the $value count passed in) to the instance (using date interval). + * @method $this subHour() Sub one hour to the instance (using date interval). + * @method $this addMinutes(int $value = 1) Add minutes (the $value count passed in) to the instance (using date interval). + * @method $this addMinute() Add one minute to the instance (using date interval). + * @method $this subMinutes(int $value = 1) Sub minutes (the $value count passed in) to the instance (using date interval). + * @method $this subMinute() Sub one minute to the instance (using date interval). + * @method $this addSeconds(int $value = 1) Add seconds (the $value count passed in) to the instance (using date interval). + * @method $this addSecond() Add one second to the instance (using date interval). + * @method $this subSeconds(int $value = 1) Sub seconds (the $value count passed in) to the instance (using date interval). + * @method $this subSecond() Sub one second to the instance (using date interval). + * @method $this addMillis(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using date interval). + * @method $this addMilli() Add one millisecond to the instance (using date interval). + * @method $this subMillis(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using date interval). + * @method $this subMilli() Sub one millisecond to the instance (using date interval). + * @method $this addMilliseconds(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using date interval). + * @method $this addMillisecond() Add one millisecond to the instance (using date interval). + * @method $this subMilliseconds(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using date interval). + * @method $this subMillisecond() Sub one millisecond to the instance (using date interval). + * @method $this addMicros(int $value = 1) Add microseconds (the $value count passed in) to the instance (using date interval). + * @method $this addMicro() Add one microsecond to the instance (using date interval). + * @method $this subMicros(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using date interval). + * @method $this subMicro() Sub one microsecond to the instance (using date interval). + * @method $this addMicroseconds(int $value = 1) Add microseconds (the $value count passed in) to the instance (using date interval). + * @method $this addMicrosecond() Add one microsecond to the instance (using date interval). + * @method $this subMicroseconds(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using date interval). + * @method $this subMicrosecond() Sub one microsecond to the instance (using date interval). + * @method $this addMillennia(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval). + * @method $this addMillennium() Add one millennium to the instance (using date interval). + * @method $this subMillennia(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval). + * @method $this subMillennium() Sub one millennium to the instance (using date interval). + * @method $this addMillenniaWithOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this addMillenniumWithOverflow() Add one millennium to the instance (using date interval) with overflow explicitly allowed. + * @method $this subMillenniaWithOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this subMillenniumWithOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly allowed. + * @method $this addMillenniaWithoutOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addMillenniumWithoutOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMillenniaWithoutOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMillenniumWithoutOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addMillenniaWithNoOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addMillenniumWithNoOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMillenniaWithNoOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMillenniumWithNoOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addMillenniaNoOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addMillenniumNoOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMillenniaNoOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMillenniumNoOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addCenturies(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval). + * @method $this addCentury() Add one century to the instance (using date interval). + * @method $this subCenturies(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval). + * @method $this subCentury() Sub one century to the instance (using date interval). + * @method $this addCenturiesWithOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this addCenturyWithOverflow() Add one century to the instance (using date interval) with overflow explicitly allowed. + * @method $this subCenturiesWithOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this subCenturyWithOverflow() Sub one century to the instance (using date interval) with overflow explicitly allowed. + * @method $this addCenturiesWithoutOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addCenturyWithoutOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subCenturiesWithoutOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subCenturyWithoutOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addCenturiesWithNoOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addCenturyWithNoOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subCenturiesWithNoOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subCenturyWithNoOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addCenturiesNoOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addCenturyNoOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subCenturiesNoOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subCenturyNoOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addDecades(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval). + * @method $this addDecade() Add one decade to the instance (using date interval). + * @method $this subDecades(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval). + * @method $this subDecade() Sub one decade to the instance (using date interval). + * @method $this addDecadesWithOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this addDecadeWithOverflow() Add one decade to the instance (using date interval) with overflow explicitly allowed. + * @method $this subDecadesWithOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this subDecadeWithOverflow() Sub one decade to the instance (using date interval) with overflow explicitly allowed. + * @method $this addDecadesWithoutOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addDecadeWithoutOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subDecadesWithoutOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subDecadeWithoutOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addDecadesWithNoOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addDecadeWithNoOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subDecadesWithNoOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subDecadeWithNoOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addDecadesNoOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addDecadeNoOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subDecadesNoOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subDecadeNoOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addQuarters(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval). + * @method $this addQuarter() Add one quarter to the instance (using date interval). + * @method $this subQuarters(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval). + * @method $this subQuarter() Sub one quarter to the instance (using date interval). + * @method $this addQuartersWithOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this addQuarterWithOverflow() Add one quarter to the instance (using date interval) with overflow explicitly allowed. + * @method $this subQuartersWithOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this subQuarterWithOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly allowed. + * @method $this addQuartersWithoutOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addQuarterWithoutOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subQuartersWithoutOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subQuarterWithoutOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addQuartersWithNoOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addQuarterWithNoOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subQuartersWithNoOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subQuarterWithNoOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addQuartersNoOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addQuarterNoOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subQuartersNoOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subQuarterNoOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addWeeks(int $value = 1) Add weeks (the $value count passed in) to the instance (using date interval). + * @method $this addWeek() Add one week to the instance (using date interval). + * @method $this subWeeks(int $value = 1) Sub weeks (the $value count passed in) to the instance (using date interval). + * @method $this subWeek() Sub one week to the instance (using date interval). + * @method $this addWeekdays(int $value = 1) Add weekdays (the $value count passed in) to the instance (using date interval). + * @method $this addWeekday() Add one weekday to the instance (using date interval). + * @method $this subWeekdays(int $value = 1) Sub weekdays (the $value count passed in) to the instance (using date interval). + * @method $this subWeekday() Sub one weekday to the instance (using date interval). + * @method $this addRealMicros(int $value = 1) Add microseconds (the $value count passed in) to the instance (using timestamp). + * @method $this addRealMicro() Add one microsecond to the instance (using timestamp). + * @method $this subRealMicros(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using timestamp). + * @method $this subRealMicro() Sub one microsecond to the instance (using timestamp). + * @method CarbonPeriod microsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each microsecond or every X microseconds if a factor is given. + * @method $this addRealMicroseconds(int $value = 1) Add microseconds (the $value count passed in) to the instance (using timestamp). + * @method $this addRealMicrosecond() Add one microsecond to the instance (using timestamp). + * @method $this subRealMicroseconds(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using timestamp). + * @method $this subRealMicrosecond() Sub one microsecond to the instance (using timestamp). + * @method CarbonPeriod microsecondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each microsecond or every X microseconds if a factor is given. + * @method $this addRealMillis(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using timestamp). + * @method $this addRealMilli() Add one millisecond to the instance (using timestamp). + * @method $this subRealMillis(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using timestamp). + * @method $this subRealMilli() Sub one millisecond to the instance (using timestamp). + * @method CarbonPeriod millisUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millisecond or every X milliseconds if a factor is given. + * @method $this addRealMilliseconds(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using timestamp). + * @method $this addRealMillisecond() Add one millisecond to the instance (using timestamp). + * @method $this subRealMilliseconds(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using timestamp). + * @method $this subRealMillisecond() Sub one millisecond to the instance (using timestamp). + * @method CarbonPeriod millisecondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millisecond or every X milliseconds if a factor is given. + * @method $this addRealSeconds(int $value = 1) Add seconds (the $value count passed in) to the instance (using timestamp). + * @method $this addRealSecond() Add one second to the instance (using timestamp). + * @method $this subRealSeconds(int $value = 1) Sub seconds (the $value count passed in) to the instance (using timestamp). + * @method $this subRealSecond() Sub one second to the instance (using timestamp). + * @method CarbonPeriod secondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each second or every X seconds if a factor is given. + * @method $this addRealMinutes(int $value = 1) Add minutes (the $value count passed in) to the instance (using timestamp). + * @method $this addRealMinute() Add one minute to the instance (using timestamp). + * @method $this subRealMinutes(int $value = 1) Sub minutes (the $value count passed in) to the instance (using timestamp). + * @method $this subRealMinute() Sub one minute to the instance (using timestamp). + * @method CarbonPeriod minutesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each minute or every X minutes if a factor is given. + * @method $this addRealHours(int $value = 1) Add hours (the $value count passed in) to the instance (using timestamp). + * @method $this addRealHour() Add one hour to the instance (using timestamp). + * @method $this subRealHours(int $value = 1) Sub hours (the $value count passed in) to the instance (using timestamp). + * @method $this subRealHour() Sub one hour to the instance (using timestamp). + * @method CarbonPeriod hoursUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each hour or every X hours if a factor is given. + * @method $this addRealDays(int $value = 1) Add days (the $value count passed in) to the instance (using timestamp). + * @method $this addRealDay() Add one day to the instance (using timestamp). + * @method $this subRealDays(int $value = 1) Sub days (the $value count passed in) to the instance (using timestamp). + * @method $this subRealDay() Sub one day to the instance (using timestamp). + * @method CarbonPeriod daysUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each day or every X days if a factor is given. + * @method $this addRealWeeks(int $value = 1) Add weeks (the $value count passed in) to the instance (using timestamp). + * @method $this addRealWeek() Add one week to the instance (using timestamp). + * @method $this subRealWeeks(int $value = 1) Sub weeks (the $value count passed in) to the instance (using timestamp). + * @method $this subRealWeek() Sub one week to the instance (using timestamp). + * @method CarbonPeriod weeksUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each week or every X weeks if a factor is given. + * @method $this addRealMonths(int $value = 1) Add months (the $value count passed in) to the instance (using timestamp). + * @method $this addRealMonth() Add one month to the instance (using timestamp). + * @method $this subRealMonths(int $value = 1) Sub months (the $value count passed in) to the instance (using timestamp). + * @method $this subRealMonth() Sub one month to the instance (using timestamp). + * @method CarbonPeriod monthsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each month or every X months if a factor is given. + * @method $this addRealQuarters(int $value = 1) Add quarters (the $value count passed in) to the instance (using timestamp). + * @method $this addRealQuarter() Add one quarter to the instance (using timestamp). + * @method $this subRealQuarters(int $value = 1) Sub quarters (the $value count passed in) to the instance (using timestamp). + * @method $this subRealQuarter() Sub one quarter to the instance (using timestamp). + * @method CarbonPeriod quartersUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each quarter or every X quarters if a factor is given. + * @method $this addRealYears(int $value = 1) Add years (the $value count passed in) to the instance (using timestamp). + * @method $this addRealYear() Add one year to the instance (using timestamp). + * @method $this subRealYears(int $value = 1) Sub years (the $value count passed in) to the instance (using timestamp). + * @method $this subRealYear() Sub one year to the instance (using timestamp). + * @method CarbonPeriod yearsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each year or every X years if a factor is given. + * @method $this addRealDecades(int $value = 1) Add decades (the $value count passed in) to the instance (using timestamp). + * @method $this addRealDecade() Add one decade to the instance (using timestamp). + * @method $this subRealDecades(int $value = 1) Sub decades (the $value count passed in) to the instance (using timestamp). + * @method $this subRealDecade() Sub one decade to the instance (using timestamp). + * @method CarbonPeriod decadesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each decade or every X decades if a factor is given. + * @method $this addRealCenturies(int $value = 1) Add centuries (the $value count passed in) to the instance (using timestamp). + * @method $this addRealCentury() Add one century to the instance (using timestamp). + * @method $this subRealCenturies(int $value = 1) Sub centuries (the $value count passed in) to the instance (using timestamp). + * @method $this subRealCentury() Sub one century to the instance (using timestamp). + * @method CarbonPeriod centuriesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each century or every X centuries if a factor is given. + * @method $this addRealMillennia(int $value = 1) Add millennia (the $value count passed in) to the instance (using timestamp). + * @method $this addRealMillennium() Add one millennium to the instance (using timestamp). + * @method $this subRealMillennia(int $value = 1) Sub millennia (the $value count passed in) to the instance (using timestamp). + * @method $this subRealMillennium() Sub one millennium to the instance (using timestamp). + * @method CarbonPeriod millenniaUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millennium or every X millennia if a factor is given. + * @method $this roundYear(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. + * @method $this roundYears(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. + * @method $this floorYear(float $precision = 1) Truncate the current instance year with given precision. + * @method $this floorYears(float $precision = 1) Truncate the current instance year with given precision. + * @method $this ceilYear(float $precision = 1) Ceil the current instance year with given precision. + * @method $this ceilYears(float $precision = 1) Ceil the current instance year with given precision. + * @method $this roundMonth(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. + * @method $this roundMonths(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. + * @method $this floorMonth(float $precision = 1) Truncate the current instance month with given precision. + * @method $this floorMonths(float $precision = 1) Truncate the current instance month with given precision. + * @method $this ceilMonth(float $precision = 1) Ceil the current instance month with given precision. + * @method $this ceilMonths(float $precision = 1) Ceil the current instance month with given precision. + * @method $this roundDay(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method $this roundDays(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method $this floorDay(float $precision = 1) Truncate the current instance day with given precision. + * @method $this floorDays(float $precision = 1) Truncate the current instance day with given precision. + * @method $this ceilDay(float $precision = 1) Ceil the current instance day with given precision. + * @method $this ceilDays(float $precision = 1) Ceil the current instance day with given precision. + * @method $this roundHour(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. + * @method $this roundHours(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. + * @method $this floorHour(float $precision = 1) Truncate the current instance hour with given precision. + * @method $this floorHours(float $precision = 1) Truncate the current instance hour with given precision. + * @method $this ceilHour(float $precision = 1) Ceil the current instance hour with given precision. + * @method $this ceilHours(float $precision = 1) Ceil the current instance hour with given precision. + * @method $this roundMinute(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. + * @method $this roundMinutes(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. + * @method $this floorMinute(float $precision = 1) Truncate the current instance minute with given precision. + * @method $this floorMinutes(float $precision = 1) Truncate the current instance minute with given precision. + * @method $this ceilMinute(float $precision = 1) Ceil the current instance minute with given precision. + * @method $this ceilMinutes(float $precision = 1) Ceil the current instance minute with given precision. + * @method $this roundSecond(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. + * @method $this roundSeconds(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. + * @method $this floorSecond(float $precision = 1) Truncate the current instance second with given precision. + * @method $this floorSeconds(float $precision = 1) Truncate the current instance second with given precision. + * @method $this ceilSecond(float $precision = 1) Ceil the current instance second with given precision. + * @method $this ceilSeconds(float $precision = 1) Ceil the current instance second with given precision. + * @method $this roundMillennium(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. + * @method $this roundMillennia(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. + * @method $this floorMillennium(float $precision = 1) Truncate the current instance millennium with given precision. + * @method $this floorMillennia(float $precision = 1) Truncate the current instance millennium with given precision. + * @method $this ceilMillennium(float $precision = 1) Ceil the current instance millennium with given precision. + * @method $this ceilMillennia(float $precision = 1) Ceil the current instance millennium with given precision. + * @method $this roundCentury(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. + * @method $this roundCenturies(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. + * @method $this floorCentury(float $precision = 1) Truncate the current instance century with given precision. + * @method $this floorCenturies(float $precision = 1) Truncate the current instance century with given precision. + * @method $this ceilCentury(float $precision = 1) Ceil the current instance century with given precision. + * @method $this ceilCenturies(float $precision = 1) Ceil the current instance century with given precision. + * @method $this roundDecade(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. + * @method $this roundDecades(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. + * @method $this floorDecade(float $precision = 1) Truncate the current instance decade with given precision. + * @method $this floorDecades(float $precision = 1) Truncate the current instance decade with given precision. + * @method $this ceilDecade(float $precision = 1) Ceil the current instance decade with given precision. + * @method $this ceilDecades(float $precision = 1) Ceil the current instance decade with given precision. + * @method $this roundQuarter(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. + * @method $this roundQuarters(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. + * @method $this floorQuarter(float $precision = 1) Truncate the current instance quarter with given precision. + * @method $this floorQuarters(float $precision = 1) Truncate the current instance quarter with given precision. + * @method $this ceilQuarter(float $precision = 1) Ceil the current instance quarter with given precision. + * @method $this ceilQuarters(float $precision = 1) Ceil the current instance quarter with given precision. + * @method $this roundMillisecond(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. + * @method $this roundMilliseconds(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. + * @method $this floorMillisecond(float $precision = 1) Truncate the current instance millisecond with given precision. + * @method $this floorMilliseconds(float $precision = 1) Truncate the current instance millisecond with given precision. + * @method $this ceilMillisecond(float $precision = 1) Ceil the current instance millisecond with given precision. + * @method $this ceilMilliseconds(float $precision = 1) Ceil the current instance millisecond with given precision. + * @method $this roundMicrosecond(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. + * @method $this roundMicroseconds(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. + * @method $this floorMicrosecond(float $precision = 1) Truncate the current instance microsecond with given precision. + * @method $this floorMicroseconds(float $precision = 1) Truncate the current instance microsecond with given precision. + * @method $this ceilMicrosecond(float $precision = 1) Ceil the current instance microsecond with given precision. + * @method $this ceilMicroseconds(float $precision = 1) Ceil the current instance microsecond with given precision. + * @method string shortAbsoluteDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'Absolute' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longAbsoluteDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'Absolute' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'Relative' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'Relative' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeToNowDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'RelativeToNow' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeToNowDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'RelativeToNow' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeToOtherDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'RelativeToOther' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeToOtherDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'RelativeToOther' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method static static|false createFromFormat(string $format, string $time, DateTimeZone|string|false|null $timezone = null) Parse a string into a new Carbon object according to the specified format. + * @method static static __set_state(array $array) https://php.net/manual/en/datetime.set-state.php + * + * + */ +class Carbon extends DateTime implements CarbonInterface +{ + use Date; + + /** + * Returns true if the current class/instance is mutable. + * + * @return bool + */ + public static function isMutable() + { + return true; + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/CarbonConverterInterface.php b/vendor/nesbot/carbon/src/Carbon/CarbonConverterInterface.php new file mode 100644 index 0000000..1ce967b --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/CarbonConverterInterface.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +use DateTimeInterface; + +interface CarbonConverterInterface +{ + public function convertDate(DateTimeInterface $dateTime, bool $negated = false): CarbonInterface; +} diff --git a/vendor/nesbot/carbon/src/Carbon/CarbonImmutable.php b/vendor/nesbot/carbon/src/Carbon/CarbonImmutable.php new file mode 100644 index 0000000..4c9c1cf --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/CarbonImmutable.php @@ -0,0 +1,582 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +use Carbon\Traits\Date; +use Carbon\Traits\DeprecatedProperties; +use DateTimeImmutable; +use DateTimeInterface; +use DateTimeZone; + +/** + * A simple API extension for DateTimeImmutable. + * + * @mixin DeprecatedProperties + * + * + * + * @property int $year + * @property int $yearIso + * @property int $month + * @property int $day + * @property int $hour + * @property int $minute + * @property int $second + * @property int $micro + * @property int $microsecond + * @property int|float|string $timestamp seconds since the Unix Epoch + * @property string $englishDayOfWeek the day of week in English + * @property string $shortEnglishDayOfWeek the abbreviated day of week in English + * @property string $englishMonth the month in English + * @property string $shortEnglishMonth the abbreviated month in English + * @property int $milliseconds + * @property int $millisecond + * @property int $milli + * @property int $week 1 through 53 + * @property int $isoWeek 1 through 53 + * @property int $weekYear year according to week format + * @property int $isoWeekYear year according to ISO week format + * @property int $dayOfYear 1 through 366 + * @property int $age does a diffInYears() with default parameters + * @property int $offset the timezone offset in seconds from UTC + * @property int $offsetMinutes the timezone offset in minutes from UTC + * @property int $offsetHours the timezone offset in hours from UTC + * @property CarbonTimeZone $timezone the current timezone + * @property CarbonTimeZone $tz alias of $timezone + * @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 $weekOfYear ISO-8601 week number of year, weeks starting on Monday + * @property-read int $daysInMonth number of days in the given month + * @property-read string $latinMeridiem "am"/"pm" (Ante meridiem or Post meridiem latin lowercase mark) + * @property-read string $latinUpperMeridiem "AM"/"PM" (Ante meridiem or Post meridiem latin uppercase mark) + * @property-read string $timezoneAbbreviatedName the current timezone abbreviated name + * @property-read string $tzAbbrName alias of $timezoneAbbreviatedName + * @property-read string $dayName long name of weekday translated according to Carbon locale, in english if no translation available for current language + * @property-read string $shortDayName short name of weekday translated according to Carbon locale, in english if no translation available for current language + * @property-read string $minDayName very short name of weekday translated according to Carbon locale, in english if no translation available for current language + * @property-read string $monthName long name of month translated according to Carbon locale, in english if no translation available for current language + * @property-read string $shortMonthName short name of month translated according to Carbon locale, in english if no translation available for current language + * @property-read string $meridiem lowercase meridiem mark translated according to Carbon locale, in latin if no translation available for current language + * @property-read string $upperMeridiem uppercase meridiem mark translated according to Carbon locale, in latin if no translation available for current language + * @property-read int $noZeroHour current hour from 1 to 24 + * @property-read int $weeksInYear 51 through 53 + * @property-read int $isoWeeksInYear 51 through 53 + * @property-read int $weekOfMonth 1 through 5 + * @property-read int $weekNumberInMonth 1 through 5 + * @property-read int $firstWeekDay 0 through 6 + * @property-read int $lastWeekDay 0 through 6 + * @property-read int $daysInYear 365 or 366 + * @property-read int $quarter the quarter of this instance, 1 - 4 + * @property-read int $decade the decade of this instance + * @property-read int $century the century of this instance + * @property-read int $millennium the millennium of this instance + * @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 the current timezone name + * @property-read string $tzName alias of $timezoneName + * @property-read string $locale locale of the current instance + * + * @method bool isUtc() Check if the current instance has UTC timezone. (Both isUtc and isUTC cases are valid.) + * @method bool isLocal() Check if the current instance has non-UTC timezone. + * @method bool isValid() Check if the current instance is a valid date. + * @method bool isDST() Check if the current instance is in a daylight saving time. + * @method bool isSunday() Checks if the instance day is sunday. + * @method bool isMonday() Checks if the instance day is monday. + * @method bool isTuesday() Checks if the instance day is tuesday. + * @method bool isWednesday() Checks if the instance day is wednesday. + * @method bool isThursday() Checks if the instance day is thursday. + * @method bool isFriday() Checks if the instance day is friday. + * @method bool isSaturday() Checks if the instance day is saturday. + * @method bool isSameYear(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same year as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentYear() Checks if the instance is in the same year as the current moment. + * @method bool isNextYear() Checks if the instance is in the same year as the current moment next year. + * @method bool isLastYear() Checks if the instance is in the same year as the current moment last year. + * @method bool isSameWeek(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same week as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentWeek() Checks if the instance is in the same week as the current moment. + * @method bool isNextWeek() Checks if the instance is in the same week as the current moment next week. + * @method bool isLastWeek() Checks if the instance is in the same week as the current moment last week. + * @method bool isSameDay(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same day as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentDay() Checks if the instance is in the same day as the current moment. + * @method bool isNextDay() Checks if the instance is in the same day as the current moment next day. + * @method bool isLastDay() Checks if the instance is in the same day as the current moment last day. + * @method bool isSameHour(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same hour as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentHour() Checks if the instance is in the same hour as the current moment. + * @method bool isNextHour() Checks if the instance is in the same hour as the current moment next hour. + * @method bool isLastHour() Checks if the instance is in the same hour as the current moment last hour. + * @method bool isSameMinute(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same minute as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMinute() Checks if the instance is in the same minute as the current moment. + * @method bool isNextMinute() Checks if the instance is in the same minute as the current moment next minute. + * @method bool isLastMinute() Checks if the instance is in the same minute as the current moment last minute. + * @method bool isSameSecond(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same second as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentSecond() Checks if the instance is in the same second as the current moment. + * @method bool isNextSecond() Checks if the instance is in the same second as the current moment next second. + * @method bool isLastSecond() Checks if the instance is in the same second as the current moment last second. + * @method bool isSameMicro(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same microsecond as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMicro() Checks if the instance is in the same microsecond as the current moment. + * @method bool isNextMicro() Checks if the instance is in the same microsecond as the current moment next microsecond. + * @method bool isLastMicro() Checks if the instance is in the same microsecond as the current moment last microsecond. + * @method bool isSameMicrosecond(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same microsecond as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMicrosecond() Checks if the instance is in the same microsecond as the current moment. + * @method bool isNextMicrosecond() Checks if the instance is in the same microsecond as the current moment next microsecond. + * @method bool isLastMicrosecond() Checks if the instance is in the same microsecond as the current moment last microsecond. + * @method bool isCurrentMonth() Checks if the instance is in the same month as the current moment. + * @method bool isNextMonth() Checks if the instance is in the same month as the current moment next month. + * @method bool isLastMonth() Checks if the instance is in the same month as the current moment last month. + * @method bool isCurrentQuarter() Checks if the instance is in the same quarter as the current moment. + * @method bool isNextQuarter() Checks if the instance is in the same quarter as the current moment next quarter. + * @method bool isLastQuarter() Checks if the instance is in the same quarter as the current moment last quarter. + * @method bool isSameDecade(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same decade as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentDecade() Checks if the instance is in the same decade as the current moment. + * @method bool isNextDecade() Checks if the instance is in the same decade as the current moment next decade. + * @method bool isLastDecade() Checks if the instance is in the same decade as the current moment last decade. + * @method bool isSameCentury(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same century as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentCentury() Checks if the instance is in the same century as the current moment. + * @method bool isNextCentury() Checks if the instance is in the same century as the current moment next century. + * @method bool isLastCentury() Checks if the instance is in the same century as the current moment last century. + * @method bool isSameMillennium(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same millennium as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMillennium() Checks if the instance is in the same millennium as the current moment. + * @method bool isNextMillennium() Checks if the instance is in the same millennium as the current moment next millennium. + * @method bool isLastMillennium() Checks if the instance is in the same millennium as the current moment last millennium. + * @method CarbonImmutable years(int $value) Set current instance year to the given value. + * @method CarbonImmutable year(int $value) Set current instance year to the given value. + * @method CarbonImmutable setYears(int $value) Set current instance year to the given value. + * @method CarbonImmutable setYear(int $value) Set current instance year to the given value. + * @method CarbonImmutable months(int $value) Set current instance month to the given value. + * @method CarbonImmutable month(int $value) Set current instance month to the given value. + * @method CarbonImmutable setMonths(int $value) Set current instance month to the given value. + * @method CarbonImmutable setMonth(int $value) Set current instance month to the given value. + * @method CarbonImmutable days(int $value) Set current instance day to the given value. + * @method CarbonImmutable day(int $value) Set current instance day to the given value. + * @method CarbonImmutable setDays(int $value) Set current instance day to the given value. + * @method CarbonImmutable setDay(int $value) Set current instance day to the given value. + * @method CarbonImmutable hours(int $value) Set current instance hour to the given value. + * @method CarbonImmutable hour(int $value) Set current instance hour to the given value. + * @method CarbonImmutable setHours(int $value) Set current instance hour to the given value. + * @method CarbonImmutable setHour(int $value) Set current instance hour to the given value. + * @method CarbonImmutable minutes(int $value) Set current instance minute to the given value. + * @method CarbonImmutable minute(int $value) Set current instance minute to the given value. + * @method CarbonImmutable setMinutes(int $value) Set current instance minute to the given value. + * @method CarbonImmutable setMinute(int $value) Set current instance minute to the given value. + * @method CarbonImmutable seconds(int $value) Set current instance second to the given value. + * @method CarbonImmutable second(int $value) Set current instance second to the given value. + * @method CarbonImmutable setSeconds(int $value) Set current instance second to the given value. + * @method CarbonImmutable setSecond(int $value) Set current instance second to the given value. + * @method CarbonImmutable millis(int $value) Set current instance millisecond to the given value. + * @method CarbonImmutable milli(int $value) Set current instance millisecond to the given value. + * @method CarbonImmutable setMillis(int $value) Set current instance millisecond to the given value. + * @method CarbonImmutable setMilli(int $value) Set current instance millisecond to the given value. + * @method CarbonImmutable milliseconds(int $value) Set current instance millisecond to the given value. + * @method CarbonImmutable millisecond(int $value) Set current instance millisecond to the given value. + * @method CarbonImmutable setMilliseconds(int $value) Set current instance millisecond to the given value. + * @method CarbonImmutable setMillisecond(int $value) Set current instance millisecond to the given value. + * @method CarbonImmutable micros(int $value) Set current instance microsecond to the given value. + * @method CarbonImmutable micro(int $value) Set current instance microsecond to the given value. + * @method CarbonImmutable setMicros(int $value) Set current instance microsecond to the given value. + * @method CarbonImmutable setMicro(int $value) Set current instance microsecond to the given value. + * @method CarbonImmutable microseconds(int $value) Set current instance microsecond to the given value. + * @method CarbonImmutable microsecond(int $value) Set current instance microsecond to the given value. + * @method CarbonImmutable setMicroseconds(int $value) Set current instance microsecond to the given value. + * @method CarbonImmutable setMicrosecond(int $value) Set current instance microsecond to the given value. + * @method CarbonImmutable addYears(int $value = 1) Add years (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addYear() Add one year to the instance (using date interval). + * @method CarbonImmutable subYears(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subYear() Sub one year to the instance (using date interval). + * @method CarbonImmutable addYearsWithOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addYearWithOverflow() Add one year to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subYearsWithOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subYearWithOverflow() Sub one year to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addYearsWithoutOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addYearWithoutOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subYearsWithoutOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subYearWithoutOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addYearsWithNoOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addYearWithNoOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subYearsWithNoOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subYearWithNoOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addYearsNoOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addYearNoOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subYearsNoOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subYearNoOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addMonths(int $value = 1) Add months (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addMonth() Add one month to the instance (using date interval). + * @method CarbonImmutable subMonths(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subMonth() Sub one month to the instance (using date interval). + * @method CarbonImmutable addMonthsWithOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addMonthWithOverflow() Add one month to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subMonthsWithOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subMonthWithOverflow() Sub one month to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addMonthsWithoutOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addMonthWithoutOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMonthsWithoutOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMonthWithoutOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addMonthsWithNoOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addMonthWithNoOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMonthsWithNoOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMonthWithNoOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addMonthsNoOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addMonthNoOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMonthsNoOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMonthNoOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addDays(int $value = 1) Add days (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addDay() Add one day to the instance (using date interval). + * @method CarbonImmutable subDays(int $value = 1) Sub days (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subDay() Sub one day to the instance (using date interval). + * @method CarbonImmutable addHours(int $value = 1) Add hours (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addHour() Add one hour to the instance (using date interval). + * @method CarbonImmutable subHours(int $value = 1) Sub hours (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subHour() Sub one hour to the instance (using date interval). + * @method CarbonImmutable addMinutes(int $value = 1) Add minutes (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addMinute() Add one minute to the instance (using date interval). + * @method CarbonImmutable subMinutes(int $value = 1) Sub minutes (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subMinute() Sub one minute to the instance (using date interval). + * @method CarbonImmutable addSeconds(int $value = 1) Add seconds (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addSecond() Add one second to the instance (using date interval). + * @method CarbonImmutable subSeconds(int $value = 1) Sub seconds (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subSecond() Sub one second to the instance (using date interval). + * @method CarbonImmutable addMillis(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addMilli() Add one millisecond to the instance (using date interval). + * @method CarbonImmutable subMillis(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subMilli() Sub one millisecond to the instance (using date interval). + * @method CarbonImmutable addMilliseconds(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addMillisecond() Add one millisecond to the instance (using date interval). + * @method CarbonImmutable subMilliseconds(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subMillisecond() Sub one millisecond to the instance (using date interval). + * @method CarbonImmutable addMicros(int $value = 1) Add microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addMicro() Add one microsecond to the instance (using date interval). + * @method CarbonImmutable subMicros(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subMicro() Sub one microsecond to the instance (using date interval). + * @method CarbonImmutable addMicroseconds(int $value = 1) Add microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addMicrosecond() Add one microsecond to the instance (using date interval). + * @method CarbonImmutable subMicroseconds(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subMicrosecond() Sub one microsecond to the instance (using date interval). + * @method CarbonImmutable addMillennia(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addMillennium() Add one millennium to the instance (using date interval). + * @method CarbonImmutable subMillennia(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subMillennium() Sub one millennium to the instance (using date interval). + * @method CarbonImmutable addMillenniaWithOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addMillenniumWithOverflow() Add one millennium to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subMillenniaWithOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subMillenniumWithOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addMillenniaWithoutOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addMillenniumWithoutOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMillenniaWithoutOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMillenniumWithoutOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addMillenniaWithNoOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addMillenniumWithNoOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMillenniaWithNoOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMillenniumWithNoOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addMillenniaNoOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addMillenniumNoOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMillenniaNoOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMillenniumNoOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addCenturies(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addCentury() Add one century to the instance (using date interval). + * @method CarbonImmutable subCenturies(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subCentury() Sub one century to the instance (using date interval). + * @method CarbonImmutable addCenturiesWithOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addCenturyWithOverflow() Add one century to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subCenturiesWithOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subCenturyWithOverflow() Sub one century to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addCenturiesWithoutOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addCenturyWithoutOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subCenturiesWithoutOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subCenturyWithoutOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addCenturiesWithNoOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addCenturyWithNoOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subCenturiesWithNoOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subCenturyWithNoOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addCenturiesNoOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addCenturyNoOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subCenturiesNoOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subCenturyNoOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addDecades(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addDecade() Add one decade to the instance (using date interval). + * @method CarbonImmutable subDecades(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subDecade() Sub one decade to the instance (using date interval). + * @method CarbonImmutable addDecadesWithOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addDecadeWithOverflow() Add one decade to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subDecadesWithOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subDecadeWithOverflow() Sub one decade to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addDecadesWithoutOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addDecadeWithoutOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subDecadesWithoutOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subDecadeWithoutOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addDecadesWithNoOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addDecadeWithNoOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subDecadesWithNoOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subDecadeWithNoOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addDecadesNoOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addDecadeNoOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subDecadesNoOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subDecadeNoOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addQuarters(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addQuarter() Add one quarter to the instance (using date interval). + * @method CarbonImmutable subQuarters(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subQuarter() Sub one quarter to the instance (using date interval). + * @method CarbonImmutable addQuartersWithOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addQuarterWithOverflow() Add one quarter to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subQuartersWithOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subQuarterWithOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addQuartersWithoutOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addQuarterWithoutOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subQuartersWithoutOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subQuarterWithoutOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addQuartersWithNoOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addQuarterWithNoOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subQuartersWithNoOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subQuarterWithNoOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addQuartersNoOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addQuarterNoOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subQuartersNoOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subQuarterNoOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addWeeks(int $value = 1) Add weeks (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addWeek() Add one week to the instance (using date interval). + * @method CarbonImmutable subWeeks(int $value = 1) Sub weeks (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subWeek() Sub one week to the instance (using date interval). + * @method CarbonImmutable addWeekdays(int $value = 1) Add weekdays (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addWeekday() Add one weekday to the instance (using date interval). + * @method CarbonImmutable subWeekdays(int $value = 1) Sub weekdays (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subWeekday() Sub one weekday to the instance (using date interval). + * @method CarbonImmutable addRealMicros(int $value = 1) Add microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealMicro() Add one microsecond to the instance (using timestamp). + * @method CarbonImmutable subRealMicros(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealMicro() Sub one microsecond to the instance (using timestamp). + * @method CarbonPeriod microsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each microsecond or every X microseconds if a factor is given. + * @method CarbonImmutable addRealMicroseconds(int $value = 1) Add microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealMicrosecond() Add one microsecond to the instance (using timestamp). + * @method CarbonImmutable subRealMicroseconds(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealMicrosecond() Sub one microsecond to the instance (using timestamp). + * @method CarbonPeriod microsecondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each microsecond or every X microseconds if a factor is given. + * @method CarbonImmutable addRealMillis(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealMilli() Add one millisecond to the instance (using timestamp). + * @method CarbonImmutable subRealMillis(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealMilli() Sub one millisecond to the instance (using timestamp). + * @method CarbonPeriod millisUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millisecond or every X milliseconds if a factor is given. + * @method CarbonImmutable addRealMilliseconds(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealMillisecond() Add one millisecond to the instance (using timestamp). + * @method CarbonImmutable subRealMilliseconds(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealMillisecond() Sub one millisecond to the instance (using timestamp). + * @method CarbonPeriod millisecondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millisecond or every X milliseconds if a factor is given. + * @method CarbonImmutable addRealSeconds(int $value = 1) Add seconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealSecond() Add one second to the instance (using timestamp). + * @method CarbonImmutable subRealSeconds(int $value = 1) Sub seconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealSecond() Sub one second to the instance (using timestamp). + * @method CarbonPeriod secondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each second or every X seconds if a factor is given. + * @method CarbonImmutable addRealMinutes(int $value = 1) Add minutes (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealMinute() Add one minute to the instance (using timestamp). + * @method CarbonImmutable subRealMinutes(int $value = 1) Sub minutes (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealMinute() Sub one minute to the instance (using timestamp). + * @method CarbonPeriod minutesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each minute or every X minutes if a factor is given. + * @method CarbonImmutable addRealHours(int $value = 1) Add hours (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealHour() Add one hour to the instance (using timestamp). + * @method CarbonImmutable subRealHours(int $value = 1) Sub hours (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealHour() Sub one hour to the instance (using timestamp). + * @method CarbonPeriod hoursUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each hour or every X hours if a factor is given. + * @method CarbonImmutable addRealDays(int $value = 1) Add days (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealDay() Add one day to the instance (using timestamp). + * @method CarbonImmutable subRealDays(int $value = 1) Sub days (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealDay() Sub one day to the instance (using timestamp). + * @method CarbonPeriod daysUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each day or every X days if a factor is given. + * @method CarbonImmutable addRealWeeks(int $value = 1) Add weeks (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealWeek() Add one week to the instance (using timestamp). + * @method CarbonImmutable subRealWeeks(int $value = 1) Sub weeks (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealWeek() Sub one week to the instance (using timestamp). + * @method CarbonPeriod weeksUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each week or every X weeks if a factor is given. + * @method CarbonImmutable addRealMonths(int $value = 1) Add months (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealMonth() Add one month to the instance (using timestamp). + * @method CarbonImmutable subRealMonths(int $value = 1) Sub months (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealMonth() Sub one month to the instance (using timestamp). + * @method CarbonPeriod monthsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each month or every X months if a factor is given. + * @method CarbonImmutable addRealQuarters(int $value = 1) Add quarters (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealQuarter() Add one quarter to the instance (using timestamp). + * @method CarbonImmutable subRealQuarters(int $value = 1) Sub quarters (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealQuarter() Sub one quarter to the instance (using timestamp). + * @method CarbonPeriod quartersUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each quarter or every X quarters if a factor is given. + * @method CarbonImmutable addRealYears(int $value = 1) Add years (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealYear() Add one year to the instance (using timestamp). + * @method CarbonImmutable subRealYears(int $value = 1) Sub years (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealYear() Sub one year to the instance (using timestamp). + * @method CarbonPeriod yearsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each year or every X years if a factor is given. + * @method CarbonImmutable addRealDecades(int $value = 1) Add decades (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealDecade() Add one decade to the instance (using timestamp). + * @method CarbonImmutable subRealDecades(int $value = 1) Sub decades (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealDecade() Sub one decade to the instance (using timestamp). + * @method CarbonPeriod decadesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each decade or every X decades if a factor is given. + * @method CarbonImmutable addRealCenturies(int $value = 1) Add centuries (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealCentury() Add one century to the instance (using timestamp). + * @method CarbonImmutable subRealCenturies(int $value = 1) Sub centuries (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealCentury() Sub one century to the instance (using timestamp). + * @method CarbonPeriod centuriesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each century or every X centuries if a factor is given. + * @method CarbonImmutable addRealMillennia(int $value = 1) Add millennia (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealMillennium() Add one millennium to the instance (using timestamp). + * @method CarbonImmutable subRealMillennia(int $value = 1) Sub millennia (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealMillennium() Sub one millennium to the instance (using timestamp). + * @method CarbonPeriod millenniaUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millennium or every X millennia if a factor is given. + * @method CarbonImmutable roundYear(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. + * @method CarbonImmutable roundYears(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. + * @method CarbonImmutable floorYear(float $precision = 1) Truncate the current instance year with given precision. + * @method CarbonImmutable floorYears(float $precision = 1) Truncate the current instance year with given precision. + * @method CarbonImmutable ceilYear(float $precision = 1) Ceil the current instance year with given precision. + * @method CarbonImmutable ceilYears(float $precision = 1) Ceil the current instance year with given precision. + * @method CarbonImmutable roundMonth(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. + * @method CarbonImmutable roundMonths(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. + * @method CarbonImmutable floorMonth(float $precision = 1) Truncate the current instance month with given precision. + * @method CarbonImmutable floorMonths(float $precision = 1) Truncate the current instance month with given precision. + * @method CarbonImmutable ceilMonth(float $precision = 1) Ceil the current instance month with given precision. + * @method CarbonImmutable ceilMonths(float $precision = 1) Ceil the current instance month with given precision. + * @method CarbonImmutable roundDay(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method CarbonImmutable roundDays(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method CarbonImmutable floorDay(float $precision = 1) Truncate the current instance day with given precision. + * @method CarbonImmutable floorDays(float $precision = 1) Truncate the current instance day with given precision. + * @method CarbonImmutable ceilDay(float $precision = 1) Ceil the current instance day with given precision. + * @method CarbonImmutable ceilDays(float $precision = 1) Ceil the current instance day with given precision. + * @method CarbonImmutable roundHour(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. + * @method CarbonImmutable roundHours(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. + * @method CarbonImmutable floorHour(float $precision = 1) Truncate the current instance hour with given precision. + * @method CarbonImmutable floorHours(float $precision = 1) Truncate the current instance hour with given precision. + * @method CarbonImmutable ceilHour(float $precision = 1) Ceil the current instance hour with given precision. + * @method CarbonImmutable ceilHours(float $precision = 1) Ceil the current instance hour with given precision. + * @method CarbonImmutable roundMinute(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. + * @method CarbonImmutable roundMinutes(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. + * @method CarbonImmutable floorMinute(float $precision = 1) Truncate the current instance minute with given precision. + * @method CarbonImmutable floorMinutes(float $precision = 1) Truncate the current instance minute with given precision. + * @method CarbonImmutable ceilMinute(float $precision = 1) Ceil the current instance minute with given precision. + * @method CarbonImmutable ceilMinutes(float $precision = 1) Ceil the current instance minute with given precision. + * @method CarbonImmutable roundSecond(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. + * @method CarbonImmutable roundSeconds(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. + * @method CarbonImmutable floorSecond(float $precision = 1) Truncate the current instance second with given precision. + * @method CarbonImmutable floorSeconds(float $precision = 1) Truncate the current instance second with given precision. + * @method CarbonImmutable ceilSecond(float $precision = 1) Ceil the current instance second with given precision. + * @method CarbonImmutable ceilSeconds(float $precision = 1) Ceil the current instance second with given precision. + * @method CarbonImmutable roundMillennium(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. + * @method CarbonImmutable roundMillennia(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. + * @method CarbonImmutable floorMillennium(float $precision = 1) Truncate the current instance millennium with given precision. + * @method CarbonImmutable floorMillennia(float $precision = 1) Truncate the current instance millennium with given precision. + * @method CarbonImmutable ceilMillennium(float $precision = 1) Ceil the current instance millennium with given precision. + * @method CarbonImmutable ceilMillennia(float $precision = 1) Ceil the current instance millennium with given precision. + * @method CarbonImmutable roundCentury(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. + * @method CarbonImmutable roundCenturies(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. + * @method CarbonImmutable floorCentury(float $precision = 1) Truncate the current instance century with given precision. + * @method CarbonImmutable floorCenturies(float $precision = 1) Truncate the current instance century with given precision. + * @method CarbonImmutable ceilCentury(float $precision = 1) Ceil the current instance century with given precision. + * @method CarbonImmutable ceilCenturies(float $precision = 1) Ceil the current instance century with given precision. + * @method CarbonImmutable roundDecade(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. + * @method CarbonImmutable roundDecades(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. + * @method CarbonImmutable floorDecade(float $precision = 1) Truncate the current instance decade with given precision. + * @method CarbonImmutable floorDecades(float $precision = 1) Truncate the current instance decade with given precision. + * @method CarbonImmutable ceilDecade(float $precision = 1) Ceil the current instance decade with given precision. + * @method CarbonImmutable ceilDecades(float $precision = 1) Ceil the current instance decade with given precision. + * @method CarbonImmutable roundQuarter(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. + * @method CarbonImmutable roundQuarters(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. + * @method CarbonImmutable floorQuarter(float $precision = 1) Truncate the current instance quarter with given precision. + * @method CarbonImmutable floorQuarters(float $precision = 1) Truncate the current instance quarter with given precision. + * @method CarbonImmutable ceilQuarter(float $precision = 1) Ceil the current instance quarter with given precision. + * @method CarbonImmutable ceilQuarters(float $precision = 1) Ceil the current instance quarter with given precision. + * @method CarbonImmutable roundMillisecond(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. + * @method CarbonImmutable roundMilliseconds(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. + * @method CarbonImmutable floorMillisecond(float $precision = 1) Truncate the current instance millisecond with given precision. + * @method CarbonImmutable floorMilliseconds(float $precision = 1) Truncate the current instance millisecond with given precision. + * @method CarbonImmutable ceilMillisecond(float $precision = 1) Ceil the current instance millisecond with given precision. + * @method CarbonImmutable ceilMilliseconds(float $precision = 1) Ceil the current instance millisecond with given precision. + * @method CarbonImmutable roundMicrosecond(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. + * @method CarbonImmutable roundMicroseconds(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. + * @method CarbonImmutable floorMicrosecond(float $precision = 1) Truncate the current instance microsecond with given precision. + * @method CarbonImmutable floorMicroseconds(float $precision = 1) Truncate the current instance microsecond with given precision. + * @method CarbonImmutable ceilMicrosecond(float $precision = 1) Ceil the current instance microsecond with given precision. + * @method CarbonImmutable ceilMicroseconds(float $precision = 1) Ceil the current instance microsecond with given precision. + * @method string shortAbsoluteDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'Absolute' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longAbsoluteDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'Absolute' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'Relative' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'Relative' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeToNowDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'RelativeToNow' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeToNowDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'RelativeToNow' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeToOtherDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'RelativeToOther' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeToOtherDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'RelativeToOther' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method static static|false createFromFormat(string $format, string $time, DateTimeZone|string|false|null $timezone = null) Parse a string into a new CarbonImmutable object according to the specified format. + * @method static static __set_state(array $array) https://php.net/manual/en/datetime.set-state.php + * + * + */ +class CarbonImmutable extends DateTimeImmutable implements CarbonInterface +{ + use Date { + __clone as dateTraitClone; + } + + public function __clone() + { + $this->dateTraitClone(); + $this->endOfTime = false; + $this->startOfTime = false; + } + + /** + * Create a very old date representing start of time. + * + * @return static + */ + public static function startOfTime(): self + { + $date = static::parse('0001-01-01')->years(self::getStartOfTimeYear()); + $date->startOfTime = true; + + return $date; + } + + /** + * Create a very far date representing end of time. + * + * @return static + */ + public static function endOfTime(): self + { + $date = static::parse('9999-12-31 23:59:59.999999')->years(self::getEndOfTimeYear()); + $date->endOfTime = true; + + return $date; + } + + /** + * @codeCoverageIgnore + */ + private static function getEndOfTimeYear(): int + { + if (version_compare(PHP_VERSION, '7.3.0-dev', '<')) { + return 145261681241552; + } + + // Remove if https://bugs.php.net/bug.php?id=81107 is fixed + if (version_compare(PHP_VERSION, '8.1.0-dev', '>=')) { + return 1118290769066902787; + } + + return PHP_INT_MAX; + } + + /** + * @codeCoverageIgnore + */ + private static function getStartOfTimeYear(): int + { + if (version_compare(PHP_VERSION, '7.3.0-dev', '<')) { + return -135908816449551; + } + + // Remove if https://bugs.php.net/bug.php?id=81107 is fixed + if (version_compare(PHP_VERSION, '8.1.0-dev', '>=')) { + return -1118290769066898816; + } + + return max(PHP_INT_MIN, -9223372036854773760); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/CarbonInterface.php b/vendor/nesbot/carbon/src/Carbon/CarbonInterface.php new file mode 100644 index 0000000..d63c4b8 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/CarbonInterface.php @@ -0,0 +1,5143 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +use BadMethodCallException; +use Carbon\Exceptions\BadComparisonUnitException; +use Carbon\Exceptions\ImmutableException; +use Carbon\Exceptions\InvalidDateException; +use Carbon\Exceptions\InvalidFormatException; +use Carbon\Exceptions\UnknownGetterException; +use Carbon\Exceptions\UnknownMethodException; +use Carbon\Exceptions\UnknownSetterException; +use Closure; +use DateInterval; +use DateTime; +use DateTimeImmutable; +use DateTimeInterface; +use DateTimeZone; +use JsonSerializable; +use ReflectionException; +use ReturnTypeWillChange; +use Symfony\Component\Translation\TranslatorInterface; +use Throwable; + +/** + * Common interface for Carbon and CarbonImmutable. + * + * + * + * @property int $year + * @property int $yearIso + * @property int $month + * @property int $day + * @property int $hour + * @property int $minute + * @property int $second + * @property int $micro + * @property int $microsecond + * @property int|float|string $timestamp seconds since the Unix Epoch + * @property string $englishDayOfWeek the day of week in English + * @property string $shortEnglishDayOfWeek the abbreviated day of week in English + * @property string $englishMonth the month in English + * @property string $shortEnglishMonth the abbreviated month in English + * @property int $milliseconds + * @property int $millisecond + * @property int $milli + * @property int $week 1 through 53 + * @property int $isoWeek 1 through 53 + * @property int $weekYear year according to week format + * @property int $isoWeekYear year according to ISO week format + * @property int $dayOfYear 1 through 366 + * @property int $age does a diffInYears() with default parameters + * @property int $offset the timezone offset in seconds from UTC + * @property int $offsetMinutes the timezone offset in minutes from UTC + * @property int $offsetHours the timezone offset in hours from UTC + * @property CarbonTimeZone $timezone the current timezone + * @property CarbonTimeZone $tz alias of $timezone + * @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 $weekOfYear ISO-8601 week number of year, weeks starting on Monday + * @property-read int $daysInMonth number of days in the given month + * @property-read string $latinMeridiem "am"/"pm" (Ante meridiem or Post meridiem latin lowercase mark) + * @property-read string $latinUpperMeridiem "AM"/"PM" (Ante meridiem or Post meridiem latin uppercase mark) + * @property-read string $timezoneAbbreviatedName the current timezone abbreviated name + * @property-read string $tzAbbrName alias of $timezoneAbbreviatedName + * @property-read string $dayName long name of weekday translated according to Carbon locale, in english if no translation available for current language + * @property-read string $shortDayName short name of weekday translated according to Carbon locale, in english if no translation available for current language + * @property-read string $minDayName very short name of weekday translated according to Carbon locale, in english if no translation available for current language + * @property-read string $monthName long name of month translated according to Carbon locale, in english if no translation available for current language + * @property-read string $shortMonthName short name of month translated according to Carbon locale, in english if no translation available for current language + * @property-read string $meridiem lowercase meridiem mark translated according to Carbon locale, in latin if no translation available for current language + * @property-read string $upperMeridiem uppercase meridiem mark translated according to Carbon locale, in latin if no translation available for current language + * @property-read int $noZeroHour current hour from 1 to 24 + * @property-read int $weeksInYear 51 through 53 + * @property-read int $isoWeeksInYear 51 through 53 + * @property-read int $weekOfMonth 1 through 5 + * @property-read int $weekNumberInMonth 1 through 5 + * @property-read int $firstWeekDay 0 through 6 + * @property-read int $lastWeekDay 0 through 6 + * @property-read int $daysInYear 365 or 366 + * @property-read int $quarter the quarter of this instance, 1 - 4 + * @property-read int $decade the decade of this instance + * @property-read int $century the century of this instance + * @property-read int $millennium the millennium of this instance + * @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 the current timezone name + * @property-read string $tzName alias of $timezoneName + * @property-read string $locale locale of the current instance + * + * @method bool isUtc() Check if the current instance has UTC timezone. (Both isUtc and isUTC cases are valid.) + * @method bool isLocal() Check if the current instance has non-UTC timezone. + * @method bool isValid() Check if the current instance is a valid date. + * @method bool isDST() Check if the current instance is in a daylight saving time. + * @method bool isSunday() Checks if the instance day is sunday. + * @method bool isMonday() Checks if the instance day is monday. + * @method bool isTuesday() Checks if the instance day is tuesday. + * @method bool isWednesday() Checks if the instance day is wednesday. + * @method bool isThursday() Checks if the instance day is thursday. + * @method bool isFriday() Checks if the instance day is friday. + * @method bool isSaturday() Checks if the instance day is saturday. + * @method bool isSameYear(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same year as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentYear() Checks if the instance is in the same year as the current moment. + * @method bool isNextYear() Checks if the instance is in the same year as the current moment next year. + * @method bool isLastYear() Checks if the instance is in the same year as the current moment last year. + * @method bool isSameWeek(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same week as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentWeek() Checks if the instance is in the same week as the current moment. + * @method bool isNextWeek() Checks if the instance is in the same week as the current moment next week. + * @method bool isLastWeek() Checks if the instance is in the same week as the current moment last week. + * @method bool isSameDay(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same day as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentDay() Checks if the instance is in the same day as the current moment. + * @method bool isNextDay() Checks if the instance is in the same day as the current moment next day. + * @method bool isLastDay() Checks if the instance is in the same day as the current moment last day. + * @method bool isSameHour(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same hour as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentHour() Checks if the instance is in the same hour as the current moment. + * @method bool isNextHour() Checks if the instance is in the same hour as the current moment next hour. + * @method bool isLastHour() Checks if the instance is in the same hour as the current moment last hour. + * @method bool isSameMinute(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same minute as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMinute() Checks if the instance is in the same minute as the current moment. + * @method bool isNextMinute() Checks if the instance is in the same minute as the current moment next minute. + * @method bool isLastMinute() Checks if the instance is in the same minute as the current moment last minute. + * @method bool isSameSecond(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same second as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentSecond() Checks if the instance is in the same second as the current moment. + * @method bool isNextSecond() Checks if the instance is in the same second as the current moment next second. + * @method bool isLastSecond() Checks if the instance is in the same second as the current moment last second. + * @method bool isSameMicro(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same microsecond as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMicro() Checks if the instance is in the same microsecond as the current moment. + * @method bool isNextMicro() Checks if the instance is in the same microsecond as the current moment next microsecond. + * @method bool isLastMicro() Checks if the instance is in the same microsecond as the current moment last microsecond. + * @method bool isSameMicrosecond(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same microsecond as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMicrosecond() Checks if the instance is in the same microsecond as the current moment. + * @method bool isNextMicrosecond() Checks if the instance is in the same microsecond as the current moment next microsecond. + * @method bool isLastMicrosecond() Checks if the instance is in the same microsecond as the current moment last microsecond. + * @method bool isCurrentMonth() Checks if the instance is in the same month as the current moment. + * @method bool isNextMonth() Checks if the instance is in the same month as the current moment next month. + * @method bool isLastMonth() Checks if the instance is in the same month as the current moment last month. + * @method bool isCurrentQuarter() Checks if the instance is in the same quarter as the current moment. + * @method bool isNextQuarter() Checks if the instance is in the same quarter as the current moment next quarter. + * @method bool isLastQuarter() Checks if the instance is in the same quarter as the current moment last quarter. + * @method bool isSameDecade(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same decade as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentDecade() Checks if the instance is in the same decade as the current moment. + * @method bool isNextDecade() Checks if the instance is in the same decade as the current moment next decade. + * @method bool isLastDecade() Checks if the instance is in the same decade as the current moment last decade. + * @method bool isSameCentury(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same century as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentCentury() Checks if the instance is in the same century as the current moment. + * @method bool isNextCentury() Checks if the instance is in the same century as the current moment next century. + * @method bool isLastCentury() Checks if the instance is in the same century as the current moment last century. + * @method bool isSameMillennium(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same millennium as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMillennium() Checks if the instance is in the same millennium as the current moment. + * @method bool isNextMillennium() Checks if the instance is in the same millennium as the current moment next millennium. + * @method bool isLastMillennium() Checks if the instance is in the same millennium as the current moment last millennium. + * @method CarbonInterface years(int $value) Set current instance year to the given value. + * @method CarbonInterface year(int $value) Set current instance year to the given value. + * @method CarbonInterface setYears(int $value) Set current instance year to the given value. + * @method CarbonInterface setYear(int $value) Set current instance year to the given value. + * @method CarbonInterface months(int $value) Set current instance month to the given value. + * @method CarbonInterface month(int $value) Set current instance month to the given value. + * @method CarbonInterface setMonths(int $value) Set current instance month to the given value. + * @method CarbonInterface setMonth(int $value) Set current instance month to the given value. + * @method CarbonInterface days(int $value) Set current instance day to the given value. + * @method CarbonInterface day(int $value) Set current instance day to the given value. + * @method CarbonInterface setDays(int $value) Set current instance day to the given value. + * @method CarbonInterface setDay(int $value) Set current instance day to the given value. + * @method CarbonInterface hours(int $value) Set current instance hour to the given value. + * @method CarbonInterface hour(int $value) Set current instance hour to the given value. + * @method CarbonInterface setHours(int $value) Set current instance hour to the given value. + * @method CarbonInterface setHour(int $value) Set current instance hour to the given value. + * @method CarbonInterface minutes(int $value) Set current instance minute to the given value. + * @method CarbonInterface minute(int $value) Set current instance minute to the given value. + * @method CarbonInterface setMinutes(int $value) Set current instance minute to the given value. + * @method CarbonInterface setMinute(int $value) Set current instance minute to the given value. + * @method CarbonInterface seconds(int $value) Set current instance second to the given value. + * @method CarbonInterface second(int $value) Set current instance second to the given value. + * @method CarbonInterface setSeconds(int $value) Set current instance second to the given value. + * @method CarbonInterface setSecond(int $value) Set current instance second to the given value. + * @method CarbonInterface millis(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface milli(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface setMillis(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface setMilli(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface milliseconds(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface millisecond(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface setMilliseconds(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface setMillisecond(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface micros(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface micro(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface setMicros(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface setMicro(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface microseconds(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface microsecond(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface setMicroseconds(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface setMicrosecond(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface addYears(int $value = 1) Add years (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addYear() Add one year to the instance (using date interval). + * @method CarbonInterface subYears(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subYear() Sub one year to the instance (using date interval). + * @method CarbonInterface addYearsWithOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addYearWithOverflow() Add one year to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subYearsWithOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subYearWithOverflow() Sub one year to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addYearsWithoutOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addYearWithoutOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearsWithoutOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearWithoutOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addYearsWithNoOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addYearWithNoOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearsWithNoOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearWithNoOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addYearsNoOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addYearNoOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearsNoOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearNoOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonths(int $value = 1) Add months (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMonth() Add one month to the instance (using date interval). + * @method CarbonInterface subMonths(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMonth() Sub one month to the instance (using date interval). + * @method CarbonInterface addMonthsWithOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addMonthWithOverflow() Add one month to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subMonthsWithOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subMonthWithOverflow() Sub one month to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addMonthsWithoutOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonthWithoutOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthsWithoutOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthWithoutOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonthsWithNoOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonthWithNoOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthsWithNoOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthWithNoOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonthsNoOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonthNoOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthsNoOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthNoOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDays(int $value = 1) Add days (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addDay() Add one day to the instance (using date interval). + * @method CarbonInterface subDays(int $value = 1) Sub days (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subDay() Sub one day to the instance (using date interval). + * @method CarbonInterface addHours(int $value = 1) Add hours (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addHour() Add one hour to the instance (using date interval). + * @method CarbonInterface subHours(int $value = 1) Sub hours (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subHour() Sub one hour to the instance (using date interval). + * @method CarbonInterface addMinutes(int $value = 1) Add minutes (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMinute() Add one minute to the instance (using date interval). + * @method CarbonInterface subMinutes(int $value = 1) Sub minutes (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMinute() Sub one minute to the instance (using date interval). + * @method CarbonInterface addSeconds(int $value = 1) Add seconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addSecond() Add one second to the instance (using date interval). + * @method CarbonInterface subSeconds(int $value = 1) Sub seconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subSecond() Sub one second to the instance (using date interval). + * @method CarbonInterface addMillis(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMilli() Add one millisecond to the instance (using date interval). + * @method CarbonInterface subMillis(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMilli() Sub one millisecond to the instance (using date interval). + * @method CarbonInterface addMilliseconds(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMillisecond() Add one millisecond to the instance (using date interval). + * @method CarbonInterface subMilliseconds(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMillisecond() Sub one millisecond to the instance (using date interval). + * @method CarbonInterface addMicros(int $value = 1) Add microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMicro() Add one microsecond to the instance (using date interval). + * @method CarbonInterface subMicros(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMicro() Sub one microsecond to the instance (using date interval). + * @method CarbonInterface addMicroseconds(int $value = 1) Add microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMicrosecond() Add one microsecond to the instance (using date interval). + * @method CarbonInterface subMicroseconds(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMicrosecond() Sub one microsecond to the instance (using date interval). + * @method CarbonInterface addMillennia(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMillennium() Add one millennium to the instance (using date interval). + * @method CarbonInterface subMillennia(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMillennium() Sub one millennium to the instance (using date interval). + * @method CarbonInterface addMillenniaWithOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addMillenniumWithOverflow() Add one millennium to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subMillenniaWithOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subMillenniumWithOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addMillenniaWithoutOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMillenniumWithoutOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniaWithoutOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniumWithoutOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMillenniaWithNoOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMillenniumWithNoOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniaWithNoOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniumWithNoOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMillenniaNoOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMillenniumNoOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniaNoOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniumNoOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturies(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addCentury() Add one century to the instance (using date interval). + * @method CarbonInterface subCenturies(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subCentury() Sub one century to the instance (using date interval). + * @method CarbonInterface addCenturiesWithOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addCenturyWithOverflow() Add one century to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subCenturiesWithOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subCenturyWithOverflow() Sub one century to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addCenturiesWithoutOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturyWithoutOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturiesWithoutOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturyWithoutOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturiesWithNoOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturyWithNoOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturiesWithNoOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturyWithNoOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturiesNoOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturyNoOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturiesNoOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturyNoOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecades(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addDecade() Add one decade to the instance (using date interval). + * @method CarbonInterface subDecades(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subDecade() Sub one decade to the instance (using date interval). + * @method CarbonInterface addDecadesWithOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addDecadeWithOverflow() Add one decade to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subDecadesWithOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subDecadeWithOverflow() Sub one decade to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addDecadesWithoutOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecadeWithoutOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadesWithoutOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadeWithoutOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecadesWithNoOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecadeWithNoOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadesWithNoOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadeWithNoOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecadesNoOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecadeNoOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadesNoOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadeNoOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuarters(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addQuarter() Add one quarter to the instance (using date interval). + * @method CarbonInterface subQuarters(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subQuarter() Sub one quarter to the instance (using date interval). + * @method CarbonInterface addQuartersWithOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addQuarterWithOverflow() Add one quarter to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subQuartersWithOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subQuarterWithOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addQuartersWithoutOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuarterWithoutOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuartersWithoutOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuarterWithoutOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuartersWithNoOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuarterWithNoOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuartersWithNoOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuarterWithNoOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuartersNoOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuarterNoOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuartersNoOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuarterNoOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addWeeks(int $value = 1) Add weeks (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addWeek() Add one week to the instance (using date interval). + * @method CarbonInterface subWeeks(int $value = 1) Sub weeks (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subWeek() Sub one week to the instance (using date interval). + * @method CarbonInterface addWeekdays(int $value = 1) Add weekdays (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addWeekday() Add one weekday to the instance (using date interval). + * @method CarbonInterface subWeekdays(int $value = 1) Sub weekdays (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subWeekday() Sub one weekday to the instance (using date interval). + * @method CarbonInterface addRealMicros(int $value = 1) Add microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealMicro() Add one microsecond to the instance (using timestamp). + * @method CarbonInterface subRealMicros(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealMicro() Sub one microsecond to the instance (using timestamp). + * @method CarbonPeriod microsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each microsecond or every X microseconds if a factor is given. + * @method CarbonInterface addRealMicroseconds(int $value = 1) Add microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealMicrosecond() Add one microsecond to the instance (using timestamp). + * @method CarbonInterface subRealMicroseconds(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealMicrosecond() Sub one microsecond to the instance (using timestamp). + * @method CarbonPeriod microsecondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each microsecond or every X microseconds if a factor is given. + * @method CarbonInterface addRealMillis(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealMilli() Add one millisecond to the instance (using timestamp). + * @method CarbonInterface subRealMillis(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealMilli() Sub one millisecond to the instance (using timestamp). + * @method CarbonPeriod millisUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millisecond or every X milliseconds if a factor is given. + * @method CarbonInterface addRealMilliseconds(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealMillisecond() Add one millisecond to the instance (using timestamp). + * @method CarbonInterface subRealMilliseconds(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealMillisecond() Sub one millisecond to the instance (using timestamp). + * @method CarbonPeriod millisecondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millisecond or every X milliseconds if a factor is given. + * @method CarbonInterface addRealSeconds(int $value = 1) Add seconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealSecond() Add one second to the instance (using timestamp). + * @method CarbonInterface subRealSeconds(int $value = 1) Sub seconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealSecond() Sub one second to the instance (using timestamp). + * @method CarbonPeriod secondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each second or every X seconds if a factor is given. + * @method CarbonInterface addRealMinutes(int $value = 1) Add minutes (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealMinute() Add one minute to the instance (using timestamp). + * @method CarbonInterface subRealMinutes(int $value = 1) Sub minutes (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealMinute() Sub one minute to the instance (using timestamp). + * @method CarbonPeriod minutesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each minute or every X minutes if a factor is given. + * @method CarbonInterface addRealHours(int $value = 1) Add hours (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealHour() Add one hour to the instance (using timestamp). + * @method CarbonInterface subRealHours(int $value = 1) Sub hours (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealHour() Sub one hour to the instance (using timestamp). + * @method CarbonPeriod hoursUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each hour or every X hours if a factor is given. + * @method CarbonInterface addRealDays(int $value = 1) Add days (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealDay() Add one day to the instance (using timestamp). + * @method CarbonInterface subRealDays(int $value = 1) Sub days (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealDay() Sub one day to the instance (using timestamp). + * @method CarbonPeriod daysUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each day or every X days if a factor is given. + * @method CarbonInterface addRealWeeks(int $value = 1) Add weeks (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealWeek() Add one week to the instance (using timestamp). + * @method CarbonInterface subRealWeeks(int $value = 1) Sub weeks (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealWeek() Sub one week to the instance (using timestamp). + * @method CarbonPeriod weeksUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each week or every X weeks if a factor is given. + * @method CarbonInterface addRealMonths(int $value = 1) Add months (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealMonth() Add one month to the instance (using timestamp). + * @method CarbonInterface subRealMonths(int $value = 1) Sub months (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealMonth() Sub one month to the instance (using timestamp). + * @method CarbonPeriod monthsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each month or every X months if a factor is given. + * @method CarbonInterface addRealQuarters(int $value = 1) Add quarters (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealQuarter() Add one quarter to the instance (using timestamp). + * @method CarbonInterface subRealQuarters(int $value = 1) Sub quarters (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealQuarter() Sub one quarter to the instance (using timestamp). + * @method CarbonPeriod quartersUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each quarter or every X quarters if a factor is given. + * @method CarbonInterface addRealYears(int $value = 1) Add years (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealYear() Add one year to the instance (using timestamp). + * @method CarbonInterface subRealYears(int $value = 1) Sub years (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealYear() Sub one year to the instance (using timestamp). + * @method CarbonPeriod yearsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each year or every X years if a factor is given. + * @method CarbonInterface addRealDecades(int $value = 1) Add decades (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealDecade() Add one decade to the instance (using timestamp). + * @method CarbonInterface subRealDecades(int $value = 1) Sub decades (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealDecade() Sub one decade to the instance (using timestamp). + * @method CarbonPeriod decadesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each decade or every X decades if a factor is given. + * @method CarbonInterface addRealCenturies(int $value = 1) Add centuries (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealCentury() Add one century to the instance (using timestamp). + * @method CarbonInterface subRealCenturies(int $value = 1) Sub centuries (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealCentury() Sub one century to the instance (using timestamp). + * @method CarbonPeriod centuriesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each century or every X centuries if a factor is given. + * @method CarbonInterface addRealMillennia(int $value = 1) Add millennia (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealMillennium() Add one millennium to the instance (using timestamp). + * @method CarbonInterface subRealMillennia(int $value = 1) Sub millennia (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealMillennium() Sub one millennium to the instance (using timestamp). + * @method CarbonPeriod millenniaUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millennium or every X millennia if a factor is given. + * @method CarbonInterface roundYear(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. + * @method CarbonInterface roundYears(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. + * @method CarbonInterface floorYear(float $precision = 1) Truncate the current instance year with given precision. + * @method CarbonInterface floorYears(float $precision = 1) Truncate the current instance year with given precision. + * @method CarbonInterface ceilYear(float $precision = 1) Ceil the current instance year with given precision. + * @method CarbonInterface ceilYears(float $precision = 1) Ceil the current instance year with given precision. + * @method CarbonInterface roundMonth(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. + * @method CarbonInterface roundMonths(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. + * @method CarbonInterface floorMonth(float $precision = 1) Truncate the current instance month with given precision. + * @method CarbonInterface floorMonths(float $precision = 1) Truncate the current instance month with given precision. + * @method CarbonInterface ceilMonth(float $precision = 1) Ceil the current instance month with given precision. + * @method CarbonInterface ceilMonths(float $precision = 1) Ceil the current instance month with given precision. + * @method CarbonInterface roundDay(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method CarbonInterface roundDays(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method CarbonInterface floorDay(float $precision = 1) Truncate the current instance day with given precision. + * @method CarbonInterface floorDays(float $precision = 1) Truncate the current instance day with given precision. + * @method CarbonInterface ceilDay(float $precision = 1) Ceil the current instance day with given precision. + * @method CarbonInterface ceilDays(float $precision = 1) Ceil the current instance day with given precision. + * @method CarbonInterface roundHour(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. + * @method CarbonInterface roundHours(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. + * @method CarbonInterface floorHour(float $precision = 1) Truncate the current instance hour with given precision. + * @method CarbonInterface floorHours(float $precision = 1) Truncate the current instance hour with given precision. + * @method CarbonInterface ceilHour(float $precision = 1) Ceil the current instance hour with given precision. + * @method CarbonInterface ceilHours(float $precision = 1) Ceil the current instance hour with given precision. + * @method CarbonInterface roundMinute(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. + * @method CarbonInterface roundMinutes(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. + * @method CarbonInterface floorMinute(float $precision = 1) Truncate the current instance minute with given precision. + * @method CarbonInterface floorMinutes(float $precision = 1) Truncate the current instance minute with given precision. + * @method CarbonInterface ceilMinute(float $precision = 1) Ceil the current instance minute with given precision. + * @method CarbonInterface ceilMinutes(float $precision = 1) Ceil the current instance minute with given precision. + * @method CarbonInterface roundSecond(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. + * @method CarbonInterface roundSeconds(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. + * @method CarbonInterface floorSecond(float $precision = 1) Truncate the current instance second with given precision. + * @method CarbonInterface floorSeconds(float $precision = 1) Truncate the current instance second with given precision. + * @method CarbonInterface ceilSecond(float $precision = 1) Ceil the current instance second with given precision. + * @method CarbonInterface ceilSeconds(float $precision = 1) Ceil the current instance second with given precision. + * @method CarbonInterface roundMillennium(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. + * @method CarbonInterface roundMillennia(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. + * @method CarbonInterface floorMillennium(float $precision = 1) Truncate the current instance millennium with given precision. + * @method CarbonInterface floorMillennia(float $precision = 1) Truncate the current instance millennium with given precision. + * @method CarbonInterface ceilMillennium(float $precision = 1) Ceil the current instance millennium with given precision. + * @method CarbonInterface ceilMillennia(float $precision = 1) Ceil the current instance millennium with given precision. + * @method CarbonInterface roundCentury(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. + * @method CarbonInterface roundCenturies(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. + * @method CarbonInterface floorCentury(float $precision = 1) Truncate the current instance century with given precision. + * @method CarbonInterface floorCenturies(float $precision = 1) Truncate the current instance century with given precision. + * @method CarbonInterface ceilCentury(float $precision = 1) Ceil the current instance century with given precision. + * @method CarbonInterface ceilCenturies(float $precision = 1) Ceil the current instance century with given precision. + * @method CarbonInterface roundDecade(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. + * @method CarbonInterface roundDecades(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. + * @method CarbonInterface floorDecade(float $precision = 1) Truncate the current instance decade with given precision. + * @method CarbonInterface floorDecades(float $precision = 1) Truncate the current instance decade with given precision. + * @method CarbonInterface ceilDecade(float $precision = 1) Ceil the current instance decade with given precision. + * @method CarbonInterface ceilDecades(float $precision = 1) Ceil the current instance decade with given precision. + * @method CarbonInterface roundQuarter(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. + * @method CarbonInterface roundQuarters(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. + * @method CarbonInterface floorQuarter(float $precision = 1) Truncate the current instance quarter with given precision. + * @method CarbonInterface floorQuarters(float $precision = 1) Truncate the current instance quarter with given precision. + * @method CarbonInterface ceilQuarter(float $precision = 1) Ceil the current instance quarter with given precision. + * @method CarbonInterface ceilQuarters(float $precision = 1) Ceil the current instance quarter with given precision. + * @method CarbonInterface roundMillisecond(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. + * @method CarbonInterface roundMilliseconds(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. + * @method CarbonInterface floorMillisecond(float $precision = 1) Truncate the current instance millisecond with given precision. + * @method CarbonInterface floorMilliseconds(float $precision = 1) Truncate the current instance millisecond with given precision. + * @method CarbonInterface ceilMillisecond(float $precision = 1) Ceil the current instance millisecond with given precision. + * @method CarbonInterface ceilMilliseconds(float $precision = 1) Ceil the current instance millisecond with given precision. + * @method CarbonInterface roundMicrosecond(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. + * @method CarbonInterface roundMicroseconds(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. + * @method CarbonInterface floorMicrosecond(float $precision = 1) Truncate the current instance microsecond with given precision. + * @method CarbonInterface floorMicroseconds(float $precision = 1) Truncate the current instance microsecond with given precision. + * @method CarbonInterface ceilMicrosecond(float $precision = 1) Ceil the current instance microsecond with given precision. + * @method CarbonInterface ceilMicroseconds(float $precision = 1) Ceil the current instance microsecond with given precision. + * @method string shortAbsoluteDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'Absolute' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longAbsoluteDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'Absolute' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'Relative' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'Relative' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeToNowDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'RelativeToNow' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeToNowDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'RelativeToNow' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeToOtherDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'RelativeToOther' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeToOtherDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'RelativeToOther' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * + * + */ +interface CarbonInterface extends DateTimeInterface, JsonSerializable +{ + /** + * Diff wording options(expressed in octal). + */ + public const NO_ZERO_DIFF = 01; + public const JUST_NOW = 02; + public const ONE_DAY_WORDS = 04; + public const TWO_DAY_WORDS = 010; + public const SEQUENTIAL_PARTS_ONLY = 020; + public const ROUND = 040; + public const FLOOR = 0100; + public const CEIL = 0200; + + /** + * Diff syntax options. + */ + public const DIFF_ABSOLUTE = 1; // backward compatibility with true + public const DIFF_RELATIVE_AUTO = 0; // backward compatibility with false + public const DIFF_RELATIVE_TO_NOW = 2; + public const DIFF_RELATIVE_TO_OTHER = 3; + + /** + * Translate string options. + */ + public const TRANSLATE_MONTHS = 1; + public const TRANSLATE_DAYS = 2; + public const TRANSLATE_UNITS = 4; + public const TRANSLATE_MERIDIEM = 8; + public const TRANSLATE_DIFF = 0x10; + public const TRANSLATE_ALL = self::TRANSLATE_MONTHS | self::TRANSLATE_DAYS | self::TRANSLATE_UNITS | self::TRANSLATE_MERIDIEM | self::TRANSLATE_DIFF; + + /** + * The day constants. + */ + public const SUNDAY = 0; + public const MONDAY = 1; + public const TUESDAY = 2; + public const WEDNESDAY = 3; + public const THURSDAY = 4; + public const FRIDAY = 5; + public const SATURDAY = 6; + + /** + * The month constants. + * These aren't used by Carbon itself but exist for + * convenience sake alone. + */ + public const JANUARY = 1; + public const FEBRUARY = 2; + public const MARCH = 3; + public const APRIL = 4; + public const MAY = 5; + public const JUNE = 6; + public const JULY = 7; + public const AUGUST = 8; + public const SEPTEMBER = 9; + public const OCTOBER = 10; + public const NOVEMBER = 11; + public const DECEMBER = 12; + + /** + * Number of X in Y. + */ + public const YEARS_PER_MILLENNIUM = 1000; + public const YEARS_PER_CENTURY = 100; + public const YEARS_PER_DECADE = 10; + public const MONTHS_PER_YEAR = 12; + public const MONTHS_PER_QUARTER = 3; + public const QUARTERS_PER_YEAR = 4; + public const WEEKS_PER_YEAR = 52; + public const WEEKS_PER_MONTH = 4; + public const DAYS_PER_YEAR = 365; + public const DAYS_PER_WEEK = 7; + public const HOURS_PER_DAY = 24; + public const MINUTES_PER_HOUR = 60; + public const SECONDS_PER_MINUTE = 60; + public const MILLISECONDS_PER_SECOND = 1000; + public const MICROSECONDS_PER_MILLISECOND = 1000; + public const MICROSECONDS_PER_SECOND = 1000000; + + /** + * Special settings to get the start of week from current locale culture. + */ + public const WEEK_DAY_AUTO = 'auto'; + + /** + * RFC7231 DateTime format. + * + * @var string + */ + public 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 + */ + public const DEFAULT_TO_STRING_FORMAT = 'Y-m-d H:i:s'; + + /** + * Format for converting mocked time, includes microseconds. + * + * @var string + */ + public const MOCK_DATETIME_FORMAT = 'Y-m-d H:i:s.u'; + + /** + * Pattern detection for ->isoFormat and ::createFromIsoFormat. + * + * @var string + */ + public const ISO_FORMAT_REGEXP = '(O[YMDHhms]|[Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY?|g{1,5}|G{1,5}|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?)'; + + // + + /** + * Dynamically handle calls to the class. + * + * @param string $method magic method name called + * @param array $parameters parameters list + * + * @throws UnknownMethodException|BadMethodCallException|ReflectionException|Throwable + * + * @return mixed + */ + public function __call($method, $parameters); + + /** + * Dynamically handle calls to the class. + * + * @param string $method magic method name called + * @param array $parameters parameters list + * + * @throws BadMethodCallException + * + * @return mixed + */ + public static function __callStatic($method, $parameters); + + /** + * Update constructedObjectId on cloned. + */ + public function __clone(); + + /** + * 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 DateTimeInterface|string|null $time + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + */ + public function __construct($time = null, $tz = null); + + /** + * Show truthy properties on var_dump(). + * + * @return array + */ + public function __debugInfo(); + + /** + * Get a part of the Carbon object + * + * @param string $name + * + * @throws UnknownGetterException + * + * @return string|int|bool|DateTimeZone|null + */ + public function __get($name); + + /** + * Check if an attribute exists on the object + * + * @param string $name + * + * @return bool + */ + public function __isset($name); + + /** + * Set a part of the Carbon object + * + * @param string $name + * @param string|int|DateTimeZone $value + * + * @throws UnknownSetterException|ReflectionException + * + * @return void + */ + public function __set($name, $value); + + /** + * The __set_state handler. + * + * @param string|array $dump + * + * @return static + */ + #[ReturnTypeWillChange] + public static function __set_state($dump); + + /** + * Returns the list of properties to dump on serialize() called on. + * + * Only used by PHP < 7.4. + * + * @return array + */ + public function __sleep(); + + /** + * Format the instance as a string using the set format + * + * @example + * ``` + * echo Carbon::now(); // Carbon instances can be cast to string + * ``` + * + * @return string + */ + public function __toString(); + + /** + * Add given units or interval to the current instance. + * + * @example $date->add('hour', 3) + * @example $date->add(15, 'days') + * @example $date->add(CarbonInterval::days(4)) + * + * @param string|DateInterval|Closure|CarbonConverterInterface $unit + * @param int $value + * @param bool|null $overflow + * + * @return static + */ + #[ReturnTypeWillChange] + public function add($unit, $value = 1, $overflow = null); + + /** + * Add seconds to the instance using timestamp. Positive $value travels + * forward while negative $value travels into the past. + * + * @param string $unit + * @param int $value + * + * @return static + */ + public function addRealUnit($unit, $value = 1); + + /** + * Add given units to the current instance. + * + * @param string $unit + * @param int $value + * @param bool|null $overflow + * + * @return static + */ + public function addUnit($unit, $value = 1, $overflow = null); + + /** + * Add any unit to a new value without overflowing current other unit given. + * + * @param string $valueUnit unit name to modify + * @param int $value amount to add to the input unit + * @param string $overflowUnit unit name to not overflow + * + * @return static + */ + public function addUnitNoOverflow($valueUnit, $value, $overflowUnit); + + /** + * Get the difference in a human readable format in the current locale from an other + * instance given to now + * + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single part) + * @param int $options human diff options + * + * @return string + */ + public function ago($syntax = null, $short = false, $parts = 1, $options = null); + + /** + * Modify the current instance to the average of a given instance (default now) and the current instance + * (second-precision). + * + * @param \Carbon\Carbon|\DateTimeInterface|null $date + * + * @return static + */ + public function average($date = null); + + /** + * Clone the current instance if it's mutable. + * + * This method is convenient to ensure you don't mutate the initial object + * but avoid to make a useless copy of it if it's already immutable. + * + * @return static + */ + public function avoidMutation(); + + /** + * Determines if the instance is between two others. + * + * The third argument allow you to specify if bounds are included or not (true by default) + * but for when you including/excluding bounds may produce different results in your application, + * we recommend to use the explicit methods ->betweenIncluded() or ->betweenExcluded() instead. + * + * @example + * ``` + * Carbon::parse('2018-07-25')->between('2018-07-14', '2018-08-01'); // true + * Carbon::parse('2018-07-25')->between('2018-08-01', '2018-08-20'); // false + * Carbon::parse('2018-07-25')->between('2018-07-25', '2018-08-01'); // true + * Carbon::parse('2018-07-25')->between('2018-07-25', '2018-08-01', false); // false + * ``` + * + * @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): bool; + + /** + * Determines if the instance is between two others, bounds excluded. + * + * @example + * ``` + * Carbon::parse('2018-07-25')->betweenExcluded('2018-07-14', '2018-08-01'); // true + * Carbon::parse('2018-07-25')->betweenExcluded('2018-08-01', '2018-08-20'); // false + * Carbon::parse('2018-07-25')->betweenExcluded('2018-07-25', '2018-08-01'); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date1 + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date2 + * + * @return bool + */ + public function betweenExcluded($date1, $date2): bool; + + /** + * Determines if the instance is between two others, bounds included. + * + * @example + * ``` + * Carbon::parse('2018-07-25')->betweenIncluded('2018-07-14', '2018-08-01'); // true + * Carbon::parse('2018-07-25')->betweenIncluded('2018-08-01', '2018-08-20'); // false + * Carbon::parse('2018-07-25')->betweenIncluded('2018-07-25', '2018-08-01'); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date1 + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date2 + * + * @return bool + */ + public function betweenIncluded($date1, $date2): bool; + + /** + * Returns either day of week + time (e.g. "Last Friday at 3:30 PM") if reference time is within 7 days, + * or a calendar date (e.g. "10/29/2017") otherwise. + * + * Language, date and time formats will change according to the current locale. + * + * @param Carbon|\DateTimeInterface|string|null $referenceTime + * @param array $formats + * + * @return string + */ + public function calendar($referenceTime = null, array $formats = []); + + /** + * Checks if the (date)time string is in a given format and valid to create a + * new instance. + * + * @example + * ``` + * Carbon::canBeCreatedFromFormat('11:12:45', 'h:i:s'); // true + * Carbon::canBeCreatedFromFormat('13:12:45', 'h:i:s'); // false + * ``` + * + * @param string $date + * @param string $format + * + * @return bool + */ + public static function canBeCreatedFromFormat($date, $format); + + /** + * 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\CarbonPeriod|\Carbon\CarbonInterval|\DateInterval|\DatePeriod|DateTimeInterface|string|null $date + * + * @return static + */ + public function carbonize($date = null); + + /** + * Cast the current instance into the given class. + * + * @param string $className The $className::instance() method will be called to cast the current object. + * + * @return DateTimeInterface + */ + public function cast(string $className); + + /** + * Ceil the current instance second with given precision if specified. + * + * @param float|int|string|\DateInterval|null $precision + * + * @return CarbonInterface + */ + public function ceil($precision = 1); + + /** + * Ceil the current instance at the given unit with given precision if specified. + * + * @param string $unit + * @param float|int $precision + * + * @return CarbonInterface + */ + public function ceilUnit($unit, $precision = 1); + + /** + * Ceil the current instance week. + * + * @param int $weekStartsAt optional start allow you to specify the day of week to use to start the week + * + * @return CarbonInterface + */ + public function ceilWeek($weekStartsAt = null); + + /** + * Similar to native modify() method of DateTime but can handle more grammars. + * + * @example + * ``` + * echo Carbon::now()->change('next 2pm'); + * ``` + * + * @link https://php.net/manual/en/datetime.modify.php + * + * @param string $modifier + * + * @return static|false + */ + public function change($modifier); + + /** + * Cleanup properties attached to the public scope of DateTime when a dump of the date is requested. + * foreach ($date as $_) {} + * serializer($date) + * var_export($date) + * get_object_vars($date) + */ + public function cleanupDumpProperties(); + + /** + * @alias copy + * + * Get a copy of the instance. + * + * @return static + */ + public function clone(); + + /** + * Get the closest date from the instance (second-precision). + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date1 + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date2 + * + * @return static + */ + public function closest($date1, $date2); + + /** + * Get a copy of the instance. + * + * @return static + */ + public function copy(); + + /** + * 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 DateTimeInterface|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 InvalidFormatException + * + * @return static|false + */ + public static function create($year = 0, $month = 1, $day = 1, $hour = 0, $minute = 0, $second = 0, $tz = null); + + /** + * 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 InvalidFormatException + * + * @return static + */ + public static function createFromDate($year = null, $month = null, $day = null, $tz = null); + + /** + * Create a Carbon instance from a specific format. + * + * @param string $format Datetime format + * @param string $time + * @param DateTimeZone|string|false|null $tz + * + * @throws InvalidFormatException + * + * @return static|false + */ + #[ReturnTypeWillChange] + public static function createFromFormat($format, $time, $tz = null); + + /** + * Create a Carbon instance from a specific ISO format (same replacements as ->isoFormat()). + * + * @param string $format Datetime format + * @param string $time + * @param DateTimeZone|string|false|null $tz optional timezone + * @param string|null $locale locale to be used for LTS, LT, LL, LLL, etc. macro-formats (en by fault, unneeded if no such macro-format in use) + * @param \Symfony\Component\Translation\TranslatorInterface $translator optional custom translator to use for macro-formats + * + * @throws InvalidFormatException + * + * @return static|false + */ + public static function createFromIsoFormat($format, $time, $tz = null, $locale = 'en', $translator = null); + + /** + * Create a Carbon instance from a specific format and a string in a given language. + * + * @param string $format Datetime format + * @param string $locale + * @param string $time + * @param DateTimeZone|string|false|null $tz + * + * @throws InvalidFormatException + * + * @return static|false + */ + public static function createFromLocaleFormat($format, $locale, $time, $tz = null); + + /** + * Create a Carbon instance from a specific ISO format and a string in a given language. + * + * @param string $format Datetime ISO format + * @param string $locale + * @param string $time + * @param DateTimeZone|string|false|null $tz + * + * @throws InvalidFormatException + * + * @return static|false + */ + public static function createFromLocaleIsoFormat($format, $locale, $time, $tz = null); + + /** + * 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 InvalidFormatException + * + * @return static + */ + public static function createFromTime($hour = 0, $minute = 0, $second = 0, $tz = null); + + /** + * Create a Carbon instance from a time string. The date portion is set to today. + * + * @param string $time + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + * + * @return static + */ + public static function createFromTimeString($time, $tz = null); + + /** + * Create a Carbon instance from a timestamp and set the timezone (use default one if not specified). + * + * Timestamp input can be given as int, float or a string containing one or more numbers. + * + * @param float|int|string $timestamp + * @param \DateTimeZone|string|null $tz + * + * @return static + */ + public static function createFromTimestamp($timestamp, $tz = null); + + /** + * Create a Carbon instance from a timestamp in milliseconds. + * + * Timestamp input can be given as int, float or a string containing one or more numbers. + * + * @param float|int|string $timestamp + * @param \DateTimeZone|string|null $tz + * + * @return static + */ + public static function createFromTimestampMs($timestamp, $tz = null); + + /** + * Create a Carbon instance from a timestamp in milliseconds. + * + * Timestamp input can be given as int, float or a string containing one or more numbers. + * + * @param float|int|string $timestamp + * + * @return static + */ + public static function createFromTimestampMsUTC($timestamp); + + /** + * Create a Carbon instance from an timestamp keeping the timezone to UTC. + * + * Timestamp input can be given as int, float or a string containing one or more numbers. + * + * @param float|int|string $timestamp + * + * @return static + */ + public static function createFromTimestampUTC($timestamp); + + /** + * 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 + * + * @throws InvalidFormatException + * + * @return static + */ + public static function createMidnightDate($year = null, $month = null, $day = null, $tz = null); + + /** + * 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 InvalidDateException + * 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 InvalidDateException + * + * @return static|false + */ + public static function createSafe($year = null, $month = null, $day = null, $hour = null, $minute = null, $second = null, $tz = null); + + /** + * Create a new Carbon instance from a specific date and time using strict validation. + * + * @see create() + * + * @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 InvalidFormatException + * + * @return static + */ + public static function createStrict(?int $year = 0, ?int $month = 1, ?int $day = 1, ?int $hour = 0, ?int $minute = 0, ?int $second = 0, $tz = null); + + /** + * Get/set the day of year. + * + * @param int|null $value new value for day of year if using as setter. + * + * @return static|int + */ + public function dayOfYear($value = null); + + /** + * Get the difference as a CarbonInterval instance. + * Return relative interval (negative if $absolute flag is not set to true and the given date is before + * current one). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return CarbonInterval + */ + public function diffAsCarbonInterval($date = null, $absolute = true, array $skip = []); + + /** + * Get the difference by the given interval using a filter closure. + * + * @param CarbonInterval $ci An interval to traverse by + * @param Closure $callback + * @param \Carbon\CarbonInterface|\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); + + /** + * Get the difference in a human readable format in the current locale from current instance to an other + * instance given (or now if null given). + * + * @example + * ``` + * echo Carbon::tomorrow()->diffForHumans() . "\n"; + * echo Carbon::tomorrow()->diffForHumans(['parts' => 2]) . "\n"; + * echo Carbon::tomorrow()->diffForHumans(['parts' => 3, 'join' => true]) . "\n"; + * echo Carbon::tomorrow()->diffForHumans(Carbon::yesterday()) . "\n"; + * echo Carbon::tomorrow()->diffForHumans(Carbon::yesterday(), ['short' => true]) . "\n"; + * ``` + * + * @param Carbon|\DateTimeInterface|string|array|null $other if array passed, will be used as parameters array, see $syntax below; + * if null passed, now will be used as comparison reference; + * if any other type, it will be converted to date and used as reference. + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'skip' entry, list of units to skip (array of strings or a single string, + * ` it can be the unit name (singular or plural) or its shortcut + * ` (y, m, w, d, h, min, s, ms, µs). + * - 'aUnit' entry, prefer "an hour" over "1 hour" if true + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * - 'other' entry (see above) + * - 'minimumUnit' entry determines the smallest unit of time to display can be long or + * ` short form of the units, e.g. 'hour' or 'h' (default value: s) + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single unit) + * @param int $options human diff options + * + * @return string + */ + public function diffForHumans($other = null, $syntax = null, $short = false, $parts = 1, $options = null); + + /** + * Get the difference in days rounded down. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInDays($date = null, $absolute = true); + + /** + * Get the difference in days using a filter closure rounded down. + * + * @param Closure $callback + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInDaysFiltered(Closure $callback, $date = null, $absolute = true); + + /** + * Get the difference in hours rounded down. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInHours($date = null, $absolute = true); + + /** + * Get the difference in hours using a filter closure rounded down. + * + * @param Closure $callback + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInHoursFiltered(Closure $callback, $date = null, $absolute = true); + + /** + * Get the difference in microseconds. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInMicroseconds($date = null, $absolute = true); + + /** + * Get the difference in milliseconds rounded down. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInMilliseconds($date = null, $absolute = true); + + /** + * Get the difference in minutes rounded down. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInMinutes($date = null, $absolute = true); + + /** + * Get the difference in months rounded down. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInMonths($date = null, $absolute = true); + + /** + * Get the difference in quarters rounded down. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInQuarters($date = null, $absolute = true); + + /** + * Get the difference in hours rounded down using timestamps. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInRealHours($date = null, $absolute = true); + + /** + * Get the difference in microseconds using timestamps. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInRealMicroseconds($date = null, $absolute = true); + + /** + * Get the difference in milliseconds rounded down using timestamps. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInRealMilliseconds($date = null, $absolute = true); + + /** + * Get the difference in minutes rounded down using timestamps. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInRealMinutes($date = null, $absolute = true); + + /** + * Get the difference in seconds using timestamps. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInRealSeconds($date = null, $absolute = true); + + /** + * Get the difference in seconds rounded down. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInSeconds($date = null, $absolute = true); + + /** + * Get the difference in weekdays rounded down. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInWeekdays($date = null, $absolute = true); + + /** + * Get the difference in weekend days using a filter rounded down. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInWeekendDays($date = null, $absolute = true); + + /** + * Get the difference in weeks rounded down. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInWeeks($date = null, $absolute = true); + + /** + * Get the difference in years + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInYears($date = null, $absolute = true); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @see settings + * + * @param int $humanDiffOption + */ + public static function disableHumanDiffOption($humanDiffOption); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @see settings + * + * @param int $humanDiffOption + */ + public static function enableHumanDiffOption($humanDiffOption); + + /** + * Modify to end of current given unit. + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16.334455') + * ->startOf('month') + * ->endOf('week', Carbon::FRIDAY); + * ``` + * + * @param string $unit + * @param array $params + * + * @return static + */ + public function endOf($unit, ...$params); + + /** + * Resets the date to end of the century and time to 23:59:59.999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfCentury(); + * ``` + * + * @return static + */ + public function endOfCentury(); + + /** + * Resets the time to 23:59:59.999999 end of day + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfDay(); + * ``` + * + * @return static + */ + public function endOfDay(); + + /** + * Resets the date to end of the decade and time to 23:59:59.999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfDecade(); + * ``` + * + * @return static + */ + public function endOfDecade(); + + /** + * Modify to end of current hour, minutes and seconds become 59 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfHour(); + * ``` + * + * @return static + */ + public function endOfHour(); + + /** + * Resets the date to end of the millennium and time to 23:59:59.999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfMillennium(); + * ``` + * + * @return static + */ + public function endOfMillennium(); + + /** + * Modify to end of current minute, seconds become 59 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfMinute(); + * ``` + * + * @return static + */ + public function endOfMinute(); + + /** + * Resets the date to end of the month and time to 23:59:59.999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfMonth(); + * ``` + * + * @return static + */ + public function endOfMonth(); + + /** + * Resets the date to end of the quarter and time to 23:59:59.999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfQuarter(); + * ``` + * + * @return static + */ + public function endOfQuarter(); + + /** + * Modify to end of current second, microseconds become 999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16.334455') + * ->endOfSecond() + * ->format('H:i:s.u'); + * ``` + * + * @return static + */ + public function endOfSecond(); + + /** + * Resets the date to end of week (defined in $weekEndsAt) and time to 23:59:59.999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfWeek() . "\n"; + * echo Carbon::parse('2018-07-25 12:45:16')->locale('ar')->endOfWeek() . "\n"; + * echo Carbon::parse('2018-07-25 12:45:16')->endOfWeek(Carbon::SATURDAY) . "\n"; + * ``` + * + * @param int $weekEndsAt optional start allow you to specify the day of week to use to end the week + * + * @return static + */ + public function endOfWeek($weekEndsAt = null); + + /** + * Resets the date to end of the year and time to 23:59:59.999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfYear(); + * ``` + * + * @return static + */ + public function endOfYear(); + + /** + * Determines if the instance is equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->eq('2018-07-25 12:45:16'); // true + * Carbon::parse('2018-07-25 12:45:16')->eq(Carbon::parse('2018-07-25 12:45:16')); // true + * Carbon::parse('2018-07-25 12:45:16')->eq('2018-07-25 12:45:17'); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see equalTo() + * + * @return bool + */ + public function eq($date): bool; + + /** + * Determines if the instance is equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->equalTo('2018-07-25 12:45:16'); // true + * Carbon::parse('2018-07-25 12:45:16')->equalTo(Carbon::parse('2018-07-25 12:45:16')); // true + * Carbon::parse('2018-07-25 12:45:16')->equalTo('2018-07-25 12:45:17'); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function equalTo($date): bool; + + /** + * 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 + * @param callable $func + * + * @return mixed + */ + public static function executeWithLocale($locale, $func); + + /** + * Get the farthest date from the instance (second-precision). + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date1 + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date2 + * + * @return static + */ + public function farthest($date1, $date2); + + /** + * 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); + + /** + * 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); + + /** + * 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); + + /** + * Get the difference in days as float (microsecond-precision). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInDays($date = null, $absolute = true); + + /** + * Get the difference in hours as float (microsecond-precision). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInHours($date = null, $absolute = true); + + /** + * Get the difference in minutes as float (microsecond-precision). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInMinutes($date = null, $absolute = true); + + /** + * Get the difference in months as float (microsecond-precision). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInMonths($date = null, $absolute = true); + + /** + * Get the difference in days as float (microsecond-precision). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInRealDays($date = null, $absolute = true); + + /** + * Get the difference in hours as float (microsecond-precision) using timestamps. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInRealHours($date = null, $absolute = true); + + /** + * Get the difference in minutes as float (microsecond-precision) using timestamps. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInRealMinutes($date = null, $absolute = true); + + /** + * Get the difference in months as float (microsecond-precision) using timestamps. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInRealMonths($date = null, $absolute = true); + + /** + * Get the difference in seconds as float (microsecond-precision) using timestamps. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInRealSeconds($date = null, $absolute = true); + + /** + * Get the difference in weeks as float (microsecond-precision). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInRealWeeks($date = null, $absolute = true); + + /** + * Get the difference in year as float (microsecond-precision) using timestamps. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInRealYears($date = null, $absolute = true); + + /** + * Get the difference in seconds as float (microsecond-precision). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInSeconds($date = null, $absolute = true); + + /** + * Get the difference in weeks as float (microsecond-precision). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInWeeks($date = null, $absolute = true); + + /** + * Get the difference in year as float (microsecond-precision). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInYears($date = null, $absolute = true); + + /** + * Round the current instance second with given precision if specified. + * + * @param float|int|string|\DateInterval|null $precision + * + * @return CarbonInterface + */ + public function floor($precision = 1); + + /** + * Truncate the current instance at the given unit with given precision if specified. + * + * @param string $unit + * @param float|int $precision + * + * @return CarbonInterface + */ + public function floorUnit($unit, $precision = 1); + + /** + * Truncate the current instance week. + * + * @param int $weekStartsAt optional start allow you to specify the day of week to use to start the week + * + * @return CarbonInterface + */ + public function floorWeek($weekStartsAt = null); + + /** + * Format the instance with the current locale. You can set the current + * locale using setlocale() https://php.net/setlocale. + * + * @deprecated It uses OS language package and strftime() which is deprecated since PHP 8.1. + * Use ->isoFormat() instead. + * Deprecated since 2.55.0 + * + * @param string $format + * + * @return string + */ + public function formatLocalized($format); + + /** + * @alias diffForHumans + * + * Get the difference in a human readable format in the current locale from current instance to an other + * instance given (or now if null given). + * + * @param Carbon|\DateTimeInterface|string|array|null $other if array passed, will be used as parameters array, see $syntax below; + * if null passed, now will be used as comparison reference; + * if any other type, it will be converted to date and used as reference. + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * - 'other' entry (see above) + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single unit) + * @param int $options human diff options + * + * @return string + */ + public function from($other = null, $syntax = null, $short = false, $parts = 1, $options = null); + + /** + * Get the difference in a human readable format in the current locale from current + * instance to now. + * + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single unit) + * @param int $options human diff options + * + * @return string + */ + public function fromNow($syntax = null, $short = false, $parts = 1, $options = null); + + /** + * Create an instance from a serialized string. + * + * @param string $value + * + * @throws InvalidFormatException + * + * @return static + */ + public static function fromSerialized($value); + + /** + * Register a custom macro. + * + * @param object|callable $macro + * @param int $priority marco with higher priority is tried first + * + * @return void + */ + public static function genericMacro($macro, $priority = 0); + + /** + * Get a part of the Carbon object + * + * @param string $name + * + * @throws UnknownGetterException + * + * @return string|int|bool|DateTimeZone|null + */ + public function get($name); + + /** + * Returns the alternative number for a given date property if available in the current locale. + * + * @param string $key date property + * + * @return string + */ + public function getAltNumber(string $key): string; + + /** + * 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(); + + /** + * Returns list of Language object for each available locale. This object allow you to get the ISO name, native + * name, region and variant of the locale. + * + * @return Language[] + */ + public static function getAvailableLocalesInfo(); + + /** + * Returns list of calendar formats for ISO formatting. + * + * @param string|null $locale current locale used if null + * + * @return array + */ + public function getCalendarFormats($locale = null); + + /** + * Get the days of the week + * + * @return array + */ + public static function getDays(); + + /** + * Return the number of days since the start of the week (using the current locale or the first parameter + * if explicitly given). + * + * @param int|null $weekStartsAt optional start allow you to specify the day of week to use to start the week, + * if not provided, start of week is inferred from the locale + * (Sunday for en_US, Monday for de_DE, etc.) + * + * @return int + */ + public function getDaysFromStartOfWeek(?int $weekStartsAt = null): int; + + /** + * Get the fallback locale. + * + * @see https://symfony.com/doc/current/components/translation.html#fallback-locales + * + * @return string|null + */ + public static function getFallbackLocale(); + + /** + * List of replacements from date() format to isoFormat(). + * + * @return array + */ + public static function getFormatsToIsoReplacements(); + + /** + * Return default humanDiff() options (merged flags as integer). + * + * @return int + */ + public static function getHumanDiffOptions(); + + /** + * Returns list of locale formats for ISO formatting. + * + * @param string|null $locale current locale used if null + * + * @return array + */ + public function getIsoFormats($locale = null); + + /** + * Returns list of locale units for ISO formatting. + * + * @return array + */ + public static function getIsoUnits(); + + /** + * {@inheritdoc} + * + * @return array + */ + #[ReturnTypeWillChange] + public static function getLastErrors(); + + /** + * Get the raw callable macro registered globally or locally for a given name. + * + * @param string $name + * + * @return callable|null + */ + public function getLocalMacro($name); + + /** + * Get the translator of the current instance or the default if none set. + * + * @return \Symfony\Component\Translation\TranslatorInterface + */ + public function getLocalTranslator(); + + /** + * Get the current translator locale. + * + * @return string + */ + public static function getLocale(); + + /** + * Get the raw callable macro registered globally for a given name. + * + * @param string $name + * + * @return callable|null + */ + public static function getMacro($name); + + /** + * get midday/noon hour + * + * @return int + */ + public static function getMidDayAt(); + + /** + * Returns the offset hour and minute formatted with +/- and a given separator (":" by default). + * For example, if the time zone is 9 hours 30 minutes, you'll get "+09:30", with "@@" as first + * argument, "+09@@30", with "" as first argument, "+0930". Negative offset will return something + * like "-12:00". + * + * @param string $separator string to place between hours and minutes (":" by default) + * + * @return string + */ + public function getOffsetString($separator = ':'); + + /** + * Returns a unit of the instance padded with 0 by default or any other string if specified. + * + * @param string $unit Carbon unit name + * @param int $length Length of the output (2 by default) + * @param string $padString String to use for padding ("0" by default) + * @param int $padType Side(s) to pad (STR_PAD_LEFT by default) + * + * @return string + */ + public function getPaddedUnit($unit, $length = 2, $padString = '0', $padType = 0); + + /** + * Returns a timestamp rounded with the given precision (6 by default). + * + * @example getPreciseTimestamp() 1532087464437474 (microsecond maximum precision) + * @example getPreciseTimestamp(6) 1532087464437474 + * @example getPreciseTimestamp(5) 153208746443747 (1/100000 second precision) + * @example getPreciseTimestamp(4) 15320874644375 (1/10000 second precision) + * @example getPreciseTimestamp(3) 1532087464437 (millisecond precision) + * @example getPreciseTimestamp(2) 153208746444 (1/100 second precision) + * @example getPreciseTimestamp(1) 15320874644 (1/10 second precision) + * @example getPreciseTimestamp(0) 1532087464 (second precision) + * @example getPreciseTimestamp(-1) 153208746 (10 second precision) + * @example getPreciseTimestamp(-2) 15320875 (100 second precision) + * + * @param int $precision + * + * @return float + */ + public function getPreciseTimestamp($precision = 6); + + /** + * Returns current local settings. + * + * @return array + */ + public function getSettings(); + + /** + * Get the Carbon instance (real or mock) to be returned when a "now" + * instance is created. + * + * @return Closure|static the current instance used for testing + */ + public static function getTestNow(); + + /** + * Return a format from H:i to H:i:s.u according to given unit precision. + * + * @param string $unitPrecision "minute", "second", "millisecond" or "microsecond" + * + * @return string + */ + public static function getTimeFormatByPrecision($unitPrecision); + + /** + * Returns the timestamp with millisecond precision. + * + * @return int + */ + public function getTimestampMs(); + + /** + * Get the translation of the current week day name (with context for languages with multiple forms). + * + * @param string|null $context whole format string + * @param string $keySuffix "", "_short" or "_min" + * @param string|null $defaultValue default value if translation missing + * + * @return string + */ + public function getTranslatedDayName($context = null, $keySuffix = '', $defaultValue = null); + + /** + * Get the translation of the current abbreviated week day name (with context for languages with multiple forms). + * + * @param string|null $context whole format string + * + * @return string + */ + public function getTranslatedMinDayName($context = null); + + /** + * Get the translation of the current month day name (with context for languages with multiple forms). + * + * @param string|null $context whole format string + * @param string $keySuffix "" or "_short" + * @param string|null $defaultValue default value if translation missing + * + * @return string + */ + public function getTranslatedMonthName($context = null, $keySuffix = '', $defaultValue = null); + + /** + * Get the translation of the current short week day name (with context for languages with multiple forms). + * + * @param string|null $context whole format string + * + * @return string + */ + public function getTranslatedShortDayName($context = null); + + /** + * Get the translation of the current short month day name (with context for languages with multiple forms). + * + * @param string|null $context whole format string + * + * @return string + */ + public function getTranslatedShortMonthName($context = null); + + /** + * Returns raw translation message for a given key. + * + * @param string $key key to find + * @param string|null $locale current locale used if null + * @param string|null $default default value if translation returns the key + * @param \Symfony\Component\Translation\TranslatorInterface $translator an optional translator to use + * + * @return string + */ + public function getTranslationMessage(string $key, ?string $locale = null, ?string $default = null, $translator = null); + + /** + * Returns raw translation message for a given key. + * + * @param \Symfony\Component\Translation\TranslatorInterface $translator the translator to use + * @param string $key key to find + * @param string|null $locale current locale used if null + * @param string|null $default default value if translation returns the key + * + * @return string + */ + public static function getTranslationMessageWith($translator, string $key, ?string $locale = null, ?string $default = null); + + /** + * Get the default translator instance in use. + * + * @return \Symfony\Component\Translation\TranslatorInterface + */ + public static function getTranslator(); + + /** + * Get the last day of week + * + * @return int + */ + public static function getWeekEndsAt(); + + /** + * Get the first day of week + * + * @return int + */ + public static function getWeekStartsAt(); + + /** + * Get weekend days + * + * @return array + */ + public static function getWeekendDays(); + + /** + * Determines if the instance is greater (after) than another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->greaterThan('2018-07-25 12:45:15'); // true + * Carbon::parse('2018-07-25 12:45:16')->greaterThan('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->greaterThan('2018-07-25 12:45:17'); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function greaterThan($date): bool; + + /** + * Determines if the instance is greater (after) than or equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->greaterThanOrEqualTo('2018-07-25 12:45:15'); // true + * Carbon::parse('2018-07-25 12:45:16')->greaterThanOrEqualTo('2018-07-25 12:45:16'); // true + * Carbon::parse('2018-07-25 12:45:16')->greaterThanOrEqualTo('2018-07-25 12:45:17'); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function greaterThanOrEqualTo($date): bool; + + /** + * Determines if the instance is greater (after) than another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->gt('2018-07-25 12:45:15'); // true + * Carbon::parse('2018-07-25 12:45:16')->gt('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->gt('2018-07-25 12:45:17'); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see greaterThan() + * + * @return bool + */ + public function gt($date): bool; + + /** + * Determines if the instance is greater (after) than or equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->gte('2018-07-25 12:45:15'); // true + * Carbon::parse('2018-07-25 12:45:16')->gte('2018-07-25 12:45:16'); // true + * Carbon::parse('2018-07-25 12:45:16')->gte('2018-07-25 12:45:17'); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see greaterThanOrEqualTo() + * + * @return bool + */ + public function gte($date): bool; + + /** + * Checks if the (date)time string is in a given format. + * + * @example + * ``` + * Carbon::hasFormat('11:12:45', 'h:i:s'); // true + * Carbon::hasFormat('13:12:45', 'h:i:s'); // false + * ``` + * + * @param string $date + * @param string $format + * + * @return bool + */ + public static function hasFormat($date, $format); + + /** + * Checks if the (date)time string is in a given format. + * + * @example + * ``` + * Carbon::hasFormatWithModifiers('31/08/2015', 'd#m#Y'); // true + * Carbon::hasFormatWithModifiers('31/08/2015', 'm#d#Y'); // false + * ``` + * + * @param string $date + * @param string $format + * + * @return bool + */ + public static function hasFormatWithModifiers($date, $format): bool; + + /** + * Checks if macro is registered globally or locally. + * + * @param string $name + * + * @return bool + */ + public function hasLocalMacro($name); + + /** + * Return true if the current instance has its own translator. + * + * @return bool + */ + public function hasLocalTranslator(); + + /** + * Checks if macro is registered globally. + * + * @param string $name + * + * @return bool + */ + public static function hasMacro($name); + + /** + * 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); + + /** + * 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(); + + /** + * Create a Carbon instance from a DateTime one. + * + * @param DateTimeInterface $date + * + * @return static + */ + public static function instance($date); + + /** + * Returns true if the current date matches the given string. + * + * @example + * ``` + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('2019')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('2018')); // false + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('2019-06')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('06-02')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('2019-06-02')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('Sunday')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('June')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('12:23')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('12:23:45')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('12:23:00')); // false + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('12h')); // true + * var_dump(Carbon::parse('2019-06-02 15:23:45')->is('3pm')); // true + * var_dump(Carbon::parse('2019-06-02 15:23:45')->is('3am')); // false + * ``` + * + * @param string $tester day name, month name, hour, date, etc. as string + * + * @return bool + */ + public function is(string $tester); + + /** + * Determines if the instance is greater (after) than another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->isAfter('2018-07-25 12:45:15'); // true + * Carbon::parse('2018-07-25 12:45:16')->isAfter('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->isAfter('2018-07-25 12:45:17'); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see greaterThan() + * + * @return bool + */ + public function isAfter($date): bool; + + /** + * Determines if the instance is less (before) than another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->isBefore('2018-07-25 12:45:15'); // false + * Carbon::parse('2018-07-25 12:45:16')->isBefore('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->isBefore('2018-07-25 12:45:17'); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see lessThan() + * + * @return bool + */ + public function isBefore($date): bool; + + /** + * Determines if the instance is between two others + * + * @example + * ``` + * Carbon::parse('2018-07-25')->isBetween('2018-07-14', '2018-08-01'); // true + * Carbon::parse('2018-07-25')->isBetween('2018-08-01', '2018-08-20'); // false + * Carbon::parse('2018-07-25')->isBetween('2018-07-25', '2018-08-01'); // true + * Carbon::parse('2018-07-25')->isBetween('2018-07-25', '2018-08-01', false); // false + * ``` + * + * @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 isBetween($date1, $date2, $equal = true): bool; + + /** + * Check if its the birthday. Compares the date/month values of the two dates. + * + * @example + * ``` + * Carbon::now()->subYears(5)->isBirthday(); // true + * Carbon::now()->subYears(5)->subDay()->isBirthday(); // false + * Carbon::parse('2019-06-05')->isBirthday(Carbon::parse('2001-06-05')); // true + * Carbon::parse('2019-06-05')->isBirthday(Carbon::parse('2001-06-06')); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|null $date The instance to compare with or null to use current day. + * + * @return bool + */ + public function isBirthday($date = null); + + /** + * Determines if the instance is in the current unit given. + * + * @example + * ``` + * Carbon::now()->isCurrentUnit('hour'); // true + * Carbon::now()->subHours(2)->isCurrentUnit('hour'); // false + * ``` + * + * @param string $unit The unit to test. + * + * @throws BadMethodCallException + * + * @return bool + */ + public function isCurrentUnit($unit); + + /** + * Checks if this day is a specific day of the week. + * + * @example + * ``` + * Carbon::parse('2019-07-17')->isDayOfWeek(Carbon::WEDNESDAY); // true + * Carbon::parse('2019-07-17')->isDayOfWeek(Carbon::FRIDAY); // false + * Carbon::parse('2019-07-17')->isDayOfWeek('Wednesday'); // true + * Carbon::parse('2019-07-17')->isDayOfWeek('Friday'); // false + * ``` + * + * @param int $dayOfWeek + * + * @return bool + */ + public function isDayOfWeek($dayOfWeek); + + /** + * Check if the instance is end of day. + * + * @example + * ``` + * Carbon::parse('2019-02-28 23:59:59.999999')->isEndOfDay(); // true + * Carbon::parse('2019-02-28 23:59:59.123456')->isEndOfDay(); // true + * Carbon::parse('2019-02-28 23:59:59')->isEndOfDay(); // true + * Carbon::parse('2019-02-28 23:59:58.999999')->isEndOfDay(); // false + * Carbon::parse('2019-02-28 23:59:59.999999')->isEndOfDay(true); // true + * Carbon::parse('2019-02-28 23:59:59.123456')->isEndOfDay(true); // false + * Carbon::parse('2019-02-28 23:59:59')->isEndOfDay(true); // false + * ``` + * + * @param bool $checkMicroseconds check time at microseconds precision + * + * @return bool + */ + public function isEndOfDay($checkMicroseconds = false); + + /** + * Returns true if the date was created using CarbonImmutable::endOfTime() + * + * @return bool + */ + public function isEndOfTime(): bool; + + /** + * Determines if the instance is in the future, ie. greater (after) than now. + * + * @example + * ``` + * Carbon::now()->addHours(5)->isFuture(); // true + * Carbon::now()->subHours(5)->isFuture(); // false + * ``` + * + * @return bool + */ + public function isFuture(); + + /** + * Returns true if the current class/instance is immutable. + * + * @return bool + */ + public static function isImmutable(); + + /** + * Check if today is the last day of the Month + * + * @example + * ``` + * Carbon::parse('2019-02-28')->isLastOfMonth(); // true + * Carbon::parse('2019-03-28')->isLastOfMonth(); // false + * Carbon::parse('2019-03-30')->isLastOfMonth(); // false + * Carbon::parse('2019-03-31')->isLastOfMonth(); // true + * Carbon::parse('2019-04-30')->isLastOfMonth(); // true + * ``` + * + * @return bool + */ + public function isLastOfMonth(); + + /** + * Determines if the instance is a leap year. + * + * @example + * ``` + * Carbon::parse('2020-01-01')->isLeapYear(); // true + * Carbon::parse('2019-01-01')->isLeapYear(); // false + * ``` + * + * @return bool + */ + public function isLeapYear(); + + /** + * Determines if the instance is a long year (using ISO 8601 year). + * + * @example + * ``` + * Carbon::parse('2015-01-01')->isLongIsoYear(); // true + * Carbon::parse('2016-01-01')->isLongIsoYear(); // true + * Carbon::parse('2016-01-03')->isLongIsoYear(); // false + * Carbon::parse('2019-12-29')->isLongIsoYear(); // false + * Carbon::parse('2019-12-30')->isLongIsoYear(); // true + * ``` + * + * @see https://en.wikipedia.org/wiki/ISO_8601#Week_dates + * + * @return bool + */ + public function isLongIsoYear(); + + /** + * Determines if the instance is a long year (using calendar year). + * + * ⚠️ This method completely ignores month and day to use the numeric year number, + * it's not correct if the exact date matters. For instance as `2019-12-30` is already + * in the first week of the 2020 year, if you want to know from this date if ISO week + * year 2020 is a long year, use `isLongIsoYear` instead. + * + * @example + * ``` + * Carbon::create(2015)->isLongYear(); // true + * Carbon::create(2016)->isLongYear(); // false + * ``` + * + * @see https://en.wikipedia.org/wiki/ISO_8601#Week_dates + * + * @return bool + */ + public function isLongYear(); + + /** + * Check if the instance is midday. + * + * @example + * ``` + * Carbon::parse('2019-02-28 11:59:59.999999')->isMidday(); // false + * Carbon::parse('2019-02-28 12:00:00')->isMidday(); // true + * Carbon::parse('2019-02-28 12:00:00.999999')->isMidday(); // true + * Carbon::parse('2019-02-28 12:00:01')->isMidday(); // false + * ``` + * + * @return bool + */ + public function isMidday(); + + /** + * Check if the instance is start of day / midnight. + * + * @example + * ``` + * Carbon::parse('2019-02-28 00:00:00')->isMidnight(); // true + * Carbon::parse('2019-02-28 00:00:00.999999')->isMidnight(); // true + * Carbon::parse('2019-02-28 00:00:01')->isMidnight(); // false + * ``` + * + * @return bool + */ + public function isMidnight(); + + /** + * Returns true if a property can be changed via setter. + * + * @param string $unit + * + * @return bool + */ + public static function isModifiableUnit($unit); + + /** + * Returns true if the current class/instance is mutable. + * + * @return bool + */ + public static function isMutable(); + + /** + * Determines if the instance is in the past, ie. less (before) than now. + * + * @example + * ``` + * Carbon::now()->subHours(5)->isPast(); // true + * Carbon::now()->addHours(5)->isPast(); // false + * ``` + * + * @return bool + */ + public function isPast(); + + /** + * Compares the formatted values of the two dates. + * + * @example + * ``` + * Carbon::parse('2019-06-13')->isSameAs('Y-d', Carbon::parse('2019-12-13')); // true + * Carbon::parse('2019-06-13')->isSameAs('Y-d', Carbon::parse('2019-06-14')); // false + * ``` + * + * @param string $format date formats to compare. + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date instance to compare with or null to use current day. + * + * @return bool + */ + public function isSameAs($format, $date = null); + + /** + * Checks if the passed in date is in the same month as the instance´s month. + * + * @example + * ``` + * Carbon::parse('2019-01-12')->isSameMonth(Carbon::parse('2019-01-01')); // true + * Carbon::parse('2019-01-12')->isSameMonth(Carbon::parse('2019-02-01')); // false + * Carbon::parse('2019-01-12')->isSameMonth(Carbon::parse('2018-01-01')); // false + * Carbon::parse('2019-01-12')->isSameMonth(Carbon::parse('2018-01-01'), false); // true + * ``` + * + * @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 = true); + + /** + * Checks if the passed in date is in the same quarter as the instance quarter (and year if needed). + * + * @example + * ``` + * Carbon::parse('2019-01-12')->isSameQuarter(Carbon::parse('2019-03-01')); // true + * Carbon::parse('2019-01-12')->isSameQuarter(Carbon::parse('2019-04-01')); // false + * Carbon::parse('2019-01-12')->isSameQuarter(Carbon::parse('2018-03-01')); // false + * Carbon::parse('2019-01-12')->isSameQuarter(Carbon::parse('2018-03-01'), false); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|string|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 = true); + + /** + * Determines if the instance is in the current unit given. + * + * @example + * ``` + * Carbon::parse('2019-01-13')->isSameUnit('year', Carbon::parse('2019-12-25')); // true + * Carbon::parse('2018-12-13')->isSameUnit('year', Carbon::parse('2019-12-25')); // false + * ``` + * + * @param string $unit singular unit string + * @param \Carbon\Carbon|\DateTimeInterface|null $date instance to compare with or null to use current day. + * + * @throws BadComparisonUnitException + * + * @return bool + */ + public function isSameUnit($unit, $date = null); + + /** + * Check if the instance is start of day / midnight. + * + * @example + * ``` + * Carbon::parse('2019-02-28 00:00:00')->isStartOfDay(); // true + * Carbon::parse('2019-02-28 00:00:00.999999')->isStartOfDay(); // true + * Carbon::parse('2019-02-28 00:00:01')->isStartOfDay(); // false + * Carbon::parse('2019-02-28 00:00:00.000000')->isStartOfDay(true); // true + * Carbon::parse('2019-02-28 00:00:00.000012')->isStartOfDay(true); // false + * ``` + * + * @param bool $checkMicroseconds check time at microseconds precision + * + * @return bool + */ + public function isStartOfDay($checkMicroseconds = false); + + /** + * Returns true if the date was created using CarbonImmutable::startOfTime() + * + * @return bool + */ + public function isStartOfTime(): bool; + + /** + * Returns true if the strict mode is globally in use, false else. + * (It can be overridden in specific instances.) + * + * @return bool + */ + public static function isStrictModeEnabled(); + + /** + * Determines if the instance is today. + * + * @example + * ``` + * Carbon::today()->isToday(); // true + * Carbon::tomorrow()->isToday(); // false + * ``` + * + * @return bool + */ + public function isToday(); + + /** + * Determines if the instance is tomorrow. + * + * @example + * ``` + * Carbon::tomorrow()->isTomorrow(); // true + * Carbon::yesterday()->isTomorrow(); // false + * ``` + * + * @return bool + */ + public function isTomorrow(); + + /** + * Determines if the instance is a weekday. + * + * @example + * ``` + * Carbon::parse('2019-07-14')->isWeekday(); // false + * Carbon::parse('2019-07-15')->isWeekday(); // true + * ``` + * + * @return bool + */ + public function isWeekday(); + + /** + * Determines if the instance is a weekend day. + * + * @example + * ``` + * Carbon::parse('2019-07-14')->isWeekend(); // true + * Carbon::parse('2019-07-15')->isWeekend(); // false + * ``` + * + * @return bool + */ + public function isWeekend(); + + /** + * Determines if the instance is yesterday. + * + * @example + * ``` + * Carbon::yesterday()->isYesterday(); // true + * Carbon::tomorrow()->isYesterday(); // false + * ``` + * + * @return bool + */ + public function isYesterday(); + + /** + * Format in the current language using ISO replacement patterns. + * + * @param string $format + * @param string|null $originalFormat provide context if a chunk has been passed alone + * + * @return string + */ + public function isoFormat(string $format, ?string $originalFormat = null): string; + + /** + * Get/set the week number using given first day of week and first + * day of year included in the first week. Or use ISO format if no settings + * given. + * + * @param int|null $week + * @param int|null $dayOfWeek + * @param int|null $dayOfYear + * + * @return int|static + */ + public function isoWeek($week = null, $dayOfWeek = null, $dayOfYear = null); + + /** + * Set/get the week number of year using given first day of week and first + * day of year included in the first week. Or use ISO format if no settings + * given. + * + * @param int|null $year if null, act as a getter, if not null, set the year and return current instance. + * @param int|null $dayOfWeek first date of week from 0 (Sunday) to 6 (Saturday) + * @param int|null $dayOfYear first day of year included in the week #1 + * + * @return int|static + */ + public function isoWeekYear($year = null, $dayOfWeek = null, $dayOfYear = null); + + /** + * Get/set the ISO weekday from 1 (Monday) to 7 (Sunday). + * + * @param int|null $value new value for weekday if using as setter. + * + * @return static|int + */ + public function isoWeekday($value = null); + + /** + * Get the number of weeks of the current week-year using given first day of week and first + * day of year included in the first week. Or use ISO format if no settings + * given. + * + * @param int|null $dayOfWeek first date of week from 0 (Sunday) to 6 (Saturday) + * @param int|null $dayOfYear first day of year included in the week #1 + * + * @return int + */ + public function isoWeeksInYear($dayOfWeek = null, $dayOfYear = null); + + /** + * Prepare the object for JSON serialization. + * + * @return array|string + */ + #[ReturnTypeWillChange] + public function jsonSerialize(); + + /** + * 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); + + /** + * 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); + + /** + * 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); + + /** + * Determines if the instance is less (before) than another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->lessThan('2018-07-25 12:45:15'); // false + * Carbon::parse('2018-07-25 12:45:16')->lessThan('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->lessThan('2018-07-25 12:45:17'); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function lessThan($date): bool; + + /** + * Determines if the instance is less (before) or equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->lessThanOrEqualTo('2018-07-25 12:45:15'); // false + * Carbon::parse('2018-07-25 12:45:16')->lessThanOrEqualTo('2018-07-25 12:45:16'); // true + * Carbon::parse('2018-07-25 12:45:16')->lessThanOrEqualTo('2018-07-25 12:45:17'); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function lessThanOrEqualTo($date): bool; + + /** + * Get/set the locale for the current instance. + * + * @param string|null $locale + * @param string ...$fallbackLocales + * + * @return $this|string + */ + public function locale(?string $locale = null, ...$fallbackLocales); + + /** + * 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); + + /** + * 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); + + /** + * 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); + + /** + * 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); + + /** + * 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); + + /** + * Determines if the instance is less (before) than another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->lt('2018-07-25 12:45:15'); // false + * Carbon::parse('2018-07-25 12:45:16')->lt('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->lt('2018-07-25 12:45:17'); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see lessThan() + * + * @return bool + */ + public function lt($date): bool; + + /** + * Determines if the instance is less (before) or equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->lte('2018-07-25 12:45:15'); // false + * Carbon::parse('2018-07-25 12:45:16')->lte('2018-07-25 12:45:16'); // true + * Carbon::parse('2018-07-25 12:45:16')->lte('2018-07-25 12:45:17'); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see lessThanOrEqualTo() + * + * @return bool + */ + public function lte($date): bool; + + /** + * Register a custom macro. + * + * @example + * ``` + * $userSettings = [ + * 'locale' => 'pt', + * 'timezone' => 'America/Sao_Paulo', + * ]; + * Carbon::macro('userFormat', function () use ($userSettings) { + * return $this->copy()->locale($userSettings['locale'])->tz($userSettings['timezone'])->calendar(); + * }); + * echo Carbon::yesterday()->hours(11)->userFormat(); + * ``` + * + * @param string $name + * @param object|callable $macro + * + * @return void + */ + public static function macro($name, $macro); + + /** + * 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 + * + * @throws InvalidFormatException + * + * @return static|null + */ + public static function make($var); + + /** + * Get the maximum instance between a given instance (default now) and the current instance. + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return static + */ + public function max($date = null); + + /** + * Create a Carbon instance for the greatest supported date. + * + * @return static + */ + public static function maxValue(); + + /** + * 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 the meridiem of the current time in the current locale. + * + * @param bool $isLower if true, returns lowercase variant if available in the current locale. + * + * @return string + */ + public function meridiem(bool $isLower = false): string; + + /** + * Modify to midday, default to self::$midDayAt + * + * @return static + */ + public function midDay(); + + /** + * Get the minimum instance between a given instance (default now) and the current instance. + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return static + */ + public function min($date = null); + + /** + * Create a Carbon instance for the lowest supported date. + * + * @return static + */ + public static function minValue(); + + /** + * 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); + + /** + * Mix another object into the class. + * + * @example + * ``` + * Carbon::mixin(new class { + * public function addMoon() { + * return function () { + * return $this->addDays(30); + * }; + * } + * public function subMoon() { + * return function () { + * return $this->subDays(30); + * }; + * } + * }); + * $fullMoon = Carbon::create('2018-12-22'); + * $nextFullMoon = $fullMoon->addMoon(); + * $blackMoon = Carbon::create('2019-01-06'); + * $previousBlackMoon = $blackMoon->subMoon(); + * echo "$nextFullMoon\n"; + * echo "$previousBlackMoon\n"; + * ``` + * + * @param object|string $mixin + * + * @throws ReflectionException + * + * @return void + */ + public static function mixin($mixin); + + /** + * Calls \DateTime::modify if mutable or \DateTimeImmutable::modify else. + * + * @see https://php.net/manual/en/datetime.modify.php + * + * @return static|false + */ + #[ReturnTypeWillChange] + public function modify($modify); + + /** + * Determines if the instance is not equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->ne('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->ne(Carbon::parse('2018-07-25 12:45:16')); // false + * Carbon::parse('2018-07-25 12:45:16')->ne('2018-07-25 12:45:17'); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see notEqualTo() + * + * @return bool + */ + public function ne($date): bool; + + /** + * Modify to the next occurrence of a given modifier such as a day of + * the week. If no modifier 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 string|int|null $modifier + * + * @return static|false + */ + public function next($modifier = null); + + /** + * Go forward to the next weekday. + * + * @return static + */ + public function nextWeekday(); + + /** + * Go forward to the next weekend day. + * + * @return static + */ + public function nextWeekendDay(); + + /** + * Determines if the instance is not equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->notEqualTo('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->notEqualTo(Carbon::parse('2018-07-25 12:45:16')); // false + * Carbon::parse('2018-07-25 12:45:16')->notEqualTo('2018-07-25 12:45:17'); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function notEqualTo($date): bool; + + /** + * Get a Carbon instance for the current date and time. + * + * @param DateTimeZone|string|null $tz + * + * @return static + */ + public static function now($tz = null); + + /** + * Returns a present instance in the same timezone. + * + * @return static + */ + public function nowWithSameTz(); + + /** + * 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); + + /** + * 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); + + /** + * 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); + + /** + * Return a property with its ordinal. + * + * @param string $key + * @param string|null $period + * + * @return string + */ + public function ordinal(string $key, ?string $period = null): string; + + /** + * 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|DateTimeInterface|null $time + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + * + * @return static + */ + public static function parse($time = null, $tz = null); + + /** + * Create a carbon instance from a localized string (in French, Japanese, Arabic, etc.). + * + * @param string $time date/time string in the given language (may also contain English). + * @param string|null $locale if locale is null or not specified, current global locale will be + * used instead. + * @param DateTimeZone|string|null $tz optional timezone for the new instance. + * + * @throws InvalidFormatException + * + * @return static + */ + public static function parseFromLocale($time, $locale = null, $tz = null); + + /** + * Returns standardized plural of a given singular/plural unit name (in English). + * + * @param string $unit + * + * @return string + */ + public static function pluralUnit(string $unit): string; + + /** + * Modify to the previous occurrence of a given modifier such as a 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 string|int|null $modifier + * + * @return static|false + */ + public function previous($modifier = null); + + /** + * Go backward to the previous weekday. + * + * @return static + */ + public function previousWeekday(); + + /** + * Go backward to the previous weekend day. + * + * @return static + */ + public function previousWeekendDay(); + + /** + * Create a iterable CarbonPeriod object from current date to a given end date (and optional interval). + * + * @param \DateTimeInterface|Carbon|CarbonImmutable|null $end period end date + * @param int|\DateInterval|string|null $interval period default interval or number of the given $unit + * @param string|null $unit if specified, $interval must be an integer + * + * @return CarbonPeriod + */ + public function range($end = null, $interval = null, $unit = null); + + /** + * Call native PHP DateTime/DateTimeImmutable add() method. + * + * @param DateInterval $interval + * + * @return static + */ + public function rawAdd(DateInterval $interval); + + /** + * Create a Carbon instance from a specific format. + * + * @param string $format Datetime format + * @param string $time + * @param DateTimeZone|string|false|null $tz + * + * @throws InvalidFormatException + * + * @return static|false + */ + public static function rawCreateFromFormat($format, $time, $tz = null); + + /** + * @see https://php.net/manual/en/datetime.format.php + * + * @param string $format + * + * @return string + */ + public function rawFormat($format); + + /** + * 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|DateTimeInterface|null $time + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + * + * @return static + */ + public static function rawParse($time = null, $tz = null); + + /** + * Call native PHP DateTime/DateTimeImmutable sub() method. + * + * @param DateInterval $interval + * + * @return static + */ + public function rawSub(DateInterval $interval); + + /** + * Remove all macros and generic macros. + */ + public static function resetMacros(); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addMonthsWithOverflow/addMonthsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @see settings + * + * Reset the month overflow behavior. + * + * @return void + */ + public static function resetMonthsOverflow(); + + /** + * Reset the format used to the default when type juggling a Carbon instance to a string + * + * @return void + */ + public static function resetToStringFormat(); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addYearsWithOverflow/addYearsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @see settings + * + * Reset the month overflow behavior. + * + * @return void + */ + public static function resetYearsOverflow(); + + /** + * Round the current instance second with given precision if specified. + * + * @param float|int|string|\DateInterval|null $precision + * @param string $function + * + * @return CarbonInterface + */ + public function round($precision = 1, $function = 'round'); + + /** + * Round the current instance at the given unit with given precision if specified and the given function. + * + * @param string $unit + * @param float|int $precision + * @param string $function + * + * @return CarbonInterface + */ + public function roundUnit($unit, $precision = 1, $function = 'round'); + + /** + * Round the current instance week. + * + * @param int $weekStartsAt optional start allow you to specify the day of week to use to start the week + * + * @return CarbonInterface + */ + public function roundWeek($weekStartsAt = null); + + /** + * The number of seconds since midnight. + * + * @return int + */ + public function secondsSinceMidnight(); + + /** + * The number of seconds until 23:59:59. + * + * @return int + */ + public function secondsUntilEndOfDay(); + + /** + * Return a serialized string of the instance. + * + * @return string + */ + public function serialize(); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather transform Carbon object before the serialization. + * + * JSON serialize all Carbon instances using the given callback. + * + * @param callable $callback + * + * @return void + */ + public static function serializeUsing($callback); + + /** + * Set a part of the Carbon object + * + * @param string|array $name + * @param string|int|DateTimeZone $value + * + * @throws ImmutableException|UnknownSetterException + * + * @return $this + */ + public function set($name, $value = null); + + /** + * Set the date with gregorian year, month and day numbers. + * + * @see https://php.net/manual/en/datetime.setdate.php + * + * @param int $year + * @param int $month + * @param int $day + * + * @return static + */ + #[ReturnTypeWillChange] + public function setDate($year, $month, $day); + + /** + * Set the year, month, and date for this instance to that of the passed instance. + * + * @param Carbon|DateTimeInterface $date now if null + * + * @return static + */ + public function setDateFrom($date = null); + + /** + * 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 + * @param int $microseconds + * + * @return static + */ + public function setDateTime($year, $month, $day, $hour, $minute, $second = 0, $microseconds = 0); + + /** + * Set the date and time for this instance to that of the passed instance. + * + * @param Carbon|DateTimeInterface $date + * + * @return static + */ + public function setDateTimeFrom($date = null); + + /** + * Set the day (keeping the current time) to the start of the week + the number of days passed as the first + * parameter. First day of week is driven by the locale unless explicitly set with the second parameter. + * + * @param int $numberOfDays number of days to add after the start of the current week + * @param int|null $weekStartsAt optional start allow you to specify the day of week to use to start the week, + * if not provided, start of week is inferred from the locale + * (Sunday for en_US, Monday for de_DE, etc.) + * + * @return static + */ + public function setDaysFromStartOfWeek(int $numberOfDays, ?int $weekStartsAt = null); + + /** + * Set the fallback locale. + * + * @see https://symfony.com/doc/current/components/translation.html#fallback-locales + * + * @param string $locale + */ + public static function setFallbackLocale($locale); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @see settings + * + * @param int $humanDiffOptions + */ + public static function setHumanDiffOptions($humanDiffOptions); + + /** + * Set a date according to the ISO 8601 standard - using weeks and day offsets rather than specific dates. + * + * @see https://php.net/manual/en/datetime.setisodate.php + * + * @param int $year + * @param int $week + * @param int $day + * + * @return static + */ + #[ReturnTypeWillChange] + public function setISODate($year, $week, $day = 1); + + /** + * Set the translator for the current instance. + * + * @param \Symfony\Component\Translation\TranslatorInterface $translator + * + * @return $this + */ + public function setLocalTranslator(TranslatorInterface $translator); + + /** + * Set the current translator locale and indicate if the source locale file exists. + * Pass 'auto' as locale to use closest language from the current LC_TIME locale. + * + * @param string $locale locale ex. en + * + * @return bool + */ + public static function setLocale($locale); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather consider mid-day is always 12pm, then if you need to test if it's an other + * hour, test it explicitly: + * $date->format('G') == 13 + * or to set explicitly to a given hour: + * $date->setTime(13, 0, 0, 0) + * + * Set midday/noon hour + * + * @param int $hour midday hour + * + * @return void + */ + public static function setMidDayAt($hour); + + /** + * 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. + * + * Only the moment is mocked with setTestNow(), the timezone will still be the one passed + * as parameter of date_default_timezone_get() as a fallback (see setTestNowAndTimezone()). + * + * To clear the test instance call this method using the default + * parameter of null. + * + * /!\ Use this method for unit tests only. + * + * @param DateTimeInterface|Closure|static|string|false|null $testNow real or mock Carbon instance + */ + public static function setTestNow($testNow = null); + + /** + * 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(). + * + * It will also align default timezone (e.g. call date_default_timezone_set()) with + * the second argument or if null, with the timezone of the given date object. + * + * To clear the test instance call this method using the default + * parameter of null. + * + * /!\ Use this method for unit tests only. + * + * @param DateTimeInterface|Closure|static|string|false|null $testNow real or mock Carbon instance + */ + public static function setTestNowAndTimezone($testNow = null, $tz = null); + + /** + * Resets the current time of the DateTime object to a different time. + * + * @see https://php.net/manual/en/datetime.settime.php + * + * @param int $hour + * @param int $minute + * @param int $second + * @param int $microseconds + * + * @return static + */ + #[ReturnTypeWillChange] + public function setTime($hour, $minute, $second = 0, $microseconds = 0); + + /** + * Set the hour, minute, second and microseconds for this instance to that of the passed instance. + * + * @param Carbon|DateTimeInterface $date now if null + * + * @return static + */ + public function setTimeFrom($date = null); + + /** + * Set the time by time string. + * + * @param string $time + * + * @return static + */ + public function setTimeFromTimeString($time); + + /** + * Set the instance's timestamp. + * + * Timestamp input can be given as int, float or a string containing one or more numbers. + * + * @param float|int|string $unixTimestamp + * + * @return static + */ + #[ReturnTypeWillChange] + public function setTimestamp($unixTimestamp); + + /** + * Set the instance's timezone from a string or object. + * + * @param DateTimeZone|string $value + * + * @return static + */ + #[ReturnTypeWillChange] + public function setTimezone($value); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather let Carbon object being cast to string with DEFAULT_TO_STRING_FORMAT, and + * use other method or custom format passed to format() method if you need to dump another string + * format. + * + * Set the default format used when type juggling a Carbon instance to a string. + * + * @param string|Closure|null $format + * + * @return void + */ + public static function setToStringFormat($format); + + /** + * Set the default translator instance to use. + * + * @param \Symfony\Component\Translation\TranslatorInterface $translator + * + * @return void + */ + public static function setTranslator(TranslatorInterface $translator); + + /** + * Set specified unit to new given value. + * + * @param string $unit year, month, day, hour, minute, second or microsecond + * @param int $value new value for given unit + * + * @return static + */ + public function setUnit($unit, $value = null); + + /** + * Set any unit to a new value without overflowing current other unit given. + * + * @param string $valueUnit unit name to modify + * @param int $value new value for the input unit + * @param string $overflowUnit unit name to not overflow + * + * @return static + */ + public function setUnitNoOverflow($valueUnit, $value, $overflowUnit); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use UTF-8 language packages on every machine. + * + * Set if UTF8 will be used for localized date/time. + * + * @param bool $utf8 + */ + public static function setUtf8($utf8); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * Use $weekStartsAt optional parameter instead when using startOfWeek, floorWeek, ceilWeek + * or roundWeek method. You can also use the 'first_day_of_week' locale setting to change the + * start of week according to current locale selected and implicitly the end of week. + * + * Set the last day of week + * + * @param int|string $day week end day (or 'auto' to get the day before the first day of week + * from Carbon::getLocale() culture). + * + * @return void + */ + public static function setWeekEndsAt($day); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * Use $weekEndsAt optional parameter instead when using endOfWeek method. You can also use the + * 'first_day_of_week' locale setting to change the start of week according to current locale + * selected and implicitly the end of week. + * + * Set the first day of week + * + * @param int|string $day week start day (or 'auto' to get the first day of week from Carbon::getLocale() culture). + * + * @return void + */ + public static function setWeekStartsAt($day); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather consider week-end is always saturday and sunday, and if you have some custom + * week-end days to handle, give to those days an other name and create a macro for them: + * + * ``` + * Carbon::macro('isDayOff', function ($date) { + * return $date->isSunday() || $date->isMonday(); + * }); + * Carbon::macro('isNotDayOff', function ($date) { + * return !$date->isDayOff(); + * }); + * if ($someDate->isDayOff()) ... + * if ($someDate->isNotDayOff()) ... + * // Add 5 not-off days + * $count = 5; + * while ($someDate->isDayOff() || ($count-- > 0)) { + * $someDate->addDay(); + * } + * ``` + * + * Set weekend days + * + * @param array $days + * + * @return void + */ + public static function setWeekendDays($days); + + /** + * Set specific options. + * - strictMode: true|false|null + * - monthOverflow: true|false|null + * - yearOverflow: true|false|null + * - humanDiffOptions: int|null + * - toStringFormat: string|Closure|null + * - toJsonFormat: string|Closure|null + * - locale: string|null + * - timezone: \DateTimeZone|string|int|null + * - macros: array|null + * - genericMacros: array|null + * + * @param array $settings + * + * @return $this|static + */ + public function settings(array $settings); + + /** + * Set the instance's timezone from a string or object and add/subtract the offset difference. + * + * @param DateTimeZone|string $value + * + * @return static + */ + public function shiftTimezone($value); + + /** + * Get the month overflow global behavior (can be overridden in specific instances). + * + * @return bool + */ + public static function shouldOverflowMonths(); + + /** + * Get the month overflow global behavior (can be overridden in specific instances). + * + * @return bool + */ + public static function shouldOverflowYears(); + + /** + * @alias diffForHumans + * + * Get the difference in a human readable format in the current locale from current instance to an other + * instance given (or now if null given). + */ + public function since($other = null, $syntax = null, $short = false, $parts = 1, $options = null); + + /** + * Returns standardized singular of a given singular/plural unit name (in English). + * + * @param string $unit + * + * @return string + */ + public static function singularUnit(string $unit): string; + + /** + * Modify to start of current given unit. + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16.334455') + * ->startOf('month') + * ->endOf('week', Carbon::FRIDAY); + * ``` + * + * @param string $unit + * @param array $params + * + * @return static + */ + public function startOf($unit, ...$params); + + /** + * Resets the date to the first day of the century and the time to 00:00:00 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfCentury(); + * ``` + * + * @return static + */ + public function startOfCentury(); + + /** + * Resets the time to 00:00:00 start of day + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfDay(); + * ``` + * + * @return static + */ + public function startOfDay(); + + /** + * Resets the date to the first day of the decade and the time to 00:00:00 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfDecade(); + * ``` + * + * @return static + */ + public function startOfDecade(); + + /** + * Modify to start of current hour, minutes and seconds become 0 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfHour(); + * ``` + * + * @return static + */ + public function startOfHour(); + + /** + * Resets the date to the first day of the millennium and the time to 00:00:00 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfMillennium(); + * ``` + * + * @return static + */ + public function startOfMillennium(); + + /** + * Modify to start of current minute, seconds become 0 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfMinute(); + * ``` + * + * @return static + */ + public function startOfMinute(); + + /** + * Resets the date to the first day of the month and the time to 00:00:00 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfMonth(); + * ``` + * + * @return static + */ + public function startOfMonth(); + + /** + * Resets the date to the first day of the quarter and the time to 00:00:00 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfQuarter(); + * ``` + * + * @return static + */ + public function startOfQuarter(); + + /** + * Modify to start of current second, microseconds become 0 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16.334455') + * ->startOfSecond() + * ->format('H:i:s.u'); + * ``` + * + * @return static + */ + public function startOfSecond(); + + /** + * Resets the date to the first day of week (defined in $weekStartsAt) and the time to 00:00:00 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfWeek() . "\n"; + * echo Carbon::parse('2018-07-25 12:45:16')->locale('ar')->startOfWeek() . "\n"; + * echo Carbon::parse('2018-07-25 12:45:16')->startOfWeek(Carbon::SUNDAY) . "\n"; + * ``` + * + * @param int $weekStartsAt optional start allow you to specify the day of week to use to start the week + * + * @return static + */ + public function startOfWeek($weekStartsAt = null); + + /** + * Resets the date to the first day of the year and the time to 00:00:00 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfYear(); + * ``` + * + * @return static + */ + public function startOfYear(); + + /** + * Subtract given units or interval to the current instance. + * + * @example $date->sub('hour', 3) + * @example $date->sub(15, 'days') + * @example $date->sub(CarbonInterval::days(4)) + * + * @param string|DateInterval|Closure|CarbonConverterInterface $unit + * @param int $value + * @param bool|null $overflow + * + * @return static + */ + #[ReturnTypeWillChange] + public function sub($unit, $value = 1, $overflow = null); + + public function subRealUnit($unit, $value = 1); + + /** + * Subtract given units to the current instance. + * + * @param string $unit + * @param int $value + * @param bool|null $overflow + * + * @return static + */ + public function subUnit($unit, $value = 1, $overflow = null); + + /** + * Subtract any unit to a new value without overflowing current other unit given. + * + * @param string $valueUnit unit name to modify + * @param int $value amount to subtract to the input unit + * @param string $overflowUnit unit name to not overflow + * + * @return static + */ + public function subUnitNoOverflow($valueUnit, $value, $overflowUnit); + + /** + * Subtract given units or interval to the current instance. + * + * @see sub() + * + * @param string|DateInterval $unit + * @param int $value + * @param bool|null $overflow + * + * @return static + */ + public function subtract($unit, $value = 1, $overflow = null); + + /** + * Get the difference in a human readable format in the current locale from current instance to an other + * instance given (or now if null given). + * + * @return string + */ + public function timespan($other = null, $timezone = null); + + /** + * Set the instance's timestamp. + * + * Timestamp input can be given as int, float or a string containing one or more numbers. + * + * @param float|int|string $unixTimestamp + * + * @return static + */ + public function timestamp($unixTimestamp); + + /** + * @alias setTimezone + * + * @param DateTimeZone|string $value + * + * @return static + */ + public function timezone($value); + + /** + * 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|\DateTimeInterface|string|array|null $other if array passed, will be used as parameters array, see $syntax below; + * if null passed, now will be used as comparison reference; + * if any other type, it will be converted to date and used as reference. + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * - 'other' entry (see above) + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single unit) + * @param int $options human diff options + * + * @return string + */ + public function to($other = null, $syntax = null, $short = false, $parts = 1, $options = null); + + /** + * Get default array representation. + * + * @example + * ``` + * var_dump(Carbon::now()->toArray()); + * ``` + * + * @return array + */ + public function toArray(); + + /** + * Format the instance as ATOM + * + * @example + * ``` + * echo Carbon::now()->toAtomString(); + * ``` + * + * @return string + */ + public function toAtomString(); + + /** + * Format the instance as COOKIE + * + * @example + * ``` + * echo Carbon::now()->toCookieString(); + * ``` + * + * @return string + */ + public function toCookieString(); + + /** + * @alias toDateTime + * + * Return native DateTime PHP object matching the current instance. + * + * @example + * ``` + * var_dump(Carbon::now()->toDate()); + * ``` + * + * @return DateTime + */ + public function toDate(); + + /** + * Format the instance as date + * + * @example + * ``` + * echo Carbon::now()->toDateString(); + * ``` + * + * @return string + */ + public function toDateString(); + + /** + * Return native DateTime PHP object matching the current instance. + * + * @example + * ``` + * var_dump(Carbon::now()->toDateTime()); + * ``` + * + * @return DateTime + */ + public function toDateTime(); + + /** + * Return native toDateTimeImmutable PHP object matching the current instance. + * + * @example + * ``` + * var_dump(Carbon::now()->toDateTimeImmutable()); + * ``` + * + * @return DateTimeImmutable + */ + public function toDateTimeImmutable(); + + /** + * Format the instance as date and time T-separated with no timezone + * + * @example + * ``` + * echo Carbon::now()->toDateTimeLocalString(); + * echo "\n"; + * echo Carbon::now()->toDateTimeLocalString('minute'); // You can specify precision among: minute, second, millisecond and microsecond + * ``` + * + * @param string $unitPrecision + * + * @return string + */ + public function toDateTimeLocalString($unitPrecision = 'second'); + + /** + * Format the instance as date and time + * + * @example + * ``` + * echo Carbon::now()->toDateTimeString(); + * ``` + * + * @param string $unitPrecision + * + * @return string + */ + public function toDateTimeString($unitPrecision = 'second'); + + /** + * Format the instance with day, date and time + * + * @example + * ``` + * echo Carbon::now()->toDayDateTimeString(); + * ``` + * + * @return string + */ + public function toDayDateTimeString(); + + /** + * Format the instance as a readable date + * + * @example + * ``` + * echo Carbon::now()->toFormattedDateString(); + * ``` + * + * @return string + */ + public function toFormattedDateString(); + + /** + * Format the instance with the day, and a readable date + * + * @example + * ``` + * echo Carbon::now()->toFormattedDayDateString(); + * ``` + * + * @return string + */ + public function toFormattedDayDateString(): string; + + /** + * 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); + + /** + * Return a immutable copy of the instance. + * + * @return CarbonImmutable + */ + public function toImmutable(); + + /** + * Format the instance as ISO8601 + * + * @example + * ``` + * echo Carbon::now()->toIso8601String(); + * ``` + * + * @return string + */ + public function toIso8601String(); + + /** + * Convert the instance to UTC and return as Zulu ISO8601 + * + * @example + * ``` + * echo Carbon::now()->toIso8601ZuluString(); + * ``` + * + * @param string $unitPrecision + * + * @return string + */ + public function toIso8601ZuluString($unitPrecision = 'second'); + + /** + * 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 a mutable copy of the instance. + * + * @return Carbon + */ + public function toMutable(); + + /** + * Get the difference in a human readable format in the current locale from an other + * instance given to now + * + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single part) + * @param int $options human diff options + * + * @return string + */ + public function toNow($syntax = null, $short = false, $parts = 1, $options = null); + + /** + * Get default object representation. + * + * @example + * ``` + * var_dump(Carbon::now()->toObject()); + * ``` + * + * @return object + */ + public function toObject(); + + /** + * Create a iterable CarbonPeriod object from current date to a given end date (and optional interval). + * + * @param \DateTimeInterface|Carbon|CarbonImmutable|int|null $end period end date or recurrences count if int + * @param int|\DateInterval|string|null $interval period default interval or number of the given $unit + * @param string|null $unit if specified, $interval must be an integer + * + * @return CarbonPeriod + */ + public function toPeriod($end = null, $interval = null, $unit = null); + + /** + * Format the instance as RFC1036 + * + * @example + * ``` + * echo Carbon::now()->toRfc1036String(); + * ``` + * + * @return string + */ + public function toRfc1036String(); + + /** + * Format the instance as RFC1123 + * + * @example + * ``` + * echo Carbon::now()->toRfc1123String(); + * ``` + * + * @return string + */ + public function toRfc1123String(); + + /** + * Format the instance as RFC2822 + * + * @example + * ``` + * echo Carbon::now()->toRfc2822String(); + * ``` + * + * @return string + */ + public function toRfc2822String(); + + /** + * Format the instance as RFC3339 + * + * @param bool $extended + * + * @example + * ``` + * echo Carbon::now()->toRfc3339String() . "\n"; + * echo Carbon::now()->toRfc3339String(true) . "\n"; + * ``` + * + * @return string + */ + public function toRfc3339String($extended = false); + + /** + * Format the instance as RFC7231 + * + * @example + * ``` + * echo Carbon::now()->toRfc7231String(); + * ``` + * + * @return string + */ + public function toRfc7231String(); + + /** + * Format the instance as RFC822 + * + * @example + * ``` + * echo Carbon::now()->toRfc822String(); + * ``` + * + * @return string + */ + public function toRfc822String(); + + /** + * Format the instance as RFC850 + * + * @example + * ``` + * echo Carbon::now()->toRfc850String(); + * ``` + * + * @return string + */ + public function toRfc850String(); + + /** + * Format the instance as RSS + * + * @example + * ``` + * echo Carbon::now()->toRssString(); + * ``` + * + * @return string + */ + public function toRssString(); + + /** + * Returns english human readable complete date string. + * + * @example + * ``` + * echo Carbon::now()->toString(); + * ``` + * + * @return string + */ + public function toString(); + + /** + * Format the instance as time + * + * @example + * ``` + * echo Carbon::now()->toTimeString(); + * ``` + * + * @param string $unitPrecision + * + * @return string + */ + public function toTimeString($unitPrecision = 'second'); + + /** + * Format the instance as W3C + * + * @example + * ``` + * echo Carbon::now()->toW3cString(); + * ``` + * + * @return string + */ + public function toW3cString(); + + /** + * Create a Carbon instance for today. + * + * @param DateTimeZone|string|null $tz + * + * @return static + */ + public static function today($tz = null); + + /** + * Create a Carbon instance for tomorrow. + * + * @param DateTimeZone|string|null $tz + * + * @return static + */ + public static function tomorrow($tz = null); + + /** + * Translate using translation string or callback available. + * + * @param string $key + * @param array $parameters + * @param string|int|float|null $number + * @param \Symfony\Component\Translation\TranslatorInterface|null $translator + * @param bool $altNumbers + * + * @return string + */ + public function translate(string $key, array $parameters = [], $number = null, ?TranslatorInterface $translator = null, bool $altNumbers = false): string; + + /** + * Returns the alternative number for a given integer if available in the current locale. + * + * @param int $number + * + * @return string + */ + public function translateNumber(int $number): string; + + /** + * Translate a time string from a locale to an other. + * + * @param string $timeString date/time/duration string to translate (may also contain English) + * @param string|null $from input locale of the $timeString parameter (`Carbon::getLocale()` by default) + * @param string|null $to output locale of the result returned (`"en"` by default) + * @param int $mode specify what to translate with options: + * - self::TRANSLATE_ALL (default) + * - CarbonInterface::TRANSLATE_MONTHS + * - CarbonInterface::TRANSLATE_DAYS + * - CarbonInterface::TRANSLATE_UNITS + * - CarbonInterface::TRANSLATE_MERIDIEM + * You can use pipe to group: CarbonInterface::TRANSLATE_MONTHS | CarbonInterface::TRANSLATE_DAYS + * + * @return string + */ + public static function translateTimeString($timeString, $from = null, $to = null, $mode = self::TRANSLATE_ALL); + + /** + * Translate a time string from the current locale (`$date->locale()`) to an other. + * + * @param string $timeString time string to translate + * @param string|null $to output locale of the result returned ("en" by default) + * + * @return string + */ + public function translateTimeStringTo($timeString, $to = null); + + /** + * Translate using translation string or callback available. + * + * @param \Symfony\Component\Translation\TranslatorInterface $translator + * @param string $key + * @param array $parameters + * @param null $number + * + * @return string + */ + public static function translateWith(TranslatorInterface $translator, string $key, array $parameters = [], $number = null): string; + + /** + * Format as ->format() do (using date replacements patterns from https://php.net/manual/en/function.date.php) + * but translate words whenever possible (months, day names, etc.) using the current locale. + * + * @param string $format + * + * @return string + */ + public function translatedFormat(string $format): string; + + /** + * Set the timezone or returns the timezone name if no arguments passed. + * + * @param DateTimeZone|string $value + * + * @return static|string + */ + public function tz($value = null); + + /** + * @alias getTimestamp + * + * Returns the UNIX timestamp for the current date. + * + * @return int + */ + public function unix(); + + /** + * @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|\DateTimeInterface|string|array|null $other if array passed, will be used as parameters array, see $syntax below; + * if null passed, now will be used as comparison reference; + * if any other type, it will be converted to date and used as reference. + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * - 'other' entry (see above) + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single unit) + * @param int $options human diff options + * + * @return string + */ + public function until($other = null, $syntax = null, $short = false, $parts = 1, $options = null); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addMonthsWithOverflow/addMonthsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @see settings + * + * Indicates if months should be calculated with overflow. + * + * @param bool $monthsOverflow + * + * @return void + */ + public static function useMonthsOverflow($monthsOverflow = true); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @see settings + * + * Enable the strict mode (or disable with passing false). + * + * @param bool $strictModeEnabled + */ + public static function useStrictMode($strictModeEnabled = true); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addYearsWithOverflow/addYearsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @see settings + * + * Indicates if years should be calculated with overflow. + * + * @param bool $yearsOverflow + * + * @return void + */ + public static function useYearsOverflow($yearsOverflow = true); + + /** + * Set the instance's timezone to UTC. + * + * @return static + */ + public function utc(); + + /** + * Returns the minutes offset to UTC if no arguments passed, else set the timezone with given minutes shift passed. + * + * @param int|null $minuteOffset + * + * @return int|static + */ + public function utcOffset(?int $minuteOffset = null); + + /** + * Returns the milliseconds timestamps used amongst other by Date javascript objects. + * + * @return float + */ + public function valueOf(); + + /** + * Get/set the week number using given first day of week and first + * day of year included in the first week. Or use US format if no settings + * given (Sunday / Jan 6). + * + * @param int|null $week + * @param int|null $dayOfWeek + * @param int|null $dayOfYear + * + * @return int|static + */ + public function week($week = null, $dayOfWeek = null, $dayOfYear = null); + + /** + * Set/get the week number of year using given first day of week and first + * day of year included in the first week. Or use US format if no settings + * given (Sunday / Jan 6). + * + * @param int|null $year if null, act as a getter, if not null, set the year and return current instance. + * @param int|null $dayOfWeek first date of week from 0 (Sunday) to 6 (Saturday) + * @param int|null $dayOfYear first day of year included in the week #1 + * + * @return int|static + */ + public function weekYear($year = null, $dayOfWeek = null, $dayOfYear = null); + + /** + * Get/set the weekday from 0 (Sunday) to 6 (Saturday). + * + * @param int|null $value new value for weekday if using as setter. + * + * @return static|int + */ + public function weekday($value = null); + + /** + * Get the number of weeks of the current week-year using given first day of week and first + * day of year included in the first week. Or use US format if no settings + * given (Sunday / Jan 6). + * + * @param int|null $dayOfWeek first date of week from 0 (Sunday) to 6 (Saturday) + * @param int|null $dayOfYear first day of year included in the week #1 + * + * @return int + */ + public function weeksInYear($dayOfWeek = null, $dayOfYear = null); + + /** + * Temporarily sets a static date to be used within the callback. + * Using setTestNow to set the date, executing the callback, then + * clearing the test instance. + * + * /!\ Use this method for unit tests only. + * + * @template T + * + * @param DateTimeInterface|Closure|static|string|false|null $testNow real or mock Carbon instance + * @param Closure(): T $callback + * + * @return mixed + * @phpstan-return T + */ + public static function withTestNow($testNow, $callback); + + /** + * Create a Carbon instance for yesterday. + * + * @param DateTimeZone|string|null $tz + * + * @return static + */ + public static function yesterday($tz = null); + + // +} diff --git a/vendor/nesbot/carbon/src/Carbon/CarbonInterval.php b/vendor/nesbot/carbon/src/Carbon/CarbonInterval.php new file mode 100644 index 0000000..9eb49c0 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/CarbonInterval.php @@ -0,0 +1,2844 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +use Carbon\Exceptions\BadFluentConstructorException; +use Carbon\Exceptions\BadFluentSetterException; +use Carbon\Exceptions\InvalidCastException; +use Carbon\Exceptions\InvalidIntervalException; +use Carbon\Exceptions\ParseErrorException; +use Carbon\Exceptions\UnitNotConfiguredException; +use Carbon\Exceptions\UnknownGetterException; +use Carbon\Exceptions\UnknownSetterException; +use Carbon\Exceptions\UnknownUnitException; +use Carbon\Traits\IntervalRounding; +use Carbon\Traits\IntervalStep; +use Carbon\Traits\MagicParameter; +use Carbon\Traits\Mixin; +use Carbon\Traits\Options; +use Carbon\Traits\ToStringFormat; +use Closure; +use DateInterval; +use DateMalformedIntervalStringException; +use DateTimeInterface; +use DateTimeZone; +use Exception; +use ReflectionException; +use ReturnTypeWillChange; +use Throwable; + +/** + * 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 int $microseconds Total microseconds of the current interval. + * @property int $milliseconds Total milliseconds of the current interval. + * @property int $microExcludeMilli Remaining microseconds without the milliseconds. + * @property int $dayzExcludeWeeks Total days remaining in the final week of the current instance (days % 7). + * @property 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. + * @property-read float $totalMilliseconds Number of milliseconds equivalent to the interval. + * @property-read float $totalMicroseconds Number of microseconds equivalent to the interval. + * @property-read string $locale locale of the current instance + * + * @method static CarbonInterval years($years = 1) Create instance specifying a number of years or modify the number of years if called on an instance. + * @method static CarbonInterval year($years = 1) Alias for years() + * @method static CarbonInterval months($months = 1) Create instance specifying a number of months or modify the number of months if called on an instance. + * @method static CarbonInterval month($months = 1) Alias for months() + * @method static CarbonInterval weeks($weeks = 1) Create instance specifying a number of weeks or modify the number of weeks if called on an instance. + * @method static CarbonInterval week($weeks = 1) Alias for weeks() + * @method static CarbonInterval days($days = 1) Create instance specifying a number of days or modify the number of days if called on an instance. + * @method static CarbonInterval dayz($days = 1) Alias for days() + * @method static CarbonInterval daysExcludeWeeks($days = 1) Create instance specifying a number of days or modify the number of days (keeping the current number of weeks) if called on an instance. + * @method static CarbonInterval dayzExcludeWeeks($days = 1) Alias for daysExcludeWeeks() + * @method static CarbonInterval day($days = 1) Alias for days() + * @method static CarbonInterval hours($hours = 1) Create instance specifying a number of hours or modify the number of hours if called on an instance. + * @method static CarbonInterval hour($hours = 1) Alias for hours() + * @method static CarbonInterval minutes($minutes = 1) Create instance specifying a number of minutes or modify the number of minutes if called on an instance. + * @method static CarbonInterval minute($minutes = 1) Alias for minutes() + * @method static CarbonInterval seconds($seconds = 1) Create instance specifying a number of seconds or modify the number of seconds if called on an instance. + * @method static CarbonInterval second($seconds = 1) Alias for seconds() + * @method static CarbonInterval milliseconds($milliseconds = 1) Create instance specifying a number of milliseconds or modify the number of milliseconds if called on an instance. + * @method static CarbonInterval millisecond($milliseconds = 1) Alias for milliseconds() + * @method static CarbonInterval microseconds($microseconds = 1) Create instance specifying a number of microseconds or modify the number of microseconds if called on an instance. + * @method static CarbonInterval microsecond($microseconds = 1) Alias for microseconds() + * @method $this addYears(int $years) Add given number of years to the current interval + * @method $this subYears(int $years) Subtract given number of years to the current interval + * @method $this addMonths(int $months) Add given number of months to the current interval + * @method $this subMonths(int $months) Subtract given number of months to the current interval + * @method $this addWeeks(int|float $weeks) Add given number of weeks to the current interval + * @method $this subWeeks(int|float $weeks) Subtract given number of weeks to the current interval + * @method $this addDays(int|float $days) Add given number of days to the current interval + * @method $this subDays(int|float $days) Subtract given number of days to the current interval + * @method $this addHours(int|float $hours) Add given number of hours to the current interval + * @method $this subHours(int|float $hours) Subtract given number of hours to the current interval + * @method $this addMinutes(int|float $minutes) Add given number of minutes to the current interval + * @method $this subMinutes(int|float $minutes) Subtract given number of minutes to the current interval + * @method $this addSeconds(int|float $seconds) Add given number of seconds to the current interval + * @method $this subSeconds(int|float $seconds) Subtract given number of seconds to the current interval + * @method $this addMilliseconds(int|float $milliseconds) Add given number of milliseconds to the current interval + * @method $this subMilliseconds(int|float $milliseconds) Subtract given number of milliseconds to the current interval + * @method $this addMicroseconds(int|float $microseconds) Add given number of microseconds to the current interval + * @method $this subMicroseconds(int|float $microseconds) Subtract given number of microseconds to the current interval + * @method $this roundYear(int|float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. + * @method $this roundYears(int|float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. + * @method $this floorYear(int|float $precision = 1) Truncate the current instance year with given precision. + * @method $this floorYears(int|float $precision = 1) Truncate the current instance year with given precision. + * @method $this ceilYear(int|float $precision = 1) Ceil the current instance year with given precision. + * @method $this ceilYears(int|float $precision = 1) Ceil the current instance year with given precision. + * @method $this roundMonth(int|float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. + * @method $this roundMonths(int|float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. + * @method $this floorMonth(int|float $precision = 1) Truncate the current instance month with given precision. + * @method $this floorMonths(int|float $precision = 1) Truncate the current instance month with given precision. + * @method $this ceilMonth(int|float $precision = 1) Ceil the current instance month with given precision. + * @method $this ceilMonths(int|float $precision = 1) Ceil the current instance month with given precision. + * @method $this roundWeek(int|float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method $this roundWeeks(int|float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method $this floorWeek(int|float $precision = 1) Truncate the current instance day with given precision. + * @method $this floorWeeks(int|float $precision = 1) Truncate the current instance day with given precision. + * @method $this ceilWeek(int|float $precision = 1) Ceil the current instance day with given precision. + * @method $this ceilWeeks(int|float $precision = 1) Ceil the current instance day with given precision. + * @method $this roundDay(int|float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method $this roundDays(int|float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method $this floorDay(int|float $precision = 1) Truncate the current instance day with given precision. + * @method $this floorDays(int|float $precision = 1) Truncate the current instance day with given precision. + * @method $this ceilDay(int|float $precision = 1) Ceil the current instance day with given precision. + * @method $this ceilDays(int|float $precision = 1) Ceil the current instance day with given precision. + * @method $this roundHour(int|float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. + * @method $this roundHours(int|float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. + * @method $this floorHour(int|float $precision = 1) Truncate the current instance hour with given precision. + * @method $this floorHours(int|float $precision = 1) Truncate the current instance hour with given precision. + * @method $this ceilHour(int|float $precision = 1) Ceil the current instance hour with given precision. + * @method $this ceilHours(int|float $precision = 1) Ceil the current instance hour with given precision. + * @method $this roundMinute(int|float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. + * @method $this roundMinutes(int|float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. + * @method $this floorMinute(int|float $precision = 1) Truncate the current instance minute with given precision. + * @method $this floorMinutes(int|float $precision = 1) Truncate the current instance minute with given precision. + * @method $this ceilMinute(int|float $precision = 1) Ceil the current instance minute with given precision. + * @method $this ceilMinutes(int|float $precision = 1) Ceil the current instance minute with given precision. + * @method $this roundSecond(int|float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. + * @method $this roundSeconds(int|float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. + * @method $this floorSecond(int|float $precision = 1) Truncate the current instance second with given precision. + * @method $this floorSeconds(int|float $precision = 1) Truncate the current instance second with given precision. + * @method $this ceilSecond(int|float $precision = 1) Ceil the current instance second with given precision. + * @method $this ceilSeconds(int|float $precision = 1) Ceil the current instance second with given precision. + * @method $this roundMillennium(int|float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. + * @method $this roundMillennia(int|float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. + * @method $this floorMillennium(int|float $precision = 1) Truncate the current instance millennium with given precision. + * @method $this floorMillennia(int|float $precision = 1) Truncate the current instance millennium with given precision. + * @method $this ceilMillennium(int|float $precision = 1) Ceil the current instance millennium with given precision. + * @method $this ceilMillennia(int|float $precision = 1) Ceil the current instance millennium with given precision. + * @method $this roundCentury(int|float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. + * @method $this roundCenturies(int|float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. + * @method $this floorCentury(int|float $precision = 1) Truncate the current instance century with given precision. + * @method $this floorCenturies(int|float $precision = 1) Truncate the current instance century with given precision. + * @method $this ceilCentury(int|float $precision = 1) Ceil the current instance century with given precision. + * @method $this ceilCenturies(int|float $precision = 1) Ceil the current instance century with given precision. + * @method $this roundDecade(int|float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. + * @method $this roundDecades(int|float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. + * @method $this floorDecade(int|float $precision = 1) Truncate the current instance decade with given precision. + * @method $this floorDecades(int|float $precision = 1) Truncate the current instance decade with given precision. + * @method $this ceilDecade(int|float $precision = 1) Ceil the current instance decade with given precision. + * @method $this ceilDecades(int|float $precision = 1) Ceil the current instance decade with given precision. + * @method $this roundQuarter(int|float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. + * @method $this roundQuarters(int|float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. + * @method $this floorQuarter(int|float $precision = 1) Truncate the current instance quarter with given precision. + * @method $this floorQuarters(int|float $precision = 1) Truncate the current instance quarter with given precision. + * @method $this ceilQuarter(int|float $precision = 1) Ceil the current instance quarter with given precision. + * @method $this ceilQuarters(int|float $precision = 1) Ceil the current instance quarter with given precision. + * @method $this roundMillisecond(int|float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. + * @method $this roundMilliseconds(int|float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. + * @method $this floorMillisecond(int|float $precision = 1) Truncate the current instance millisecond with given precision. + * @method $this floorMilliseconds(int|float $precision = 1) Truncate the current instance millisecond with given precision. + * @method $this ceilMillisecond(int|float $precision = 1) Ceil the current instance millisecond with given precision. + * @method $this ceilMilliseconds(int|float $precision = 1) Ceil the current instance millisecond with given precision. + * @method $this roundMicrosecond(int|float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. + * @method $this roundMicroseconds(int|float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. + * @method $this floorMicrosecond(int|float $precision = 1) Truncate the current instance microsecond with given precision. + * @method $this floorMicroseconds(int|float $precision = 1) Truncate the current instance microsecond with given precision. + * @method $this ceilMicrosecond(int|float $precision = 1) Ceil the current instance microsecond with given precision. + * @method $this ceilMicroseconds(int|float $precision = 1) Ceil the current instance microsecond with given precision. + */ +class CarbonInterval extends DateInterval implements CarbonConverterInterface +{ + use IntervalRounding; + use IntervalStep; + use MagicParameter; + use Mixin { + Mixin::mixin as baseMixin; + } + use Options; + use ToStringFormat; + + /** + * Interval spec period designators + */ + public const PERIOD_PREFIX = 'P'; + public const PERIOD_YEARS = 'Y'; + public const PERIOD_MONTHS = 'M'; + public const PERIOD_DAYS = 'D'; + public const PERIOD_TIME_PREFIX = 'T'; + public const PERIOD_HOURS = 'H'; + public const PERIOD_MINUTES = 'M'; + public 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 + */ + protected static $formats = [ + 'y' => 'y', + 'Y' => 'y', + 'o' => 'y', + 'm' => 'm', + 'n' => 'm', + 'W' => 'weeks', + 'd' => 'd', + 'j' => 'd', + 'z' => 'd', + 'h' => 'h', + 'g' => 'h', + 'H' => 'h', + 'G' => 'h', + 'i' => 'i', + 's' => 's', + 'u' => 'micro', + 'v' => 'milli', + ]; + + /** + * @var array|null + */ + private static $flipCascadeFactors; + + /** + * The registered macros. + * + * @var array + */ + protected static $macros = []; + + /** + * Timezone handler for settings() method. + * + * @var mixed + */ + protected $tzName; + + /** + * Set the instance's timezone from a string or object. + * + * @param \DateTimeZone|string $tzName + * + * @return static + */ + public function setTimezone($tzName) + { + $this->tzName = $tzName; + + return $this; + } + + /** + * @internal + * + * Set the instance's timezone from a string or object and add/subtract the offset difference. + * + * @param \DateTimeZone|string $tzName + * + * @return static + */ + public function shiftTimezone($tzName) + { + $this->tzName = $tzName; + + return $this; + } + + /** + * 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 ?: static::getDefaultCascadeFactors(); + } + + protected static function getDefaultCascadeFactors(): array + { + return [ + 'milliseconds' => [Carbon::MICROSECONDS_PER_MILLISECOND, 'microseconds'], + 'seconds' => [Carbon::MILLISECONDS_PER_SECOND, 'milliseconds'], + 'minutes' => [Carbon::SECONDS_PER_MINUTE, 'seconds'], + 'hours' => [Carbon::MINUTES_PER_HOUR, 'minutes'], + 'dayz' => [Carbon::HOURS_PER_DAY, 'hours'], + 'weeks' => [Carbon::DAYS_PER_WEEK, 'dayz'], + 'months' => [Carbon::WEEKS_PER_MONTH, 'weeks'], + 'years' => [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 = []; + + foreach (static::getCascadeFactors() as $to => [$factor, $from]) { + self::$flipCascadeFactors[self::standardizeUnit($from)] = [self::standardizeUnit($to), $factor]; + } + } + + return self::$flipCascadeFactors; + } + + /** + * Set default cascading factors for ->cascade() method. + * + * @param array $cascadeFactors + */ + public static function setCascadeFactors(array $cascadeFactors) + { + self::$flipCascadeFactors = null; + static::$cascadeFactors = $cascadeFactors; + } + + /////////////////////////////////////////////////////////////////// + //////////////////////////// CONSTRUCTORS ///////////////////////// + /////////////////////////////////////////////////////////////////// + + /** + * Create a new CarbonInterval instance. + * + * @param Closure|DateInterval|string|int|null $years + * @param int|null $months + * @param int|null $weeks + * @param int|null $days + * @param int|null $hours + * @param int|null $minutes + * @param int|null $seconds + * @param int|null $microseconds + * + * @throws Exception when the interval_spec (passed as $years) cannot be parsed as an interval. + */ + public function __construct($years = 1, $months = null, $weeks = null, $days = null, $hours = null, $minutes = null, $seconds = null, $microseconds = null) + { + if ($years instanceof Closure) { + $this->step = $years; + $years = null; + } + + if ($years instanceof DateInterval) { + parent::__construct(static::getDateIntervalSpec($years)); + $this->f = $years->f; + self::copyNegativeUnits($years, $this); + + return; + } + + $spec = $years; + + if (!\is_string($spec) || (float) $years || preg_match('/^[\d.]/', $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); + + if ($microseconds !== null) { + $this->f = $microseconds / Carbon::MICROSECONDS_PER_SECOND; + } + } + + /** + * Returns the factor for a given source-to-target couple. + * + * @param string $source + * @param string $target + * + * @return int|float|null + */ + public static function getFactor($source, $target) + { + $source = self::standardizeUnit($source); + $target = self::standardizeUnit($target); + $factors = self::getFlipCascadeFactors(); + + if (isset($factors[$source])) { + [$to, $factor] = $factors[$source]; + + if ($to === $target) { + return $factor; + } + + return $factor * static::getFactor($to, $target); + } + + return null; + } + + /** + * Returns the factor for a given source-to-target couple if set, + * else try to find the appropriate constant as the factor, such as Carbon::DAYS_PER_WEEK. + * + * @param string $source + * @param string $target + * + * @return int|float|null + */ + public static function getFactorWithDefault($source, $target) + { + $factor = self::getFactor($source, $target); + + if ($factor) { + return $factor; + } + + static $defaults = [ + 'month' => ['year' => Carbon::MONTHS_PER_YEAR], + 'week' => ['month' => Carbon::WEEKS_PER_MONTH], + 'day' => ['week' => Carbon::DAYS_PER_WEEK], + 'hour' => ['day' => Carbon::HOURS_PER_DAY], + 'minute' => ['hour' => Carbon::MINUTES_PER_HOUR], + 'second' => ['minute' => Carbon::SECONDS_PER_MINUTE], + 'millisecond' => ['second' => Carbon::MILLISECONDS_PER_SECOND], + 'microsecond' => ['millisecond' => Carbon::MICROSECONDS_PER_MILLISECOND], + ]; + + return $defaults[$source][$target] ?? null; + } + + /** + * Returns current config for days per week. + * + * @return int|float + */ + public static function getDaysPerWeek() + { + return static::getFactor('dayz', 'weeks') ?: Carbon::DAYS_PER_WEEK; + } + + /** + * Returns current config for hours per day. + * + * @return int|float + */ + public static function getHoursPerDay() + { + return static::getFactor('hours', 'dayz') ?: Carbon::HOURS_PER_DAY; + } + + /** + * Returns current config for minutes per hour. + * + * @return int|float + */ + public static function getMinutesPerHour() + { + return static::getFactor('minutes', 'hours') ?: Carbon::MINUTES_PER_HOUR; + } + + /** + * Returns current config for seconds per minute. + * + * @return int|float + */ + public static function getSecondsPerMinute() + { + return static::getFactor('seconds', 'minutes') ?: Carbon::SECONDS_PER_MINUTE; + } + + /** + * Returns current config for microseconds per second. + * + * @return int|float + */ + public static function getMillisecondsPerSecond() + { + return static::getFactor('milliseconds', 'seconds') ?: Carbon::MILLISECONDS_PER_SECOND; + } + + /** + * Returns current config for microseconds per second. + * + * @return int|float + */ + public static function getMicrosecondsPerMillisecond() + { + return static::getFactor('microseconds', 'milliseconds') ?: Carbon::MICROSECONDS_PER_MILLISECOND; + } + + /** + * 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 + * @param int $microseconds + * + * @throws Exception when the interval_spec (passed as $years) cannot be parsed as an interval. + * + * @return static + */ + public static function create($years = 1, $months = null, $weeks = null, $days = null, $hours = null, $minutes = null, $seconds = null, $microseconds = null) + { + return new static($years, $months, $weeks, $days, $hours, $minutes, $seconds, $microseconds); + } + + /** + * Parse a string into a new CarbonInterval object according to the specified format. + * + * @example + * ``` + * echo Carboninterval::createFromFormat('H:i', '1:30'); + * ``` + * + * @param string $format Format of the $interval input string + * @param string|null $interval Input string to convert into an interval + * + * @throws \Carbon\Exceptions\ParseErrorException when the $interval cannot be parsed as an interval. + * + * @return static + */ + public static function createFromFormat(string $format, ?string $interval) + { + $instance = new static(0); + $length = mb_strlen($format); + + if (preg_match('/s([,.])([uv])$/', $format, $match)) { + $interval = explode($match[1], $interval); + $index = \count($interval) - 1; + $interval[$index] = str_pad($interval[$index], $match[2] === 'v' ? 3 : 6, '0'); + $interval = implode($match[1], $interval); + } + + $interval = $interval ?? ''; + + for ($index = 0; $index < $length; $index++) { + $expected = mb_substr($format, $index, 1); + $nextCharacter = mb_substr($interval, 0, 1); + $unit = static::$formats[$expected] ?? null; + + if ($unit) { + if (!preg_match('/^-?\d+/', $interval, $match)) { + throw new ParseErrorException('number', $nextCharacter); + } + + $interval = mb_substr($interval, mb_strlen($match[0])); + $instance->$unit += (int) ($match[0]); + + continue; + } + + if ($nextCharacter !== $expected) { + throw new ParseErrorException( + "'$expected'", + $nextCharacter, + 'Allowed substitutes for interval formats are '.implode(', ', array_keys(static::$formats))."\n". + 'See https://php.net/manual/en/function.date.php for their meaning' + ); + } + + $interval = mb_substr($interval, 1); + } + + if ($interval !== '') { + throw new ParseErrorException( + 'end of string', + $interval + ); + } + + return $instance; + } + + /** + * Get a copy of the instance. + * + * @return static + */ + public function copy() + { + $date = new static(0); + $date->copyProperties($this); + $date->step = $this->step; + + return $date; + } + + /** + * Get a copy of the instance. + * + * @return static + */ + public function clone() + { + return $this->copy(); + } + + /** + * 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 $method magic method name called + * @param array $parameters parameters list + * + * @return static|null + */ + public static function __callStatic($method, $parameters) + { + try { + $interval = new static(0); + $localStrictModeEnabled = $interval->localStrictModeEnabled; + $interval->localStrictModeEnabled = true; + + $result = static::hasMacro($method) + ? static::bindMacroContext(null, function () use (&$method, &$parameters, &$interval) { + return $interval->callMacro($method, $parameters); + }) + : $interval->$method(...$parameters); + + $interval->localStrictModeEnabled = $localStrictModeEnabled; + + return $result; + } catch (BadFluentSetterException $exception) { + if (Carbon::isStrictModeEnabled()) { + throw new BadFluentConstructorException($method, 0, $exception); + } + + return null; + } + } + + /** + * Return the current context from inside a macro callee or a new one if static. + * + * @return static + */ + protected static function this() + { + return end(static::$macroContextStack) ?: new static(0); + } + + /** + * 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; + $milliseconds = 0; + $microseconds = 0; + + $pattern = '/(\d+(?:\.\d+)?)\h*([^\d\h]*)/i'; + preg_match_all($pattern, $intervalDefinition, $parts, PREG_SET_ORDER); + + while ([$part, $value, $unit] = array_shift($parts)) { + $intValue = (int) $value; + $fraction = (float) $value - $intValue; + + // Fix calculation precision + switch (round($fraction, 6)) { + case 1: + $fraction = 0; + $intValue++; + + break; + case 0: + $fraction = 0; + + break; + } + + switch ($unit === 'µs' ? 'µs' : strtolower($unit)) { + case 'millennia': + case 'millennium': + $years += $intValue * CarbonInterface::YEARS_PER_MILLENNIUM; + + break; + + case 'century': + case 'centuries': + $years += $intValue * CarbonInterface::YEARS_PER_CENTURY; + + break; + + case 'decade': + case 'decades': + $years += $intValue * CarbonInterface::YEARS_PER_DECADE; + + break; + + case 'year': + case 'years': + case 'y': + case 'yr': + case 'yrs': + $years += $intValue; + + break; + + case 'quarter': + case 'quarters': + $months += $intValue * CarbonInterface::MONTHS_PER_QUARTER; + + break; + + case 'month': + case 'months': + case 'mo': + case 'mos': + $months += $intValue; + + break; + + case 'week': + case 'weeks': + case 'w': + $weeks += $intValue; + + if ($fraction) { + $parts[] = [null, $fraction * static::getDaysPerWeek(), 'd']; + } + + break; + + case 'day': + case 'days': + case 'd': + $days += $intValue; + + if ($fraction) { + $parts[] = [null, $fraction * static::getHoursPerDay(), 'h']; + } + + break; + + case 'hour': + case 'hours': + case 'h': + $hours += $intValue; + + if ($fraction) { + $parts[] = [null, $fraction * static::getMinutesPerHour(), 'm']; + } + + break; + + case 'minute': + case 'minutes': + case 'm': + $minutes += $intValue; + + if ($fraction) { + $parts[] = [null, $fraction * static::getSecondsPerMinute(), 's']; + } + + break; + + case 'second': + case 'seconds': + case 's': + $seconds += $intValue; + + if ($fraction) { + $parts[] = [null, $fraction * static::getMillisecondsPerSecond(), 'ms']; + } + + break; + + case 'millisecond': + case 'milliseconds': + case 'milli': + case 'ms': + $milliseconds += $intValue; + + if ($fraction) { + $microseconds += round($fraction * static::getMicrosecondsPerMillisecond()); + } + + break; + + case 'microsecond': + case 'microseconds': + case 'micro': + case 'µs': + $microseconds += $intValue; + + break; + + default: + throw new InvalidIntervalException( + sprintf('Invalid part %s in definition %s', $part, $intervalDefinition) + ); + } + } + + return new static($years, $months, $weeks, $days, $hours, $minutes, $seconds, $milliseconds * Carbon::MICROSECONDS_PER_MILLISECOND + $microseconds); + } + + /** + * Creates a CarbonInterval from string using a different locale. + * + * @param string $interval interval string in the given language (may also contain English). + * @param string|null $locale if locale is null or not specified, current global locale will be used instead. + * + * @return static + */ + public static function parseFromLocale($interval, $locale = null) + { + return static::fromString(Carbon::translateTimeString($interval, $locale ?: static::getLocale(), 'en')); + } + + private static function castIntervalToClass(DateInterval $interval, string $className, array $skip = []) + { + $mainClass = DateInterval::class; + + if (!is_a($className, $mainClass, true)) { + throw new InvalidCastException("$className is not a sub-class of $mainClass."); + } + + $microseconds = $interval->f; + $instance = new $className(static::getDateIntervalSpec($interval, false, $skip)); + + if ($microseconds) { + $instance->f = $microseconds; + } + + if ($interval instanceof self && is_a($className, self::class, true)) { + self::copyStep($interval, $instance); + } + + self::copyNegativeUnits($interval, $instance); + + return $instance; + } + + private static function copyNegativeUnits(DateInterval $from, DateInterval $to): void + { + $to->invert = $from->invert; + + foreach (['y', 'm', 'd', 'h', 'i', 's'] as $unit) { + if ($from->$unit < 0) { + $to->$unit *= -1; + } + } + } + + private static function copyStep(self $from, self $to): void + { + $to->setStep($from->getStep()); + } + + /** + * Cast the current instance into the given class. + * + * @param string $className The $className::instance() method will be called to cast the current object. + * + * @return DateInterval + */ + public function cast(string $className) + { + return self::castIntervalToClass($this, $className); + } + + /** + * 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. + * + * @param DateInterval $interval + * + * @return static + */ + public static function instance(DateInterval $interval, array $skip = []) + { + return self::castIntervalToClass($interval, static::class, $skip); + } + + /** + * 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|int|DateInterval|string|Closure|null $interval interval or number of the given $unit + * @param string|null $unit if specified, $interval must be an integer + * + * @return static|null + */ + public static function make($interval, $unit = null) + { + if ($unit) { + $interval = "$interval ".Carbon::pluralUnit($unit); + } + + if ($interval instanceof DateInterval) { + return static::instance($interval); + } + + if ($interval instanceof Closure) { + return new static($interval); + } + + if (!\is_string($interval)) { + return null; + } + + return static::makeFromString($interval); + } + + protected static function makeFromString(string $interval) + { + $interval = preg_replace('/\s+/', ' ', trim($interval)); + + if (preg_match('/^P[T\d]/', $interval)) { + return new static($interval); + } + + if (preg_match('/^(?:\h*\d+(?:\.\d+)?\h*[a-z]+)+$/i', $interval)) { + return static::fromString($interval); + } + + // @codeCoverageIgnoreStart + try { + /** @var static $interval */ + $interval = static::createFromDateString($interval); + } catch (DateMalformedIntervalStringException $e) { + return null; + } + // @codeCoverageIgnoreEnd + + return !$interval || $interval->isEmpty() ? null : $interval; + } + + protected function resolveInterval($interval) + { + if (!($interval instanceof self)) { + return self::make($interval); + } + + return $interval; + } + + /** + * Sets up a DateInterval from the relative parts of the string. + * + * @param string $time + * + * @return static + * + * @link https://php.net/manual/en/dateinterval.createfromdatestring.php + */ + #[ReturnTypeWillChange] + public static function createFromDateString($time) + { + $interval = @parent::createFromDateString(strtr($time, [ + ',' => ' ', + ' and ' => ' ', + ])); + + if ($interval instanceof DateInterval) { + $interval = static::instance($interval); + } + + return $interval; + } + + /////////////////////////////////////////////////////////////////// + ///////////////////////// GETTERS AND SETTERS ///////////////////// + /////////////////////////////////////////////////////////////////// + + /** + * Get a part of the CarbonInterval object. + * + * @param string $name + * + * @throws UnknownGetterException + * + * @return int|float|string + */ + public function get($name) + { + if (str_starts_with($name, '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 'milli': + case 'milliseconds': + return (int) (round($this->f * Carbon::MICROSECONDS_PER_SECOND) / Carbon::MICROSECONDS_PER_MILLISECOND); + + case 'micro': + case 'microseconds': + return (int) round($this->f * Carbon::MICROSECONDS_PER_SECOND); + + case 'microExcludeMilli': + return (int) round($this->f * Carbon::MICROSECONDS_PER_SECOND) % Carbon::MICROSECONDS_PER_MILLISECOND; + + case 'weeks': + return (int) ($this->d / (int) static::getDaysPerWeek()); + + case 'daysExcludeWeeks': + case 'dayzExcludeWeeks': + return $this->d % (int) static::getDaysPerWeek(); + + case 'locale': + return $this->getTranslatorLocale(); + + default: + throw new UnknownGetterException($name); + } + } + + /** + * Get a part of the CarbonInterval object. + * + * @param string $name + * + * @throws UnknownGetterException + * + * @return int|float|string + */ + public function __get($name) + { + return $this->get($name); + } + + /** + * Set a part of the CarbonInterval object. + * + * @param string|array $name + * @param int $value + * + * @throws UnknownSetterException + * + * @return $this + */ + public function set($name, $value = null) + { + $properties = \is_array($name) ? $name : [$name => $value]; + + foreach ($properties as $key => $value) { + switch (Carbon::singularUnit(rtrim($key, 'z'))) { + case 'year': + $this->y = $value; + + break; + + case 'month': + $this->m = $value; + + break; + + case 'week': + $this->d = $value * (int) static::getDaysPerWeek(); + + break; + + case 'day': + $this->d = $value; + + break; + + case 'daysexcludeweek': + case 'dayzexcludeweek': + $this->d = $this->weeks * (int) static::getDaysPerWeek() + $value; + + break; + + case 'hour': + $this->h = $value; + + break; + + case 'minute': + $this->i = $value; + + break; + + case 'second': + $this->s = $value; + + break; + + case 'milli': + case 'millisecond': + $this->microseconds = $value * Carbon::MICROSECONDS_PER_MILLISECOND + $this->microseconds % Carbon::MICROSECONDS_PER_MILLISECOND; + + break; + + case 'micro': + case 'microsecond': + $this->f = $value / Carbon::MICROSECONDS_PER_SECOND; + + break; + + default: + if ($this->localStrictModeEnabled ?? Carbon::isStrictModeEnabled()) { + throw new UnknownSetterException($key); + } + + $this->$key = $value; + } + } + + return $this; + } + + /** + * Set a part of the CarbonInterval object. + * + * @param string $name + * @param int $value + * + * @throws UnknownSetterException + */ + public function __set($name, $value) + { + $this->set($name, $value); + } + + /** + * 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; + } + + /** + * Returns true if the interval is empty for each unit. + * + * @return bool + */ + public function isEmpty() + { + return $this->years === 0 && + $this->months === 0 && + $this->dayz === 0 && + !$this->days && + $this->hours === 0 && + $this->minutes === 0 && + $this->seconds === 0 && + $this->microseconds === 0; + } + + /** + * Register a custom macro. + * + * @example + * ``` + * CarbonInterval::macro('twice', function () { + * return $this->times(2); + * }); + * echo CarbonInterval::hours(2)->twice(); + * ``` + * + * @param string $name + * @param object|callable $macro + * + * @return void + */ + public static function macro($name, $macro) + { + static::$macros[$name] = $macro; + } + + /** + * Register macros from a mixin object. + * + * @example + * ``` + * CarbonInterval::mixin(new class { + * public function daysToHours() { + * return function () { + * $this->hours += $this->days; + * $this->days = 0; + * + * return $this; + * }; + * } + * public function hoursToDays() { + * return function () { + * $this->days += $this->hours; + * $this->hours = 0; + * + * return $this; + * }; + * } + * }); + * echo CarbonInterval::hours(5)->hoursToDays() . "\n"; + * echo CarbonInterval::days(5)->daysToHours() . "\n"; + * ``` + * + * @param object|string $mixin + * + * @throws ReflectionException + * + * @return void + */ + public static function mixin($mixin) + { + static::baseMixin($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]; + + if ($macro instanceof Closure) { + $boundMacro = @$macro->bindTo($this, static::class) ?: @$macro->bindTo(null, static::class); + + return ($boundMacro ?: $macro)(...$parameters); + } + + return $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 $method magic method name called + * @param array $parameters parameters list + * + * @throws BadFluentSetterException|Throwable + * + * @return static + */ + public function __call($method, $parameters) + { + if (static::hasMacro($method)) { + return static::bindMacroContext($this, function () use (&$method, &$parameters) { + return $this->callMacro($method, $parameters); + }); + } + + $roundedValue = $this->callRoundMethod($method, $parameters); + + if ($roundedValue !== null) { + return $roundedValue; + } + + if (preg_match('/^(?add|sub)(?[A-Z].*)$/', $method, $match)) { + $value = $this->getMagicParameter($parameters, 0, Carbon::pluralUnit($match['unit']), 0); + + return $this->{$match['method']}($value, $match['unit']); + } + + $value = $this->getMagicParameter($parameters, 0, Carbon::pluralUnit($method), 1); + + try { + $this->set($method, $value); + } catch (UnknownSetterException $exception) { + if ($this->localStrictModeEnabled ?? Carbon::isStrictModeEnabled()) { + throw new BadFluentSetterException($method, 0, $exception); + } + } + + return $this; + } + + protected function getForHumansInitialVariables($syntax, $short) + { + if (\is_array($syntax)) { + return $syntax; + } + + if (\is_int($short)) { + return [ + 'parts' => $short, + 'short' => false, + ]; + } + + if (\is_bool($syntax)) { + return [ + 'short' => $syntax, + 'syntax' => CarbonInterface::DIFF_ABSOLUTE, + ]; + } + + return []; + } + + /** + * @param mixed $syntax + * @param mixed $short + * @param mixed $parts + * @param mixed $options + * + * @return array + */ + protected function getForHumansParameters($syntax = null, $short = false, $parts = -1, $options = null) + { + $optionalSpace = ' '; + $default = $this->getTranslationMessage('list.0') ?? $this->getTranslationMessage('list') ?? ' '; + $join = $default === '' ? '' : ' '; + $altNumbers = false; + $aUnit = false; + $minimumUnit = 's'; + $skip = []; + extract($this->getForHumansInitialVariables($syntax, $short)); + $skip = array_map('strtolower', array_filter((array) $skip, static function ($value) { + return \is_string($value) && $value !== ''; + })); + + if ($syntax === null) { + $syntax = CarbonInterface::DIFF_ABSOLUTE; + } + + if ($parts === -1) { + $parts = INF; + } + + if ($options === null) { + $options = static::getHumanDiffOptions(); + } + + if ($join === false) { + $join = ' '; + } elseif ($join === true) { + $join = [ + $default, + $this->getTranslationMessage('list.1') ?? $default, + ]; + } + + if ($altNumbers && $altNumbers !== true) { + $language = new Language($this->locale); + $altNumbers = \in_array($language->getCode(), (array) $altNumbers, true); + } + + if (\is_array($join)) { + [$default, $last] = $join; + + if ($default !== ' ') { + $optionalSpace = ''; + } + + $join = function ($list) use ($default, $last) { + if (\count($list) < 2) { + return implode('', $list); + } + + $end = array_pop($list); + + return implode($default, $list).$last.$end; + }; + } + + if (\is_string($join)) { + if ($join !== ' ') { + $optionalSpace = ''; + } + + $glue = $join; + $join = function ($list) use ($glue) { + return implode($glue, $list); + }; + } + + $interpolations = [ + ':optional-space' => $optionalSpace, + ]; + + return [$syntax, $short, $parts, $options, $join, $aUnit, $altNumbers, $interpolations, $minimumUnit, $skip]; + } + + protected static function getRoundingMethodFromOptions(int $options): ?string + { + if ($options & CarbonInterface::ROUND) { + return 'round'; + } + + if ($options & CarbonInterface::CEIL) { + return 'ceil'; + } + + if ($options & CarbonInterface::FLOOR) { + return 'floor'; + } + + return null; + } + + /** + * Returns interval values as an array where key are the unit names and values the counts. + * + * @return int[] + */ + public function toArray() + { + return [ + 'years' => $this->years, + 'months' => $this->months, + 'weeks' => $this->weeks, + 'days' => $this->daysExcludeWeeks, + 'hours' => $this->hours, + 'minutes' => $this->minutes, + 'seconds' => $this->seconds, + 'microseconds' => $this->microseconds, + ]; + } + + /** + * Returns interval non-zero values as an array where key are the unit names and values the counts. + * + * @return int[] + */ + public function getNonZeroValues() + { + return array_filter($this->toArray(), 'intval'); + } + + /** + * Returns interval values as an array where key are the unit names and values the counts + * from the biggest non-zero one the the smallest non-zero one. + * + * @return int[] + */ + public function getValuesSequence() + { + $nonZeroValues = $this->getNonZeroValues(); + + if ($nonZeroValues === []) { + return []; + } + + $keys = array_keys($nonZeroValues); + $firstKey = $keys[0]; + $lastKey = $keys[\count($keys) - 1]; + $values = []; + $record = false; + + foreach ($this->toArray() as $unit => $count) { + if ($unit === $firstKey) { + $record = true; + } + + if ($record) { + $values[$unit] = $count; + } + + if ($unit === $lastKey) { + $record = false; + } + } + + return $values; + } + + /** + * Get the current interval in a human readable format in the current locale. + * + * @example + * ``` + * echo CarbonInterval::fromString('4d 3h 40m')->forHumans() . "\n"; + * echo CarbonInterval::fromString('4d 3h 40m')->forHumans(['parts' => 2]) . "\n"; + * echo CarbonInterval::fromString('4d 3h 40m')->forHumans(['parts' => 3, 'join' => true]) . "\n"; + * echo CarbonInterval::fromString('4d 3h 40m')->forHumans(['short' => true]) . "\n"; + * echo CarbonInterval::fromString('1d 24h')->forHumans(['join' => ' or ']) . "\n"; + * echo CarbonInterval::fromString('1d 24h')->forHumans(['minimumUnit' => 'hour']) . "\n"; + * ``` + * + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'skip' entry, list of units to skip (array of strings or a single string, + * ` it can be the unit name (singular or plural) or its shortcut + * ` (y, m, w, d, h, min, s, ms, µs). + * - 'aUnit' entry, prefer "an hour" over "1 hour" if true + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * - 'minimumUnit' entry determines the smallest unit of time to display can be long or + * ` short form of the units, e.g. 'hour' or 'h' (default value: s) + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: -1: no limits) + * @param int $options human diff options + * + * @throws Exception + * + * @return string + */ + public function forHumans($syntax = null, $short = false, $parts = -1, $options = null) + { + [$syntax, $short, $parts, $options, $join, $aUnit, $altNumbers, $interpolations, $minimumUnit, $skip] = $this + ->getForHumansParameters($syntax, $short, $parts, $options); + + $interval = []; + + $syntax = (int) ($syntax ?? CarbonInterface::DIFF_ABSOLUTE); + $absolute = $syntax === CarbonInterface::DIFF_ABSOLUTE; + $relativeToNow = $syntax === CarbonInterface::DIFF_RELATIVE_TO_NOW; + $count = 1; + $unit = $short ? 's' : 'second'; + $isFuture = $this->invert === 1; + $transId = $relativeToNow ? ($isFuture ? 'from_now' : 'ago') : ($isFuture ? 'after' : 'before'); + $declensionMode = null; + + /** @var \Symfony\Component\Translation\Translator $translator */ + $translator = $this->getLocalTranslator(); + + $handleDeclensions = function ($unit, $count, $index = 0, $parts = 1) use ($interpolations, $transId, $translator, $altNumbers, $absolute, &$declensionMode) { + if (!$absolute) { + $declensionMode = $declensionMode ?? $this->translate($transId.'_mode'); + + if ($this->needsDeclension($declensionMode, $index, $parts)) { + // Some languages have special pluralization for past and future tense. + $key = $unit.'_'.$transId; + $result = $this->translate($key, $interpolations, $count, $translator, $altNumbers); + + if ($result !== $key) { + return $result; + } + } + } + + $result = $this->translate($unit, $interpolations, $count, $translator, $altNumbers); + + if ($result !== $unit) { + return $result; + } + + return null; + }; + + $intervalValues = $this; + $method = static::getRoundingMethodFromOptions($options); + + if ($method) { + $previousCount = INF; + + while ( + \count($intervalValues->getNonZeroValues()) > $parts && + ($count = \count($keys = array_keys($intervalValues->getValuesSequence()))) > 1 + ) { + $index = min($count, $previousCount - 1) - 2; + + if ($index < 0) { + break; + } + + $intervalValues = $this->copy()->roundUnit( + $keys[$index], + 1, + $method + ); + $previousCount = $count; + } + } + + $diffIntervalArray = [ + ['value' => $intervalValues->years, 'unit' => 'year', 'unitShort' => 'y'], + ['value' => $intervalValues->months, 'unit' => 'month', 'unitShort' => 'm'], + ['value' => $intervalValues->weeks, 'unit' => 'week', 'unitShort' => 'w'], + ['value' => $intervalValues->daysExcludeWeeks, 'unit' => 'day', 'unitShort' => 'd'], + ['value' => $intervalValues->hours, 'unit' => 'hour', 'unitShort' => 'h'], + ['value' => $intervalValues->minutes, 'unit' => 'minute', 'unitShort' => 'min'], + ['value' => $intervalValues->seconds, 'unit' => 'second', 'unitShort' => 's'], + ['value' => $intervalValues->milliseconds, 'unit' => 'millisecond', 'unitShort' => 'ms'], + ['value' => $intervalValues->microExcludeMilli, 'unit' => 'microsecond', 'unitShort' => 'µs'], + ]; + + if (!empty($skip)) { + foreach ($diffIntervalArray as $index => &$unitData) { + $nextIndex = $index + 1; + + if ($unitData['value'] && + isset($diffIntervalArray[$nextIndex]) && + \count(array_intersect([$unitData['unit'], $unitData['unit'].'s', $unitData['unitShort']], $skip)) + ) { + $diffIntervalArray[$nextIndex]['value'] += $unitData['value'] * + self::getFactorWithDefault($diffIntervalArray[$nextIndex]['unit'], $unitData['unit']); + $unitData['value'] = 0; + } + } + } + + $transChoice = function ($short, $unitData, $index, $parts) use ($absolute, $handleDeclensions, $translator, $aUnit, $altNumbers, $interpolations) { + $count = $unitData['value']; + + if ($short) { + $result = $handleDeclensions($unitData['unitShort'], $count, $index, $parts); + + if ($result !== null) { + return $result; + } + } elseif ($aUnit) { + $result = $handleDeclensions('a_'.$unitData['unit'], $count, $index, $parts); + + if ($result !== null) { + return $result; + } + } + + if (!$absolute) { + return $handleDeclensions($unitData['unit'], $count, $index, $parts); + } + + return $this->translate($unitData['unit'], $interpolations, $count, $translator, $altNumbers); + }; + + $fallbackUnit = ['second', 's']; + + foreach ($diffIntervalArray as $diffIntervalData) { + if ($diffIntervalData['value'] > 0) { + $unit = $short ? $diffIntervalData['unitShort'] : $diffIntervalData['unit']; + $count = $diffIntervalData['value']; + $interval[] = [$short, $diffIntervalData]; + } elseif ($options & CarbonInterface::SEQUENTIAL_PARTS_ONLY && \count($interval) > 0) { + break; + } + + // break the loop after we get the required number of parts in array + if (\count($interval) >= $parts) { + break; + } + + // break the loop after we have reached the minimum unit + if (\in_array($minimumUnit, [$diffIntervalData['unit'], $diffIntervalData['unitShort']], true)) { + $fallbackUnit = [$diffIntervalData['unit'], $diffIntervalData['unitShort']]; + + break; + } + } + + $actualParts = \count($interval); + + foreach ($interval as $index => &$item) { + $item = $transChoice($item[0], $item[1], $index, $actualParts); + } + + if (\count($interval) === 0) { + if ($relativeToNow && $options & CarbonInterface::JUST_NOW) { + $key = 'diff_now'; + $translation = $this->translate($key, $interpolations, null, $translator); + + if ($translation !== $key) { + return $translation; + } + } + + $count = $options & CarbonInterface::NO_ZERO_DIFF ? 1 : 0; + $unit = $fallbackUnit[$short ? 1 : 0]; + $interval[] = $this->translate($unit, $interpolations, $count, $translator, $altNumbers); + } + + // join the interval parts by a space + $time = $join($interval); + + unset($diffIntervalArray, $interval); + + if ($absolute) { + return $time; + } + + $isFuture = $this->invert === 1; + + $transId = $relativeToNow ? ($isFuture ? 'from_now' : 'ago') : ($isFuture ? 'after' : 'before'); + + if ($parts === 1) { + if ($relativeToNow && $unit === 'day') { + if ($count === 1 && $options & CarbonInterface::ONE_DAY_WORDS) { + $key = $isFuture ? 'diff_tomorrow' : 'diff_yesterday'; + $translation = $this->translate($key, $interpolations, null, $translator); + + if ($translation !== $key) { + return $translation; + } + } + + if ($count === 2 && $options & CarbonInterface::TWO_DAY_WORDS) { + $key = $isFuture ? 'diff_after_tomorrow' : 'diff_before_yesterday'; + $translation = $this->translate($key, $interpolations, null, $translator); + + if ($translation !== $key) { + return $translation; + } + } + } + + $aTime = $aUnit ? $handleDeclensions('a_'.$unit, $count) : null; + + $time = $aTime ?: $handleDeclensions($unit, $count) ?: $time; + } + + $time = [':time' => $time]; + + return $this->translate($transId, array_merge($time, $interpolations, $time), null, $translator); + } + + /** + * Format the instance as a string using the forHumans() function. + * + * @throws Exception + * + * @return string + */ + public function __toString() + { + $format = $this->localToStringFormat ?? static::$toStringFormat; + + if (!$format) { + return $this->forHumans(); + } + + if ($format instanceof Closure) { + return $format($this); + } + + return $this->format($format); + } + + /** + * Return native DateInterval PHP object matching the current instance. + * + * @example + * ``` + * var_dump(CarbonInterval::hours(2)->toDateInterval()); + * ``` + * + * @return DateInterval + */ + public function toDateInterval() + { + return self::castIntervalToClass($this, DateInterval::class); + } + + /** + * Convert the interval to a CarbonPeriod. + * + * @param DateTimeInterface|string|int ...$params Start date, [end date or recurrences] and optional settings. + * + * @return CarbonPeriod + */ + public function toPeriod(...$params) + { + if ($this->tzName) { + $tz = \is_string($this->tzName) ? new DateTimeZone($this->tzName) : $this->tzName; + + if ($tz instanceof DateTimeZone) { + array_unshift($params, $tz); + } + } + + return CarbonPeriod::create($this, ...$params); + } + + /** + * Invert the interval. + * + * @param bool|int $inverted if a parameter is passed, the passed value cast as 1 or 0 is used + * as the new value of the ->invert property. + * + * @return $this + */ + public function invert($inverted = null) + { + $this->invert = (\func_num_args() === 0 ? !$this->invert : $inverted) ? 1 : 0; + + return $this; + } + + protected function solveNegativeInterval() + { + if (!$this->isEmpty() && $this->years <= 0 && $this->months <= 0 && $this->dayz <= 0 && $this->hours <= 0 && $this->minutes <= 0 && $this->seconds <= 0 && $this->microseconds <= 0) { + $this->years *= -1; + $this->months *= -1; + $this->dayz *= -1; + $this->hours *= -1; + $this->minutes *= -1; + $this->seconds *= -1; + $this->microseconds *= -1; + $this->invert(); + } + + return $this; + } + + /** + * Add the passed interval to the current instance. + * + * @param string|DateInterval $unit + * @param int|float $value + * + * @return $this + */ + public function add($unit, $value = 1) + { + if (is_numeric($unit)) { + [$value, $unit] = [$unit, $value]; + } + + if (\is_string($unit) && !preg_match('/^\s*\d/', $unit)) { + $unit = "$value $unit"; + $value = 1; + } + + $interval = static::make($unit); + + if (!$interval) { + throw new InvalidIntervalException('This type of data cannot be added/subtracted.'); + } + + if ($value !== 1) { + $interval->times($value); + } + + $sign = ($this->invert === 1) !== ($interval->invert === 1) ? -1 : 1; + $this->years += $interval->y * $sign; + $this->months += $interval->m * $sign; + $this->dayz += ($interval->days === false ? $interval->d : $interval->days) * $sign; + $this->hours += $interval->h * $sign; + $this->minutes += $interval->i * $sign; + $this->seconds += $interval->s * $sign; + $this->microseconds += $interval->microseconds * $sign; + + $this->solveNegativeInterval(); + + return $this; + } + + /** + * Subtract the passed interval to the current instance. + * + * @param string|DateInterval $unit + * @param int|float $value + * + * @return $this + */ + public function sub($unit, $value = 1) + { + if (is_numeric($unit)) { + [$value, $unit] = [$unit, $value]; + } + + return $this->add($unit, -(float) $value); + } + + /** + * Subtract the passed interval to the current instance. + * + * @param string|DateInterval $unit + * @param int|float $value + * + * @return $this + */ + public function subtract($unit, $value = 1) + { + return $this->sub($unit, $value); + } + + /** + * Add given parameters to the current interval. + * + * @param int $years + * @param int $months + * @param int|float $weeks + * @param int|float $days + * @param int|float $hours + * @param int|float $minutes + * @param int|float $seconds + * @param int|float $microseconds + * + * @return $this + */ + public function plus( + $years = 0, + $months = 0, + $weeks = 0, + $days = 0, + $hours = 0, + $minutes = 0, + $seconds = 0, + $microseconds = 0 + ): self { + return $this->add(" + $years years $months months $weeks weeks $days days + $hours hours $minutes minutes $seconds seconds $microseconds microseconds + "); + } + + /** + * Add given parameters to the current interval. + * + * @param int $years + * @param int $months + * @param int|float $weeks + * @param int|float $days + * @param int|float $hours + * @param int|float $minutes + * @param int|float $seconds + * @param int|float $microseconds + * + * @return $this + */ + public function minus( + $years = 0, + $months = 0, + $weeks = 0, + $days = 0, + $hours = 0, + $minutes = 0, + $seconds = 0, + $microseconds = 0 + ): self { + return $this->sub(" + $years years $months months $weeks weeks $days days + $hours hours $minutes minutes $seconds seconds $microseconds microseconds + "); + } + + /** + * Multiply current instance given number of times. times() is naive, it multiplies each unit + * (so day can be greater than 31, hour can be greater than 23, etc.) and the result is rounded + * separately for each unit. + * + * Use times() when you want a fast and approximated calculation that does not cascade units. + * + * For a precise and cascaded calculation, + * + * @see multiply() + * + * @param float|int $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); + $this->microseconds = (int) round($this->microseconds * $factor); + + return $this; + } + + /** + * Divide current instance by a given divider. shares() is naive, it divides each unit separately + * and the result is rounded for each unit. So 5 hours and 20 minutes shared by 3 becomes 2 hours + * and 7 minutes. + * + * Use shares() when you want a fast and approximated calculation that does not cascade units. + * + * For a precise and cascaded calculation, + * + * @see divide() + * + * @param float|int $divider + * + * @return $this + */ + public function shares($divider) + { + return $this->times(1 / $divider); + } + + protected function copyProperties(self $interval, $ignoreSign = false) + { + $this->years = $interval->years; + $this->months = $interval->months; + $this->dayz = $interval->dayz; + $this->hours = $interval->hours; + $this->minutes = $interval->minutes; + $this->seconds = $interval->seconds; + $this->microseconds = $interval->microseconds; + + if (!$ignoreSign) { + $this->invert = $interval->invert; + } + + return $this; + } + + /** + * Multiply and cascade current instance by a given factor. + * + * @param float|int $factor + * + * @return $this + */ + public function multiply($factor) + { + if ($factor < 0) { + $this->invert = $this->invert ? 0 : 1; + $factor = -$factor; + } + + $yearPart = (int) floor($this->years * $factor); // Split calculation to prevent imprecision + + if ($yearPart) { + $this->years -= $yearPart / $factor; + } + + return $this->copyProperties( + static::create($yearPart) + ->microseconds(abs($this->totalMicroseconds) * $factor) + ->cascade(), + true + ); + } + + /** + * Divide and cascade current instance by a given divider. + * + * @param float|int $divider + * + * @return $this + */ + public function divide($divider) + { + return $this->multiply(1 / $divider); + } + + /** + * Get the interval_spec string of a date interval. + * + * @param DateInterval $interval + * + * @return string + */ + public static function getDateIntervalSpec(DateInterval $interval, bool $microseconds = false, array $skip = []) + { + $date = array_filter([ + static::PERIOD_YEARS => abs($interval->y), + static::PERIOD_MONTHS => abs($interval->m), + static::PERIOD_DAYS => abs($interval->d), + ]); + + if ( + $interval->days >= CarbonInterface::DAYS_PER_WEEK * CarbonInterface::WEEKS_PER_MONTH && + (!isset($date[static::PERIOD_YEARS]) || \count(array_intersect(['y', 'year', 'years'], $skip))) && + (!isset($date[static::PERIOD_MONTHS]) || \count(array_intersect(['m', 'month', 'months'], $skip))) + ) { + $date = [ + static::PERIOD_DAYS => abs($interval->days), + ]; + } + + $seconds = abs($interval->s); + if ($microseconds && $interval->f > 0) { + $seconds = sprintf('%d.%06d', $seconds, abs($interval->f) * 1000000); + } + + $time = array_filter([ + static::PERIOD_HOURS => abs($interval->h), + static::PERIOD_MINUTES => abs($interval->i), + static::PERIOD_SECONDS => $seconds, + ]); + + $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(bool $microseconds = false) + { + return static::getDateIntervalSpec($this, $microseconds); + } + + /** + * Comparing 2 date intervals. + * + * @param DateInterval $first + * @param DateInterval $second + * + * @return int + */ + public static function compareDateIntervals(DateInterval $first, DateInterval $second) + { + $current = Carbon::now(); + $passed = $current->avoidMutation()->add($second); + $current->add($first); + + 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); + } + + private function invertCascade(array $values) + { + return $this->set(array_map(function ($value) { + return -$value; + }, $values))->doCascade(true)->invert(); + } + + private function doCascade(bool $deep) + { + $originalData = $this->toArray(); + $originalData['milliseconds'] = (int) ($originalData['microseconds'] / static::getMicrosecondsPerMillisecond()); + $originalData['microseconds'] = $originalData['microseconds'] % static::getMicrosecondsPerMillisecond(); + $originalData['weeks'] = (int) ($this->d / static::getDaysPerWeek()); + $originalData['daysExcludeWeeks'] = fmod($this->d, static::getDaysPerWeek()); + unset($originalData['days']); + $newData = $originalData; + $previous = []; + + foreach (self::getFlipCascadeFactors() as $source => [$target, $factor]) { + foreach (['source', 'target'] as $key) { + if ($$key === 'dayz') { + $$key = 'daysExcludeWeeks'; + } + } + + $value = $newData[$source]; + $modulo = fmod($factor + fmod($value, $factor), $factor); + $newData[$source] = $modulo; + $newData[$target] += ($value - $modulo) / $factor; + + $decimalPart = fmod($newData[$source], 1); + + if ($decimalPart !== 0.0) { + $unit = $source; + + foreach ($previous as [$subUnit, $subFactor]) { + $newData[$unit] -= $decimalPart; + $newData[$subUnit] += $decimalPart * $subFactor; + $decimalPart = fmod($newData[$subUnit], 1); + + if ($decimalPart === 0.0) { + break; + } + + $unit = $subUnit; + } + } + + array_unshift($previous, [$source, $factor]); + } + + $positive = null; + + if (!$deep) { + foreach ($newData as $value) { + if ($value) { + if ($positive === null) { + $positive = ($value > 0); + + continue; + } + + if (($value > 0) !== $positive) { + return $this->invertCascade($originalData) + ->solveNegativeInterval(); + } + } + } + } + + return $this->set($newData) + ->solveNegativeInterval(); + } + + /** + * Convert overflowed values into bigger units. + * + * @return $this + */ + public function cascade() + { + return $this->doCascade(false); + } + + public function hasNegativeValues(): bool + { + foreach ($this->toArray() as $value) { + if ($value < 0) { + return true; + } + } + + return false; + } + + public function hasPositiveValues(): bool + { + foreach ($this->toArray() as $value) { + if ($value > 0) { + return true; + } + } + + return false; + } + + /** + * Get amount of given unit equivalent to the interval. + * + * @param string $unit + * + * @throws UnknownUnitException|UnitNotConfiguredException + * + * @return float + */ + public function total($unit) + { + $realUnit = $unit = strtolower($unit); + + if (\in_array($unit, ['days', 'weeks'])) { + $realUnit = 'dayz'; + } elseif (!\in_array($unit, ['microseconds', 'milliseconds', 'seconds', 'minutes', 'hours', 'dayz', 'months', 'years'])) { + throw new UnknownUnitException($unit); + } + + $result = 0; + $cumulativeFactor = 0; + $unitFound = false; + $factors = self::getFlipCascadeFactors(); + $daysPerWeek = (int) static::getDaysPerWeek(); + + $values = [ + 'years' => $this->years, + 'months' => $this->months, + 'weeks' => (int) ($this->d / $daysPerWeek), + 'dayz' => fmod($this->d, $daysPerWeek), + 'hours' => $this->hours, + 'minutes' => $this->minutes, + 'seconds' => $this->seconds, + 'milliseconds' => (int) ($this->microseconds / Carbon::MICROSECONDS_PER_MILLISECOND), + 'microseconds' => $this->microseconds % Carbon::MICROSECONDS_PER_MILLISECOND, + ]; + + if (isset($factors['dayz']) && $factors['dayz'][0] !== 'weeks') { + $values['dayz'] += $values['weeks'] * $daysPerWeek; + $values['weeks'] = 0; + } + + foreach ($factors as $source => [$target, $factor]) { + if ($source === $realUnit) { + $unitFound = true; + $value = $values[$source]; + $result += $value; + $cumulativeFactor = 1; + } + + if ($factor === false) { + if ($unitFound) { + break; + } + + $result = 0; + $cumulativeFactor = 0; + + continue; + } + + if ($target === $realUnit) { + $unitFound = true; + } + + if ($cumulativeFactor) { + $cumulativeFactor *= $factor; + $result += $values[$target] * $cumulativeFactor; + + continue; + } + + $value = $values[$source]; + + $result = ($result + $value) / $factor; + } + + if (isset($target) && !$cumulativeFactor) { + $result += $values[$target]; + } + + if (!$unitFound) { + throw new UnitNotConfiguredException($unit); + } + + if ($this->invert) { + $result *= -1; + } + + if ($unit === 'weeks') { + $result /= $daysPerWeek; + } + + // Cast as int numbers with no decimal part + return fmod($result, 1) === 0.0 ? (int) $result : $result; + } + + /** + * Determines if the instance is equal to another + * + * @param CarbonInterval|DateInterval|mixed $interval + * + * @see equalTo() + * + * @return bool + */ + public function eq($interval): bool + { + return $this->equalTo($interval); + } + + /** + * Determines if the instance is equal to another + * + * @param CarbonInterval|DateInterval|mixed $interval + * + * @return bool + */ + public function equalTo($interval): bool + { + $interval = $this->resolveInterval($interval); + + return $interval !== null && $this->totalMicroseconds === $interval->totalMicroseconds; + } + + /** + * Determines if the instance is not equal to another + * + * @param CarbonInterval|DateInterval|mixed $interval + * + * @see notEqualTo() + * + * @return bool + */ + public function ne($interval): bool + { + return $this->notEqualTo($interval); + } + + /** + * Determines if the instance is not equal to another + * + * @param CarbonInterval|DateInterval|mixed $interval + * + * @return bool + */ + public function notEqualTo($interval): bool + { + return !$this->eq($interval); + } + + /** + * Determines if the instance is greater (longer) than another + * + * @param CarbonInterval|DateInterval|mixed $interval + * + * @see greaterThan() + * + * @return bool + */ + public function gt($interval): bool + { + return $this->greaterThan($interval); + } + + /** + * Determines if the instance is greater (longer) than another + * + * @param CarbonInterval|DateInterval|mixed $interval + * + * @return bool + */ + public function greaterThan($interval): bool + { + $interval = $this->resolveInterval($interval); + + return $interval === null || $this->totalMicroseconds > $interval->totalMicroseconds; + } + + /** + * Determines if the instance is greater (longer) than or equal to another + * + * @param CarbonInterval|DateInterval|mixed $interval + * + * @see greaterThanOrEqualTo() + * + * @return bool + */ + public function gte($interval): bool + { + return $this->greaterThanOrEqualTo($interval); + } + + /** + * Determines if the instance is greater (longer) than or equal to another + * + * @param CarbonInterval|DateInterval|mixed $interval + * + * @return bool + */ + public function greaterThanOrEqualTo($interval): bool + { + return $this->greaterThan($interval) || $this->equalTo($interval); + } + + /** + * Determines if the instance is less (shorter) than another + * + * @param CarbonInterval|DateInterval|mixed $interval + * + * @see lessThan() + * + * @return bool + */ + public function lt($interval): bool + { + return $this->lessThan($interval); + } + + /** + * Determines if the instance is less (shorter) than another + * + * @param CarbonInterval|DateInterval|mixed $interval + * + * @return bool + */ + public function lessThan($interval): bool + { + $interval = $this->resolveInterval($interval); + + return $interval !== null && $this->totalMicroseconds < $interval->totalMicroseconds; + } + + /** + * Determines if the instance is less (shorter) than or equal to another + * + * @param CarbonInterval|DateInterval|mixed $interval + * + * @see lessThanOrEqualTo() + * + * @return bool + */ + public function lte($interval): bool + { + return $this->lessThanOrEqualTo($interval); + } + + /** + * Determines if the instance is less (shorter) than or equal to another + * + * @param CarbonInterval|DateInterval|mixed $interval + * + * @return bool + */ + public function lessThanOrEqualTo($interval): bool + { + return $this->lessThan($interval) || $this->equalTo($interval); + } + + /** + * Determines if the instance is between two others. + * + * The third argument allow you to specify if bounds are included or not (true by default) + * but for when you including/excluding bounds may produce different results in your application, + * we recommend to use the explicit methods ->betweenIncluded() or ->betweenExcluded() instead. + * + * @example + * ``` + * CarbonInterval::hours(48)->between(CarbonInterval::day(), CarbonInterval::days(3)); // true + * CarbonInterval::hours(48)->between(CarbonInterval::day(), CarbonInterval::hours(36)); // false + * CarbonInterval::hours(48)->between(CarbonInterval::day(), CarbonInterval::days(2)); // true + * CarbonInterval::hours(48)->between(CarbonInterval::day(), CarbonInterval::days(2), false); // false + * ``` + * + * @param CarbonInterval|DateInterval|mixed $interval1 + * @param CarbonInterval|DateInterval|mixed $interval2 + * @param bool $equal Indicates if an equal to comparison should be done + * + * @return bool + */ + public function between($interval1, $interval2, $equal = true): bool + { + return $equal + ? $this->greaterThanOrEqualTo($interval1) && $this->lessThanOrEqualTo($interval2) + : $this->greaterThan($interval1) && $this->lessThan($interval2); + } + + /** + * Determines if the instance is between two others, bounds excluded. + * + * @example + * ``` + * CarbonInterval::hours(48)->betweenExcluded(CarbonInterval::day(), CarbonInterval::days(3)); // true + * CarbonInterval::hours(48)->betweenExcluded(CarbonInterval::day(), CarbonInterval::hours(36)); // false + * CarbonInterval::hours(48)->betweenExcluded(CarbonInterval::day(), CarbonInterval::days(2)); // true + * ``` + * + * @param CarbonInterval|DateInterval|mixed $interval1 + * @param CarbonInterval|DateInterval|mixed $interval2 + * + * @return bool + */ + public function betweenIncluded($interval1, $interval2): bool + { + return $this->between($interval1, $interval2, true); + } + + /** + * Determines if the instance is between two others, bounds excluded. + * + * @example + * ``` + * CarbonInterval::hours(48)->betweenExcluded(CarbonInterval::day(), CarbonInterval::days(3)); // true + * CarbonInterval::hours(48)->betweenExcluded(CarbonInterval::day(), CarbonInterval::hours(36)); // false + * CarbonInterval::hours(48)->betweenExcluded(CarbonInterval::day(), CarbonInterval::days(2)); // false + * ``` + * + * @param CarbonInterval|DateInterval|mixed $interval1 + * @param CarbonInterval|DateInterval|mixed $interval2 + * + * @return bool + */ + public function betweenExcluded($interval1, $interval2): bool + { + return $this->between($interval1, $interval2, false); + } + + /** + * Determines if the instance is between two others + * + * @example + * ``` + * CarbonInterval::hours(48)->isBetween(CarbonInterval::day(), CarbonInterval::days(3)); // true + * CarbonInterval::hours(48)->isBetween(CarbonInterval::day(), CarbonInterval::hours(36)); // false + * CarbonInterval::hours(48)->isBetween(CarbonInterval::day(), CarbonInterval::days(2)); // true + * CarbonInterval::hours(48)->isBetween(CarbonInterval::day(), CarbonInterval::days(2), false); // false + * ``` + * + * @param CarbonInterval|DateInterval|mixed $interval1 + * @param CarbonInterval|DateInterval|mixed $interval2 + * @param bool $equal Indicates if an equal to comparison should be done + * + * @return bool + */ + public function isBetween($interval1, $interval2, $equal = true): bool + { + return $this->between($interval1, $interval2, $equal); + } + + /** + * Round the current instance at the given unit with given precision if specified and the given function. + * + * @param string $unit + * @param float|int|string|DateInterval|null $precision + * @param string $function + * + * @throws Exception + * + * @return $this + */ + public function roundUnit($unit, $precision = 1, $function = 'round') + { + if (static::getCascadeFactors() !== static::getDefaultCascadeFactors()) { + $value = $function($this->total($unit) / $precision) * $precision; + $inverted = $value < 0; + + return $this->copyProperties(self::fromString( + number_format(abs($value), 12, '.', '').' '.$unit + )->invert($inverted)->cascade()); + } + + $base = CarbonImmutable::parse('2000-01-01 00:00:00', 'UTC') + ->roundUnit($unit, $precision, $function); + $next = $base->add($this); + $inverted = $next < $base; + + if ($inverted) { + $next = $base->sub($this); + } + + $this->copyProperties( + $next + ->roundUnit($unit, $precision, $function) + ->diffAsCarbonInterval($base) + ); + + return $this->invert($inverted); + } + + /** + * Truncate the current instance at the given unit with given precision if specified. + * + * @param string $unit + * @param float|int|string|DateInterval|null $precision + * + * @throws Exception + * + * @return $this + */ + public function floorUnit($unit, $precision = 1) + { + return $this->roundUnit($unit, $precision, 'floor'); + } + + /** + * Ceil the current instance at the given unit with given precision if specified. + * + * @param string $unit + * @param float|int|string|DateInterval|null $precision + * + * @throws Exception + * + * @return $this + */ + public function ceilUnit($unit, $precision = 1) + { + return $this->roundUnit($unit, $precision, 'ceil'); + } + + /** + * Round the current instance second with given precision if specified. + * + * @param float|int|string|DateInterval|null $precision + * @param string $function + * + * @throws Exception + * + * @return $this + */ + public function round($precision = 1, $function = 'round') + { + return $this->roundWith($precision, $function); + } + + /** + * Round the current instance second with given precision if specified. + * + * @param float|int|string|DateInterval|null $precision + * + * @throws Exception + * + * @return $this + */ + public function floor($precision = 1) + { + return $this->round($precision, 'floor'); + } + + /** + * Ceil the current instance second with given precision if specified. + * + * @param float|int|string|DateInterval|null $precision + * + * @throws Exception + * + * @return $this + */ + public function ceil($precision = 1) + { + return $this->round($precision, 'ceil'); + } + + private function needsDeclension(string $mode, int $index, int $parts): bool + { + switch ($mode) { + case 'last': + return $index === $parts - 1; + default: + return true; + } + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/CarbonPeriod.php b/vendor/nesbot/carbon/src/Carbon/CarbonPeriod.php new file mode 100644 index 0000000..0167914 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/CarbonPeriod.php @@ -0,0 +1,2714 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +use Carbon\Exceptions\EndLessPeriodException; +use Carbon\Exceptions\InvalidCastException; +use Carbon\Exceptions\InvalidIntervalException; +use Carbon\Exceptions\InvalidPeriodDateException; +use Carbon\Exceptions\InvalidPeriodParameterException; +use Carbon\Exceptions\NotACarbonClassException; +use Carbon\Exceptions\NotAPeriodException; +use Carbon\Exceptions\UnknownGetterException; +use Carbon\Exceptions\UnknownMethodException; +use Carbon\Exceptions\UnreachableException; +use Carbon\Traits\IntervalRounding; +use Carbon\Traits\Mixin; +use Carbon\Traits\Options; +use Carbon\Traits\ToStringFormat; +use Closure; +use Countable; +use DateInterval; +use DatePeriod; +use DateTime; +use DateTimeImmutable; +use DateTimeInterface; +use DateTimeZone; +use InvalidArgumentException; +use Iterator; +use JsonSerializable; +use ReflectionException; +use ReturnTypeWillChange; +use RuntimeException; + +/** + * Substitution of DatePeriod with some modifications and many more features. + * + * @property-read int|float $recurrences number of recurrences (if end not set). + * @property-read bool $include_start_date rather the start date is included in the iteration. + * @property-read bool $include_end_date rather the end date is included in the iteration (if recurrences not set). + * @property-read CarbonInterface $start Period start date. + * @property-read CarbonInterface $current Current date from the iteration. + * @property-read CarbonInterface $end Period end date. + * @property-read CarbonInterval $interval Underlying date interval instance. Always present, one day by default. + * + * @method static CarbonPeriod start($date, $inclusive = null) Create instance specifying start date or modify the start date if called on an instance. + * @method static CarbonPeriod since($date, $inclusive = null) Alias for start(). + * @method static CarbonPeriod sinceNow($inclusive = null) Create instance with start date set to now or set the start date to now if called on an instance. + * @method static CarbonPeriod end($date = null, $inclusive = null) Create instance specifying end date or modify the end date if called on an instance. + * @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 or set the end date to now if called on an instance. + * @method static CarbonPeriod dates($start, $end = null) Create instance with start and end dates or modify the start and end dates if called on an instance. + * @method static CarbonPeriod between($start, $end = null) Create instance with start and end dates or modify the start and end dates if called on an instance. + * @method static CarbonPeriod recurrences($recurrences = null) Create instance with maximum number of recurrences or modify the number of recurrences if called on an instance. + * @method static CarbonPeriod times($recurrences = null) Alias for recurrences(). + * @method static CarbonPeriod options($options = null) Create instance with options or modify the options if called on an instance. + * @method static CarbonPeriod toggle($options, $state = null) Create instance with options toggled on or off, or toggle options if called on an instance. + * @method static CarbonPeriod filter($callback, $name = null) Create instance with filter added to the stack or append a filter if called on an instance. + * @method static CarbonPeriod push($callback, $name = null) Alias for filter(). + * @method static CarbonPeriod prepend($callback, $name = null) Create instance with filter prepended to the stack or prepend a filter if called on an instance. + * @method static CarbonPeriod filters(array $filters = []) Create instance with filters stack or replace the whole filters stack if called on an instance. + * @method static CarbonPeriod interval($interval) Create instance with given date interval or modify the interval if called on an instance. + * @method static CarbonPeriod each($interval) Create instance with given date interval or modify the interval if called on an instance. + * @method static CarbonPeriod every($interval) Create instance with given date interval or modify the interval if called on an instance. + * @method static CarbonPeriod step($interval) Create instance with given date interval or modify the interval if called on an instance. + * @method static CarbonPeriod stepBy($interval) Create instance with given date interval or modify the interval if called on an instance. + * @method static CarbonPeriod invert() Create instance with inverted date interval or invert the interval if called on an instance. + * @method static CarbonPeriod years($years = 1) Create instance specifying a number of years for date interval or replace the interval by the given a number of years if called on an instance. + * @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 or replace the interval by the given a number of months if called on an instance. + * @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 or replace the interval by the given a number of weeks if called on an instance. + * @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 or replace the interval by the given a number of days if called on an instance. + * @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 or replace the interval by the given a number of hours if called on an instance. + * @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 or replace the interval by the given a number of minutes if called on an instance. + * @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 or replace the interval by the given a number of seconds if called on an instance. + * @method static CarbonPeriod second($seconds = 1) Alias for seconds(). + * @method $this roundYear(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. + * @method $this roundYears(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. + * @method $this floorYear(float $precision = 1) Truncate the current instance year with given precision. + * @method $this floorYears(float $precision = 1) Truncate the current instance year with given precision. + * @method $this ceilYear(float $precision = 1) Ceil the current instance year with given precision. + * @method $this ceilYears(float $precision = 1) Ceil the current instance year with given precision. + * @method $this roundMonth(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. + * @method $this roundMonths(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. + * @method $this floorMonth(float $precision = 1) Truncate the current instance month with given precision. + * @method $this floorMonths(float $precision = 1) Truncate the current instance month with given precision. + * @method $this ceilMonth(float $precision = 1) Ceil the current instance month with given precision. + * @method $this ceilMonths(float $precision = 1) Ceil the current instance month with given precision. + * @method $this roundWeek(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method $this roundWeeks(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method $this floorWeek(float $precision = 1) Truncate the current instance day with given precision. + * @method $this floorWeeks(float $precision = 1) Truncate the current instance day with given precision. + * @method $this ceilWeek(float $precision = 1) Ceil the current instance day with given precision. + * @method $this ceilWeeks(float $precision = 1) Ceil the current instance day with given precision. + * @method $this roundDay(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method $this roundDays(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method $this floorDay(float $precision = 1) Truncate the current instance day with given precision. + * @method $this floorDays(float $precision = 1) Truncate the current instance day with given precision. + * @method $this ceilDay(float $precision = 1) Ceil the current instance day with given precision. + * @method $this ceilDays(float $precision = 1) Ceil the current instance day with given precision. + * @method $this roundHour(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. + * @method $this roundHours(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. + * @method $this floorHour(float $precision = 1) Truncate the current instance hour with given precision. + * @method $this floorHours(float $precision = 1) Truncate the current instance hour with given precision. + * @method $this ceilHour(float $precision = 1) Ceil the current instance hour with given precision. + * @method $this ceilHours(float $precision = 1) Ceil the current instance hour with given precision. + * @method $this roundMinute(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. + * @method $this roundMinutes(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. + * @method $this floorMinute(float $precision = 1) Truncate the current instance minute with given precision. + * @method $this floorMinutes(float $precision = 1) Truncate the current instance minute with given precision. + * @method $this ceilMinute(float $precision = 1) Ceil the current instance minute with given precision. + * @method $this ceilMinutes(float $precision = 1) Ceil the current instance minute with given precision. + * @method $this roundSecond(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. + * @method $this roundSeconds(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. + * @method $this floorSecond(float $precision = 1) Truncate the current instance second with given precision. + * @method $this floorSeconds(float $precision = 1) Truncate the current instance second with given precision. + * @method $this ceilSecond(float $precision = 1) Ceil the current instance second with given precision. + * @method $this ceilSeconds(float $precision = 1) Ceil the current instance second with given precision. + * @method $this roundMillennium(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. + * @method $this roundMillennia(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. + * @method $this floorMillennium(float $precision = 1) Truncate the current instance millennium with given precision. + * @method $this floorMillennia(float $precision = 1) Truncate the current instance millennium with given precision. + * @method $this ceilMillennium(float $precision = 1) Ceil the current instance millennium with given precision. + * @method $this ceilMillennia(float $precision = 1) Ceil the current instance millennium with given precision. + * @method $this roundCentury(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. + * @method $this roundCenturies(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. + * @method $this floorCentury(float $precision = 1) Truncate the current instance century with given precision. + * @method $this floorCenturies(float $precision = 1) Truncate the current instance century with given precision. + * @method $this ceilCentury(float $precision = 1) Ceil the current instance century with given precision. + * @method $this ceilCenturies(float $precision = 1) Ceil the current instance century with given precision. + * @method $this roundDecade(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. + * @method $this roundDecades(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. + * @method $this floorDecade(float $precision = 1) Truncate the current instance decade with given precision. + * @method $this floorDecades(float $precision = 1) Truncate the current instance decade with given precision. + * @method $this ceilDecade(float $precision = 1) Ceil the current instance decade with given precision. + * @method $this ceilDecades(float $precision = 1) Ceil the current instance decade with given precision. + * @method $this roundQuarter(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. + * @method $this roundQuarters(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. + * @method $this floorQuarter(float $precision = 1) Truncate the current instance quarter with given precision. + * @method $this floorQuarters(float $precision = 1) Truncate the current instance quarter with given precision. + * @method $this ceilQuarter(float $precision = 1) Ceil the current instance quarter with given precision. + * @method $this ceilQuarters(float $precision = 1) Ceil the current instance quarter with given precision. + * @method $this roundMillisecond(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. + * @method $this roundMilliseconds(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. + * @method $this floorMillisecond(float $precision = 1) Truncate the current instance millisecond with given precision. + * @method $this floorMilliseconds(float $precision = 1) Truncate the current instance millisecond with given precision. + * @method $this ceilMillisecond(float $precision = 1) Ceil the current instance millisecond with given precision. + * @method $this ceilMilliseconds(float $precision = 1) Ceil the current instance millisecond with given precision. + * @method $this roundMicrosecond(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. + * @method $this roundMicroseconds(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. + * @method $this floorMicrosecond(float $precision = 1) Truncate the current instance microsecond with given precision. + * @method $this floorMicroseconds(float $precision = 1) Truncate the current instance microsecond with given precision. + * @method $this ceilMicrosecond(float $precision = 1) Ceil the current instance microsecond with given precision. + * @method $this ceilMicroseconds(float $precision = 1) Ceil the current instance microsecond with given precision. + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class CarbonPeriod implements Iterator, Countable, JsonSerializable +{ + use IntervalRounding; + use Mixin { + Mixin::mixin as baseMixin; + } + use Options; + use ToStringFormat; + + /** + * Built-in filter for limit by recurrences. + * + * @var callable + */ + public const RECURRENCES_FILTER = [self::class, 'filterRecurrences']; + + /** + * Built-in filter for limit to an end. + * + * @var callable + */ + public const END_DATE_FILTER = [self::class, 'filterEndDate']; + + /** + * Special value which can be returned by filters to end iteration. Also a filter. + * + * @var callable + */ + public const END_ITERATION = [self::class, 'endIteration']; + + /** + * Exclude start date from iteration. + * + * @var int + */ + public const EXCLUDE_START_DATE = 1; + + /** + * Exclude end date from iteration. + * + * @var int + */ + public const EXCLUDE_END_DATE = 2; + + /** + * Yield CarbonImmutable instances. + * + * @var int + */ + public const IMMUTABLE = 4; + + /** + * Number of maximum attempts before giving up on finding next valid date. + * + * @var int + */ + public const NEXT_MAX_ATTEMPTS = 1000; + + /** + * Number of maximum attempts before giving up on finding end date. + * + * @var int + */ + public const END_MAX_ATTEMPTS = 10000; + + /** + * The registered macros. + * + * @var array + */ + protected static $macros = []; + + /** + * Date class of iteration items. + * + * @var string + */ + protected $dateClass = Carbon::class; + + /** + * Underlying date interval instance. Always present, one day by default. + * + * @var CarbonInterval + */ + protected $dateInterval; + + /** + * True once __construct is finished. + * + * @var bool + */ + protected $constructed = false; + + /** + * Whether current date interval was set by default. + * + * @var bool + */ + protected $isDefaultInterval; + + /** + * The filters stack. + * + * @var array + */ + protected $filters = []; + + /** + * Period start date. Applied on rewind. Always present, now by default. + * + * @var CarbonInterface + */ + protected $startDate; + + /** + * Period end date. For inverted interval should be before the start date. Applied via a filter. + * + * @var CarbonInterface|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 CarbonInterface + */ + 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; + + /** + * Timezone handler for settings() method. + * + * @var mixed + */ + protected $tzName; + + /** + * Make a CarbonPeriod instance from given variable if possible. + * + * @param mixed $var + * + * @return static|null + */ + public static function make($var) + { + try { + return static::instance($var); + } catch (NotAPeriodException $e) { + return static::create($var); + } + } + + /** + * Create a new instance from a DatePeriod or CarbonPeriod object. + * + * @param CarbonPeriod|DatePeriod $period + * + * @return static + */ + public static function instance($period) + { + if ($period instanceof static) { + return $period->copy(); + } + + if ($period instanceof self) { + return new static( + $period->getStartDate(), + $period->getEndDate() ?: $period->getRecurrences(), + $period->getDateInterval(), + $period->getOptions() + ); + } + + if ($period instanceof DatePeriod) { + return new static( + $period->start, + $period->end ?: ($period->recurrences - 1), + $period->interval, + $period->include_start_date ? 0 : static::EXCLUDE_START_DATE + ); + } + + $class = static::class; + $type = \gettype($period); + + throw new NotAPeriodException( + 'Argument 1 passed to '.$class.'::'.__METHOD__.'() '. + 'must be an instance of DatePeriod or '.$class.', '. + ($type === 'object' ? 'instance of '.\get_class($period) : $type).' given.' + ); + } + + /** + * Create a new instance. + * + * @return static + */ + public static function create(...$params) + { + return static::createFromArray($params); + } + + /** + * Create a new instance from an array of parameters. + * + * @param array $params + * + * @return static + */ + public static function createFromArray(array $params) + { + return new static(...$params); + } + + /** + * 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) + { + return $interval->h || $interval->i || $interval->s || $interval->f; + } + + /** + * 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. + * + * @SuppressWarnings(PHPMD.ElseExpression) + * + * @param string $iso + * + * @return array + */ + protected static function parseIso8601($iso) + { + $result = []; + + $interval = null; + $start = null; + $end = null; + + foreach (explode('/', $iso) as $key => $part) { + if ($key === 0 && preg_match('/^R(\d*|INF)$/', $part, $match)) { + $parsed = \strlen($match[1]) ? (($match[1] !== 'INF') ? (int) $match[1] : INF) : 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 InvalidPeriodParameterException("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('/\d+/', '[0-9]+', preg_quote($target, '/')).'$/'; + + $result = preg_replace($pattern, $target, $source, 1, $count); + + return $count ? $result : $target; + } + + /** + * Register a custom macro. + * + * @example + * ``` + * CarbonPeriod::macro('middle', function () { + * return $this->getStartDate()->average($this->getEndDate()); + * }); + * echo CarbonPeriod::since('2011-05-12')->until('2011-06-03')->middle(); + * ``` + * + * @param string $name + * @param object|callable $macro + * + * @return void + */ + public static function macro($name, $macro) + { + static::$macros[$name] = $macro; + } + + /** + * Register macros from a mixin object. + * + * @example + * ``` + * CarbonPeriod::mixin(new class { + * public function addDays() { + * return function ($count = 1) { + * return $this->setStartDate( + * $this->getStartDate()->addDays($count) + * )->setEndDate( + * $this->getEndDate()->addDays($count) + * ); + * }; + * } + * public function subDays() { + * return function ($count = 1) { + * return $this->setStartDate( + * $this->getStartDate()->subDays($count) + * )->setEndDate( + * $this->getEndDate()->subDays($count) + * ); + * }; + * } + * }); + * echo CarbonPeriod::create('2000-01-01', '2000-02-01')->addDays(5)->subDays(3); + * ``` + * + * @param object|string $mixin + * + * @throws ReflectionException + * + * @return void + */ + public static function mixin($mixin) + { + static::baseMixin($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) + { + $date = new static(); + + if (static::hasMacro($method)) { + return static::bindMacroContext(null, function () use (&$method, &$parameters, &$date) { + return $date->callMacro($method, $parameters); + }); + } + + return $date->$method(...$parameters); + } + + /** + * CarbonPeriod constructor. + * + * @SuppressWarnings(PHPMD.ElseExpression) + * + * @throws InvalidArgumentException + */ + public function __construct(...$arguments) + { + // 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. + + $argumentsCount = \count($arguments); + + if ($argumentsCount && static::isIso8601($iso = $arguments[0])) { + array_splice($arguments, 0, 1, static::parseIso8601($iso)); + } + + if ($argumentsCount === 1) { + if ($arguments[0] instanceof DatePeriod) { + $arguments = [ + $arguments[0]->start, + $arguments[0]->end ?: ($arguments[0]->recurrences - 1), + $arguments[0]->interval, + $arguments[0]->include_start_date ? 0 : static::EXCLUDE_START_DATE, + ]; + } elseif ($arguments[0] instanceof self) { + $arguments = [ + $arguments[0]->getStartDate(), + $arguments[0]->getEndDate() ?: $arguments[0]->getRecurrences(), + $arguments[0]->getDateInterval(), + $arguments[0]->getOptions(), + ]; + } + } + + foreach ($arguments as $argument) { + $parsedDate = null; + + if ($argument instanceof DateTimeZone) { + $this->setTimezone($argument); + } elseif ($this->dateInterval === null && + ( + (\is_string($argument) && preg_match( + '/^(-?\d(\d(?![\/-])|[^\d\/-]([\/-])?)*|P[T\d].*|(?:\h*\d+(?:\.\d+)?\h*[a-z]+)+)$/i', + $argument + )) || + $argument instanceof DateInterval || + $argument instanceof Closure + ) && + $parsedInterval = @CarbonInterval::make($argument) + ) { + $this->setDateInterval($parsedInterval); + } elseif ($this->startDate === null && $parsedDate = $this->makeDateTime($argument)) { + $this->setStartDate($parsedDate); + } elseif ($this->endDate === null && ($parsedDate = $parsedDate ?? $this->makeDateTime($argument))) { + $this->setEndDate($parsedDate); + } 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 InvalidPeriodParameterException('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); + } + + $this->constructed = true; + } + + /** + * Get a copy of the instance. + * + * @return static + */ + public function copy() + { + return clone $this; + } + + /** + * Prepare the instance to be set (self if mutable to be mutated, + * copy if immutable to generate a new instance). + * + * @return static + */ + protected function copyIfImmutable() + { + return $this; + } + + /** + * Get the getter for a property allowing both `DatePeriod` snakeCase and camelCase names. + * + * @param string $name + * + * @return callable|null + */ + protected function getGetter(string $name) + { + switch (strtolower(preg_replace('/[A-Z]/', '_$0', $name))) { + case 'start': + case 'start_date': + return [$this, 'getStartDate']; + case 'end': + case 'end_date': + return [$this, 'getEndDate']; + case 'interval': + case 'date_interval': + return [$this, 'getDateInterval']; + case 'recurrences': + return [$this, 'getRecurrences']; + case 'include_start_date': + return [$this, 'isStartIncluded']; + case 'include_end_date': + return [$this, 'isEndIncluded']; + case 'current': + return [$this, 'current']; + default: + return null; + } + } + + /** + * Get a property allowing both `DatePeriod` snakeCase and camelCase names. + * + * @param string $name + * + * @return bool|CarbonInterface|CarbonInterval|int|null + */ + public function get(string $name) + { + $getter = $this->getGetter($name); + + if ($getter) { + return $getter(); + } + + throw new UnknownGetterException($name); + } + + /** + * Get a property allowing both `DatePeriod` snakeCase and camelCase names. + * + * @param string $name + * + * @return bool|CarbonInterface|CarbonInterval|int|null + */ + public function __get(string $name) + { + return $this->get($name); + } + + /** + * Check if an attribute exists on the object + * + * @param string $name + * + * @return bool + */ + public function __isset(string $name): bool + { + return $this->getGetter($name) !== null; + } + + /** + * @alias copy + * + * Get a copy of the instance. + * + * @return static + */ + public function clone() + { + return clone $this; + } + + /** + * Set the iteration item class. + * + * @param string $dateClass + * + * @return static + */ + public function setDateClass(string $dateClass) + { + if (!is_a($dateClass, CarbonInterface::class, true)) { + throw new NotACarbonClassException($dateClass); + } + + $self = $this->copyIfImmutable(); + $self->dateClass = $dateClass; + + if (is_a($dateClass, Carbon::class, true)) { + $self->options = $self->options & ~static::IMMUTABLE; + } elseif (is_a($dateClass, CarbonImmutable::class, true)) { + $self->options = $self->options | static::IMMUTABLE; + } + + return $self; + } + + /** + * Returns iteration item date class. + * + * @return string + */ + public function getDateClass(): string + { + return $this->dateClass; + } + + /** + * Change the period date interval. + * + * @param DateInterval|string $interval + * + * @throws InvalidIntervalException + * + * @return static + */ + public function setDateInterval($interval) + { + if (!$interval = CarbonInterval::make($interval)) { + throw new InvalidIntervalException('Invalid interval.'); + } + + if ($interval->spec() === 'PT0S' && !$interval->f && !$interval->getStep()) { + throw new InvalidIntervalException('Empty interval is not accepted.'); + } + + $self = $this->copyIfImmutable(); + $self->dateInterval = $interval; + + $self->isDefaultInterval = false; + + $self->handleChangedParameters(); + + return $self; + } + + /** + * Invert the period date interval. + * + * @return static + */ + public function invertDateInterval() + { + return $this->setDateInterval($this->dateInterval->invert()); + } + + /** + * Set start and end date. + * + * @param DateTime|DateTimeInterface|string $start + * @param DateTime|DateTimeInterface|string|null $end + * + * @return static + */ + public function setDates($start, $end) + { + return $this->setStartDate($start)->setEndDate($end); + } + + /** + * Change the period options. + * + * @param int|null $options + * + * @throws InvalidArgumentException + * + * @return static + */ + public function setOptions($options) + { + if (!\is_int($options) && $options !== null) { + throw new InvalidPeriodParameterException('Invalid options.'); + } + + $self = $this->copyIfImmutable(); + $self->options = $options ?: 0; + + $self->handleChangedParameters(); + + return $self; + } + + /** + * 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 static + */ + 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 static + */ + public function excludeStartDate($state = true) + { + return $this->toggleOptions(static::EXCLUDE_START_DATE, $state); + } + + /** + * Toggle EXCLUDE_END_DATE option. + * + * @param bool $state + * + * @return static + */ + 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. + * + * @param string|null $rounding Optional rounding 'floor', 'ceil', 'round' using the period interval. + * + * @return CarbonInterface + */ + public function getStartDate(string $rounding = null) + { + $date = $this->startDate->avoidMutation(); + + return $rounding ? $date->round($this->getDateInterval(), $rounding) : $date; + } + + /** + * Get end date of the period. + * + * @param string|null $rounding Optional rounding 'floor', 'ceil', 'round' using the period interval. + * + * @return CarbonInterface|null + */ + public function getEndDate(string $rounding = null) + { + if (!$this->endDate) { + return null; + } + + $date = $this->endDate->avoidMutation(); + + return $rounding ? $date->round($this->getDateInterval(), $rounding) : $date; + } + + /** + * Get number of recurrences. + * + * @return int|float|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; + } + + /** + * Returns true if the start date should be included. + * + * @return bool + */ + public function isStartIncluded() + { + return !$this->isStartExcluded(); + } + + /** + * Returns true if the end date should be included. + * + * @return bool + */ + public function isEndIncluded() + { + return !$this->isEndExcluded(); + } + + /** + * Return the start if it's included by option, else return the start + 1 period interval. + * + * @return CarbonInterface + */ + public function getIncludedStartDate() + { + $start = $this->getStartDate(); + + if ($this->isStartExcluded()) { + return $start->add($this->getDateInterval()); + } + + return $start; + } + + /** + * Return the end if it's included by option, else return the end - 1 period interval. + * Warning: if the period has no fixed end, this method will iterate the period to calculate it. + * + * @return CarbonInterface + */ + public function getIncludedEndDate() + { + $end = $this->getEndDate(); + + if (!$end) { + return $this->calculateEnd(); + } + + if ($this->isEndExcluded()) { + return $end->sub($this->getDateInterval()); + } + + return $end; + } + + /** + * Add a filter to the stack. + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * + * @param callable $callback + * @param string $name + * + * @return static + */ + public function addFilter($callback, $name = null) + { + $self = $this->copyIfImmutable(); + $tuple = $self->createFilterTuple(\func_get_args()); + + $self->filters[] = $tuple; + + $self->handleChangedParameters(); + + return $self; + } + + /** + * Prepend a filter to the stack. + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * + * @param callable $callback + * @param string $name + * + * @return static + */ + public function prependFilter($callback, $name = null) + { + $self = $this->copyIfImmutable(); + $tuple = $self->createFilterTuple(\func_get_args()); + + array_unshift($self->filters, $tuple); + + $self->handleChangedParameters(); + + return $self; + } + + /** + * Remove a filter by instance or name. + * + * @param callable|string $filter + * + * @return static + */ + public function removeFilter($filter) + { + $self = $this->copyIfImmutable(); + $key = \is_callable($filter) ? 0 : 1; + + $self->filters = array_values(array_filter( + $this->filters, + function ($tuple) use ($key, $filter) { + return $tuple[$key] !== $filter; + } + )); + + $self->updateInternalState(); + + $self->handleChangedParameters(); + + return $self; + } + + /** + * 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 static + */ + public function setFilters(array $filters) + { + $self = $this->copyIfImmutable(); + $self->filters = $filters; + + $self->updateInternalState(); + + $self->handleChangedParameters(); + + return $self; + } + + /** + * Reset filters stack. + * + * @return static + */ + public function resetFilters() + { + $self = $this->copyIfImmutable(); + $self->filters = []; + + if ($self->endDate !== null) { + $self->filters[] = [static::END_DATE_FILTER, null]; + } + + if ($self->recurrences !== null) { + $self->filters[] = [static::RECURRENCES_FILTER, null]; + } + + $self->handleChangedParameters(); + + return $self; + } + + /** + * Add a recurrences filter (set maximum number of recurrences). + * + * @param int|float|null $recurrences + * + * @throws InvalidArgumentException + * + * @return static + */ + public function setRecurrences($recurrences) + { + if ((!is_numeric($recurrences) && $recurrences !== null) || $recurrences < 0) { + throw new InvalidPeriodParameterException('Invalid number of recurrences.'); + } + + if ($recurrences === null) { + return $this->removeFilter(static::RECURRENCES_FILTER); + } + + /** @var self $self */ + $self = $this->copyIfImmutable(); + $self->recurrences = $recurrences === INF ? INF : (int) $recurrences; + + if (!$self->hasFilter(static::RECURRENCES_FILTER)) { + return $self->addFilter(static::RECURRENCES_FILTER); + } + + $self->handleChangedParameters(); + + return $self; + } + + /** + * Change the period start date. + * + * @param DateTime|DateTimeInterface|string $date + * @param bool|null $inclusive + * + * @throws InvalidPeriodDateException + * + * @return static + */ + public function setStartDate($date, $inclusive = null) + { + if (!$this->isInfiniteDate($date) && !($date = ([$this->dateClass, 'make'])($date))) { + throw new InvalidPeriodDateException('Invalid start date.'); + } + + $self = $this->copyIfImmutable(); + $self->startDate = $date; + + if ($inclusive !== null) { + $self = $self->toggleOptions(static::EXCLUDE_START_DATE, !$inclusive); + } + + return $self; + } + + /** + * Change the period end date. + * + * @param DateTime|DateTimeInterface|string|null $date + * @param bool|null $inclusive + * + * @throws \InvalidArgumentException + * + * @return static + */ + public function setEndDate($date, $inclusive = null) + { + if ($date !== null && !$this->isInfiniteDate($date) && !$date = ([$this->dateClass, 'make'])($date)) { + throw new InvalidPeriodDateException('Invalid end date.'); + } + + if (!$date) { + return $this->removeFilter(static::END_DATE_FILTER); + } + + $self = $this->copyIfImmutable(); + $self->endDate = $date; + + if ($inclusive !== null) { + $self = $self->toggleOptions(static::EXCLUDE_END_DATE, !$inclusive); + } + + if (!$self->hasFilter(static::END_DATE_FILTER)) { + return $self->addFilter(static::END_DATE_FILTER); + } + + $self->handleChangedParameters(); + + return $self; + } + + /** + * Check if the current position is valid. + * + * @return bool + */ + #[ReturnTypeWillChange] + public function valid() + { + return $this->validateCurrentDate() === true; + } + + /** + * Return the current key. + * + * @return int|null + */ + #[ReturnTypeWillChange] + public function key() + { + return $this->valid() + ? $this->key + : null; + } + + /** + * Return the current date. + * + * @return CarbonInterface|null + */ + #[ReturnTypeWillChange] + public function current() + { + return $this->valid() + ? $this->prepareForReturn($this->current) + : null; + } + + /** + * Move forward to the next date. + * + * @throws RuntimeException + * + * @return void + */ + #[ReturnTypeWillChange] + 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 + */ + #[ReturnTypeWillChange] + public function rewind() + { + $this->key = 0; + $this->current = ([$this->dateClass, 'make'])($this->startDate); + $settings = $this->getSettings(); + + if ($this->hasLocalTranslator()) { + $settings['locale'] = $this->getTranslatorLocale(); + } + + $this->current->settings($settings); + $this->timezone = static::intervalHasTime($this->dateInterval) ? $this->current->getTimezone() : null; + + if ($this->timezone) { + $this->current = $this->current->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(); + } + + /** + * Format the date period as ISO 8601. + * + * @return string + */ + public function toIso8601String() + { + $parts = []; + + 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() + { + $format = $this->localToStringFormat ?? static::$toStringFormat; + + if ($format instanceof Closure) { + return $format($this); + } + + $translator = ([$this->dateClass, 'getTranslator'])(); + + $parts = []; + + $format = $format ?? ( + !$this->startDate->isStartOfDay() || ($this->endDate && !$this->endDate->isStartOfDay()) + ? 'Y-m-d H:i:s' + : 'Y-m-d' + ); + + if ($this->recurrences !== null) { + $parts[] = $this->translate('period_recurrences', [], $this->recurrences, $translator); + } + + $parts[] = $this->translate('period_interval', [':interval' => $this->dateInterval->forHumans([ + 'join' => true, + ])], null, $translator); + + $parts[] = $this->translate('period_start_date', [':date' => $this->startDate->rawFormat($format)], null, $translator); + + if ($this->endDate !== null) { + $parts[] = $this->translate('period_end_date', [':date' => $this->endDate->rawFormat($format)], null, $translator); + } + + $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(); + } + + /** + * Cast the current instance into the given class. + * + * @param string $className The $className::instance() method will be called to cast the current object. + * + * @return DatePeriod + */ + public function cast(string $className) + { + if (!method_exists($className, 'instance')) { + if (is_a($className, DatePeriod::class, true)) { + return new $className( + $this->rawDate($this->getStartDate()), + $this->getDateInterval(), + $this->getEndDate() ? $this->rawDate($this->getIncludedEndDate()) : $this->getRecurrences(), + $this->isStartExcluded() ? DatePeriod::EXCLUDE_START_DATE : 0 + ); + } + + throw new InvalidCastException("$className has not the instance() method needed to cast the date."); + } + + return $className::instance($this); + } + + /** + * Return native DatePeriod PHP object matching the current instance. + * + * @example + * ``` + * var_dump(CarbonPeriod::create('2021-01-05', '2021-02-15')->toDatePeriod()); + * ``` + * + * @return DatePeriod + */ + public function toDatePeriod() + { + return $this->cast(DatePeriod::class); + } + + /** + * Return `true` if the period has no custom filter and is guaranteed to be endless. + * + * Note that we can't check if a period is endless as soon as it has custom filters + * because filters can emit `CarbonPeriod::END_ITERATION` to stop the iteration in + * a way we can't predict without actually iterating the period. + */ + public function isUnfilteredAndEndLess(): bool + { + foreach ($this->filters as $filter) { + switch ($filter) { + case [static::RECURRENCES_FILTER, null]: + if ($this->recurrences !== null && is_finite($this->recurrences)) { + return false; + } + + break; + + case [static::END_DATE_FILTER, null]: + if ($this->endDate !== null && !$this->endDate->isEndOfTime()) { + return false; + } + + break; + + default: + return false; + } + } + + return true; + } + + /** + * Convert the date period into an array without changing current iteration state. + * + * @return CarbonInterface[] + */ + public function toArray() + { + if ($this->isUnfilteredAndEndLess()) { + throw new EndLessPeriodException("Endless period can't be converted to array nor counted."); + } + + $state = [ + $this->key, + $this->current ? $this->current->avoidMutation() : null, + $this->validationResult, + ]; + + $result = iterator_to_array($this); + + [$this->key, $this->current, $this->validationResult] = $state; + + return $result; + } + + /** + * Count dates in the date period. + * + * @return int + */ + #[ReturnTypeWillChange] + public function count() + { + return \count($this->toArray()); + } + + /** + * Return the first date in the date period. + * + * @return CarbonInterface|null + */ + public function first() + { + if ($this->isUnfilteredAndEndLess()) { + foreach ($this as $date) { + $this->rewind(); + + return $date; + } + + return null; + } + + return ($this->toArray() ?: [])[0] ?? null; + } + + /** + * Return the last date in the date period. + * + * @return CarbonInterface|null + */ + public function last() + { + $array = $this->toArray(); + + return $array ? $array[\count($array) - 1] : null; + } + + /** + * 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 static::bindMacroContext($this, function () use (&$method, &$parameters) { + return $this->callMacro($method, $parameters); + }); + } + + $roundedValue = $this->callRoundMethod($method, $parameters); + + if ($roundedValue !== null) { + return $roundedValue; + } + + switch ($method) { + case 'start': + case 'since': + self::setDefaultParameters($parameters, [ + [0, 'date', null], + ]); + + return $this->setStartDate(...$parameters); + + case 'sinceNow': + return $this->setStartDate(new Carbon(), ...$parameters); + + case 'end': + case 'until': + self::setDefaultParameters($parameters, [ + [0, 'date', null], + ]); + + return $this->setEndDate(...$parameters); + + case 'untilNow': + return $this->setEndDate(new Carbon(), ...$parameters); + + case 'dates': + case 'between': + self::setDefaultParameters($parameters, [ + [0, 'start', null], + [1, 'end', null], + ]); + + return $this->setDates(...$parameters); + + case 'recurrences': + case 'times': + self::setDefaultParameters($parameters, [ + [0, 'recurrences', null], + ]); + + return $this->setRecurrences(...$parameters); + + case 'options': + self::setDefaultParameters($parameters, [ + [0, 'options', null], + ]); + + return $this->setOptions(...$parameters); + + case 'toggle': + self::setDefaultParameters($parameters, [ + [0, 'options', null], + ]); + + return $this->toggleOptions(...$parameters); + + case 'filter': + case 'push': + return $this->addFilter(...$parameters); + + case 'prepend': + return $this->prependFilter(...$parameters); + + case 'filters': + self::setDefaultParameters($parameters, [ + [0, 'filters', []], + ]); + + return $this->setFilters(...$parameters); + + case 'interval': + case 'each': + case 'every': + case 'step': + case 'stepBy': + return $this->setDateInterval(...$parameters); + + 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(( + // Override default P1D when instantiating via fluent setters. + [$this->isDefaultInterval ? new CarbonInterval('PT0S') : $this->dateInterval, $method] + )(...$parameters)); + } + + if ($this->localStrictModeEnabled ?? Carbon::isStrictModeEnabled()) { + throw new UnknownMethodException($method); + } + + return $this; + } + + /** + * Set the instance's timezone from a string or object and apply it to start/end. + * + * @param \DateTimeZone|string $timezone + * + * @return static + */ + public function setTimezone($timezone) + { + $self = $this->copyIfImmutable(); + $self->tzName = $timezone; + $self->timezone = $timezone; + + if ($self->startDate) { + $self = $self->setStartDate($self->startDate->setTimezone($timezone)); + } + + if ($self->endDate) { + $self = $self->setEndDate($self->endDate->setTimezone($timezone)); + } + + return $self; + } + + /** + * Set the instance's timezone from a string or object and add/subtract the offset difference to start/end. + * + * @param \DateTimeZone|string $timezone + * + * @return static + */ + public function shiftTimezone($timezone) + { + $self = $this->copyIfImmutable(); + $self->tzName = $timezone; + $self->timezone = $timezone; + + if ($self->startDate) { + $self = $self->setStartDate($self->startDate->shiftTimezone($timezone)); + } + + if ($self->endDate) { + $self = $self->setEndDate($self->endDate->shiftTimezone($timezone)); + } + + return $self; + } + + /** + * Returns the end is set, else calculated from start an recurrences. + * + * @param string|null $rounding Optional rounding 'floor', 'ceil', 'round' using the period interval. + * + * @return CarbonInterface + */ + public function calculateEnd(string $rounding = null) + { + if ($end = $this->getEndDate($rounding)) { + return $end; + } + + if ($this->dateInterval->isEmpty()) { + return $this->getStartDate($rounding); + } + + $date = $this->getEndFromRecurrences() ?? $this->iterateUntilEnd(); + + if ($date && $rounding) { + $date = $date->avoidMutation()->round($this->getDateInterval(), $rounding); + } + + return $date; + } + + /** + * @return CarbonInterface|null + */ + private function getEndFromRecurrences() + { + if ($this->recurrences === null) { + throw new UnreachableException( + "Could not calculate period end without either explicit end or recurrences.\n". + "If you're looking for a forever-period, use ->setRecurrences(INF)." + ); + } + + if ($this->recurrences === INF) { + $start = $this->getStartDate(); + + return $start < $start->avoidMutation()->add($this->getDateInterval()) + ? CarbonImmutable::endOfTime() + : CarbonImmutable::startOfTime(); + } + + if ($this->filters === [[static::RECURRENCES_FILTER, null]]) { + return $this->getStartDate()->avoidMutation()->add( + $this->getDateInterval()->times( + $this->recurrences - ($this->isStartExcluded() ? 0 : 1) + ) + ); + } + + return null; + } + + /** + * @return CarbonInterface|null + */ + private function iterateUntilEnd() + { + $attempts = 0; + $date = null; + + foreach ($this as $date) { + if (++$attempts > static::END_MAX_ATTEMPTS) { + throw new UnreachableException( + 'Could not calculate period end after iterating '.static::END_MAX_ATTEMPTS.' times.' + ); + } + } + + return $date; + } + + /** + * Returns true if the current period overlaps the given one (if 1 parameter passed) + * or the period between 2 dates (if 2 parameters passed). + * + * @param CarbonPeriod|\DateTimeInterface|Carbon|CarbonImmutable|string $rangeOrRangeStart + * @param \DateTimeInterface|Carbon|CarbonImmutable|string|null $rangeEnd + * + * @return bool + */ + public function overlaps($rangeOrRangeStart, $rangeEnd = null) + { + $range = $rangeEnd ? static::create($rangeOrRangeStart, $rangeEnd) : $rangeOrRangeStart; + + if (!($range instanceof self)) { + $range = static::create($range); + } + + [$start, $end] = $this->orderCouple($this->getStartDate(), $this->calculateEnd()); + [$rangeStart, $rangeEnd] = $this->orderCouple($range->getStartDate(), $range->calculateEnd()); + + return $end > $rangeStart && $rangeEnd > $start; + } + + /** + * Execute a given function on each date of the period. + * + * @example + * ``` + * Carbon::create('2020-11-29')->daysUntil('2020-12-24')->forEach(function (Carbon $date) { + * echo $date->diffInDays('2020-12-25')." days before Christmas!\n"; + * }); + * ``` + * + * @param callable $callback + */ + public function forEach(callable $callback) + { + foreach ($this as $date) { + $callback($date); + } + } + + /** + * Execute a given function on each date of the period and yield the result of this function. + * + * @example + * ``` + * $period = Carbon::create('2020-11-29')->daysUntil('2020-12-24'); + * echo implode("\n", iterator_to_array($period->map(function (Carbon $date) { + * return $date->diffInDays('2020-12-25').' days before Christmas!'; + * }))); + * ``` + * + * @param callable $callback + * + * @return \Generator + */ + public function map(callable $callback) + { + foreach ($this as $date) { + yield $callback($date); + } + } + + /** + * Determines if the instance is equal to another. + * Warning: if options differ, instances wil never be equal. + * + * @param mixed $period + * + * @see equalTo() + * + * @return bool + */ + public function eq($period): bool + { + return $this->equalTo($period); + } + + /** + * Determines if the instance is equal to another. + * Warning: if options differ, instances wil never be equal. + * + * @param mixed $period + * + * @return bool + */ + public function equalTo($period): bool + { + if (!($period instanceof self)) { + $period = self::make($period); + } + + $end = $this->getEndDate(); + + return $period !== null + && $this->getDateInterval()->eq($period->getDateInterval()) + && $this->getStartDate()->eq($period->getStartDate()) + && ($end ? $end->eq($period->getEndDate()) : $this->getRecurrences() === $period->getRecurrences()) + && ($this->getOptions() & (~static::IMMUTABLE)) === ($period->getOptions() & (~static::IMMUTABLE)); + } + + /** + * Determines if the instance is not equal to another. + * Warning: if options differ, instances wil never be equal. + * + * @param mixed $period + * + * @see notEqualTo() + * + * @return bool + */ + public function ne($period): bool + { + return $this->notEqualTo($period); + } + + /** + * Determines if the instance is not equal to another. + * Warning: if options differ, instances wil never be equal. + * + * @param mixed $period + * + * @return bool + */ + public function notEqualTo($period): bool + { + return !$this->eq($period); + } + + /** + * Determines if the start date is before an other given date. + * (Rather start/end are included by options is ignored.) + * + * @param mixed $date + * + * @return bool + */ + public function startsBefore($date = null): bool + { + return $this->getStartDate()->lessThan($this->resolveCarbon($date)); + } + + /** + * Determines if the start date is before or the same as a given date. + * (Rather start/end are included by options is ignored.) + * + * @param mixed $date + * + * @return bool + */ + public function startsBeforeOrAt($date = null): bool + { + return $this->getStartDate()->lessThanOrEqualTo($this->resolveCarbon($date)); + } + + /** + * Determines if the start date is after an other given date. + * (Rather start/end are included by options is ignored.) + * + * @param mixed $date + * + * @return bool + */ + public function startsAfter($date = null): bool + { + return $this->getStartDate()->greaterThan($this->resolveCarbon($date)); + } + + /** + * Determines if the start date is after or the same as a given date. + * (Rather start/end are included by options is ignored.) + * + * @param mixed $date + * + * @return bool + */ + public function startsAfterOrAt($date = null): bool + { + return $this->getStartDate()->greaterThanOrEqualTo($this->resolveCarbon($date)); + } + + /** + * Determines if the start date is the same as a given date. + * (Rather start/end are included by options is ignored.) + * + * @param mixed $date + * + * @return bool + */ + public function startsAt($date = null): bool + { + return $this->getStartDate()->equalTo($this->resolveCarbon($date)); + } + + /** + * Determines if the end date is before an other given date. + * (Rather start/end are included by options is ignored.) + * + * @param mixed $date + * + * @return bool + */ + public function endsBefore($date = null): bool + { + return $this->calculateEnd()->lessThan($this->resolveCarbon($date)); + } + + /** + * Determines if the end date is before or the same as a given date. + * (Rather start/end are included by options is ignored.) + * + * @param mixed $date + * + * @return bool + */ + public function endsBeforeOrAt($date = null): bool + { + return $this->calculateEnd()->lessThanOrEqualTo($this->resolveCarbon($date)); + } + + /** + * Determines if the end date is after an other given date. + * (Rather start/end are included by options is ignored.) + * + * @param mixed $date + * + * @return bool + */ + public function endsAfter($date = null): bool + { + return $this->calculateEnd()->greaterThan($this->resolveCarbon($date)); + } + + /** + * Determines if the end date is after or the same as a given date. + * (Rather start/end are included by options is ignored.) + * + * @param mixed $date + * + * @return bool + */ + public function endsAfterOrAt($date = null): bool + { + return $this->calculateEnd()->greaterThanOrEqualTo($this->resolveCarbon($date)); + } + + /** + * Determines if the end date is the same as a given date. + * (Rather start/end are included by options is ignored.) + * + * @param mixed $date + * + * @return bool + */ + public function endsAt($date = null): bool + { + return $this->calculateEnd()->equalTo($this->resolveCarbon($date)); + } + + /** + * Return true if start date is now or later. + * (Rather start/end are included by options is ignored.) + * + * @return bool + */ + public function isStarted(): bool + { + return $this->startsBeforeOrAt(); + } + + /** + * Return true if end date is now or later. + * (Rather start/end are included by options is ignored.) + * + * @return bool + */ + public function isEnded(): bool + { + return $this->endsBeforeOrAt(); + } + + /** + * Return true if now is between start date (included) and end date (excluded). + * (Rather start/end are included by options is ignored.) + * + * @return bool + */ + public function isInProgress(): bool + { + return $this->isStarted() && !$this->isEnded(); + } + + /** + * Round the current instance at the given unit with given precision if specified and the given function. + * + * @param string $unit + * @param float|int|string|\DateInterval|null $precision + * @param string $function + * + * @return static + */ + public function roundUnit($unit, $precision = 1, $function = 'round') + { + $self = $this->copyIfImmutable(); + $self = $self->setStartDate($self->getStartDate()->roundUnit($unit, $precision, $function)); + + if ($self->endDate) { + $self = $self->setEndDate($self->getEndDate()->roundUnit($unit, $precision, $function)); + } + + return $self->setDateInterval($self->getDateInterval()->roundUnit($unit, $precision, $function)); + } + + /** + * Truncate the current instance at the given unit with given precision if specified. + * + * @param string $unit + * @param float|int|string|\DateInterval|null $precision + * + * @return static + */ + public function floorUnit($unit, $precision = 1) + { + return $this->roundUnit($unit, $precision, 'floor'); + } + + /** + * Ceil the current instance at the given unit with given precision if specified. + * + * @param string $unit + * @param float|int|string|\DateInterval|null $precision + * + * @return static + */ + public function ceilUnit($unit, $precision = 1) + { + return $this->roundUnit($unit, $precision, 'ceil'); + } + + /** + * Round the current instance second with given precision if specified (else period interval is used). + * + * @param float|int|string|\DateInterval|null $precision + * @param string $function + * + * @return static + */ + public function round($precision = null, $function = 'round') + { + return $this->roundWith( + $precision ?? $this->getDateInterval()->setLocalTranslator(TranslatorImmutable::get('en'))->forHumans(), + $function + ); + } + + /** + * Round the current instance second with given precision if specified (else period interval is used). + * + * @param float|int|string|\DateInterval|null $precision + * + * @return static + */ + public function floor($precision = null) + { + return $this->round($precision, 'floor'); + } + + /** + * Ceil the current instance second with given precision if specified (else period interval is used). + * + * @param float|int|string|\DateInterval|null $precision + * + * @return static + */ + public function ceil($precision = null) + { + return $this->round($precision, 'ceil'); + } + + /** + * Specify data which should be serialized to JSON. + * + * @link https://php.net/manual/en/jsonserializable.jsonserialize.php + * + * @return CarbonInterface[] + */ + #[ReturnTypeWillChange] + public function jsonSerialize() + { + return $this->toArray(); + } + + /** + * Return true if the given date is between start and end. + * + * @param \Carbon\Carbon|\Carbon\CarbonPeriod|\Carbon\CarbonInterval|\DateInterval|\DatePeriod|\DateTimeInterface|string|null $date + * + * @return bool + */ + public function contains($date = null): bool + { + $startMethod = 'startsBefore'.($this->isStartIncluded() ? 'OrAt' : ''); + $endMethod = 'endsAfter'.($this->isEndIncluded() ? 'OrAt' : ''); + + return $this->$startMethod($date) && $this->$endMethod($date); + } + + /** + * Return true if the current period follows a given other period (with no overlap). + * For instance, [2019-08-01 -> 2019-08-12] follows [2019-07-29 -> 2019-07-31] + * Note than in this example, follows() would be false if 2019-08-01 or 2019-07-31 was excluded by options. + * + * @param \Carbon\CarbonPeriod|\DatePeriod|string $period + * + * @return bool + */ + public function follows($period, ...$arguments): bool + { + $period = $this->resolveCarbonPeriod($period, ...$arguments); + + return $this->getIncludedStartDate()->equalTo($period->getIncludedEndDate()->add($period->getDateInterval())); + } + + /** + * Return true if the given other period follows the current one (with no overlap). + * For instance, [2019-07-29 -> 2019-07-31] is followed by [2019-08-01 -> 2019-08-12] + * Note than in this example, isFollowedBy() would be false if 2019-08-01 or 2019-07-31 was excluded by options. + * + * @param \Carbon\CarbonPeriod|\DatePeriod|string $period + * + * @return bool + */ + public function isFollowedBy($period, ...$arguments): bool + { + $period = $this->resolveCarbonPeriod($period, ...$arguments); + + return $period->follows($this); + } + + /** + * Return true if the given period either follows or is followed by the current one. + * + * @see follows() + * @see isFollowedBy() + * + * @param \Carbon\CarbonPeriod|\DatePeriod|string $period + * + * @return bool + */ + public function isConsecutiveWith($period, ...$arguments): bool + { + return $this->follows($period, ...$arguments) || $this->isFollowedBy($period, ...$arguments); + } + + /** + * 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; + } + } + + /** + * 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 [$method, array_shift($parameters)]; + } + + return [function ($date) use ($method, $parameters) { + return ([$date, $method])(...$parameters); + }, $method]; + } + + /** + * 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 function isCarbonPredicateMethod($callable) + { + return \is_string($callable) && str_starts_with($callable, 'is') && + (method_exists($this->dateClass, $callable) || ([$this->dateClass, 'hasMacro'])($callable)); + } + + /** + * Recurrences filter callback (limits number of recurrences). + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * + * @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; + } + + /** + * 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() + { + if (($this->getOptions() & static::IMMUTABLE) && $this->dateClass === Carbon::class) { + $this->dateClass = CarbonImmutable::class; + } elseif (!($this->getOptions() & static::IMMUTABLE) && $this->dateClass === CarbonImmutable::class) { + $this->dateClass = Carbon::class; + } + + $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|string + */ + protected function validateCurrentDate() + { + if ($this->current === null) { + $this->rewind(); + } + + // Check after the first rewind to avoid repeating the initial validation. + return $this->validationResult ?? ($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->avoidMutation(), + $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 CarbonInterface $date + * + * @return CarbonInterface + */ + protected function prepareForReturn(CarbonInterface $date) + { + $date = ([$this->dateClass, 'make'])($date); + + if ($this->timezone) { + $date = $date->setTimezone($this->timezone); + } + + return $date; + } + + /** + * 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 = $this->current->add($this->dateInterval); + + $this->validationResult = null; + + if (++$attempts > static::NEXT_MAX_ATTEMPTS) { + throw new UnreachableException('Could not find next valid date.'); + } + } while ($this->validateCurrentDate() === false); + } + + /** + * Call given macro. + * + * @param string $name + * @param array $parameters + * + * @return mixed + */ + protected function callMacro($name, $parameters) + { + $macro = static::$macros[$name]; + + if ($macro instanceof Closure) { + $boundMacro = @$macro->bindTo($this, static::class) ?: @$macro->bindTo(null, static::class); + + return ($boundMacro ?: $macro)(...$parameters); + } + + return $macro(...$parameters); + } + + /** + * 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|\Carbon\CarbonPeriod|\Carbon\CarbonInterval|\DateInterval|\DatePeriod|\DateTimeInterface|string|null $date + * + * @return \Carbon\CarbonInterface + */ + protected function resolveCarbon($date = null) + { + return $this->getStartDate()->nowWithSameTz()->carbonize($date); + } + + /** + * Resolve passed arguments or DatePeriod to a CarbonPeriod object. + * + * @param mixed $period + * @param mixed ...$arguments + * + * @return static + */ + protected function resolveCarbonPeriod($period, ...$arguments) + { + if ($period instanceof self) { + return $period; + } + + return $period instanceof DatePeriod + ? static::instance($period) + : static::create($period, ...$arguments); + } + + private function orderCouple($first, $second): array + { + return $first > $second ? [$second, $first] : [$first, $second]; + } + + private function makeDateTime($value): ?DateTimeInterface + { + if ($value instanceof DateTimeInterface) { + return $value; + } + + if (\is_string($value)) { + $value = trim($value); + + if (!preg_match('/^P[\dT]/', $value) && + !preg_match('/^R\d/', $value) && + preg_match('/[a-z\d]/i', $value) + ) { + return Carbon::parse($value, $this->tzName); + } + } + + return null; + } + + private function isInfiniteDate($date): bool + { + return $date instanceof CarbonInterface && ($date->isEndOfTime() || $date->isStartOfTime()); + } + + private function rawDate($date): ?DateTimeInterface + { + if ($date === false || $date === null) { + return null; + } + + if ($date instanceof CarbonInterface) { + return $date->isMutable() + ? $date->toDateTime() + : $date->toDateTimeImmutable(); + } + + if (\in_array(\get_class($date), [DateTime::class, DateTimeImmutable::class], true)) { + return $date; + } + + $class = $date instanceof DateTime ? DateTime::class : DateTimeImmutable::class; + + return new $class($date->format('Y-m-d H:i:s.u'), $date->getTimezone()); + } + + private static function setDefaultParameters(array &$parameters, array $defaults): void + { + foreach ($defaults as [$index, $name, $value]) { + if (!\array_key_exists($index, $parameters) && !\array_key_exists($name, $parameters)) { + $parameters[$index] = $value; + } + } + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/CarbonPeriodImmutable.php b/vendor/nesbot/carbon/src/Carbon/CarbonPeriodImmutable.php new file mode 100644 index 0000000..cda0733 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/CarbonPeriodImmutable.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 Carbon; + +class CarbonPeriodImmutable extends CarbonPeriod +{ + /** + * Date class of iteration items. + * + * @var string + */ + protected $dateClass = CarbonImmutable::class; + + /** + * Prepare the instance to be set (self if mutable to be mutated, + * copy if immutable to generate a new instance). + * + * @return static + */ + protected function copyIfImmutable() + { + return $this->constructed ? clone $this : $this; + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/CarbonTimeZone.php b/vendor/nesbot/carbon/src/Carbon/CarbonTimeZone.php new file mode 100644 index 0000000..c81899f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/CarbonTimeZone.php @@ -0,0 +1,320 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +use Carbon\Exceptions\InvalidCastException; +use Carbon\Exceptions\InvalidTimeZoneException; +use DateTimeInterface; +use DateTimeZone; +use Throwable; + +class CarbonTimeZone extends DateTimeZone +{ + public function __construct($timezone = null) + { + parent::__construct(static::getDateTimeZoneNameFromMixed($timezone)); + } + + protected static function parseNumericTimezone($timezone) + { + if ($timezone <= -100 || $timezone >= 100) { + throw new InvalidTimeZoneException('Absolute timezone offset cannot be greater than 100.'); + } + + return ($timezone >= 0 ? '+' : '').ltrim($timezone, '+').':00'; + } + + protected static function getDateTimeZoneNameFromMixed($timezone) + { + if ($timezone === null) { + return date_default_timezone_get(); + } + + if (\is_string($timezone)) { + $timezone = preg_replace('/^\s*([+-]\d+)(\d{2})\s*$/', '$1:$2', $timezone); + } + + if (is_numeric($timezone)) { + return static::parseNumericTimezone($timezone); + } + + return $timezone; + } + + protected static function getDateTimeZoneFromName(&$name) + { + return @timezone_open($name = (string) static::getDateTimeZoneNameFromMixed($name)); + } + + /** + * Cast the current instance into the given class. + * + * @param string $className The $className::instance() method will be called to cast the current object. + * + * @return DateTimeZone + */ + public function cast(string $className) + { + if (!method_exists($className, 'instance')) { + if (is_a($className, DateTimeZone::class, true)) { + return new $className($this->getName()); + } + + throw new InvalidCastException("$className has not the instance() method needed to cast the date."); + } + + return $className::instance($this); + } + + /** + * Create a CarbonTimeZone from mixed input. + * + * @param DateTimeZone|string|int|null $object original value to get CarbonTimeZone from it. + * @param DateTimeZone|string|int|null $objectDump dump of the object for error messages. + * + * @throws InvalidTimeZoneException + * + * @return false|static + */ + public static function instance($object = null, $objectDump = null) + { + $tz = $object; + + if ($tz instanceof static) { + return $tz; + } + + if ($tz === null) { + return new static(); + } + + if (!$tz instanceof DateTimeZone) { + $tz = static::getDateTimeZoneFromName($object); + } + + if ($tz !== false) { + return new static($tz->getName()); + } + + if (Carbon::isStrictModeEnabled()) { + throw new InvalidTimeZoneException('Unknown or bad timezone ('.($objectDump ?: $object).')'); + } + + return false; + } + + /** + * Returns abbreviated name of the current timezone according to DST setting. + * + * @param bool $dst + * + * @return string + */ + public function getAbbreviatedName($dst = false) + { + $name = $this->getName(); + + foreach ($this->listAbbreviations() as $abbreviation => $zones) { + foreach ($zones as $zone) { + if ($zone['timezone_id'] === $name && $zone['dst'] == $dst) { + return $abbreviation; + } + } + } + + return 'unknown'; + } + + /** + * @alias getAbbreviatedName + * + * Returns abbreviated name of the current timezone according to DST setting. + * + * @param bool $dst + * + * @return string + */ + public function getAbbr($dst = false) + { + return $this->getAbbreviatedName($dst); + } + + /** + * Get the offset as string "sHH:MM" (such as "+00:00" or "-12:30"). + * + * @param DateTimeInterface|null $date + * + * @return string + */ + public function toOffsetName(DateTimeInterface $date = null) + { + return static::getOffsetNameFromMinuteOffset( + $this->getOffset($date ?: Carbon::now($this)) / 60 + ); + } + + /** + * Returns a new CarbonTimeZone object using the offset string instead of region string. + * + * @param DateTimeInterface|null $date + * + * @return CarbonTimeZone + */ + public function toOffsetTimeZone(DateTimeInterface $date = null) + { + return new static($this->toOffsetName($date)); + } + + /** + * Returns the first region string (such as "America/Toronto") that matches the current timezone or + * false if no match is found. + * + * @see timezone_name_from_abbr native PHP function. + * + * @param DateTimeInterface|null $date + * @param int $isDst + * + * @return string|false + */ + public function toRegionName(DateTimeInterface $date = null, $isDst = 1) + { + $name = $this->getName(); + $firstChar = substr($name, 0, 1); + + if ($firstChar !== '+' && $firstChar !== '-') { + return $name; + } + + $date = $date ?: Carbon::now($this); + + // Integer construction no longer supported since PHP 8 + // @codeCoverageIgnoreStart + try { + $offset = @$this->getOffset($date) ?: 0; + } catch (Throwable $e) { + $offset = 0; + } + // @codeCoverageIgnoreEnd + + $name = @timezone_name_from_abbr('', $offset, $isDst); + + if ($name) { + return $name; + } + + foreach (timezone_identifiers_list() as $timezone) { + if (Carbon::instance($date)->tz($timezone)->getOffset() === $offset) { + return $timezone; + } + } + + return false; + } + + /** + * Returns a new CarbonTimeZone object using the region string instead of offset string. + * + * @param DateTimeInterface|null $date + * + * @return CarbonTimeZone|false + */ + public function toRegionTimeZone(DateTimeInterface $date = null) + { + $tz = $this->toRegionName($date); + + if ($tz !== false) { + return new static($tz); + } + + if (Carbon::isStrictModeEnabled()) { + throw new InvalidTimeZoneException('Unknown timezone for offset '.$this->getOffset($date ?: Carbon::now($this)).' seconds.'); + } + + return false; + } + + /** + * Cast to string (get timezone name). + * + * @return string + */ + public function __toString() + { + return $this->getName(); + } + + /** + * Return the type number: + * + * Type 1; A UTC offset, such as -0300 + * Type 2; A timezone abbreviation, such as GMT + * Type 3: A timezone identifier, such as Europe/London + */ + public function getType(): int + { + return preg_match('/"timezone_type";i:(\d)/', serialize($this), $match) ? (int) $match[1] : 3; + } + + /** + * Create a CarbonTimeZone from mixed input. + * + * @param DateTimeZone|string|int|null $object + * + * @return false|static + */ + public static function create($object = null) + { + return static::instance($object); + } + + /** + * Create a CarbonTimeZone from int/float hour offset. + * + * @param float $hourOffset number of hour of the timezone shift (can be decimal). + * + * @return false|static + */ + public static function createFromHourOffset(float $hourOffset) + { + return static::createFromMinuteOffset($hourOffset * Carbon::MINUTES_PER_HOUR); + } + + /** + * Create a CarbonTimeZone from int/float minute offset. + * + * @param float $minuteOffset number of total minutes of the timezone shift. + * + * @return false|static + */ + public static function createFromMinuteOffset(float $minuteOffset) + { + return static::instance(static::getOffsetNameFromMinuteOffset($minuteOffset)); + } + + /** + * Convert a total minutes offset into a standardized timezone offset string. + * + * @param float $minutes number of total minutes of the timezone shift. + * + * @return string + */ + public static function getOffsetNameFromMinuteOffset(float $minutes): string + { + $minutes = round($minutes); + $unsignedMinutes = abs($minutes); + + return ($minutes < 0 ? '-' : '+'). + str_pad((string) floor($unsignedMinutes / 60), 2, '0', STR_PAD_LEFT). + ':'. + str_pad((string) ($unsignedMinutes % 60), 2, '0', STR_PAD_LEFT); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Cli/Invoker.php b/vendor/nesbot/carbon/src/Carbon/Cli/Invoker.php new file mode 100644 index 0000000..4f35d6c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Cli/Invoker.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Cli; + +class Invoker +{ + public const CLI_CLASS_NAME = 'Carbon\\Cli'; + + protected function runWithCli(string $className, array $parameters): bool + { + $cli = new $className(); + + return $cli(...$parameters); + } + + public function __invoke(...$parameters): bool + { + if (class_exists(self::CLI_CLASS_NAME)) { + return $this->runWithCli(self::CLI_CLASS_NAME, $parameters); + } + + $function = (($parameters[1] ?? '') === 'install' ? ($parameters[2] ?? null) : null) ?: 'shell_exec'; + $function('composer require carbon-cli/carbon-cli --no-interaction'); + + echo 'Installation succeeded.'; + + return true; + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Doctrine/CarbonDoctrineType.php b/vendor/nesbot/carbon/src/Carbon/Doctrine/CarbonDoctrineType.php new file mode 100644 index 0000000..ccc457f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Doctrine/CarbonDoctrineType.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Doctrine; + +use Doctrine\DBAL\Platforms\AbstractPlatform; + +interface CarbonDoctrineType +{ + public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform); + + public function convertToPHPValue($value, AbstractPlatform $platform); + + public function convertToDatabaseValue($value, AbstractPlatform $platform); +} diff --git a/vendor/nesbot/carbon/src/Carbon/Doctrine/CarbonImmutableType.php b/vendor/nesbot/carbon/src/Carbon/Doctrine/CarbonImmutableType.php new file mode 100644 index 0000000..bf476a7 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Doctrine/CarbonImmutableType.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Doctrine; + +use Doctrine\DBAL\Platforms\AbstractPlatform; + +class CarbonImmutableType extends DateTimeImmutableType implements CarbonDoctrineType +{ + /** + * {@inheritdoc} + * + * @return string + */ + public function getName() + { + return 'carbon_immutable'; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function requiresSQLCommentHint(AbstractPlatform $platform) + { + return true; + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Doctrine/CarbonType.php b/vendor/nesbot/carbon/src/Carbon/Doctrine/CarbonType.php new file mode 100644 index 0000000..9289d84 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Doctrine/CarbonType.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Doctrine; + +use Doctrine\DBAL\Platforms\AbstractPlatform; + +class CarbonType extends DateTimeType implements CarbonDoctrineType +{ + /** + * {@inheritdoc} + * + * @return string + */ + public function getName() + { + return 'carbon'; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function requiresSQLCommentHint(AbstractPlatform $platform) + { + return true; + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Doctrine/CarbonTypeConverter.php b/vendor/nesbot/carbon/src/Carbon/Doctrine/CarbonTypeConverter.php new file mode 100644 index 0000000..ecfe17e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Doctrine/CarbonTypeConverter.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 Carbon\Doctrine; + +use Carbon\Carbon; +use Carbon\CarbonInterface; +use DateTimeInterface; +use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Types\ConversionException; +use Exception; + +/** + * @template T of CarbonInterface + */ +trait CarbonTypeConverter +{ + /** + * @return class-string + */ + protected function getCarbonClassName(): string + { + return Carbon::class; + } + + /** + * @return string + */ + public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) + { + $precision = $fieldDeclaration['precision'] ?: 10; + + if ($fieldDeclaration['secondPrecision'] ?? false) { + $precision = 0; + } + + if ($precision === 10) { + $precision = DateTimeDefaultPrecision::get(); + } + + $type = parent::getSQLDeclaration($fieldDeclaration, $platform); + + if (!$precision) { + return $type; + } + + if (str_contains($type, '(')) { + return preg_replace('/\(\d+\)/', "($precision)", $type); + } + + [$before, $after] = explode(' ', "$type "); + + return trim("$before($precision) $after"); + } + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * + * @return T|null + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + $class = $this->getCarbonClassName(); + + if ($value === null || is_a($value, $class)) { + return $value; + } + + if ($value instanceof DateTimeInterface) { + return $class::instance($value); + } + + $date = null; + $error = null; + + try { + $date = $class::parse($value); + } catch (Exception $exception) { + $error = $exception; + } + + if (!$date) { + throw ConversionException::conversionFailedFormat( + $value, + $this->getName(), + 'Y-m-d H:i:s.u or any format supported by '.$class.'::parse()', + $error + ); + } + + return $date; + } + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * + * @return string|null + */ + public function convertToDatabaseValue($value, AbstractPlatform $platform) + { + if ($value === null) { + return $value; + } + + if ($value instanceof DateTimeInterface) { + return $value->format('Y-m-d H:i:s.u'); + } + + throw ConversionException::conversionFailedInvalidType( + $value, + $this->getName(), + ['null', 'DateTime', 'Carbon'] + ); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Doctrine/DateTimeDefaultPrecision.php b/vendor/nesbot/carbon/src/Carbon/Doctrine/DateTimeDefaultPrecision.php new file mode 100644 index 0000000..642fd41 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Doctrine/DateTimeDefaultPrecision.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Doctrine; + +class DateTimeDefaultPrecision +{ + private static $precision = 6; + + /** + * Change the default Doctrine datetime and datetime_immutable precision. + * + * @param int $precision + */ + public static function set(int $precision): void + { + self::$precision = $precision; + } + + /** + * Get the default Doctrine datetime and datetime_immutable precision. + * + * @return int + */ + public static function get(): int + { + return self::$precision; + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Doctrine/DateTimeImmutableType.php b/vendor/nesbot/carbon/src/Carbon/Doctrine/DateTimeImmutableType.php new file mode 100644 index 0000000..4992710 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Doctrine/DateTimeImmutableType.php @@ -0,0 +1,24 @@ + */ + use CarbonTypeConverter; + + /** + * @return class-string + */ + protected function getCarbonClassName(): string + { + return CarbonImmutable::class; + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Doctrine/DateTimeType.php b/vendor/nesbot/carbon/src/Carbon/Doctrine/DateTimeType.php new file mode 100644 index 0000000..29b0bb9 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Doctrine/DateTimeType.php @@ -0,0 +1,16 @@ + */ + use CarbonTypeConverter; +} diff --git a/vendor/nesbot/carbon/src/Carbon/Exceptions/BadComparisonUnitException.php b/vendor/nesbot/carbon/src/Carbon/Exceptions/BadComparisonUnitException.php new file mode 100644 index 0000000..3ca8837 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Exceptions/BadComparisonUnitException.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use Throwable; + +class BadComparisonUnitException extends UnitException +{ + /** + * The unit. + * + * @var string + */ + protected $unit; + + /** + * Constructor. + * + * @param string $unit + * @param int $code + * @param Throwable|null $previous + */ + public function __construct($unit, $code = 0, Throwable $previous = null) + { + $this->unit = $unit; + + parent::__construct("Bad comparison unit: '$unit'", $code, $previous); + } + + /** + * Get the unit. + * + * @return string + */ + public function getUnit(): string + { + return $this->unit; + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Exceptions/BadFluentConstructorException.php b/vendor/nesbot/carbon/src/Carbon/Exceptions/BadFluentConstructorException.php new file mode 100644 index 0000000..2e222e5 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Exceptions/BadFluentConstructorException.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 Carbon\Exceptions; + +use BadMethodCallException as BaseBadMethodCallException; +use Throwable; + +class BadFluentConstructorException extends BaseBadMethodCallException implements BadMethodCallException +{ + /** + * The method. + * + * @var string + */ + protected $method; + + /** + * Constructor. + * + * @param string $method + * @param int $code + * @param Throwable|null $previous + */ + public function __construct($method, $code = 0, Throwable $previous = null) + { + $this->method = $method; + + parent::__construct(sprintf("Unknown fluent constructor '%s'.", $method), $code, $previous); + } + + /** + * Get the method. + * + * @return string + */ + public function getMethod(): string + { + return $this->method; + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Exceptions/BadFluentSetterException.php b/vendor/nesbot/carbon/src/Carbon/Exceptions/BadFluentSetterException.php new file mode 100644 index 0000000..4ceaa2e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Exceptions/BadFluentSetterException.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 Carbon\Exceptions; + +use BadMethodCallException as BaseBadMethodCallException; +use Throwable; + +class BadFluentSetterException extends BaseBadMethodCallException implements BadMethodCallException +{ + /** + * The setter. + * + * @var string + */ + protected $setter; + + /** + * Constructor. + * + * @param string $setter + * @param int $code + * @param Throwable|null $previous + */ + public function __construct($setter, $code = 0, Throwable $previous = null) + { + $this->setter = $setter; + + parent::__construct(sprintf("Unknown fluent setter '%s'", $setter), $code, $previous); + } + + /** + * Get the setter. + * + * @return string + */ + public function getSetter(): string + { + return $this->setter; + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Exceptions/BadMethodCallException.php b/vendor/nesbot/carbon/src/Carbon/Exceptions/BadMethodCallException.php new file mode 100644 index 0000000..108206d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Exceptions/BadMethodCallException.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +interface BadMethodCallException extends Exception +{ + // +} diff --git a/vendor/nesbot/carbon/src/Carbon/Exceptions/EndLessPeriodException.php b/vendor/nesbot/carbon/src/Carbon/Exceptions/EndLessPeriodException.php new file mode 100644 index 0000000..e104926 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Exceptions/EndLessPeriodException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use RuntimeException as BaseRuntimeException; + +final class EndLessPeriodException extends BaseRuntimeException implements RuntimeException +{ + // +} diff --git a/vendor/nesbot/carbon/src/Carbon/Exceptions/Exception.php b/vendor/nesbot/carbon/src/Carbon/Exceptions/Exception.php new file mode 100644 index 0000000..8ad747e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Exceptions/Exception.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +interface Exception +{ + // +} diff --git a/vendor/nesbot/carbon/src/Carbon/Exceptions/ImmutableException.php b/vendor/nesbot/carbon/src/Carbon/Exceptions/ImmutableException.php new file mode 100644 index 0000000..db334c6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Exceptions/ImmutableException.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use RuntimeException as BaseRuntimeException; +use Throwable; + +class ImmutableException extends BaseRuntimeException implements RuntimeException +{ + /** + * The value. + * + * @var string + */ + protected $value; + + /** + * Constructor. + * + * @param string $value the immutable type/value + * @param int $code + * @param Throwable|null $previous + */ + public function __construct($value, $code = 0, Throwable $previous = null) + { + $this->value = $value; + parent::__construct("$value is immutable.", $code, $previous); + } + + /** + * Get the value. + * + * @return string + */ + public function getValue(): string + { + return $this->value; + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidArgumentException.php b/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidArgumentException.php new file mode 100644 index 0000000..5b013cd --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidArgumentException.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +interface InvalidArgumentException extends Exception +{ + // +} diff --git a/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidCastException.php b/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidCastException.php new file mode 100644 index 0000000..a421401 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidCastException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use InvalidArgumentException as BaseInvalidArgumentException; + +class InvalidCastException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + // +} diff --git a/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidDateException.php b/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidDateException.php new file mode 100644 index 0000000..c9ecb6b --- /dev/null +++ b/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 InvalidArgumentException as BaseInvalidArgumentException; +use Throwable; + +class InvalidDateException extends BaseInvalidArgumentException implements 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 Throwable|null $previous + */ + public function __construct($field, $value, $code = 0, Throwable $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/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidFormatException.php b/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidFormatException.php new file mode 100644 index 0000000..92d55fe --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidFormatException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use InvalidArgumentException as BaseInvalidArgumentException; + +class InvalidFormatException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + // +} diff --git a/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidIntervalException.php b/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidIntervalException.php new file mode 100644 index 0000000..69cf412 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidIntervalException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use InvalidArgumentException as BaseInvalidArgumentException; + +class InvalidIntervalException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + // +} diff --git a/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidPeriodDateException.php b/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidPeriodDateException.php new file mode 100644 index 0000000..9bd84a9 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidPeriodDateException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use InvalidArgumentException as BaseInvalidArgumentException; + +class InvalidPeriodDateException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + // +} diff --git a/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidPeriodParameterException.php b/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidPeriodParameterException.php new file mode 100644 index 0000000..cf2c902 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidPeriodParameterException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use InvalidArgumentException as BaseInvalidArgumentException; + +class InvalidPeriodParameterException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + // +} diff --git a/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidTimeZoneException.php b/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidTimeZoneException.php new file mode 100644 index 0000000..f725955 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidTimeZoneException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use InvalidArgumentException as BaseInvalidArgumentException; + +class InvalidTimeZoneException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + // +} diff --git a/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidTypeException.php b/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidTypeException.php new file mode 100644 index 0000000..2c8ec9b --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidTypeException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use InvalidArgumentException as BaseInvalidArgumentException; + +class InvalidTypeException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + // +} diff --git a/vendor/nesbot/carbon/src/Carbon/Exceptions/NotACarbonClassException.php b/vendor/nesbot/carbon/src/Carbon/Exceptions/NotACarbonClassException.php new file mode 100644 index 0000000..7a87632 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Exceptions/NotACarbonClassException.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 Carbon\Exceptions; + +use Carbon\CarbonInterface; +use InvalidArgumentException as BaseInvalidArgumentException; +use Throwable; + +class NotACarbonClassException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + /** + * The className. + * + * @var string + */ + protected $className; + + /** + * Constructor. + * + * @param string $className + * @param int $code + * @param Throwable|null $previous + */ + public function __construct($className, $code = 0, Throwable $previous = null) + { + $this->className = $className; + + parent::__construct(sprintf('Given class does not implement %s: %s', CarbonInterface::class, $className), $code, $previous); + } + + /** + * Get the className. + * + * @return string + */ + public function getClassName(): string + { + return $this->className; + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Exceptions/NotAPeriodException.php b/vendor/nesbot/carbon/src/Carbon/Exceptions/NotAPeriodException.php new file mode 100644 index 0000000..4edd7a4 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Exceptions/NotAPeriodException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use InvalidArgumentException as BaseInvalidArgumentException; + +class NotAPeriodException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + // +} diff --git a/vendor/nesbot/carbon/src/Carbon/Exceptions/NotLocaleAwareException.php b/vendor/nesbot/carbon/src/Carbon/Exceptions/NotLocaleAwareException.php new file mode 100644 index 0000000..f2c5468 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Exceptions/NotLocaleAwareException.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use InvalidArgumentException as BaseInvalidArgumentException; +use Throwable; + +class NotLocaleAwareException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + /** + * Constructor. + * + * @param mixed $object + * @param int $code + * @param Throwable|null $previous + */ + public function __construct($object, $code = 0, Throwable $previous = null) + { + $dump = \is_object($object) ? \get_class($object) : \gettype($object); + + parent::__construct("$dump does neither implements Symfony\Contracts\Translation\LocaleAwareInterface nor getLocale() method.", $code, $previous); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Exceptions/OutOfRangeException.php b/vendor/nesbot/carbon/src/Carbon/Exceptions/OutOfRangeException.php new file mode 100644 index 0000000..2c586d0 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Exceptions/OutOfRangeException.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use InvalidArgumentException as BaseInvalidArgumentException; +use Throwable; + +// This will extends OutOfRangeException instead of InvalidArgumentException since 3.0.0 +// use OutOfRangeException as BaseOutOfRangeException; + +class OutOfRangeException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + /** + * The unit or name of the value. + * + * @var string + */ + private $unit; + + /** + * The range minimum. + * + * @var mixed + */ + private $min; + + /** + * The range maximum. + * + * @var mixed + */ + private $max; + + /** + * The invalid value. + * + * @var mixed + */ + private $value; + + /** + * Constructor. + * + * @param string $unit + * @param mixed $min + * @param mixed $max + * @param mixed $value + * @param int $code + * @param Throwable|null $previous + */ + public function __construct($unit, $min, $max, $value, $code = 0, Throwable $previous = null) + { + $this->unit = $unit; + $this->min = $min; + $this->max = $max; + $this->value = $value; + + parent::__construct("$unit must be between $min and $max, $value given", $code, $previous); + } + + /** + * @return mixed + */ + public function getMax() + { + return $this->max; + } + + /** + * @return mixed + */ + public function getMin() + { + return $this->min; + } + + /** + * @return mixed + */ + public function getUnit() + { + return $this->unit; + } + + /** + * @return mixed + */ + public function getValue() + { + return $this->value; + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Exceptions/ParseErrorException.php b/vendor/nesbot/carbon/src/Carbon/Exceptions/ParseErrorException.php new file mode 100644 index 0000000..5416fd1 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Exceptions/ParseErrorException.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 Carbon\Exceptions; + +use InvalidArgumentException as BaseInvalidArgumentException; +use Throwable; + +class ParseErrorException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + /** + * The expected. + * + * @var string + */ + protected $expected; + + /** + * The actual. + * + * @var string + */ + protected $actual; + + /** + * The help message. + * + * @var string + */ + protected $help; + + /** + * Constructor. + * + * @param string $expected + * @param string $actual + * @param int $code + * @param Throwable|null $previous + */ + public function __construct($expected, $actual, $help = '', $code = 0, Throwable $previous = null) + { + $this->expected = $expected; + $this->actual = $actual; + $this->help = $help; + + $actual = $actual === '' ? 'data is missing' : "get '$actual'"; + + parent::__construct(trim("Format expected $expected but $actual\n$help"), $code, $previous); + } + + /** + * Get the expected. + * + * @return string + */ + public function getExpected(): string + { + return $this->expected; + } + + /** + * Get the actual. + * + * @return string + */ + public function getActual(): string + { + return $this->actual; + } + + /** + * Get the help message. + * + * @return string + */ + public function getHelp(): string + { + return $this->help; + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Exceptions/RuntimeException.php b/vendor/nesbot/carbon/src/Carbon/Exceptions/RuntimeException.php new file mode 100644 index 0000000..ad196f7 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Exceptions/RuntimeException.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +interface RuntimeException extends Exception +{ + // +} diff --git a/vendor/nesbot/carbon/src/Carbon/Exceptions/UnitException.php b/vendor/nesbot/carbon/src/Carbon/Exceptions/UnitException.php new file mode 100644 index 0000000..ee99953 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Exceptions/UnitException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use InvalidArgumentException as BaseInvalidArgumentException; + +class UnitException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + // +} diff --git a/vendor/nesbot/carbon/src/Carbon/Exceptions/UnitNotConfiguredException.php b/vendor/nesbot/carbon/src/Carbon/Exceptions/UnitNotConfiguredException.php new file mode 100644 index 0000000..0e72305 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Exceptions/UnitNotConfiguredException.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use Throwable; + +class UnitNotConfiguredException extends UnitException +{ + /** + * The unit. + * + * @var string + */ + protected $unit; + + /** + * Constructor. + * + * @param string $unit + * @param int $code + * @param Throwable|null $previous + */ + public function __construct($unit, $code = 0, Throwable $previous = null) + { + $this->unit = $unit; + + parent::__construct("Unit $unit have no configuration to get total from other units.", $code, $previous); + } + + /** + * Get the unit. + * + * @return string + */ + public function getUnit(): string + { + return $this->unit; + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Exceptions/UnknownGetterException.php b/vendor/nesbot/carbon/src/Carbon/Exceptions/UnknownGetterException.php new file mode 100644 index 0000000..5c50497 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Exceptions/UnknownGetterException.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 Carbon\Exceptions; + +use InvalidArgumentException as BaseInvalidArgumentException; +use Throwable; + +class UnknownGetterException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + /** + * The getter. + * + * @var string + */ + protected $getter; + + /** + * Constructor. + * + * @param string $getter getter name + * @param int $code + * @param Throwable|null $previous + */ + public function __construct($getter, $code = 0, Throwable $previous = null) + { + $this->getter = $getter; + + parent::__construct("Unknown getter '$getter'", $code, $previous); + } + + /** + * Get the getter. + * + * @return string + */ + public function getGetter(): string + { + return $this->getter; + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Exceptions/UnknownMethodException.php b/vendor/nesbot/carbon/src/Carbon/Exceptions/UnknownMethodException.php new file mode 100644 index 0000000..75273a7 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Exceptions/UnknownMethodException.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 Carbon\Exceptions; + +use BadMethodCallException as BaseBadMethodCallException; +use Throwable; + +class UnknownMethodException extends BaseBadMethodCallException implements BadMethodCallException +{ + /** + * The method. + * + * @var string + */ + protected $method; + + /** + * Constructor. + * + * @param string $method + * @param int $code + * @param Throwable|null $previous + */ + public function __construct($method, $code = 0, Throwable $previous = null) + { + $this->method = $method; + + parent::__construct("Method $method does not exist.", $code, $previous); + } + + /** + * Get the method. + * + * @return string + */ + public function getMethod(): string + { + return $this->method; + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Exceptions/UnknownSetterException.php b/vendor/nesbot/carbon/src/Carbon/Exceptions/UnknownSetterException.php new file mode 100644 index 0000000..a795f5d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Exceptions/UnknownSetterException.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 Carbon\Exceptions; + +use InvalidArgumentException as BaseInvalidArgumentException; +use Throwable; + +class UnknownSetterException extends BaseInvalidArgumentException implements BadMethodCallException +{ + /** + * The setter. + * + * @var string + */ + protected $setter; + + /** + * Constructor. + * + * @param string $setter setter name + * @param int $code + * @param Throwable|null $previous + */ + public function __construct($setter, $code = 0, Throwable $previous = null) + { + $this->setter = $setter; + + parent::__construct("Unknown setter '$setter'", $code, $previous); + } + + /** + * Get the setter. + * + * @return string + */ + public function getSetter(): string + { + return $this->setter; + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Exceptions/UnknownUnitException.php b/vendor/nesbot/carbon/src/Carbon/Exceptions/UnknownUnitException.php new file mode 100644 index 0000000..ecd7f7a --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Exceptions/UnknownUnitException.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use Throwable; + +class UnknownUnitException extends UnitException +{ + /** + * The unit. + * + * @var string + */ + protected $unit; + + /** + * Constructor. + * + * @param string $unit + * @param int $code + * @param Throwable|null $previous + */ + public function __construct($unit, $code = 0, Throwable $previous = null) + { + $this->unit = $unit; + + parent::__construct("Unknown unit '$unit'.", $code, $previous); + } + + /** + * Get the unit. + * + * @return string + */ + public function getUnit(): string + { + return $this->unit; + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Exceptions/UnreachableException.php b/vendor/nesbot/carbon/src/Carbon/Exceptions/UnreachableException.php new file mode 100644 index 0000000..1654ab1 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Exceptions/UnreachableException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use RuntimeException as BaseRuntimeException; + +class UnreachableException extends BaseRuntimeException implements RuntimeException +{ + // +} diff --git a/vendor/nesbot/carbon/src/Carbon/Factory.php b/vendor/nesbot/carbon/src/Carbon/Factory.php new file mode 100644 index 0000000..d497535 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Factory.php @@ -0,0 +1,326 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +use Closure; +use DateTimeInterface; +use ReflectionMethod; + +/** + * A factory to generate Carbon instances with common settings. + * + * + * + * @method bool canBeCreatedFromFormat($date, $format) Checks if the (date)time string is in a given format and valid to create a + * new instance. + * @method Carbon|false create($year = 0, $month = 1, $day = 1, $hour = 0, $minute = 0, $second = 0, $tz = null) 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. + * @method Carbon createFromDate($year = null, $month = null, $day = null, $tz = null) Create a Carbon instance from just a date. The time portion is set to now. + * @method Carbon|false createFromFormat($format, $time, $tz = null) Create a Carbon instance from a specific format. + * @method Carbon|false createFromIsoFormat($format, $time, $tz = null, $locale = 'en', $translator = null) Create a Carbon instance from a specific ISO format (same replacements as ->isoFormat()). + * @method Carbon|false createFromLocaleFormat($format, $locale, $time, $tz = null) Create a Carbon instance from a specific format and a string in a given language. + * @method Carbon|false createFromLocaleIsoFormat($format, $locale, $time, $tz = null) Create a Carbon instance from a specific ISO format and a string in a given language. + * @method Carbon createFromTime($hour = 0, $minute = 0, $second = 0, $tz = null) Create a Carbon instance from just a time. The date portion is set to today. + * @method Carbon createFromTimeString($time, $tz = null) Create a Carbon instance from a time string. The date portion is set to today. + * @method Carbon createFromTimestamp($timestamp, $tz = null) Create a Carbon instance from a timestamp and set the timezone (use default one if not specified). + * Timestamp input can be given as int, float or a string containing one or more numbers. + * @method Carbon createFromTimestampMs($timestamp, $tz = null) Create a Carbon instance from a timestamp in milliseconds. + * Timestamp input can be given as int, float or a string containing one or more numbers. + * @method Carbon createFromTimestampMsUTC($timestamp) Create a Carbon instance from a timestamp in milliseconds. + * Timestamp input can be given as int, float or a string containing one or more numbers. + * @method Carbon createFromTimestampUTC($timestamp) Create a Carbon instance from an timestamp keeping the timezone to UTC. + * Timestamp input can be given as int, float or a string containing one or more numbers. + * @method Carbon createMidnightDate($year = null, $month = null, $day = null, $tz = null) Create a Carbon instance from just a date. The time portion is set to midnight. + * @method Carbon|false createSafe($year = null, $month = null, $day = null, $hour = null, $minute = null, $second = null, $tz = null) 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 InvalidDateException + * will be thrown. + * @method CarbonInterface createStrict(?int $year = 0, ?int $month = 1, ?int $day = 1, ?int $hour = 0, ?int $minute = 0, ?int $second = 0, $tz = null) Create a new Carbon instance from a specific date and time using strict validation. + * @method Carbon disableHumanDiffOption($humanDiffOption) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @method Carbon enableHumanDiffOption($humanDiffOption) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @method mixed executeWithLocale($locale, $func) 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). + * @method Carbon fromSerialized($value) Create an instance from a serialized string. + * @method void genericMacro($macro, $priority = 0) Register a custom macro. + * @method array getAvailableLocales() Returns the list of internally available locales and already loaded custom locales. + * (It will ignore custom translator dynamic loading.) + * @method Language[] getAvailableLocalesInfo() Returns list of Language object for each available locale. This object allow you to get the ISO name, native + * name, region and variant of the locale. + * @method array getDays() Get the days of the week + * @method string|null getFallbackLocale() Get the fallback locale. + * @method array getFormatsToIsoReplacements() List of replacements from date() format to isoFormat(). + * @method int getHumanDiffOptions() Return default humanDiff() options (merged flags as integer). + * @method array getIsoUnits() Returns list of locale units for ISO formatting. + * @method array getLastErrors() {@inheritdoc} + * @method string getLocale() Get the current translator locale. + * @method callable|null getMacro($name) Get the raw callable macro registered globally for a given name. + * @method int getMidDayAt() get midday/noon hour + * @method Closure|Carbon getTestNow() Get the Carbon instance (real or mock) to be returned when a "now" + * instance is created. + * @method string getTimeFormatByPrecision($unitPrecision) Return a format from H:i to H:i:s.u according to given unit precision. + * @method string getTranslationMessageWith($translator, string $key, ?string $locale = null, ?string $default = null) Returns raw translation message for a given key. + * @method \Symfony\Component\Translation\TranslatorInterface getTranslator() Get the default translator instance in use. + * @method int getWeekEndsAt() Get the last day of week + * @method int getWeekStartsAt() Get the first day of week + * @method array getWeekendDays() Get weekend days + * @method bool hasFormat($date, $format) Checks if the (date)time string is in a given format. + * @method bool hasFormatWithModifiers($date, $format) Checks if the (date)time string is in a given format. + * @method bool hasMacro($name) Checks if macro is registered globally. + * @method bool hasRelativeKeywords($time) Determine if a time string will produce a relative date. + * @method bool hasTestNow() Determine if there is a valid test instance set. A valid test instance + * is anything that is not null. + * @method Carbon instance($date) Create a Carbon instance from a DateTime one. + * @method bool isImmutable() Returns true if the current class/instance is immutable. + * @method bool isModifiableUnit($unit) Returns true if a property can be changed via setter. + * @method bool isMutable() Returns true if the current class/instance is mutable. + * @method bool isStrictModeEnabled() Returns true if the strict mode is globally in use, false else. + * (It can be overridden in specific instances.) + * @method bool localeHasDiffOneDayWords($locale) 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. + * @method bool localeHasDiffSyntax($locale) 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. + * @method bool localeHasDiffTwoDayWords($locale) 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. + * @method bool localeHasPeriodSyntax($locale) 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. + * @method bool localeHasShortUnits($locale) 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. + * @method void macro($name, $macro) Register a custom macro. + * @method Carbon|null make($var) 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. + * @method Carbon maxValue() Create a Carbon instance for the greatest supported date. + * @method Carbon minValue() Create a Carbon instance for the lowest supported date. + * @method void mixin($mixin) Mix another object into the class. + * @method Carbon now($tz = null) Get a Carbon instance for the current date and time. + * @method Carbon parse($time = null, $tz = null) 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(). + * @method Carbon parseFromLocale($time, $locale = null, $tz = null) Create a carbon instance from a localized string (in French, Japanese, Arabic, etc.). + * @method string pluralUnit(string $unit) Returns standardized plural of a given singular/plural unit name (in English). + * @method Carbon|false rawCreateFromFormat($format, $time, $tz = null) Create a Carbon instance from a specific format. + * @method Carbon rawParse($time = null, $tz = null) 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(). + * @method Carbon resetMacros() Remove all macros and generic macros. + * @method void resetMonthsOverflow() @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addMonthsWithOverflow/addMonthsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @method void resetToStringFormat() Reset the format used to the default when type juggling a Carbon instance to a string + * @method void resetYearsOverflow() @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addYearsWithOverflow/addYearsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @method void serializeUsing($callback) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather transform Carbon object before the serialization. + * JSON serialize all Carbon instances using the given callback. + * @method Carbon setFallbackLocale($locale) Set the fallback locale. + * @method Carbon setHumanDiffOptions($humanDiffOptions) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @method bool setLocale($locale) Set the current translator locale and indicate if the source locale file exists. + * Pass 'auto' as locale to use closest language from the current LC_TIME locale. + * @method void setMidDayAt($hour) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather consider mid-day is always 12pm, then if you need to test if it's an other + * hour, test it explicitly: + * $date->format('G') == 13 + * or to set explicitly to a given hour: + * $date->setTime(13, 0, 0, 0) + * Set midday/noon hour + * @method Carbon setTestNow($testNow = null) 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. + * Only the moment is mocked with setTestNow(), the timezone will still be the one passed + * as parameter of date_default_timezone_get() as a fallback (see setTestNowAndTimezone()). + * To clear the test instance call this method using the default + * parameter of null. + * /!\ Use this method for unit tests only. + * @method Carbon setTestNowAndTimezone($testNow = null, $tz = null) 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(). + * It will also align default timezone (e.g. call date_default_timezone_set()) with + * the second argument or if null, with the timezone of the given date object. + * To clear the test instance call this method using the default + * parameter of null. + * /!\ Use this method for unit tests only. + * @method void setToStringFormat($format) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather let Carbon object being cast to string with DEFAULT_TO_STRING_FORMAT, and + * use other method or custom format passed to format() method if you need to dump another string + * format. + * Set the default format used when type juggling a Carbon instance to a string. + * @method void setTranslator(TranslatorInterface $translator) Set the default translator instance to use. + * @method Carbon setUtf8($utf8) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use UTF-8 language packages on every machine. + * Set if UTF8 will be used for localized date/time. + * @method void setWeekEndsAt($day) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * Use $weekStartsAt optional parameter instead when using startOfWeek, floorWeek, ceilWeek + * or roundWeek method. You can also use the 'first_day_of_week' locale setting to change the + * start of week according to current locale selected and implicitly the end of week. + * Set the last day of week + * @method void setWeekStartsAt($day) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * Use $weekEndsAt optional parameter instead when using endOfWeek method. You can also use the + * 'first_day_of_week' locale setting to change the start of week according to current locale + * selected and implicitly the end of week. + * Set the first day of week + * @method void setWeekendDays($days) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather consider week-end is always saturday and sunday, and if you have some custom + * week-end days to handle, give to those days an other name and create a macro for them: + * ``` + * Carbon::macro('isDayOff', function ($date) { + * return $date->isSunday() || $date->isMonday(); + * }); + * Carbon::macro('isNotDayOff', function ($date) { + * return !$date->isDayOff(); + * }); + * if ($someDate->isDayOff()) ... + * if ($someDate->isNotDayOff()) ... + * // Add 5 not-off days + * $count = 5; + * while ($someDate->isDayOff() || ($count-- > 0)) { + * $someDate->addDay(); + * } + * ``` + * Set weekend days + * @method bool shouldOverflowMonths() Get the month overflow global behavior (can be overridden in specific instances). + * @method bool shouldOverflowYears() Get the month overflow global behavior (can be overridden in specific instances). + * @method string singularUnit(string $unit) Returns standardized singular of a given singular/plural unit name (in English). + * @method Carbon today($tz = null) Create a Carbon instance for today. + * @method Carbon tomorrow($tz = null) Create a Carbon instance for tomorrow. + * @method string translateTimeString($timeString, $from = null, $to = null, $mode = CarbonInterface::TRANSLATE_ALL) Translate a time string from a locale to an other. + * @method string translateWith(TranslatorInterface $translator, string $key, array $parameters = [], $number = null) Translate using translation string or callback available. + * @method void useMonthsOverflow($monthsOverflow = true) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addMonthsWithOverflow/addMonthsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @method Carbon useStrictMode($strictModeEnabled = true) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @method void useYearsOverflow($yearsOverflow = true) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addYearsWithOverflow/addYearsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @method mixed withTestNow($testNow, $callback) Temporarily sets a static date to be used within the callback. + * Using setTestNow to set the date, executing the callback, then + * clearing the test instance. + * /!\ Use this method for unit tests only. + * @method Carbon yesterday($tz = null) Create a Carbon instance for yesterday. + * + * + */ +class Factory +{ + protected $className = Carbon::class; + + protected $settings = []; + + public function __construct(array $settings = [], ?string $className = null) + { + if ($className) { + $this->className = $className; + } + + $this->settings = $settings; + } + + public function getClassName() + { + return $this->className; + } + + public function setClassName(string $className) + { + $this->className = $className; + + return $this; + } + + public function className(string $className = null) + { + return $className === null ? $this->getClassName() : $this->setClassName($className); + } + + public function getSettings() + { + return $this->settings; + } + + public function setSettings(array $settings) + { + $this->settings = $settings; + + return $this; + } + + public function settings(array $settings = null) + { + return $settings === null ? $this->getSettings() : $this->setSettings($settings); + } + + public function mergeSettings(array $settings) + { + $this->settings = array_merge($this->settings, $settings); + + return $this; + } + + public function __call($name, $arguments) + { + $method = new ReflectionMethod($this->className, $name); + $settings = $this->settings; + + if ($settings && isset($settings['timezone'])) { + $tzParameters = array_filter($method->getParameters(), function ($parameter) { + return \in_array($parameter->getName(), ['tz', 'timezone'], true); + }); + + if (isset($arguments[0]) && \in_array($name, ['instance', 'make', 'create', 'parse'], true)) { + if ($arguments[0] instanceof DateTimeInterface) { + $settings['innerTimezone'] = $settings['timezone']; + } elseif (\is_string($arguments[0]) && date_parse($arguments[0])['is_localtime']) { + unset($settings['timezone'], $settings['innerTimezone']); + } + } elseif (\count($tzParameters)) { + array_splice($arguments, key($tzParameters), 0, [$settings['timezone']]); + unset($settings['timezone']); + } + } + + $result = $this->className::$name(...$arguments); + + return $result instanceof CarbonInterface && !empty($settings) + ? $result->settings($settings) + : $result; + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/FactoryImmutable.php b/vendor/nesbot/carbon/src/Carbon/FactoryImmutable.php new file mode 100644 index 0000000..715f5e7 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/FactoryImmutable.php @@ -0,0 +1,243 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +use Closure; + +/** + * A factory to generate CarbonImmutable instances with common settings. + * + * + * + * @method bool canBeCreatedFromFormat($date, $format) Checks if the (date)time string is in a given format and valid to create a + * new instance. + * @method CarbonImmutable|false create($year = 0, $month = 1, $day = 1, $hour = 0, $minute = 0, $second = 0, $tz = null) 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. + * @method CarbonImmutable createFromDate($year = null, $month = null, $day = null, $tz = null) Create a Carbon instance from just a date. The time portion is set to now. + * @method CarbonImmutable|false createFromFormat($format, $time, $tz = null) Create a Carbon instance from a specific format. + * @method CarbonImmutable|false createFromIsoFormat($format, $time, $tz = null, $locale = 'en', $translator = null) Create a Carbon instance from a specific ISO format (same replacements as ->isoFormat()). + * @method CarbonImmutable|false createFromLocaleFormat($format, $locale, $time, $tz = null) Create a Carbon instance from a specific format and a string in a given language. + * @method CarbonImmutable|false createFromLocaleIsoFormat($format, $locale, $time, $tz = null) Create a Carbon instance from a specific ISO format and a string in a given language. + * @method CarbonImmutable createFromTime($hour = 0, $minute = 0, $second = 0, $tz = null) Create a Carbon instance from just a time. The date portion is set to today. + * @method CarbonImmutable createFromTimeString($time, $tz = null) Create a Carbon instance from a time string. The date portion is set to today. + * @method CarbonImmutable createFromTimestamp($timestamp, $tz = null) Create a Carbon instance from a timestamp and set the timezone (use default one if not specified). + * Timestamp input can be given as int, float or a string containing one or more numbers. + * @method CarbonImmutable createFromTimestampMs($timestamp, $tz = null) Create a Carbon instance from a timestamp in milliseconds. + * Timestamp input can be given as int, float or a string containing one or more numbers. + * @method CarbonImmutable createFromTimestampMsUTC($timestamp) Create a Carbon instance from a timestamp in milliseconds. + * Timestamp input can be given as int, float or a string containing one or more numbers. + * @method CarbonImmutable createFromTimestampUTC($timestamp) Create a Carbon instance from an timestamp keeping the timezone to UTC. + * Timestamp input can be given as int, float or a string containing one or more numbers. + * @method CarbonImmutable createMidnightDate($year = null, $month = null, $day = null, $tz = null) Create a Carbon instance from just a date. The time portion is set to midnight. + * @method CarbonImmutable|false createSafe($year = null, $month = null, $day = null, $hour = null, $minute = null, $second = null, $tz = null) 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 InvalidDateException + * will be thrown. + * @method CarbonInterface createStrict(?int $year = 0, ?int $month = 1, ?int $day = 1, ?int $hour = 0, ?int $minute = 0, ?int $second = 0, $tz = null) Create a new Carbon instance from a specific date and time using strict validation. + * @method CarbonImmutable disableHumanDiffOption($humanDiffOption) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @method CarbonImmutable enableHumanDiffOption($humanDiffOption) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @method mixed executeWithLocale($locale, $func) 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). + * @method CarbonImmutable fromSerialized($value) Create an instance from a serialized string. + * @method void genericMacro($macro, $priority = 0) Register a custom macro. + * @method array getAvailableLocales() Returns the list of internally available locales and already loaded custom locales. + * (It will ignore custom translator dynamic loading.) + * @method Language[] getAvailableLocalesInfo() Returns list of Language object for each available locale. This object allow you to get the ISO name, native + * name, region and variant of the locale. + * @method array getDays() Get the days of the week + * @method string|null getFallbackLocale() Get the fallback locale. + * @method array getFormatsToIsoReplacements() List of replacements from date() format to isoFormat(). + * @method int getHumanDiffOptions() Return default humanDiff() options (merged flags as integer). + * @method array getIsoUnits() Returns list of locale units for ISO formatting. + * @method array getLastErrors() {@inheritdoc} + * @method string getLocale() Get the current translator locale. + * @method callable|null getMacro($name) Get the raw callable macro registered globally for a given name. + * @method int getMidDayAt() get midday/noon hour + * @method Closure|CarbonImmutable getTestNow() Get the Carbon instance (real or mock) to be returned when a "now" + * instance is created. + * @method string getTimeFormatByPrecision($unitPrecision) Return a format from H:i to H:i:s.u according to given unit precision. + * @method string getTranslationMessageWith($translator, string $key, ?string $locale = null, ?string $default = null) Returns raw translation message for a given key. + * @method \Symfony\Component\Translation\TranslatorInterface getTranslator() Get the default translator instance in use. + * @method int getWeekEndsAt() Get the last day of week + * @method int getWeekStartsAt() Get the first day of week + * @method array getWeekendDays() Get weekend days + * @method bool hasFormat($date, $format) Checks if the (date)time string is in a given format. + * @method bool hasFormatWithModifiers($date, $format) Checks if the (date)time string is in a given format. + * @method bool hasMacro($name) Checks if macro is registered globally. + * @method bool hasRelativeKeywords($time) Determine if a time string will produce a relative date. + * @method bool hasTestNow() Determine if there is a valid test instance set. A valid test instance + * is anything that is not null. + * @method CarbonImmutable instance($date) Create a Carbon instance from a DateTime one. + * @method bool isImmutable() Returns true if the current class/instance is immutable. + * @method bool isModifiableUnit($unit) Returns true if a property can be changed via setter. + * @method bool isMutable() Returns true if the current class/instance is mutable. + * @method bool isStrictModeEnabled() Returns true if the strict mode is globally in use, false else. + * (It can be overridden in specific instances.) + * @method bool localeHasDiffOneDayWords($locale) 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. + * @method bool localeHasDiffSyntax($locale) 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. + * @method bool localeHasDiffTwoDayWords($locale) 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. + * @method bool localeHasPeriodSyntax($locale) 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. + * @method bool localeHasShortUnits($locale) 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. + * @method void macro($name, $macro) Register a custom macro. + * @method CarbonImmutable|null make($var) 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. + * @method CarbonImmutable maxValue() Create a Carbon instance for the greatest supported date. + * @method CarbonImmutable minValue() Create a Carbon instance for the lowest supported date. + * @method void mixin($mixin) Mix another object into the class. + * @method CarbonImmutable now($tz = null) Get a Carbon instance for the current date and time. + * @method CarbonImmutable parse($time = null, $tz = null) 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(). + * @method CarbonImmutable parseFromLocale($time, $locale = null, $tz = null) Create a carbon instance from a localized string (in French, Japanese, Arabic, etc.). + * @method string pluralUnit(string $unit) Returns standardized plural of a given singular/plural unit name (in English). + * @method CarbonImmutable|false rawCreateFromFormat($format, $time, $tz = null) Create a Carbon instance from a specific format. + * @method CarbonImmutable rawParse($time = null, $tz = null) 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(). + * @method CarbonImmutable resetMacros() Remove all macros and generic macros. + * @method void resetMonthsOverflow() @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addMonthsWithOverflow/addMonthsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @method void resetToStringFormat() Reset the format used to the default when type juggling a Carbon instance to a string + * @method void resetYearsOverflow() @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addYearsWithOverflow/addYearsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @method void serializeUsing($callback) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather transform Carbon object before the serialization. + * JSON serialize all Carbon instances using the given callback. + * @method CarbonImmutable setFallbackLocale($locale) Set the fallback locale. + * @method CarbonImmutable setHumanDiffOptions($humanDiffOptions) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @method bool setLocale($locale) Set the current translator locale and indicate if the source locale file exists. + * Pass 'auto' as locale to use closest language from the current LC_TIME locale. + * @method void setMidDayAt($hour) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather consider mid-day is always 12pm, then if you need to test if it's an other + * hour, test it explicitly: + * $date->format('G') == 13 + * or to set explicitly to a given hour: + * $date->setTime(13, 0, 0, 0) + * Set midday/noon hour + * @method CarbonImmutable setTestNow($testNow = null) 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. + * Only the moment is mocked with setTestNow(), the timezone will still be the one passed + * as parameter of date_default_timezone_get() as a fallback (see setTestNowAndTimezone()). + * To clear the test instance call this method using the default + * parameter of null. + * /!\ Use this method for unit tests only. + * @method CarbonImmutable setTestNowAndTimezone($testNow = null, $tz = null) 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(). + * It will also align default timezone (e.g. call date_default_timezone_set()) with + * the second argument or if null, with the timezone of the given date object. + * To clear the test instance call this method using the default + * parameter of null. + * /!\ Use this method for unit tests only. + * @method void setToStringFormat($format) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather let Carbon object being cast to string with DEFAULT_TO_STRING_FORMAT, and + * use other method or custom format passed to format() method if you need to dump another string + * format. + * Set the default format used when type juggling a Carbon instance to a string. + * @method void setTranslator(TranslatorInterface $translator) Set the default translator instance to use. + * @method CarbonImmutable setUtf8($utf8) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use UTF-8 language packages on every machine. + * Set if UTF8 will be used for localized date/time. + * @method void setWeekEndsAt($day) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * Use $weekStartsAt optional parameter instead when using startOfWeek, floorWeek, ceilWeek + * or roundWeek method. You can also use the 'first_day_of_week' locale setting to change the + * start of week according to current locale selected and implicitly the end of week. + * Set the last day of week + * @method void setWeekStartsAt($day) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * Use $weekEndsAt optional parameter instead when using endOfWeek method. You can also use the + * 'first_day_of_week' locale setting to change the start of week according to current locale + * selected and implicitly the end of week. + * Set the first day of week + * @method void setWeekendDays($days) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather consider week-end is always saturday and sunday, and if you have some custom + * week-end days to handle, give to those days an other name and create a macro for them: + * ``` + * Carbon::macro('isDayOff', function ($date) { + * return $date->isSunday() || $date->isMonday(); + * }); + * Carbon::macro('isNotDayOff', function ($date) { + * return !$date->isDayOff(); + * }); + * if ($someDate->isDayOff()) ... + * if ($someDate->isNotDayOff()) ... + * // Add 5 not-off days + * $count = 5; + * while ($someDate->isDayOff() || ($count-- > 0)) { + * $someDate->addDay(); + * } + * ``` + * Set weekend days + * @method bool shouldOverflowMonths() Get the month overflow global behavior (can be overridden in specific instances). + * @method bool shouldOverflowYears() Get the month overflow global behavior (can be overridden in specific instances). + * @method string singularUnit(string $unit) Returns standardized singular of a given singular/plural unit name (in English). + * @method CarbonImmutable today($tz = null) Create a Carbon instance for today. + * @method CarbonImmutable tomorrow($tz = null) Create a Carbon instance for tomorrow. + * @method string translateTimeString($timeString, $from = null, $to = null, $mode = CarbonInterface::TRANSLATE_ALL) Translate a time string from a locale to an other. + * @method string translateWith(TranslatorInterface $translator, string $key, array $parameters = [], $number = null) Translate using translation string or callback available. + * @method void useMonthsOverflow($monthsOverflow = true) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addMonthsWithOverflow/addMonthsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @method CarbonImmutable useStrictMode($strictModeEnabled = true) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @method void useYearsOverflow($yearsOverflow = true) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addYearsWithOverflow/addYearsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @method mixed withTestNow($testNow, $callback) Temporarily sets a static date to be used within the callback. + * Using setTestNow to set the date, executing the callback, then + * clearing the test instance. + * /!\ Use this method for unit tests only. + * @method CarbonImmutable yesterday($tz = null) Create a Carbon instance for yesterday. + * + * + */ +class FactoryImmutable extends Factory +{ + protected $className = CarbonImmutable::class; +} diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/aa.php b/vendor/nesbot/carbon/src/Carbon/Lang/aa.php new file mode 100644 index 0000000..f3431e4 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/aa.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/aa_DJ.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/aa_DJ.php b/vendor/nesbot/carbon/src/Carbon/Lang/aa_DJ.php new file mode 100644 index 0000000..c6e23c0 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/aa_DJ.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['Qunxa Garablu', 'Kudo', 'Ciggilta Kudo', 'Agda Baxisso', 'Caxah Alsa', 'Qasa Dirri', 'Qado Dirri', 'Liiqen', 'Waysu', 'Diteli', 'Ximoli', 'Kaxxa Garablu'], + 'months_short' => ['qun', 'nah', 'cig', 'agd', 'cax', 'qas', 'qad', 'leq', 'way', 'dit', 'xim', 'kax'], + 'weekdays' => ['Acaada', 'Etleeni', 'Talaata', 'Arbaqa', 'Kamiisi', 'Gumqata', 'Sabti'], + 'weekdays_short' => ['aca', 'etl', 'tal', 'arb', 'kam', 'gum', 'sab'], + 'weekdays_min' => ['aca', 'etl', 'tal', 'arb', 'kam', 'gum', 'sab'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['saaku', 'carra'], + + 'year' => ':count gaqambo', // less reliable + 'y' => ':count gaqambo', // less reliable + 'a_year' => ':count gaqambo', // less reliable + + 'month' => ':count àlsa', + 'm' => ':count àlsa', + 'a_month' => ':count àlsa', + + 'day' => ':count saaku', // less reliable + 'd' => ':count saaku', // less reliable + 'a_day' => ':count saaku', // less reliable + + 'hour' => ':count ayti', // less reliable + 'h' => ':count ayti', // less reliable + 'a_hour' => ':count ayti', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/aa_ER.php b/vendor/nesbot/carbon/src/Carbon/Lang/aa_ER.php new file mode 100644 index 0000000..f8f395b --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/aa_ER.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Qunxa Garablu', 'Naharsi Kudo', 'Ciggilta Kudo', 'Agda Baxisso', 'Caxah Alsa', 'Qasa Dirri', 'Qado Dirri', 'Leqeeni', 'Waysu', 'Diteli', 'Ximoli', 'Kaxxa Garablu'], + 'months_short' => ['Qun', 'Nah', 'Cig', 'Agd', 'Cax', 'Qas', 'Qad', 'Leq', 'Way', 'Dit', 'Xim', 'Kax'], + 'weekdays' => ['Acaada', 'Etleeni', 'Talaata', 'Arbaqa', 'Kamiisi', 'Gumqata', 'Sabti'], + 'weekdays_short' => ['Aca', 'Etl', 'Tal', 'Arb', 'Kam', 'Gum', 'Sab'], + 'weekdays_min' => ['Aca', 'Etl', 'Tal', 'Arb', 'Kam', 'Gum', 'Sab'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['saaku', 'carra'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/aa_ER@saaho.php b/vendor/nesbot/carbon/src/Carbon/Lang/aa_ER@saaho.php new file mode 100644 index 0000000..6461225 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/aa_ER@saaho.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Qunxa Garablu', 'Naharsi Kudo', 'Ciggilta Kudo', 'Agda Baxisso', 'Caxah Alsa', 'Qasa Dirri', 'Qado Dirri', 'Leqeeni', 'Waysu', 'Diteli', 'Ximoli', 'Kaxxa Garablu'], + 'months_short' => ['Qun', 'Nah', 'Cig', 'Agd', 'Cax', 'Qas', 'Qad', 'Leq', 'Way', 'Dit', 'Xim', 'Kax'], + 'weekdays' => ['Naba Sambat', 'Sani', 'Salus', 'Rabuq', 'Camus', 'Jumqata', 'Qunxa Sambat'], + 'weekdays_short' => ['Nab', 'San', 'Sal', 'Rab', 'Cam', 'Jum', 'Qun'], + 'weekdays_min' => ['Nab', 'San', 'Sal', 'Rab', 'Cam', 'Jum', 'Qun'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['saaku', 'carra'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/aa_ET.php b/vendor/nesbot/carbon/src/Carbon/Lang/aa_ET.php new file mode 100644 index 0000000..e55e591 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/aa_ET.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Qunxa Garablu', 'Kudo', 'Ciggilta Kudo', 'Agda Baxisso', 'Caxah Alsa', 'Qasa Dirri', 'Qado Dirri', 'Liiqen', 'Waysu', 'Diteli', 'Ximoli', 'Kaxxa Garablu'], + 'months_short' => ['Qun', 'Kud', 'Cig', 'Agd', 'Cax', 'Qas', 'Qad', 'Leq', 'Way', 'Dit', 'Xim', 'Kax'], + 'weekdays' => ['Acaada', 'Etleeni', 'Talaata', 'Arbaqa', 'Kamiisi', 'Gumqata', 'Sabti'], + 'weekdays_short' => ['Aca', 'Etl', 'Tal', 'Arb', 'Kam', 'Gum', 'Sab'], + 'weekdays_min' => ['Aca', 'Etl', 'Tal', 'Arb', 'Kam', 'Gum', 'Sab'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['saaku', 'carra'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/af.php b/vendor/nesbot/carbon/src/Carbon/Lang/af.php new file mode 100644 index 0000000..27771d7 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/af.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - JD Isaacks + * - Pierre du Plessis + */ +return [ + 'year' => ':count jaar', + 'a_year' => '\'n jaar|:count jaar', + 'y' => ':count j.', + 'month' => ':count maand|:count maande', + 'a_month' => '\'n maand|:count maande', + 'm' => ':count maa.', + 'week' => ':count week|:count weke', + 'a_week' => '\'n week|:count weke', + 'w' => ':count w.', + 'day' => ':count dag|:count dae', + 'a_day' => '\'n dag|:count dae', + 'd' => ':count d.', + 'hour' => ':count uur', + 'a_hour' => '\'n uur|:count uur', + 'h' => ':count u.', + 'minute' => ':count minuut|:count minute', + 'a_minute' => '\'n minuut|:count minute', + 'min' => ':count min.', + 'second' => ':count sekond|:count sekondes', + 'a_second' => '\'n paar sekondes|:count sekondes', + 's' => ':count s.', + 'ago' => ':time gelede', + 'from_now' => 'oor :time', + 'after' => ':time na', + 'before' => ':time voor', + 'diff_now' => 'Nou', + 'diff_today' => 'Vandag', + 'diff_today_regexp' => 'Vandag(?:\\s+om)?', + 'diff_yesterday' => 'Gister', + 'diff_yesterday_regexp' => 'Gister(?:\\s+om)?', + 'diff_tomorrow' => 'Môre', + 'diff_tomorrow_regexp' => 'Môre(?:\\s+om)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Vandag om] LT', + 'nextDay' => '[Môre om] LT', + 'nextWeek' => 'dddd [om] LT', + 'lastDay' => '[Gister om] LT', + 'lastWeek' => '[Laas] dddd [om] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + return $number.(($number === 1 || $number === 8 || $number >= 20) ? 'ste' : 'de'); + }, + 'meridiem' => ['VM', 'NM'], + 'months' => ['Januarie', 'Februarie', 'Maart', 'April', 'Mei', 'Junie', 'Julie', 'Augustus', 'September', 'Oktober', 'November', 'Desember'], + 'months_short' => ['Jan', 'Feb', 'Mrt', 'Apr', 'Mei', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Des'], + 'weekdays' => ['Sondag', 'Maandag', 'Dinsdag', 'Woensdag', 'Donderdag', 'Vrydag', 'Saterdag'], + 'weekdays_short' => ['Son', 'Maa', 'Din', 'Woe', 'Don', 'Vry', 'Sat'], + 'weekdays_min' => ['So', 'Ma', 'Di', 'Wo', 'Do', 'Vr', 'Sa'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' en '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/af_NA.php b/vendor/nesbot/carbon/src/Carbon/Lang/af_NA.php new file mode 100644 index 0000000..f2fcf05 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/af_NA.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/af.php', [ + 'meridiem' => ['v', 'n'], + 'weekdays' => ['Sondag', 'Maandag', 'Dinsdag', 'Woensdag', 'Donderdag', 'Vrydag', 'Saterdag'], + 'weekdays_short' => ['So.', 'Ma.', 'Di.', 'Wo.', 'Do.', 'Vr.', 'Sa.'], + 'weekdays_min' => ['So.', 'Ma.', 'Di.', 'Wo.', 'Do.', 'Vr.', 'Sa.'], + 'months' => ['Januarie', 'Februarie', 'Maart', 'April', 'Mei', 'Junie', 'Julie', 'Augustus', 'September', 'Oktober', 'November', 'Desember'], + 'months_short' => ['Jan.', 'Feb.', 'Mrt.', 'Apr.', 'Mei', 'Jun.', 'Jul.', 'Aug.', 'Sep.', 'Okt.', 'Nov.', 'Des.'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-DD', + 'LL' => 'DD MMM YYYY', + 'LLL' => 'DD MMMM YYYY HH:mm', + 'LLLL' => 'dddd, DD MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/af_ZA.php b/vendor/nesbot/carbon/src/Carbon/Lang/af_ZA.php new file mode 100644 index 0000000..27896bd --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/af_ZA.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__.'/af.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/agq.php b/vendor/nesbot/carbon/src/Carbon/Lang/agq.php new file mode 100644 index 0000000..7011464 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/agq.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['a.g', 'a.k'], + 'weekdays' => ['tsuʔntsɨ', 'tsuʔukpà', 'tsuʔughɔe', 'tsuʔutɔ̀mlò', 'tsuʔumè', 'tsuʔughɨ̂m', 'tsuʔndzɨkɔʔɔ'], + 'weekdays_short' => ['nts', 'kpa', 'ghɔ', 'tɔm', 'ume', 'ghɨ', 'dzk'], + 'weekdays_min' => ['nts', 'kpa', 'ghɔ', 'tɔm', 'ume', 'ghɨ', 'dzk'], + 'months' => ['ndzɔ̀ŋɔ̀nùm', 'ndzɔ̀ŋɔ̀kƗ̀zùʔ', 'ndzɔ̀ŋɔ̀tƗ̀dʉ̀ghà', 'ndzɔ̀ŋɔ̀tǎafʉ̄ghā', 'ndzɔ̀ŋèsèe', 'ndzɔ̀ŋɔ̀nzùghò', 'ndzɔ̀ŋɔ̀dùmlo', 'ndzɔ̀ŋɔ̀kwîfɔ̀e', 'ndzɔ̀ŋɔ̀tƗ̀fʉ̀ghàdzughù', 'ndzɔ̀ŋɔ̀ghǔuwelɔ̀m', 'ndzɔ̀ŋɔ̀chwaʔàkaa wo', 'ndzɔ̀ŋèfwòo'], + 'months_short' => ['nùm', 'kɨz', 'tɨd', 'taa', 'see', 'nzu', 'dum', 'fɔe', 'dzu', 'lɔm', 'kaa', 'fwo'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM, YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/agr.php b/vendor/nesbot/carbon/src/Carbon/Lang/agr.php new file mode 100644 index 0000000..8f036ae --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/agr.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/agr_PE.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/agr_PE.php b/vendor/nesbot/carbon/src/Carbon/Lang/agr_PE.php new file mode 100644 index 0000000..54a326a --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/agr_PE.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - somosazucar.org libc-alpha@sourceware.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['Petsatin', 'Kupitin', 'Uyaitin', 'Tayutin', 'Kegketin', 'Tegmatin', 'Kuntutin', 'Yagkujutin', 'Daiktatin', 'Ipamtatin', 'Shinutin', 'Sakamtin'], + 'months_short' => ['Pet', 'Kup', 'Uya', 'Tay', 'Keg', 'Teg', 'Kun', 'Yag', 'Dait', 'Ipam', 'Shin', 'Sak'], + 'weekdays' => ['Tuntuamtin', 'Achutin', 'Kugkuktin', 'Saketin', 'Shimpitin', 'Imaptin', 'Bataetin'], + 'weekdays_short' => ['Tun', 'Ach', 'Kug', 'Sak', 'Shim', 'Im', 'Bat'], + 'weekdays_min' => ['Tun', 'Ach', 'Kug', 'Sak', 'Shim', 'Im', 'Bat'], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 7, + 'meridiem' => ['VM', 'NM'], + + 'year' => ':count yaya', // less reliable + 'y' => ':count yaya', // less reliable + 'a_year' => ':count yaya', // less reliable + + 'month' => ':count nantu', // less reliable + 'm' => ':count nantu', // less reliable + 'a_month' => ':count nantu', // less reliable + + 'day' => ':count nayaim', // less reliable + 'd' => ':count nayaim', // less reliable + 'a_day' => ':count nayaim', // less reliable + + 'hour' => ':count kuwiš', // less reliable + 'h' => ':count kuwiš', // less reliable + 'a_hour' => ':count kuwiš', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ak.php b/vendor/nesbot/carbon/src/Carbon/Lang/ak.php new file mode 100644 index 0000000..5a64be3 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ak.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/ak_GH.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ak_GH.php b/vendor/nesbot/carbon/src/Carbon/Lang/ak_GH.php new file mode 100644 index 0000000..1381946 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ak_GH.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Sugar Labs // OLPC sugarlabs.org libc-alpha@sourceware.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'YYYY/MM/DD', + ], + 'months' => ['Sanda-Ɔpɛpɔn', 'Kwakwar-Ɔgyefuo', 'Ebɔw-Ɔbenem', 'Ebɔbira-Oforisuo', 'Esusow Aketseaba-Kɔtɔnimba', 'Obirade-Ayɛwohomumu', 'Ayɛwoho-Kitawonsa', 'Difuu-Ɔsandaa', 'Fankwa-Ɛbɔ', 'Ɔbɛsɛ-Ahinime', 'Ɔberɛfɛw-Obubuo', 'Mumu-Ɔpɛnimba'], + 'months_short' => ['S-Ɔ', 'K-Ɔ', 'E-Ɔ', 'E-O', 'E-K', 'O-A', 'A-K', 'D-Ɔ', 'F-Ɛ', 'Ɔ-A', 'Ɔ-O', 'M-Ɔ'], + 'weekdays' => ['Kwesida', 'Dwowda', 'Benada', 'Wukuda', 'Yawda', 'Fida', 'Memeneda'], + 'weekdays_short' => ['Kwe', 'Dwo', 'Ben', 'Wuk', 'Yaw', 'Fia', 'Mem'], + 'weekdays_min' => ['Kwe', 'Dwo', 'Ben', 'Wuk', 'Yaw', 'Fia', 'Mem'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['AN', 'EW'], + + 'year' => ':count afe', + 'y' => ':count afe', + 'a_year' => ':count afe', + + 'month' => ':count bosume', + 'm' => ':count bosume', + 'a_month' => ':count bosume', + + 'day' => ':count ɛda', + 'd' => ':count ɛda', + 'a_day' => ':count ɛda', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/am.php b/vendor/nesbot/carbon/src/Carbon/Lang/am.php new file mode 100644 index 0000000..63bf72d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/am.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/am_ET.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/am_ET.php b/vendor/nesbot/carbon/src/Carbon/Lang/am_ET.php new file mode 100644 index 0000000..ece8062 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/am_ET.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['ጃንዩወሪ', 'ፌብሩወሪ', 'ማርች', 'ኤፕሪል', 'ሜይ', 'ጁን', 'ጁላይ', 'ኦገስት', 'ሴፕቴምበር', 'ኦክቶበር', 'ኖቬምበር', 'ዲሴምበር'], + 'months_short' => ['ጃንዩ', 'ፌብሩ', 'ማርች', 'ኤፕረ', 'ሜይ ', 'ጁን ', 'ጁላይ', 'ኦገስ', 'ሴፕቴ', 'ኦክተ', 'ኖቬም', 'ዲሴም'], + 'weekdays' => ['እሑድ', 'ሰኞ', 'ማክሰኞ', 'ረቡዕ', 'ሐሙስ', 'ዓርብ', 'ቅዳሜ'], + 'weekdays_short' => ['እሑድ', 'ሰኞ ', 'ማክሰ', 'ረቡዕ', 'ሐሙስ', 'ዓርብ', 'ቅዳሜ'], + 'weekdays_min' => ['እሑድ', 'ሰኞ ', 'ማክሰ', 'ረቡዕ', 'ሐሙስ', 'ዓርብ', 'ቅዳሜ'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['ጡዋት', 'ከሰዓት'], + + 'year' => ':count አመት', + 'y' => ':count አመት', + 'a_year' => ':count አመት', + + 'month' => ':count ወር', + 'm' => ':count ወር', + 'a_month' => ':count ወር', + + 'week' => ':count ሳምንት', + 'w' => ':count ሳምንት', + 'a_week' => ':count ሳምንት', + + 'day' => ':count ቀን', + 'd' => ':count ቀን', + 'a_day' => ':count ቀን', + + 'hour' => ':count ሰዓት', + 'h' => ':count ሰዓት', + 'a_hour' => ':count ሰዓት', + + 'minute' => ':count ደቂቃ', + 'min' => ':count ደቂቃ', + 'a_minute' => ':count ደቂቃ', + + 'second' => ':count ሴኮንድ', + 's' => ':count ሴኮንድ', + 'a_second' => ':count ሴኮንድ', + + 'ago' => 'ከ:time በፊት', + 'from_now' => 'በ:time ውስጥ', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/an.php b/vendor/nesbot/carbon/src/Carbon/Lang/an.php new file mode 100644 index 0000000..565abf2 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/an.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/an_ES.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/an_ES.php b/vendor/nesbot/carbon/src/Carbon/Lang/an_ES.php new file mode 100644 index 0000000..faf8ae0 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/an_ES.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Softaragones Jordi Mallach Pérez, Juan Pablo Martínez bug-glibc-locales@gnu.org, softaragones@softaragones.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['chinero', 'febrero', 'marzo', 'abril', 'mayo', 'chunyo', 'chuliol', 'agosto', 'setiembre', 'octubre', 'noviembre', 'aviento'], + 'months_short' => ['chi', 'feb', 'mar', 'abr', 'may', 'chn', 'chl', 'ago', 'set', 'oct', 'nov', 'avi'], + 'weekdays' => ['domingo', 'luns', 'martes', 'mierques', 'chueves', 'viernes', 'sabado'], + 'weekdays_short' => ['dom', 'lun', 'mar', 'mie', 'chu', 'vie', 'sab'], + 'weekdays_min' => ['dom', 'lun', 'mar', 'mie', 'chu', 'vie', 'sab'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'year' => ':count año', + 'y' => ':count año', + 'a_year' => ':count año', + + 'month' => ':count mes', + 'm' => ':count mes', + 'a_month' => ':count mes', + + 'week' => ':count semana', + 'w' => ':count semana', + 'a_week' => ':count semana', + + 'day' => ':count día', + 'd' => ':count día', + 'a_day' => ':count día', + + 'hour' => ':count reloch', // less reliable + 'h' => ':count reloch', // less reliable + 'a_hour' => ':count reloch', // less reliable + + 'minute' => ':count minuto', + 'min' => ':count minuto', + 'a_minute' => ':count minuto', + + 'second' => ':count segundo', + 's' => ':count segundo', + 'a_second' => ':count segundo', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/anp.php b/vendor/nesbot/carbon/src/Carbon/Lang/anp.php new file mode 100644 index 0000000..b56c67b --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/anp.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/anp_IN.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/anp_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/anp_IN.php new file mode 100644 index 0000000..11069be --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/anp_IN.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bhashaghar@googlegroups.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['जनवरी', 'फरवरी', 'मार्च', 'अप्रैल', 'मई', 'जून', 'जुलाई', 'अगस्त', 'सितंबर', 'अक्टूबर', 'नवंबर', 'दिसंबर"'], + 'months_short' => ['जनवरी', 'फरवरी', 'मार्च', 'अप्रैल', 'मई', 'जून', 'जुलाई', 'अगस्त', 'सितंबर', 'अक्टूबर', 'नवंबर', 'दिसंबर'], + 'weekdays' => ['रविवार', 'सोमवार', 'मंगलवार', 'बुधवार', 'बृहस्पतिवार', 'शुक्रवार', 'शनिवार'], + 'weekdays_short' => ['रवि', 'सोम', 'मंगल', 'बुध', 'बृहस्पति', 'शुक्र', 'शनि'], + 'weekdays_min' => ['रवि', 'सोम', 'मंगल', 'बुध', 'बृहस्पति', 'शुक्र', 'शनि'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['पूर्वाह्न', 'अपराह्न'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar.php new file mode 100644 index 0000000..5f73f63 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Atef Ben Ali (atefBB) + * - Ibrahim AshShohail + * - MLTDev + * - Mohamed Sabil (mohamedsabil83) + * - Yazan Alnugnugh (yazan-alnugnugh) + */ +$months = [ + 'يناير', + 'فبراير', + 'مارس', + 'أبريل', + 'مايو', + 'يونيو', + 'يوليو', + 'أغسطس', + 'سبتمبر', + 'أكتوبر', + 'نوفمبر', + 'ديسمبر', +]; + +return [ + 'year' => implode('|', ['{0}:count سنة', '{1}سنة', '{2}سنتين', ']2,11[:count سنوات', ']10,Inf[:count سنة']), + 'a_year' => implode('|', ['{0}:count سنة', '{1}سنة', '{2}سنتين', ']2,11[:count سنوات', ']10,Inf[:count سنة']), + 'month' => implode('|', ['{0}:count شهر', '{1}شهر', '{2}شهرين', ']2,11[:count أشهر', ']10,Inf[:count شهر']), + 'a_month' => implode('|', ['{0}:count شهر', '{1}شهر', '{2}شهرين', ']2,11[:count أشهر', ']10,Inf[:count شهر']), + 'week' => implode('|', ['{0}:count أسبوع', '{1}أسبوع', '{2}أسبوعين', ']2,11[:count أسابيع', ']10,Inf[:count أسبوع']), + 'a_week' => implode('|', ['{0}:count أسبوع', '{1}أسبوع', '{2}أسبوعين', ']2,11[:count أسابيع', ']10,Inf[:count أسبوع']), + 'day' => implode('|', ['{0}:count يوم', '{1}يوم', '{2}يومين', ']2,11[:count أيام', ']10,Inf[:count يوم']), + 'a_day' => implode('|', ['{0}:count يوم', '{1}يوم', '{2}يومين', ']2,11[:count أيام', ']10,Inf[:count يوم']), + 'hour' => implode('|', ['{0}:count ساعة', '{1}ساعة', '{2}ساعتين', ']2,11[:count ساعات', ']10,Inf[:count ساعة']), + 'a_hour' => implode('|', ['{0}:count ساعة', '{1}ساعة', '{2}ساعتين', ']2,11[:count ساعات', ']10,Inf[:count ساعة']), + 'minute' => implode('|', ['{0}:count دقيقة', '{1}دقيقة', '{2}دقيقتين', ']2,11[:count دقائق', ']10,Inf[:count دقيقة']), + 'a_minute' => implode('|', ['{0}:count دقيقة', '{1}دقيقة', '{2}دقيقتين', ']2,11[:count دقائق', ']10,Inf[:count دقيقة']), + 'second' => implode('|', ['{0}:count ثانية', '{1}ثانية', '{2}ثانيتين', ']2,11[:count ثواني', ']10,Inf[:count ثانية']), + 'a_second' => implode('|', ['{0}:count ثانية', '{1}ثانية', '{2}ثانيتين', ']2,11[:count ثواني', ']10,Inf[:count ثانية']), + 'ago' => 'منذ :time', + 'from_now' => ':time من الآن', + 'after' => 'بعد :time', + 'before' => 'قبل :time', + 'diff_now' => 'الآن', + 'diff_today' => 'اليوم', + 'diff_today_regexp' => 'اليوم(?:\\s+عند)?(?:\\s+الساعة)?', + 'diff_yesterday' => 'أمس', + 'diff_yesterday_regexp' => 'أمس(?:\\s+عند)?(?:\\s+الساعة)?', + 'diff_tomorrow' => 'غداً', + 'diff_tomorrow_regexp' => 'غدًا(?:\\s+عند)?(?:\\s+الساعة)?', + 'diff_before_yesterday' => 'قبل الأمس', + 'diff_after_tomorrow' => 'بعد غد', + 'period_recurrences' => implode('|', ['{0}مرة', '{1}مرة', '{2}:count مرتين', ']2,11[:count مرات', ']10,Inf[:count مرة']), + 'period_interval' => 'كل :interval', + 'period_start_date' => 'من :date', + 'period_end_date' => 'إلى :date', + 'months' => $months, + 'months_short' => $months, + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['أحد', 'اثنين', 'ثلاثاء', 'أربعاء', 'خميس', 'جمعة', 'سبت'], + 'weekdays_min' => ['ح', 'اث', 'ثل', 'أر', 'خم', 'ج', 'س'], + 'list' => ['، ', ' و '], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[اليوم عند الساعة] LT', + 'nextDay' => '[غدًا عند الساعة] LT', + 'nextWeek' => 'dddd [عند الساعة] LT', + 'lastDay' => '[أمس عند الساعة] LT', + 'lastWeek' => 'dddd [عند الساعة] LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['ص', 'م'], + 'weekend' => [5, 6], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_AE.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_AE.php new file mode 100644 index 0000000..35a22b1 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_AE.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + * - Abdullah-Alhariri + */ +return array_replace_recursive(require __DIR__.'/ar.php', [ + 'formats' => [ + 'L' => 'DD MMM, YYYY', + ], + 'months' => ['يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو', 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر'], + 'months_short' => ['ينا', 'فبر', 'مار', 'أبر', 'ماي', 'يون', 'يول', 'أغس', 'سبت', 'أكت', 'نوف', 'ديس'], + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت '], + 'weekdays_short' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'weekdays_min' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'alt_numbers' => ['۰۰', '۰۱', '۰۲', '۰۳', '۰٤', '۰٥', '۰٦', '۰۷', '۰۸', '۰۹', '۱۰', '۱۱', '۱۲', '۱۳', '۱٤', '۱٥', '۱٦', '۱۷', '۱۸', '۱۹', '۲۰', '۲۱', '۲۲', '۲۳', '۲٤', '۲٥', '۲٦', '۲۷', '۲۸', '۲۹', '۳۰', '۳۱', '۳۲', '۳۳', '۳٤', '۳٥', '۳٦', '۳۷', '۳۸', '۳۹', '٤۰', '٤۱', '٤۲', '٤۳', '٤٤', '٤٥', '٤٦', '٤۷', '٤۸', '٤۹', '٥۰', '٥۱', '٥۲', '٥۳', '٥٤', '٥٥', '٥٦', '٥۷', '٥۸', '٥۹', '٦۰', '٦۱', '٦۲', '٦۳', '٦٤', '٦٥', '٦٦', '٦۷', '٦۸', '٦۹', '۷۰', '۷۱', '۷۲', '۷۳', '۷٤', '۷٥', '۷٦', '۷۷', '۷۸', '۷۹', '۸۰', '۸۱', '۸۲', '۸۳', '۸٤', '۸٥', '۸٦', '۸۷', '۸۸', '۸۹', '۹۰', '۹۱', '۹۲', '۹۳', '۹٤', '۹٥', '۹٦', '۹۷', '۹۸', '۹۹'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_BH.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_BH.php new file mode 100644 index 0000000..3518096 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_BH.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + * - Abdullah-Alhariri + */ +return array_replace_recursive(require __DIR__.'/ar.php', [ + 'formats' => [ + 'L' => 'DD MMM, YYYY', + ], + 'months' => ['يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو', 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر'], + 'months_short' => ['ينا', 'فبر', 'مار', 'أبر', 'ماي', 'يون', 'يول', 'أغس', 'سبت', 'أكت', 'نوف', 'ديس'], + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'weekdays_min' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'alt_numbers' => ['۰۰', '۰۱', '۰۲', '۰۳', '۰٤', '۰٥', '۰٦', '۰۷', '۰۸', '۰۹', '۱۰', '۱۱', '۱۲', '۱۳', '۱٤', '۱٥', '۱٦', '۱۷', '۱۸', '۱۹', '۲۰', '۲۱', '۲۲', '۲۳', '۲٤', '۲٥', '۲٦', '۲۷', '۲۸', '۲۹', '۳۰', '۳۱', '۳۲', '۳۳', '۳٤', '۳٥', '۳٦', '۳۷', '۳۸', '۳۹', '٤۰', '٤۱', '٤۲', '٤۳', '٤٤', '٤٥', '٤٦', '٤۷', '٤۸', '٤۹', '٥۰', '٥۱', '٥۲', '٥۳', '٥٤', '٥٥', '٥٦', '٥۷', '٥۸', '٥۹', '٦۰', '٦۱', '٦۲', '٦۳', '٦٤', '٦٥', '٦٦', '٦۷', '٦۸', '٦۹', '۷۰', '۷۱', '۷۲', '۷۳', '۷٤', '۷٥', '۷٦', '۷۷', '۷۸', '۷۹', '۸۰', '۸۱', '۸۲', '۸۳', '۸٤', '۸٥', '۸٦', '۸۷', '۸۸', '۸۹', '۹۰', '۹۱', '۹۲', '۹۳', '۹٤', '۹٥', '۹٦', '۹۷', '۹۸', '۹۹'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_DJ.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_DJ.php new file mode 100644 index 0000000..e790b99 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_DJ.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ar.php', [ +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_DZ.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_DZ.php new file mode 100644 index 0000000..aea4eee --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_DZ.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Authors: + * - Josh Soref + * - Noureddine LOUAHEDJ + * - JD Isaacks + * - Atef Ben Ali (atefBB) + * - Mohamed Sabil (mohamedsabil83) + */ +$months = [ + 'جانفي', + 'فيفري', + 'مارس', + 'أفريل', + 'ماي', + 'جوان', + 'جويلية', + 'أوت', + 'سبتمبر', + 'أكتوبر', + 'نوفمبر', + 'ديسمبر', +]; + +return [ + 'year' => implode('|', ['{0}:count سنة', '{1}سنة', '{2}سنتين', ']2,11[:count سنوات', ']10,Inf[:count سنة']), + 'a_year' => implode('|', ['{0}:count سنة', '{1}سنة', '{2}سنتين', ']2,11[:count سنوات', ']10,Inf[:count سنة']), + 'month' => implode('|', ['{0}:count شهر', '{1}شهر', '{2}شهرين', ']2,11[:count أشهر', ']10,Inf[:count شهر']), + 'a_month' => implode('|', ['{0}:count شهر', '{1}شهر', '{2}شهرين', ']2,11[:count أشهر', ']10,Inf[:count شهر']), + 'week' => implode('|', ['{0}:count أسبوع', '{1}أسبوع', '{2}أسبوعين', ']2,11[:count أسابيع', ']10,Inf[:count أسبوع']), + 'a_week' => implode('|', ['{0}:count أسبوع', '{1}أسبوع', '{2}أسبوعين', ']2,11[:count أسابيع', ']10,Inf[:count أسبوع']), + 'day' => implode('|', ['{0}:count يوم', '{1}يوم', '{2}يومين', ']2,11[:count أيام', ']10,Inf[:count يوم']), + 'a_day' => implode('|', ['{0}:count يوم', '{1}يوم', '{2}يومين', ']2,11[:count أيام', ']10,Inf[:count يوم']), + 'hour' => implode('|', ['{0}:count ساعة', '{1}ساعة', '{2}ساعتين', ']2,11[:count ساعات', ']10,Inf[:count ساعة']), + 'a_hour' => implode('|', ['{0}:count ساعة', '{1}ساعة', '{2}ساعتين', ']2,11[:count ساعات', ']10,Inf[:count ساعة']), + 'minute' => implode('|', ['{0}:count دقيقة', '{1}دقيقة', '{2}دقيقتين', ']2,11[:count دقائق', ']10,Inf[:count دقيقة']), + 'a_minute' => implode('|', ['{0}:count دقيقة', '{1}دقيقة', '{2}دقيقتين', ']2,11[:count دقائق', ']10,Inf[:count دقيقة']), + 'second' => implode('|', ['{0}:count ثانية', '{1}ثانية', '{2}ثانيتين', ']2,11[:count ثواني', ']10,Inf[:count ثانية']), + 'a_second' => implode('|', ['{0}:count ثانية', '{1}ثانية', '{2}ثانيتين', ']2,11[:count ثواني', ']10,Inf[:count ثانية']), + 'ago' => 'منذ :time', + 'from_now' => 'في :time', + 'after' => 'بعد :time', + 'before' => 'قبل :time', + 'diff_now' => 'الآن', + 'diff_today' => 'اليوم', + 'diff_today_regexp' => 'اليوم(?:\\s+على)?(?:\\s+الساعة)?', + 'diff_yesterday' => 'أمس', + 'diff_yesterday_regexp' => 'أمس(?:\\s+على)?(?:\\s+الساعة)?', + 'diff_tomorrow' => 'غداً', + 'diff_tomorrow_regexp' => 'غدا(?:\\s+على)?(?:\\s+الساعة)?', + 'diff_before_yesterday' => 'قبل الأمس', + 'diff_after_tomorrow' => 'بعد غد', + 'period_recurrences' => implode('|', ['{0}مرة', '{1}مرة', '{2}:count مرتين', ']2,11[:count مرات', ']10,Inf[:count مرة']), + 'period_interval' => 'كل :interval', + 'period_start_date' => 'من :date', + 'period_end_date' => 'إلى :date', + 'months' => $months, + 'months_short' => $months, + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['أحد', 'اثنين', 'ثلاثاء', 'أربعاء', 'خميس', 'جمعة', 'سبت'], + 'weekdays_min' => ['أح', 'إث', 'ثلا', 'أر', 'خم', 'جم', 'سب'], + 'list' => ['، ', ' و '], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 4, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[اليوم على الساعة] LT', + 'nextDay' => '[غدا على الساعة] LT', + 'nextWeek' => 'dddd [على الساعة] LT', + 'lastDay' => '[أمس على الساعة] LT', + 'lastWeek' => 'dddd [على الساعة] LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['ص', 'م'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_EG.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_EG.php new file mode 100644 index 0000000..3518096 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_EG.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + * - Abdullah-Alhariri + */ +return array_replace_recursive(require __DIR__.'/ar.php', [ + 'formats' => [ + 'L' => 'DD MMM, YYYY', + ], + 'months' => ['يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو', 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر'], + 'months_short' => ['ينا', 'فبر', 'مار', 'أبر', 'ماي', 'يون', 'يول', 'أغس', 'سبت', 'أكت', 'نوف', 'ديس'], + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'weekdays_min' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'alt_numbers' => ['۰۰', '۰۱', '۰۲', '۰۳', '۰٤', '۰٥', '۰٦', '۰۷', '۰۸', '۰۹', '۱۰', '۱۱', '۱۲', '۱۳', '۱٤', '۱٥', '۱٦', '۱۷', '۱۸', '۱۹', '۲۰', '۲۱', '۲۲', '۲۳', '۲٤', '۲٥', '۲٦', '۲۷', '۲۸', '۲۹', '۳۰', '۳۱', '۳۲', '۳۳', '۳٤', '۳٥', '۳٦', '۳۷', '۳۸', '۳۹', '٤۰', '٤۱', '٤۲', '٤۳', '٤٤', '٤٥', '٤٦', '٤۷', '٤۸', '٤۹', '٥۰', '٥۱', '٥۲', '٥۳', '٥٤', '٥٥', '٥٦', '٥۷', '٥۸', '٥۹', '٦۰', '٦۱', '٦۲', '٦۳', '٦٤', '٦٥', '٦٦', '٦۷', '٦۸', '٦۹', '۷۰', '۷۱', '۷۲', '۷۳', '۷٤', '۷٥', '۷٦', '۷۷', '۷۸', '۷۹', '۸۰', '۸۱', '۸۲', '۸۳', '۸٤', '۸٥', '۸٦', '۸۷', '۸۸', '۸۹', '۹۰', '۹۱', '۹۲', '۹۳', '۹٤', '۹٥', '۹٦', '۹۷', '۹۸', '۹۹'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_EH.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_EH.php new file mode 100644 index 0000000..e790b99 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_EH.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ar.php', [ +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_ER.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_ER.php new file mode 100644 index 0000000..e790b99 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_ER.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ar.php', [ +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_IL.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_IL.php new file mode 100644 index 0000000..e790b99 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_IL.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ar.php', [ +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_IN.php new file mode 100644 index 0000000..5fecf70 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_IN.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/ar.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو', 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر'], + 'months_short' => ['ينا', 'فبر', 'مار', 'أبر', 'ماي', 'يون', 'يول', 'أغس', 'سبت', 'أكت', 'نوف', 'ديس'], + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'weekdays_min' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_IQ.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_IQ.php new file mode 100644 index 0000000..2d42008 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_IQ.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + * - Abdullah-Alhariri + */ +return array_replace_recursive(require __DIR__.'/ar.php', [ + 'formats' => [ + 'L' => 'DD MMM, YYYY', + ], + 'months' => ['كانون الثاني', 'شباط', 'آذار', 'نيسان', 'أيار', 'حزيران', 'تموز', 'آب', 'أيلول', 'تشرين الأول', 'تشرين الثاني', 'كانون الأول'], + 'months_short' => ['كانون الثاني', 'شباط', 'آذار', 'نيسان', 'أيار', 'حزيران', 'تموز', 'آب', 'أيلول', 'تشرين الأول', 'تشرين الثاني', 'كانون الأول'], + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'weekdays_min' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'alt_numbers' => ['۰۰', '۰۱', '۰۲', '۰۳', '۰٤', '۰٥', '۰٦', '۰۷', '۰۸', '۰۹', '۱۰', '۱۱', '۱۲', '۱۳', '۱٤', '۱٥', '۱٦', '۱۷', '۱۸', '۱۹', '۲۰', '۲۱', '۲۲', '۲۳', '۲٤', '۲٥', '۲٦', '۲۷', '۲۸', '۲۹', '۳۰', '۳۱', '۳۲', '۳۳', '۳٤', '۳٥', '۳٦', '۳۷', '۳۸', '۳۹', '٤۰', '٤۱', '٤۲', '٤۳', '٤٤', '٤٥', '٤٦', '٤۷', '٤۸', '٤۹', '٥۰', '٥۱', '٥۲', '٥۳', '٥٤', '٥٥', '٥٦', '٥۷', '٥۸', '٥۹', '٦۰', '٦۱', '٦۲', '٦۳', '٦٤', '٦٥', '٦٦', '٦۷', '٦۸', '٦۹', '۷۰', '۷۱', '۷۲', '۷۳', '۷٤', '۷٥', '۷٦', '۷۷', '۷۸', '۷۹', '۸۰', '۸۱', '۸۲', '۸۳', '۸٤', '۸٥', '۸٦', '۸۷', '۸۸', '۸۹', '۹۰', '۹۱', '۹۲', '۹۳', '۹٤', '۹٥', '۹٦', '۹۷', '۹۸', '۹۹'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_JO.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_JO.php new file mode 100644 index 0000000..2d42008 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_JO.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + * - Abdullah-Alhariri + */ +return array_replace_recursive(require __DIR__.'/ar.php', [ + 'formats' => [ + 'L' => 'DD MMM, YYYY', + ], + 'months' => ['كانون الثاني', 'شباط', 'آذار', 'نيسان', 'أيار', 'حزيران', 'تموز', 'آب', 'أيلول', 'تشرين الأول', 'تشرين الثاني', 'كانون الأول'], + 'months_short' => ['كانون الثاني', 'شباط', 'آذار', 'نيسان', 'أيار', 'حزيران', 'تموز', 'آب', 'أيلول', 'تشرين الأول', 'تشرين الثاني', 'كانون الأول'], + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'weekdays_min' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'alt_numbers' => ['۰۰', '۰۱', '۰۲', '۰۳', '۰٤', '۰٥', '۰٦', '۰۷', '۰۸', '۰۹', '۱۰', '۱۱', '۱۲', '۱۳', '۱٤', '۱٥', '۱٦', '۱۷', '۱۸', '۱۹', '۲۰', '۲۱', '۲۲', '۲۳', '۲٤', '۲٥', '۲٦', '۲۷', '۲۸', '۲۹', '۳۰', '۳۱', '۳۲', '۳۳', '۳٤', '۳٥', '۳٦', '۳۷', '۳۸', '۳۹', '٤۰', '٤۱', '٤۲', '٤۳', '٤٤', '٤٥', '٤٦', '٤۷', '٤۸', '٤۹', '٥۰', '٥۱', '٥۲', '٥۳', '٥٤', '٥٥', '٥٦', '٥۷', '٥۸', '٥۹', '٦۰', '٦۱', '٦۲', '٦۳', '٦٤', '٦٥', '٦٦', '٦۷', '٦۸', '٦۹', '۷۰', '۷۱', '۷۲', '۷۳', '۷٤', '۷٥', '۷٦', '۷۷', '۷۸', '۷۹', '۸۰', '۸۱', '۸۲', '۸۳', '۸٤', '۸٥', '۸٦', '۸۷', '۸۸', '۸۹', '۹۰', '۹۱', '۹۲', '۹۳', '۹٤', '۹٥', '۹٦', '۹۷', '۹۸', '۹۹'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_KM.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_KM.php new file mode 100644 index 0000000..e790b99 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_KM.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ar.php', [ +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_KW.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_KW.php new file mode 100644 index 0000000..b3fb1cf --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_KW.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Authors: + * - Josh Soref + * - Nusret Parlak + * - JD Isaacks + * - Atef Ben Ali (atefBB) + * - Mohamed Sabil (mohamedsabil83) + * - Abdullah-Alhariri + */ +$months = [ + 'يناير', + 'فبراير', + 'مارس', + 'أبريل', + 'ماي', + 'يونيو', + 'يوليوز', + 'غشت', + 'شتنبر', + 'أكتوبر', + 'نونبر', + 'دجنبر', +]; + +return [ + 'year' => implode('|', ['{0}:count سنة', '{1}سنة', '{2}سنتين', ']2,11[:count سنوات', ']10,Inf[:count سنة']), + 'a_year' => implode('|', ['{0}:count سنة', '{1}سنة', '{2}سنتين', ']2,11[:count سنوات', ']10,Inf[:count سنة']), + 'month' => implode('|', ['{0}:count شهر', '{1}شهر', '{2}شهرين', ']2,11[:count أشهر', ']10,Inf[:count شهر']), + 'a_month' => implode('|', ['{0}:count شهر', '{1}شهر', '{2}شهرين', ']2,11[:count أشهر', ']10,Inf[:count شهر']), + 'week' => implode('|', ['{0}:count أسبوع', '{1}أسبوع', '{2}أسبوعين', ']2,11[:count أسابيع', ']10,Inf[:count أسبوع']), + 'a_week' => implode('|', ['{0}:count أسبوع', '{1}أسبوع', '{2}أسبوعين', ']2,11[:count أسابيع', ']10,Inf[:count أسبوع']), + 'day' => implode('|', ['{0}:count يوم', '{1}يوم', '{2}يومين', ']2,11[:count أيام', ']10,Inf[:count يوم']), + 'a_day' => implode('|', ['{0}:count يوم', '{1}يوم', '{2}يومين', ']2,11[:count أيام', ']10,Inf[:count يوم']), + 'hour' => implode('|', ['{0}:count ساعة', '{1}ساعة', '{2}ساعتين', ']2,11[:count ساعات', ']10,Inf[:count ساعة']), + 'a_hour' => implode('|', ['{0}:count ساعة', '{1}ساعة', '{2}ساعتين', ']2,11[:count ساعات', ']10,Inf[:count ساعة']), + 'minute' => implode('|', ['{0}:count دقيقة', '{1}دقيقة', '{2}دقيقتين', ']2,11[:count دقائق', ']10,Inf[:count دقيقة']), + 'a_minute' => implode('|', ['{0}:count دقيقة', '{1}دقيقة', '{2}دقيقتين', ']2,11[:count دقائق', ']10,Inf[:count دقيقة']), + 'second' => implode('|', ['{0}:count ثانية', '{1}ثانية', '{2}ثانيتين', ']2,11[:count ثواني', ']10,Inf[:count ثانية']), + 'a_second' => implode('|', ['{0}:count ثانية', '{1}ثانية', '{2}ثانيتين', ']2,11[:count ثواني', ']10,Inf[:count ثانية']), + 'ago' => 'منذ :time', + 'from_now' => 'في :time', + 'after' => 'بعد :time', + 'before' => 'قبل :time', + 'diff_now' => 'الآن', + 'diff_today' => 'اليوم', + 'diff_today_regexp' => 'اليوم(?:\\s+على)?(?:\\s+الساعة)?', + 'diff_yesterday' => 'أمس', + 'diff_yesterday_regexp' => 'أمس(?:\\s+على)?(?:\\s+الساعة)?', + 'diff_tomorrow' => 'غداً', + 'diff_tomorrow_regexp' => 'غدا(?:\\s+على)?(?:\\s+الساعة)?', + 'diff_before_yesterday' => 'قبل الأمس', + 'diff_after_tomorrow' => 'بعد غد', + 'period_recurrences' => implode('|', ['{0}مرة', '{1}مرة', '{2}:count مرتين', ']2,11[:count مرات', ']10,Inf[:count مرة']), + 'period_interval' => 'كل :interval', + 'period_start_date' => 'من :date', + 'period_end_date' => 'إلى :date', + 'months' => $months, + 'months_short' => $months, + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['أحد', 'اثنين', 'ثلاثاء', 'أربعاء', 'خميس', 'جمعة', 'سبت'], + 'weekdays_min' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'list' => ['، ', ' و '], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[اليوم على الساعة] LT', + 'nextDay' => '[غدا على الساعة] LT', + 'nextWeek' => 'dddd [على الساعة] LT', + 'lastDay' => '[أمس على الساعة] LT', + 'lastWeek' => 'dddd [على الساعة] LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['ص', 'م'], + 'weekend' => [5, 6], + 'alt_numbers' => ['۰۰', '۰۱', '۰۲', '۰۳', '۰٤', '۰٥', '۰٦', '۰۷', '۰۸', '۰۹', '۱۰', '۱۱', '۱۲', '۱۳', '۱٤', '۱٥', '۱٦', '۱۷', '۱۸', '۱۹', '۲۰', '۲۱', '۲۲', '۲۳', '۲٤', '۲٥', '۲٦', '۲۷', '۲۸', '۲۹', '۳۰', '۳۱', '۳۲', '۳۳', '۳٤', '۳٥', '۳٦', '۳۷', '۳۸', '۳۹', '٤۰', '٤۱', '٤۲', '٤۳', '٤٤', '٤٥', '٤٦', '٤۷', '٤۸', '٤۹', '٥۰', '٥۱', '٥۲', '٥۳', '٥٤', '٥٥', '٥٦', '٥۷', '٥۸', '٥۹', '٦۰', '٦۱', '٦۲', '٦۳', '٦٤', '٦٥', '٦٦', '٦۷', '٦۸', '٦۹', '۷۰', '۷۱', '۷۲', '۷۳', '۷٤', '۷٥', '۷٦', '۷۷', '۷۸', '۷۹', '۸۰', '۸۱', '۸۲', '۸۳', '۸٤', '۸٥', '۸٦', '۸۷', '۸۸', '۸۹', '۹۰', '۹۱', '۹۲', '۹۳', '۹٤', '۹٥', '۹٦', '۹۷', '۹۸', '۹۹'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_LB.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_LB.php new file mode 100644 index 0000000..2792745 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_LB.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + * - Abdullah-Alhariri + */ +return array_replace_recursive(require __DIR__.'/ar.php', [ + 'formats' => [ + 'L' => 'DD MMM, YYYY', + ], + 'months' => ['كانون الثاني', 'شباط', 'آذار', 'نيسان', 'أيار', 'حزيران', 'تموز', 'آب', 'أيلول', 'تشرين الأول', 'تشرين الثاني', 'كانون الأول'], + 'months_short' => ['كانون الثاني', 'شباط', 'آذار', 'نيسان', 'أيار', 'حزيران', 'تموز', 'آب', 'أيلول', 'تشرين الأول', 'تشرين الثاني', 'كانون الأول'], + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'weekdays_min' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'alt_numbers' => ['۰۰', '۰۱', '۰۲', '۰۳', '۰٤', '۰٥', '۰٦', '۰۷', '۰۸', '۰۹', '۱۰', '۱۱', '۱۲', '۱۳', '۱٤', '۱٥', '۱٦', '۱۷', '۱۸', '۱۹', '۲۰', '۲۱', '۲۲', '۲۳', '۲٤', '۲٥', '۲٦', '۲۷', '۲۸', '۲۹', '۳۰', '۳۱', '۳۲', '۳۳', '۳٤', '۳٥', '۳٦', '۳۷', '۳۸', '۳۹', '٤۰', '٤۱', '٤۲', '٤۳', '٤٤', '٤٥', '٤٦', '٤۷', '٤۸', '٤۹', '٥۰', '٥۱', '٥۲', '٥۳', '٥٤', '٥٥', '٥٦', '٥۷', '٥۸', '٥۹', '٦۰', '٦۱', '٦۲', '٦۳', '٦٤', '٦٥', '٦٦', '٦۷', '٦۸', '٦۹', '۷۰', '۷۱', '۷۲', '۷۳', '۷٤', '۷٥', '۷٦', '۷۷', '۷۸', '۷۹', '۸۰', '۸۱', '۸۲', '۸۳', '۸٤', '۸٥', '۸٦', '۸۷', '۸۸', '۸۹', '۹۰', '۹۱', '۹۲', '۹۳', '۹٤', '۹٥', '۹٦', '۹۷', '۹۸', '۹۹'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_LY.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_LY.php new file mode 100644 index 0000000..1f0af49 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_LY.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Atef Ben Ali (atefBB) + * - Ibrahim AshShohail + * - MLTDev + */ + +$months = [ + 'يناير', + 'فبراير', + 'مارس', + 'أبريل', + 'مايو', + 'يونيو', + 'يوليو', + 'أغسطس', + 'سبتمبر', + 'أكتوبر', + 'نوفمبر', + 'ديسمبر', +]; + +return [ + 'year' => implode('|', [':count سنة', 'سنة', 'سنتين', ':count سنوات', ':count سنة']), + 'a_year' => implode('|', [':count سنة', 'سنة', 'سنتين', ':count سنوات', ':count سنة']), + 'month' => implode('|', [':count شهر', 'شهر', 'شهرين', ':count أشهر', ':count شهر']), + 'a_month' => implode('|', [':count شهر', 'شهر', 'شهرين', ':count أشهر', ':count شهر']), + 'week' => implode('|', [':count أسبوع', 'أسبوع', 'أسبوعين', ':count أسابيع', ':count أسبوع']), + 'a_week' => implode('|', [':count أسبوع', 'أسبوع', 'أسبوعين', ':count أسابيع', ':count أسبوع']), + 'day' => implode('|', [':count يوم', 'يوم', 'يومين', ':count أيام', ':count يوم']), + 'a_day' => implode('|', [':count يوم', 'يوم', 'يومين', ':count أيام', ':count يوم']), + 'hour' => implode('|', [':count ساعة', 'ساعة', 'ساعتين', ':count ساعات', ':count ساعة']), + 'a_hour' => implode('|', [':count ساعة', 'ساعة', 'ساعتين', ':count ساعات', ':count ساعة']), + 'minute' => implode('|', [':count دقيقة', 'دقيقة', 'دقيقتين', ':count دقائق', ':count دقيقة']), + 'a_minute' => implode('|', [':count دقيقة', 'دقيقة', 'دقيقتين', ':count دقائق', ':count دقيقة']), + 'second' => implode('|', [':count ثانية', 'ثانية', 'ثانيتين', ':count ثواني', ':count ثانية']), + 'a_second' => implode('|', [':count ثانية', 'ثانية', 'ثانيتين', ':count ثواني', ':count ثانية']), + 'ago' => 'منذ :time', + 'from_now' => ':time من الآن', + 'after' => 'بعد :time', + 'before' => 'قبل :time', + 'diff_now' => 'الآن', + 'diff_today' => 'اليوم', + 'diff_today_regexp' => 'اليوم(?:\\s+عند)?(?:\\s+الساعة)?', + 'diff_yesterday' => 'أمس', + 'diff_yesterday_regexp' => 'أمس(?:\\s+عند)?(?:\\s+الساعة)?', + 'diff_tomorrow' => 'غداً', + 'diff_tomorrow_regexp' => 'غدًا(?:\\s+عند)?(?:\\s+الساعة)?', + 'diff_before_yesterday' => 'قبل الأمس', + 'diff_after_tomorrow' => 'بعد غد', + 'period_recurrences' => implode('|', ['مرة', 'مرة', ':count مرتين', ':count مرات', ':count مرة']), + 'period_interval' => 'كل :interval', + 'period_start_date' => 'من :date', + 'period_end_date' => 'إلى :date', + 'months' => $months, + 'months_short' => $months, + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['أحد', 'اثنين', 'ثلاثاء', 'أربعاء', 'خميس', 'جمعة', 'سبت'], + 'weekdays_min' => ['ح', 'اث', 'ثل', 'أر', 'خم', 'ج', 'س'], + 'list' => ['، ', ' و '], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[اليوم عند الساعة] LT', + 'nextDay' => '[غدًا عند الساعة] LT', + 'nextWeek' => 'dddd [عند الساعة] LT', + 'lastDay' => '[أمس عند الساعة] LT', + 'lastWeek' => 'dddd [عند الساعة] LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['ص', 'م'], + 'weekend' => [5, 6], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_MA.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_MA.php new file mode 100644 index 0000000..047ae05 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_MA.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Authors: + * - Josh Soref + * - JD Isaacks + * - Atef Ben Ali (atefBB) + * - Mohamed Sabil (mohamedsabil83) + */ +$months = [ + 'يناير', + 'فبراير', + 'مارس', + 'أبريل', + 'ماي', + 'يونيو', + 'يوليوز', + 'غشت', + 'شتنبر', + 'أكتوبر', + 'نونبر', + 'دجنبر', +]; + +return [ + 'year' => implode('|', ['{0}:count سنة', '{1}سنة', '{2}سنتين', ']2,11[:count سنوات', ']10,Inf[:count سنة']), + 'a_year' => implode('|', ['{0}:count سنة', '{1}سنة', '{2}سنتين', ']2,11[:count سنوات', ']10,Inf[:count سنة']), + 'month' => implode('|', ['{0}:count شهر', '{1}شهر', '{2}شهرين', ']2,11[:count أشهر', ']10,Inf[:count شهر']), + 'a_month' => implode('|', ['{0}:count شهر', '{1}شهر', '{2}شهرين', ']2,11[:count أشهر', ']10,Inf[:count شهر']), + 'week' => implode('|', ['{0}:count أسبوع', '{1}أسبوع', '{2}أسبوعين', ']2,11[:count أسابيع', ']10,Inf[:count أسبوع']), + 'a_week' => implode('|', ['{0}:count أسبوع', '{1}أسبوع', '{2}أسبوعين', ']2,11[:count أسابيع', ']10,Inf[:count أسبوع']), + 'day' => implode('|', ['{0}:count يوم', '{1}يوم', '{2}يومين', ']2,11[:count أيام', ']10,Inf[:count يوم']), + 'a_day' => implode('|', ['{0}:count يوم', '{1}يوم', '{2}يومين', ']2,11[:count أيام', ']10,Inf[:count يوم']), + 'hour' => implode('|', ['{0}:count ساعة', '{1}ساعة', '{2}ساعتين', ']2,11[:count ساعات', ']10,Inf[:count ساعة']), + 'a_hour' => implode('|', ['{0}:count ساعة', '{1}ساعة', '{2}ساعتين', ']2,11[:count ساعات', ']10,Inf[:count ساعة']), + 'minute' => implode('|', ['{0}:count دقيقة', '{1}دقيقة', '{2}دقيقتين', ']2,11[:count دقائق', ']10,Inf[:count دقيقة']), + 'a_minute' => implode('|', ['{0}:count دقيقة', '{1}دقيقة', '{2}دقيقتين', ']2,11[:count دقائق', ']10,Inf[:count دقيقة']), + 'second' => implode('|', ['{0}:count ثانية', '{1}ثانية', '{2}ثانيتين', ']2,11[:count ثواني', ']10,Inf[:count ثانية']), + 'a_second' => implode('|', ['{0}:count ثانية', '{1}ثانية', '{2}ثانيتين', ']2,11[:count ثواني', ']10,Inf[:count ثانية']), + 'ago' => 'منذ :time', + 'from_now' => 'في :time', + 'after' => 'بعد :time', + 'before' => 'قبل :time', + 'diff_now' => 'الآن', + 'diff_today' => 'اليوم', + 'diff_today_regexp' => 'اليوم(?:\\s+على)?(?:\\s+الساعة)?', + 'diff_yesterday' => 'أمس', + 'diff_yesterday_regexp' => 'أمس(?:\\s+على)?(?:\\s+الساعة)?', + 'diff_tomorrow' => 'غداً', + 'diff_tomorrow_regexp' => 'غدا(?:\\s+على)?(?:\\s+الساعة)?', + 'diff_before_yesterday' => 'قبل الأمس', + 'diff_after_tomorrow' => 'بعد غد', + 'period_recurrences' => implode('|', ['{0}مرة', '{1}مرة', '{2}:count مرتين', ']2,11[:count مرات', ']10,Inf[:count مرة']), + 'period_interval' => 'كل :interval', + 'period_start_date' => 'من :date', + 'period_end_date' => 'إلى :date', + 'months' => $months, + 'months_short' => $months, + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['أحد', 'اثنين', 'ثلاثاء', 'أربعاء', 'خميس', 'جمعة', 'سبت'], + 'weekdays_min' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'list' => ['، ', ' و '], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[اليوم على الساعة] LT', + 'nextDay' => '[غدا على الساعة] LT', + 'nextWeek' => 'dddd [على الساعة] LT', + 'lastDay' => '[أمس على الساعة] LT', + 'lastWeek' => 'dddd [على الساعة] LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['ص', 'م'], + 'weekend' => [5, 6], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_MR.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_MR.php new file mode 100644 index 0000000..e790b99 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_MR.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ar.php', [ +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_OM.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_OM.php new file mode 100644 index 0000000..3518096 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_OM.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + * - Abdullah-Alhariri + */ +return array_replace_recursive(require __DIR__.'/ar.php', [ + 'formats' => [ + 'L' => 'DD MMM, YYYY', + ], + 'months' => ['يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو', 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر'], + 'months_short' => ['ينا', 'فبر', 'مار', 'أبر', 'ماي', 'يون', 'يول', 'أغس', 'سبت', 'أكت', 'نوف', 'ديس'], + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'weekdays_min' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'alt_numbers' => ['۰۰', '۰۱', '۰۲', '۰۳', '۰٤', '۰٥', '۰٦', '۰۷', '۰۸', '۰۹', '۱۰', '۱۱', '۱۲', '۱۳', '۱٤', '۱٥', '۱٦', '۱۷', '۱۸', '۱۹', '۲۰', '۲۱', '۲۲', '۲۳', '۲٤', '۲٥', '۲٦', '۲۷', '۲۸', '۲۹', '۳۰', '۳۱', '۳۲', '۳۳', '۳٤', '۳٥', '۳٦', '۳۷', '۳۸', '۳۹', '٤۰', '٤۱', '٤۲', '٤۳', '٤٤', '٤٥', '٤٦', '٤۷', '٤۸', '٤۹', '٥۰', '٥۱', '٥۲', '٥۳', '٥٤', '٥٥', '٥٦', '٥۷', '٥۸', '٥۹', '٦۰', '٦۱', '٦۲', '٦۳', '٦٤', '٦٥', '٦٦', '٦۷', '٦۸', '٦۹', '۷۰', '۷۱', '۷۲', '۷۳', '۷٤', '۷٥', '۷٦', '۷۷', '۷۸', '۷۹', '۸۰', '۸۱', '۸۲', '۸۳', '۸٤', '۸٥', '۸٦', '۸۷', '۸۸', '۸۹', '۹۰', '۹۱', '۹۲', '۹۳', '۹٤', '۹٥', '۹٦', '۹۷', '۹۸', '۹۹'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_PS.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_PS.php new file mode 100644 index 0000000..503c60d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_PS.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Abdullah-Alhariri + */ +return array_replace_recursive(require __DIR__.'/ar.php', [ + 'alt_numbers' => ['۰۰', '۰۱', '۰۲', '۰۳', '۰٤', '۰٥', '۰٦', '۰۷', '۰۸', '۰۹', '۱۰', '۱۱', '۱۲', '۱۳', '۱٤', '۱٥', '۱٦', '۱۷', '۱۸', '۱۹', '۲۰', '۲۱', '۲۲', '۲۳', '۲٤', '۲٥', '۲٦', '۲۷', '۲۸', '۲۹', '۳۰', '۳۱', '۳۲', '۳۳', '۳٤', '۳٥', '۳٦', '۳۷', '۳۸', '۳۹', '٤۰', '٤۱', '٤۲', '٤۳', '٤٤', '٤٥', '٤٦', '٤۷', '٤۸', '٤۹', '٥۰', '٥۱', '٥۲', '٥۳', '٥٤', '٥٥', '٥٦', '٥۷', '٥۸', '٥۹', '٦۰', '٦۱', '٦۲', '٦۳', '٦٤', '٦٥', '٦٦', '٦۷', '٦۸', '٦۹', '۷۰', '۷۱', '۷۲', '۷۳', '۷٤', '۷٥', '۷٦', '۷۷', '۷۸', '۷۹', '۸۰', '۸۱', '۸۲', '۸۳', '۸٤', '۸٥', '۸٦', '۸۷', '۸۸', '۸۹', '۹۰', '۹۱', '۹۲', '۹۳', '۹٤', '۹٥', '۹٦', '۹۷', '۹۸', '۹۹'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_QA.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_QA.php new file mode 100644 index 0000000..3518096 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_QA.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + * - Abdullah-Alhariri + */ +return array_replace_recursive(require __DIR__.'/ar.php', [ + 'formats' => [ + 'L' => 'DD MMM, YYYY', + ], + 'months' => ['يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو', 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر'], + 'months_short' => ['ينا', 'فبر', 'مار', 'أبر', 'ماي', 'يون', 'يول', 'أغس', 'سبت', 'أكت', 'نوف', 'ديس'], + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'weekdays_min' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'alt_numbers' => ['۰۰', '۰۱', '۰۲', '۰۳', '۰٤', '۰٥', '۰٦', '۰۷', '۰۸', '۰۹', '۱۰', '۱۱', '۱۲', '۱۳', '۱٤', '۱٥', '۱٦', '۱۷', '۱۸', '۱۹', '۲۰', '۲۱', '۲۲', '۲۳', '۲٤', '۲٥', '۲٦', '۲۷', '۲۸', '۲۹', '۳۰', '۳۱', '۳۲', '۳۳', '۳٤', '۳٥', '۳٦', '۳۷', '۳۸', '۳۹', '٤۰', '٤۱', '٤۲', '٤۳', '٤٤', '٤٥', '٤٦', '٤۷', '٤۸', '٤۹', '٥۰', '٥۱', '٥۲', '٥۳', '٥٤', '٥٥', '٥٦', '٥۷', '٥۸', '٥۹', '٦۰', '٦۱', '٦۲', '٦۳', '٦٤', '٦٥', '٦٦', '٦۷', '٦۸', '٦۹', '۷۰', '۷۱', '۷۲', '۷۳', '۷٤', '۷٥', '۷٦', '۷۷', '۷۸', '۷۹', '۸۰', '۸۱', '۸۲', '۸۳', '۸٤', '۸٥', '۸٦', '۸۷', '۸۸', '۸۹', '۹۰', '۹۱', '۹۲', '۹۳', '۹٤', '۹٥', '۹٦', '۹۷', '۹۸', '۹۹'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_SA.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_SA.php new file mode 100644 index 0000000..550b0c7 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_SA.php @@ -0,0 +1,94 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Authors: + * - Josh Soref + * - JD Isaacks + * - Atef Ben Ali (atefBB) + * - Mohamed Sabil (mohamedsabil83) + * - Abdullah-Alhariri + */ +$months = [ + 'يناير', + 'فبراير', + 'مارس', + 'أبريل', + 'مايو', + 'يونيو', + 'يوليو', + 'أغسطس', + 'سبتمبر', + 'أكتوبر', + 'نوفمبر', + 'ديسمبر', +]; + +return [ + 'year' => implode('|', ['{0}:count سنة', '{1}سنة', '{2}سنتين', ']2,11[:count سنوات', ']10,Inf[:count سنة']), + 'a_year' => implode('|', ['{0}:count سنة', '{1}سنة', '{2}سنتين', ']2,11[:count سنوات', ']10,Inf[:count سنة']), + 'month' => implode('|', ['{0}:count شهر', '{1}شهر', '{2}شهرين', ']2,11[:count أشهر', ']10,Inf[:count شهر']), + 'a_month' => implode('|', ['{0}:count شهر', '{1}شهر', '{2}شهرين', ']2,11[:count أشهر', ']10,Inf[:count شهر']), + 'week' => implode('|', ['{0}:count أسبوع', '{1}أسبوع', '{2}أسبوعين', ']2,11[:count أسابيع', ']10,Inf[:count أسبوع']), + 'a_week' => implode('|', ['{0}:count أسبوع', '{1}أسبوع', '{2}أسبوعين', ']2,11[:count أسابيع', ']10,Inf[:count أسبوع']), + 'day' => implode('|', ['{0}:count يوم', '{1}يوم', '{2}يومين', ']2,11[:count أيام', ']10,Inf[:count يوم']), + 'a_day' => implode('|', ['{0}:count يوم', '{1}يوم', '{2}يومين', ']2,11[:count أيام', ']10,Inf[:count يوم']), + 'hour' => implode('|', ['{0}:count ساعة', '{1}ساعة', '{2}ساعتين', ']2,11[:count ساعات', ']10,Inf[:count ساعة']), + 'a_hour' => implode('|', ['{0}:count ساعة', '{1}ساعة', '{2}ساعتين', ']2,11[:count ساعات', ']10,Inf[:count ساعة']), + 'minute' => implode('|', ['{0}:count دقيقة', '{1}دقيقة', '{2}دقيقتين', ']2,11[:count دقائق', ']10,Inf[:count دقيقة']), + 'a_minute' => implode('|', ['{0}:count دقيقة', '{1}دقيقة', '{2}دقيقتين', ']2,11[:count دقائق', ']10,Inf[:count دقيقة']), + 'second' => implode('|', ['{0}:count ثانية', '{1}ثانية', '{2}ثانيتين', ']2,11[:count ثواني', ']10,Inf[:count ثانية']), + 'a_second' => implode('|', ['{0}:count ثانية', '{1}ثانية', '{2}ثانيتين', ']2,11[:count ثواني', ']10,Inf[:count ثانية']), + 'ago' => 'منذ :time', + 'from_now' => 'في :time', + 'after' => 'بعد :time', + 'before' => 'قبل :time', + 'diff_now' => 'الآن', + 'diff_today' => 'اليوم', + 'diff_today_regexp' => 'اليوم(?:\\s+على)?(?:\\s+الساعة)?', + 'diff_yesterday' => 'أمس', + 'diff_yesterday_regexp' => 'أمس(?:\\s+على)?(?:\\s+الساعة)?', + 'diff_tomorrow' => 'غداً', + 'diff_tomorrow_regexp' => 'غدا(?:\\s+على)?(?:\\s+الساعة)?', + 'diff_before_yesterday' => 'قبل الأمس', + 'diff_after_tomorrow' => 'بعد غد', + 'period_recurrences' => implode('|', ['{0}مرة', '{1}مرة', '{2}:count مرتين', ']2,11[:count مرات', ']10,Inf[:count مرة']), + 'period_interval' => 'كل :interval', + 'period_start_date' => 'من :date', + 'period_end_date' => 'إلى :date', + 'months' => $months, + 'months_short' => $months, + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['أحد', 'اثنين', 'ثلاثاء', 'أربعاء', 'خميس', 'جمعة', 'سبت'], + 'weekdays_min' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'list' => ['، ', ' و '], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[اليوم على الساعة] LT', + 'nextDay' => '[غدا على الساعة] LT', + 'nextWeek' => 'dddd [على الساعة] LT', + 'lastDay' => '[أمس على الساعة] LT', + 'lastWeek' => 'dddd [على الساعة] LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['ص', 'م'], + 'weekend' => [5, 6], + 'alt_numbers' => ['۰۰', '۰۱', '۰۲', '۰۳', '۰٤', '۰٥', '۰٦', '۰۷', '۰۸', '۰۹', '۱۰', '۱۱', '۱۲', '۱۳', '۱٤', '۱٥', '۱٦', '۱۷', '۱۸', '۱۹', '۲۰', '۲۱', '۲۲', '۲۳', '۲٤', '۲٥', '۲٦', '۲۷', '۲۸', '۲۹', '۳۰', '۳۱', '۳۲', '۳۳', '۳٤', '۳٥', '۳٦', '۳۷', '۳۸', '۳۹', '٤۰', '٤۱', '٤۲', '٤۳', '٤٤', '٤٥', '٤٦', '٤۷', '٤۸', '٤۹', '٥۰', '٥۱', '٥۲', '٥۳', '٥٤', '٥٥', '٥٦', '٥۷', '٥۸', '٥۹', '٦۰', '٦۱', '٦۲', '٦۳', '٦٤', '٦٥', '٦٦', '٦۷', '٦۸', '٦۹', '۷۰', '۷۱', '۷۲', '۷۳', '۷٤', '۷٥', '۷٦', '۷۷', '۷۸', '۷۹', '۸۰', '۸۱', '۸۲', '۸۳', '۸٤', '۸٥', '۸٦', '۸۷', '۸۸', '۸۹', '۹۰', '۹۱', '۹۲', '۹۳', '۹٤', '۹٥', '۹٦', '۹۷', '۹۸', '۹۹'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_SD.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_SD.php new file mode 100644 index 0000000..3518096 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_SD.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + * - Abdullah-Alhariri + */ +return array_replace_recursive(require __DIR__.'/ar.php', [ + 'formats' => [ + 'L' => 'DD MMM, YYYY', + ], + 'months' => ['يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو', 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر'], + 'months_short' => ['ينا', 'فبر', 'مار', 'أبر', 'ماي', 'يون', 'يول', 'أغس', 'سبت', 'أكت', 'نوف', 'ديس'], + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'weekdays_min' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'alt_numbers' => ['۰۰', '۰۱', '۰۲', '۰۳', '۰٤', '۰٥', '۰٦', '۰۷', '۰۸', '۰۹', '۱۰', '۱۱', '۱۲', '۱۳', '۱٤', '۱٥', '۱٦', '۱۷', '۱۸', '۱۹', '۲۰', '۲۱', '۲۲', '۲۳', '۲٤', '۲٥', '۲٦', '۲۷', '۲۸', '۲۹', '۳۰', '۳۱', '۳۲', '۳۳', '۳٤', '۳٥', '۳٦', '۳۷', '۳۸', '۳۹', '٤۰', '٤۱', '٤۲', '٤۳', '٤٤', '٤٥', '٤٦', '٤۷', '٤۸', '٤۹', '٥۰', '٥۱', '٥۲', '٥۳', '٥٤', '٥٥', '٥٦', '٥۷', '٥۸', '٥۹', '٦۰', '٦۱', '٦۲', '٦۳', '٦٤', '٦٥', '٦٦', '٦۷', '٦۸', '٦۹', '۷۰', '۷۱', '۷۲', '۷۳', '۷٤', '۷٥', '۷٦', '۷۷', '۷۸', '۷۹', '۸۰', '۸۱', '۸۲', '۸۳', '۸٤', '۸٥', '۸٦', '۸۷', '۸۸', '۸۹', '۹۰', '۹۱', '۹۲', '۹۳', '۹٤', '۹٥', '۹٦', '۹۷', '۹۸', '۹۹'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_SO.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_SO.php new file mode 100644 index 0000000..e790b99 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_SO.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ar.php', [ +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_SS.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_SS.php new file mode 100644 index 0000000..32f3282 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_SS.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/ar.php', [ + 'formats' => [ + 'L' => 'DD MMM, YYYY', + ], + 'months' => ['يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو', 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر'], + 'months_short' => ['ينا', 'فبر', 'مار', 'أبر', 'ماي', 'يون', 'يول', 'أغس', 'سبت', 'أكت', 'نوف', 'ديس'], + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'weekdays_min' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_SY.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_SY.php new file mode 100644 index 0000000..2d42008 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_SY.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + * - Abdullah-Alhariri + */ +return array_replace_recursive(require __DIR__.'/ar.php', [ + 'formats' => [ + 'L' => 'DD MMM, YYYY', + ], + 'months' => ['كانون الثاني', 'شباط', 'آذار', 'نيسان', 'أيار', 'حزيران', 'تموز', 'آب', 'أيلول', 'تشرين الأول', 'تشرين الثاني', 'كانون الأول'], + 'months_short' => ['كانون الثاني', 'شباط', 'آذار', 'نيسان', 'أيار', 'حزيران', 'تموز', 'آب', 'أيلول', 'تشرين الأول', 'تشرين الثاني', 'كانون الأول'], + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'weekdays_min' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'alt_numbers' => ['۰۰', '۰۱', '۰۲', '۰۳', '۰٤', '۰٥', '۰٦', '۰۷', '۰۸', '۰۹', '۱۰', '۱۱', '۱۲', '۱۳', '۱٤', '۱٥', '۱٦', '۱۷', '۱۸', '۱۹', '۲۰', '۲۱', '۲۲', '۲۳', '۲٤', '۲٥', '۲٦', '۲۷', '۲۸', '۲۹', '۳۰', '۳۱', '۳۲', '۳۳', '۳٤', '۳٥', '۳٦', '۳۷', '۳۸', '۳۹', '٤۰', '٤۱', '٤۲', '٤۳', '٤٤', '٤٥', '٤٦', '٤۷', '٤۸', '٤۹', '٥۰', '٥۱', '٥۲', '٥۳', '٥٤', '٥٥', '٥٦', '٥۷', '٥۸', '٥۹', '٦۰', '٦۱', '٦۲', '٦۳', '٦٤', '٦٥', '٦٦', '٦۷', '٦۸', '٦۹', '۷۰', '۷۱', '۷۲', '۷۳', '۷٤', '۷٥', '۷٦', '۷۷', '۷۸', '۷۹', '۸۰', '۸۱', '۸۲', '۸۳', '۸٤', '۸٥', '۸٦', '۸۷', '۸۸', '۸۹', '۹۰', '۹۱', '۹۲', '۹۳', '۹٤', '۹٥', '۹٦', '۹۷', '۹۸', '۹۹'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_Shakl.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_Shakl.php new file mode 100644 index 0000000..c2d4b43 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_Shakl.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Abdellah Chadidi + * - Atef Ben Ali (atefBB) + * - Mohamed Sabil (mohamedsabil83) + */ +// Same for long and short +$months = [ + // @TODO add shakl to months + 'يناير', + 'فبراير', + 'مارس', + 'أبريل', + 'مايو', + 'يونيو', + 'يوليو', + 'أغسطس', + 'سبتمبر', + 'أكتوبر', + 'نوفمبر', + 'ديسمبر', +]; + +return [ + 'year' => implode('|', ['{0}:count سَنَة', '{1}سَنَة', '{2}سَنَتَيْن', ']2,11[:count سَنَوَات', ']10,Inf[:count سَنَة']), + 'a_year' => implode('|', ['{0}:count سَنَة', '{1}سَنَة', '{2}سَنَتَيْن', ']2,11[:count سَنَوَات', ']10,Inf[:count سَنَة']), + 'month' => implode('|', ['{0}:count شَهْرَ', '{1}شَهْرَ', '{2}شَهْرَيْن', ']2,11[:count أَشْهُر', ']10,Inf[:count شَهْرَ']), + 'a_month' => implode('|', ['{0}:count شَهْرَ', '{1}شَهْرَ', '{2}شَهْرَيْن', ']2,11[:count أَشْهُر', ']10,Inf[:count شَهْرَ']), + 'week' => implode('|', ['{0}:count أُسْبُوع', '{1}أُسْبُوع', '{2}أُسْبُوعَيْن', ']2,11[:count أَسَابِيع', ']10,Inf[:count أُسْبُوع']), + 'a_week' => implode('|', ['{0}:count أُسْبُوع', '{1}أُسْبُوع', '{2}أُسْبُوعَيْن', ']2,11[:count أَسَابِيع', ']10,Inf[:count أُسْبُوع']), + 'day' => implode('|', ['{0}:count يَوْم', '{1}يَوْم', '{2}يَوْمَيْن', ']2,11[:count أَيَّام', ']10,Inf[:count يَوْم']), + 'a_day' => implode('|', ['{0}:count يَوْم', '{1}يَوْم', '{2}يَوْمَيْن', ']2,11[:count أَيَّام', ']10,Inf[:count يَوْم']), + 'hour' => implode('|', ['{0}:count سَاعَة', '{1}سَاعَة', '{2}سَاعَتَيْن', ']2,11[:count سَاعَات', ']10,Inf[:count سَاعَة']), + 'a_hour' => implode('|', ['{0}:count سَاعَة', '{1}سَاعَة', '{2}سَاعَتَيْن', ']2,11[:count سَاعَات', ']10,Inf[:count سَاعَة']), + 'minute' => implode('|', ['{0}:count دَقِيقَة', '{1}دَقِيقَة', '{2}دَقِيقَتَيْن', ']2,11[:count دَقَائِق', ']10,Inf[:count دَقِيقَة']), + 'a_minute' => implode('|', ['{0}:count دَقِيقَة', '{1}دَقِيقَة', '{2}دَقِيقَتَيْن', ']2,11[:count دَقَائِق', ']10,Inf[:count دَقِيقَة']), + 'second' => implode('|', ['{0}:count ثَانِيَة', '{1}ثَانِيَة', '{2}ثَانِيَتَيْن', ']2,11[:count ثَوَان', ']10,Inf[:count ثَانِيَة']), + 'a_second' => implode('|', ['{0}:count ثَانِيَة', '{1}ثَانِيَة', '{2}ثَانِيَتَيْن', ']2,11[:count ثَوَان', ']10,Inf[:count ثَانِيَة']), + 'ago' => 'مُنْذُ :time', + 'from_now' => 'مِنَ الْآن :time', + 'after' => 'بَعْدَ :time', + 'before' => 'قَبْلَ :time', + + // @TODO add shakl to translations below + 'diff_now' => 'الآن', + 'diff_today' => 'اليوم', + 'diff_today_regexp' => 'اليوم(?:\\s+عند)?(?:\\s+الساعة)?', + 'diff_yesterday' => 'أمس', + 'diff_yesterday_regexp' => 'أمس(?:\\s+عند)?(?:\\s+الساعة)?', + 'diff_tomorrow' => 'غداً', + 'diff_tomorrow_regexp' => 'غدًا(?:\\s+عند)?(?:\\s+الساعة)?', + 'diff_before_yesterday' => 'قبل الأمس', + 'diff_after_tomorrow' => 'بعد غد', + 'period_recurrences' => implode('|', ['{0}مرة', '{1}مرة', '{2}:count مرتين', ']2,11[:count مرات', ']10,Inf[:count مرة']), + 'period_interval' => 'كل :interval', + 'period_start_date' => 'من :date', + 'period_end_date' => 'إلى :date', + 'months' => $months, + 'months_short' => $months, + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['أحد', 'اثنين', 'ثلاثاء', 'أربعاء', 'خميس', 'جمعة', 'سبت'], + 'weekdays_min' => ['ح', 'اث', 'ثل', 'أر', 'خم', 'ج', 'س'], + 'list' => ['، ', ' و '], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[اليوم عند الساعة] LT', + 'nextDay' => '[غدًا عند الساعة] LT', + 'nextWeek' => 'dddd [عند الساعة] LT', + 'lastDay' => '[أمس عند الساعة] LT', + 'lastWeek' => 'dddd [عند الساعة] LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['ص', 'م'], + 'weekend' => [5, 6], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_TD.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_TD.php new file mode 100644 index 0000000..e790b99 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_TD.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ar.php', [ +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_TN.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_TN.php new file mode 100644 index 0000000..f096678 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_TN.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Authors: + * - JD Isaacks + * - Atef Ben Ali (atefBB) + * - Mohamed Sabil (mohamedsabil83) + */ +$months = [ + 'جانفي', + 'فيفري', + 'مارس', + 'أفريل', + 'ماي', + 'جوان', + 'جويلية', + 'أوت', + 'سبتمبر', + 'أكتوبر', + 'نوفمبر', + 'ديسمبر', +]; + +return [ + 'year' => implode('|', ['{0}:count سنة', '{1}سنة', '{2}سنتين', ']2,11[:count سنوات', ']10,Inf[:count سنة']), + 'a_year' => implode('|', ['{0}:count سنة', '{1}سنة', '{2}سنتين', ']2,11[:count سنوات', ']10,Inf[:count سنة']), + 'month' => implode('|', ['{0}:count شهر', '{1}شهر', '{2}شهرين', ']2,11[:count أشهر', ']10,Inf[:count شهر']), + 'a_month' => implode('|', ['{0}:count شهر', '{1}شهر', '{2}شهرين', ']2,11[:count أشهر', ']10,Inf[:count شهر']), + 'week' => implode('|', ['{0}:count أسبوع', '{1}أسبوع', '{2}أسبوعين', ']2,11[:count أسابيع', ']10,Inf[:count أسبوع']), + 'a_week' => implode('|', ['{0}:count أسبوع', '{1}أسبوع', '{2}أسبوعين', ']2,11[:count أسابيع', ']10,Inf[:count أسبوع']), + 'day' => implode('|', ['{0}:count يوم', '{1}يوم', '{2}يومين', ']2,11[:count أيام', ']10,Inf[:count يوم']), + 'a_day' => implode('|', ['{0}:count يوم', '{1}يوم', '{2}يومين', ']2,11[:count أيام', ']10,Inf[:count يوم']), + 'hour' => implode('|', ['{0}:count ساعة', '{1}ساعة', '{2}ساعتين', ']2,11[:count ساعات', ']10,Inf[:count ساعة']), + 'a_hour' => implode('|', ['{0}:count ساعة', '{1}ساعة', '{2}ساعتين', ']2,11[:count ساعات', ']10,Inf[:count ساعة']), + 'minute' => implode('|', ['{0}:count دقيقة', '{1}دقيقة', '{2}دقيقتين', ']2,11[:count دقائق', ']10,Inf[:count دقيقة']), + 'a_minute' => implode('|', ['{0}:count دقيقة', '{1}دقيقة', '{2}دقيقتين', ']2,11[:count دقائق', ']10,Inf[:count دقيقة']), + 'second' => implode('|', ['{0}:count ثانية', '{1}ثانية', '{2}ثانيتين', ']2,11[:count ثواني', ']10,Inf[:count ثانية']), + 'a_second' => implode('|', ['{0}:count ثانية', '{1}ثانية', '{2}ثانيتين', ']2,11[:count ثواني', ']10,Inf[:count ثانية']), + 'ago' => 'منذ :time', + 'from_now' => 'في :time', + 'after' => 'بعد :time', + 'before' => 'قبل :time', + 'diff_now' => 'الآن', + 'diff_today' => 'اليوم', + 'diff_today_regexp' => 'اليوم(?:\\s+على)?(?:\\s+الساعة)?', + 'diff_yesterday' => 'أمس', + 'diff_yesterday_regexp' => 'أمس(?:\\s+على)?(?:\\s+الساعة)?', + 'diff_tomorrow' => 'غداً', + 'diff_tomorrow_regexp' => 'غدا(?:\\s+على)?(?:\\s+الساعة)?', + 'diff_before_yesterday' => 'قبل الأمس', + 'diff_after_tomorrow' => 'بعد غد', + 'period_recurrences' => implode('|', ['{0}مرة', '{1}مرة', '{2}:count مرتين', ']2,11[:count مرات', ']10,Inf[:count مرة']), + 'period_interval' => 'كل :interval', + 'period_start_date' => 'من :date', + 'period_end_date' => 'إلى :date', + 'months' => $months, + 'months_short' => $months, + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['أحد', 'اثنين', 'ثلاثاء', 'أربعاء', 'خميس', 'جمعة', 'سبت'], + 'weekdays_min' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'list' => ['، ', ' و '], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[اليوم على الساعة] LT', + 'nextDay' => '[غدا على الساعة] LT', + 'nextWeek' => 'dddd [على الساعة] LT', + 'lastDay' => '[أمس على الساعة] LT', + 'lastWeek' => 'dddd [على الساعة] LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['ص', 'م'], + 'weekend' => [5, 6], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_YE.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_YE.php new file mode 100644 index 0000000..169fe88 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_YE.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + * - Abdullah-Alhariri + */ +return array_replace_recursive(require __DIR__.'/ar.php', [ + 'formats' => [ + 'L' => 'DD MMM, YYYY', + ], + 'months' => ['يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو', 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر'], + 'months_short' => ['ينا', 'فبر', 'مار', 'أبر', 'ماي', 'يون', 'يول', 'أغس', 'سبت', 'أكت', 'نوف', 'ديس'], + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'weekdays_min' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'day_of_first_week_of_year' => 1, + 'alt_numbers' => ['۰۰', '۰۱', '۰۲', '۰۳', '۰٤', '۰٥', '۰٦', '۰۷', '۰۸', '۰۹', '۱۰', '۱۱', '۱۲', '۱۳', '۱٤', '۱٥', '۱٦', '۱۷', '۱۸', '۱۹', '۲۰', '۲۱', '۲۲', '۲۳', '۲٤', '۲٥', '۲٦', '۲۷', '۲۸', '۲۹', '۳۰', '۳۱', '۳۲', '۳۳', '۳٤', '۳٥', '۳٦', '۳۷', '۳۸', '۳۹', '٤۰', '٤۱', '٤۲', '٤۳', '٤٤', '٤٥', '٤٦', '٤۷', '٤۸', '٤۹', '٥۰', '٥۱', '٥۲', '٥۳', '٥٤', '٥٥', '٥٦', '٥۷', '٥۸', '٥۹', '٦۰', '٦۱', '٦۲', '٦۳', '٦٤', '٦٥', '٦٦', '٦۷', '٦۸', '٦۹', '۷۰', '۷۱', '۷۲', '۷۳', '۷٤', '۷٥', '۷٦', '۷۷', '۷۸', '۷۹', '۸۰', '۸۱', '۸۲', '۸۳', '۸٤', '۸٥', '۸٦', '۸۷', '۸۸', '۸۹', '۹۰', '۹۱', '۹۲', '۹۳', '۹٤', '۹٥', '۹٦', '۹۷', '۹۸', '۹۹'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/as.php b/vendor/nesbot/carbon/src/Carbon/Lang/as.php new file mode 100644 index 0000000..04bc3df --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/as.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/as_IN.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/as_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/as_IN.php new file mode 100644 index 0000000..5fbc3db --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/as_IN.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Amitakhya Phukan, Red Hat bug-glibc@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D-MM-YYYY', + ], + 'months' => ['জানুৱাৰী', 'ফেব্ৰুৱাৰী', 'মাৰ্চ', 'এপ্ৰিল', 'মে', 'জুন', 'জুলাই', 'আগষ্ট', 'ছেপ্তেম্বৰ', 'অক্টোবৰ', 'নৱেম্বৰ', 'ডিচেম্বৰ'], + 'months_short' => ['জানু', 'ফেব্ৰু', 'মাৰ্চ', 'এপ্ৰিল', 'মে', 'জুন', 'জুলাই', 'আগ', 'সেপ্ট', 'অক্টো', 'নভে', 'ডিসে'], + 'weekdays' => ['দেওবাৰ', 'সোমবাৰ', 'মঙ্গলবাৰ', 'বুধবাৰ', 'বৃহষ্পতিবাৰ', 'শুক্ৰবাৰ', 'শনিবাৰ'], + 'weekdays_short' => ['দেও', 'সোম', 'মঙ্গল', 'বুধ', 'বৃহষ্পতি', 'শুক্ৰ', 'শনি'], + 'weekdays_min' => ['দেও', 'সোম', 'মঙ্গল', 'বুধ', 'বৃহষ্পতি', 'শুক্ৰ', 'শনি'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['পূৰ্ব্বাহ্ন', 'অপৰাহ্ন'], + + 'year' => ':count বছৰ', + 'y' => ':count বছৰ', + 'a_year' => ':count বছৰ', + + 'month' => ':count মাহ', + 'm' => ':count মাহ', + 'a_month' => ':count মাহ', + + 'week' => ':count সপ্তাহ', + 'w' => ':count সপ্তাহ', + 'a_week' => ':count সপ্তাহ', + + 'day' => ':count বাৰ', + 'd' => ':count বাৰ', + 'a_day' => ':count বাৰ', + + 'hour' => ':count ঘণ্টা', + 'h' => ':count ঘণ্টা', + 'a_hour' => ':count ঘণ্টা', + + 'minute' => ':count মিনিট', + 'min' => ':count মিনিট', + 'a_minute' => ':count মিনিট', + + 'second' => ':count দ্বিতীয়', + 's' => ':count দ্বিতীয়', + 'a_second' => ':count দ্বিতীয়', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/asa.php b/vendor/nesbot/carbon/src/Carbon/Lang/asa.php new file mode 100644 index 0000000..03bb483 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/asa.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['icheheavo', 'ichamthi'], + 'weekdays' => ['Jumapili', 'Jumatatu', 'Jumanne', 'Jumatano', 'Alhamisi', 'Ijumaa', 'Jumamosi'], + 'weekdays_short' => ['Jpi', 'Jtt', 'Jnn', 'Jtn', 'Alh', 'Ijm', 'Jmo'], + 'weekdays_min' => ['Jpi', 'Jtt', 'Jnn', 'Jtn', 'Alh', 'Ijm', 'Jmo'], + 'months' => ['Januari', 'Februari', 'Machi', 'Aprili', 'Mei', 'Juni', 'Julai', 'Agosti', 'Septemba', 'Oktoba', 'Novemba', 'Desemba'], + 'months_short' => ['Jan', 'Feb', 'Mac', 'Apr', 'Mei', 'Jun', 'Jul', 'Ago', 'Sep', 'Okt', 'Nov', 'Dec'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ast.php b/vendor/nesbot/carbon/src/Carbon/Lang/ast.php new file mode 100644 index 0000000..d9bdebe --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ast.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Jordi Mallach jordi@gnu.org + * - Adolfo Jayme-Barrientos (fitojb) + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['de xineru', 'de febreru', 'de marzu', 'd’abril', 'de mayu', 'de xunu', 'de xunetu', 'd’agostu', 'de setiembre', 'd’ochobre', 'de payares', 'd’avientu'], + 'months_short' => ['xin', 'feb', 'mar', 'abr', 'may', 'xun', 'xnt', 'ago', 'set', 'och', 'pay', 'avi'], + 'weekdays' => ['domingu', 'llunes', 'martes', 'miércoles', 'xueves', 'vienres', 'sábadu'], + 'weekdays_short' => ['dom', 'llu', 'mar', 'mié', 'xue', 'vie', 'sáb'], + 'weekdays_min' => ['dom', 'llu', 'mar', 'mié', 'xue', 'vie', 'sáb'], + + 'year' => ':count añu|:count años', + 'y' => ':count añu|:count años', + 'a_year' => 'un añu|:count años', + + 'month' => ':count mes', + 'm' => ':count mes', + 'a_month' => 'un mes|:count mes', + + 'week' => ':count selmana|:count selmanes', + 'w' => ':count selmana|:count selmanes', + 'a_week' => 'una selmana|:count selmanes', + + 'day' => ':count día|:count díes', + 'd' => ':count día|:count díes', + 'a_day' => 'un día|:count díes', + + 'hour' => ':count hora|:count hores', + 'h' => ':count hora|:count hores', + 'a_hour' => 'una hora|:count hores', + + 'minute' => ':count minutu|:count minutos', + 'min' => ':count minutu|:count minutos', + 'a_minute' => 'un minutu|:count minutos', + + 'second' => ':count segundu|:count segundos', + 's' => ':count segundu|:count segundos', + 'a_second' => 'un segundu|:count segundos', + + 'ago' => 'hai :time', + 'from_now' => 'en :time', + 'after' => ':time dempués', + 'before' => ':time enantes', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ast_ES.php b/vendor/nesbot/carbon/src/Carbon/Lang/ast_ES.php new file mode 100644 index 0000000..04d7562 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ast_ES.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__.'/ast.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ayc.php b/vendor/nesbot/carbon/src/Carbon/Lang/ayc.php new file mode 100644 index 0000000..d6a6f63 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ayc.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/ayc_PE.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ayc_PE.php b/vendor/nesbot/carbon/src/Carbon/Lang/ayc_PE.php new file mode 100644 index 0000000..ff18504 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ayc_PE.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - runasimipi.org libc-alpha@sourceware.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['inïru', 'phiwriru', 'marsu', 'awrila', 'mayu', 'junyu', 'julyu', 'awustu', 'sitimri', 'uktuwri', 'nuwimri', 'risimri'], + 'months_short' => ['ini', 'phi', 'mar', 'awr', 'may', 'jun', 'jul', 'awu', 'sit', 'ukt', 'nuw', 'ris'], + 'weekdays' => ['tuminku', 'lunisa', 'martisa', 'mirkulisa', 'juywisa', 'wirnisa', 'sawäru'], + 'weekdays_short' => ['tum', 'lun', 'mar', 'mir', 'juy', 'wir', 'saw'], + 'weekdays_min' => ['tum', 'lun', 'mar', 'mir', 'juy', 'wir', 'saw'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['VM', 'NM'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/az.php b/vendor/nesbot/carbon/src/Carbon/Lang/az.php new file mode 100644 index 0000000..1e92106 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/az.php @@ -0,0 +1,128 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - Kunal Marwaha + * - François B + * - JD Isaacks + * - Orxan + * - Şəhriyar İmanov + * - Baran Şengül + */ +return [ + 'year' => ':count il', + 'a_year' => '{1}bir il|]1,Inf[:count il', + 'y' => ':count il', + 'month' => ':count ay', + 'a_month' => '{1}bir ay|]1,Inf[:count ay', + 'm' => ':count ay', + 'week' => ':count həftə', + 'a_week' => '{1}bir həftə|]1,Inf[:count həftə', + 'w' => ':count h.', + 'day' => ':count gün', + 'a_day' => '{1}bir gün|]1,Inf[:count gün', + 'd' => ':count g.', + 'hour' => ':count saat', + 'a_hour' => '{1}bir saat|]1,Inf[:count saat', + 'h' => ':count saat', + 'minute' => ':count d.', + 'a_minute' => '{1}bir dəqiqə|]1,Inf[:count dəqiqə', + 'min' => ':count dəqiqə', + 'second' => ':count san.', + 'a_second' => '{1}birneçə saniyə|]1,Inf[:count saniyə', + 's' => ':count saniyə', + 'ago' => ':time əvvəl', + 'from_now' => ':time sonra', + 'after' => ':time sonra', + 'before' => ':time əvvəl', + 'diff_now' => 'indi', + 'diff_today' => 'bugün', + 'diff_today_regexp' => 'bugün(?:\\s+saat)?', + 'diff_yesterday' => 'dünən', + 'diff_tomorrow' => 'sabah', + 'diff_tomorrow_regexp' => 'sabah(?:\\s+saat)?', + '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', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[bugün saat] LT', + 'nextDay' => '[sabah saat] LT', + 'nextWeek' => '[gələn həftə] dddd [saat] LT', + 'lastDay' => '[dünən] LT', + 'lastWeek' => '[keçən həftə] dddd [saat] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + if ($number === 0) { // special case for zero + return "$number-ıncı"; + } + + static $suffixes = [ + 1 => '-inci', + 5 => '-inci', + 8 => '-inci', + 70 => '-inci', + 80 => '-inci', + 2 => '-nci', + 7 => '-nci', + 20 => '-nci', + 50 => '-nci', + 3 => '-üncü', + 4 => '-üncü', + 100 => '-üncü', + 6 => '-ncı', + 9 => '-uncu', + 10 => '-uncu', + 30 => '-uncu', + 60 => '-ıncı', + 90 => '-ıncı', + ]; + + $lastDigit = $number % 10; + + return $number.($suffixes[$lastDigit] ?? $suffixes[$number % 100 - $lastDigit] ?? $suffixes[$number >= 100 ? 100 : -1] ?? ''); + }, + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'gecə'; + } + if ($hour < 12) { + return 'səhər'; + } + if ($hour < 17) { + return 'gündüz'; + } + + return 'axşam'; + }, + 'months' => ['yanvar', 'fevral', 'mart', 'aprel', 'may', 'iyun', 'iyul', 'avqust', 'sentyabr', 'oktyabr', 'noyabr', 'dekabr'], + 'months_short' => ['yan', 'fev', 'mar', 'apr', 'may', 'iyn', 'iyl', 'avq', 'sen', 'okt', 'noy', 'dek'], + 'months_standalone' => ['Yanvar', 'Fevral', 'Mart', 'Aprel', 'May', 'İyun', 'İyul', 'Avqust', 'Sentyabr', 'Oktyabr', 'Noyabr', 'Dekabr'], + 'weekdays' => ['bazar', 'bazar ertəsi', 'çərşənbə axşamı', 'çərşənbə', 'cümə axşamı', 'cümə', 'şənbə'], + 'weekdays_short' => ['baz', 'bze', 'çax', 'çər', 'cax', 'cüm', 'şən'], + 'weekdays_min' => ['bz', 'be', 'ça', 'çə', 'ca', 'cü', 'şə'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' və '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/az_AZ.php b/vendor/nesbot/carbon/src/Carbon/Lang/az_AZ.php new file mode 100644 index 0000000..2acf881 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/az_AZ.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Pablo Saratxaga pablo@mandrakesoft.com + */ +return array_replace_recursive(require __DIR__.'/az.php', [ + 'months_short' => ['Yan', 'Fev', 'Mar', 'Apr', 'May', 'İyn', 'İyl', 'Avq', 'Sen', 'Okt', 'Noy', 'Dek'], + 'weekdays' => ['bazar günü', 'bazar ertəsi', 'çərşənbə axşamı', 'çərşənbə', 'cümə axşamı', 'cümə', 'şənbə'], + 'weekdays_short' => ['baz', 'ber', 'çax', 'çər', 'cax', 'cüm', 'şnb'], + 'weekdays_min' => ['baz', 'ber', 'çax', 'çər', 'cax', 'cüm', 'şnb'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/az_Cyrl.php b/vendor/nesbot/carbon/src/Carbon/Lang/az_Cyrl.php new file mode 100644 index 0000000..28fc62f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/az_Cyrl.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/az.php', [ + 'weekdays' => ['базар', 'базар ертәси', 'чәршәнбә ахшамы', 'чәршәнбә', 'ҹүмә ахшамы', 'ҹүмә', 'шәнбә'], + 'weekdays_short' => ['Б.', 'Б.Е.', 'Ч.А.', 'Ч.', 'Ҹ.А.', 'Ҹ.', 'Ш.'], + 'weekdays_min' => ['Б.', 'Б.Е.', 'Ч.А.', 'Ч.', 'Ҹ.А.', 'Ҹ.', 'Ш.'], + 'months' => ['јанвар', 'феврал', 'март', 'апрел', 'май', 'ијун', 'ијул', 'август', 'сентјабр', 'октјабр', 'нојабр', 'декабр'], + 'months_short' => ['јан', 'фев', 'мар', 'апр', 'май', 'ијн', 'ијл', 'авг', 'сен', 'окт', 'ној', 'дек'], + 'months_standalone' => ['Јанвар', 'Феврал', 'Март', 'Апрел', 'Май', 'Ијун', 'Ијул', 'Август', 'Сентјабр', 'Октјабр', 'Нојабр', 'Декабр'], + 'meridiem' => ['а', 'п'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/az_IR.php b/vendor/nesbot/carbon/src/Carbon/Lang/az_IR.php new file mode 100644 index 0000000..991a0ef --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/az_IR.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Mousa Moradi mousamk@gmail.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'OY/OM/OD', + ], + 'months' => ['ژانویه', 'فوریه', 'مارس', 'آوریل', 'مئی', 'ژوئن', 'جولای', 'آقۇست', 'سپتامبر', 'اوْکتوْبر', 'نوْوامبر', 'دسامبر'], + 'months_short' => ['ژانویه', 'فوریه', 'مارس', 'آوریل', 'مئی', 'ژوئن', 'جولای', 'آقۇست', 'سپتامبر', 'اوْکتوْبر', 'نوْوامبر', 'دسامبر'], + 'weekdays' => ['یکشنبه', 'دوشنبه', 'سه‌شنبه', 'چارشنبه', 'جۆمعه آخشامی', 'جۆمعه', 'شنبه'], + 'weekdays_short' => ['یکشنبه', 'دوشنبه', 'سه‌شنبه', 'چارشنبه', 'جۆمعه آخشامی', 'جۆمعه', 'شنبه'], + 'weekdays_min' => ['یکشنبه', 'دوشنبه', 'سه‌شنبه', 'چارشنبه', 'جۆمعه آخشامی', 'جۆمعه', 'شنبه'], + 'first_day_of_week' => 6, + 'alt_numbers' => ['۰۰', '۰۱', '۰۲', '۰۳', '۰۴', '۰۵', '۰۶', '۰۷', '۰۸', '۰۹', '۱۰', '۱۱', '۱۲', '۱۳', '۱۴', '۱۵', '۱۶', '۱۷', '۱۸', '۱۹', '۲۰', '۲۱', '۲۲', '۲۳', '۲۴', '۲۵', '۲۶', '۲۷', '۲۸', '۲۹', '۳۰', '۳۱', '۳۲', '۳۳', '۳۴', '۳۵', '۳۶', '۳۷', '۳۸', '۳۹', '۴۰', '۴۱', '۴۲', '۴۳', '۴۴', '۴۵', '۴۶', '۴۷', '۴۸', '۴۹', '۵۰', '۵۱', '۵۲', '۵۳', '۵۴', '۵۵', '۵۶', '۵۷', '۵۸', '۵۹', '۶۰', '۶۱', '۶۲', '۶۳', '۶۴', '۶۵', '۶۶', '۶۷', '۶۸', '۶۹', '۷۰', '۷۱', '۷۲', '۷۳', '۷۴', '۷۵', '۷۶', '۷۷', '۷۸', '۷۹', '۸۰', '۸۱', '۸۲', '۸۳', '۸۴', '۸۵', '۸۶', '۸۷', '۸۸', '۸۹', '۹۰', '۹۱', '۹۲', '۹۳', '۹۴', '۹۵', '۹۶', '۹۷', '۹۸', '۹۹'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/az_Latn.php b/vendor/nesbot/carbon/src/Carbon/Lang/az_Latn.php new file mode 100644 index 0000000..0be3391 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/az_Latn.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_replace_recursive(require __DIR__.'/az.php', [ + 'meridiem' => ['a', 'p'], + 'weekdays' => ['bazar', 'bazar ertəsi', 'çərşənbə axşamı', 'çərşənbə', 'cümə axşamı', 'cümə', 'şənbə'], + 'weekdays_short' => ['B.', 'B.E.', 'Ç.A.', 'Ç.', 'C.A.', 'C.', 'Ş.'], + 'weekdays_min' => ['B.', 'B.E.', 'Ç.A.', 'Ç.', 'C.A.', 'C.', 'Ş.'], + 'months' => ['yanvar', 'fevral', 'mart', 'aprel', 'may', 'iyun', 'iyul', 'avqust', 'sentyabr', 'oktyabr', 'noyabr', 'dekabr'], + 'months_short' => ['yan', 'fev', 'mar', 'apr', 'may', 'iyn', 'iyl', 'avq', 'sen', 'okt', 'noy', 'dek'], + 'months_standalone' => ['Yanvar', 'Fevral', 'Mart', 'Aprel', 'May', 'İyun', 'İyul', 'Avqust', 'Sentyabr', 'Oktyabr', 'Noyabr', 'Dekabr'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'D MMMM YYYY, dddd HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/bas.php b/vendor/nesbot/carbon/src/Carbon/Lang/bas.php new file mode 100644 index 0000000..41bfa1d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/bas.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['I bikɛ̂glà', 'I ɓugajɔp'], + 'weekdays' => ['ŋgwà nɔ̂y', 'ŋgwà njaŋgumba', 'ŋgwà ûm', 'ŋgwà ŋgê', 'ŋgwà mbɔk', 'ŋgwà kɔɔ', 'ŋgwà jôn'], + 'weekdays_short' => ['nɔy', 'nja', 'uum', 'ŋge', 'mbɔ', 'kɔɔ', 'jon'], + 'weekdays_min' => ['nɔy', 'nja', 'uum', 'ŋge', 'mbɔ', 'kɔɔ', 'jon'], + 'months' => ['Kɔndɔŋ', 'Màcɛ̂l', 'Màtùmb', 'Màtop', 'M̀puyɛ', 'Hìlòndɛ̀', 'Njèbà', 'Hìkaŋ', 'Dìpɔ̀s', 'Bìòôm', 'Màyɛsèp', 'Lìbuy li ńyèe'], + 'months_short' => ['kɔn', 'mac', 'mat', 'mto', 'mpu', 'hil', 'nje', 'hik', 'dip', 'bio', 'may', 'liɓ'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM, YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + + 'second' => ':count móndî', // less reliable + 's' => ':count móndî', // less reliable + 'a_second' => ':count móndî', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/be.php b/vendor/nesbot/carbon/src/Carbon/Lang/be.php new file mode 100644 index 0000000..ee73636 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/be.php @@ -0,0 +1,172 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Carbon\CarbonInterface; +use Symfony\Component\Translation\PluralizationRules; + +// @codeCoverageIgnoreStart +if (class_exists(PluralizationRules::class)) { + PluralizationRules::set(static function ($number) { + return (($number % 10 == 1) && ($number % 100 != 11)) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2); + }, 'be'); +} +// @codeCoverageIgnoreEnd + +/* + * Authors: + * - Josh Soref + * - SobakaSlava + * - François B + * - Serhan Apaydın + * - JD Isaacks + * - AbadonnaAbbys + * - Siomkin Alexander + */ +return [ + 'year' => ':count год|:count гады|:count гадоў', + 'a_year' => '{1}год|:count год|:count гады|:count гадоў', + 'y' => ':count год|:count гады|:count гадоў', + 'month' => ':count месяц|:count месяцы|:count месяцаў', + 'a_month' => '{1}месяц|:count месяц|:count месяцы|:count месяцаў', + 'm' => ':count месяц|:count месяцы|:count месяцаў', + 'week' => ':count тыдзень|:count тыдні|:count тыдняў', + 'a_week' => '{1}тыдзень|:count тыдзень|:count тыдні|:count тыдняў', + 'w' => ':count тыдзень|:count тыдні|:count тыдняў', + 'day' => ':count дзень|:count дні|:count дзён', + 'a_day' => '{1}дзень|:count дзень|:count дні|:count дзён', + 'd' => ':count дн', + 'hour' => ':count гадзіну|:count гадзіны|:count гадзін', + 'a_hour' => '{1}гадзіна|:count гадзіна|:count гадзіны|:count гадзін', + 'h' => ':count гадзіна|:count гадзіны|:count гадзін', + 'minute' => ':count хвіліна|:count хвіліны|:count хвілін', + 'a_minute' => '{1}хвіліна|:count хвіліна|:count хвіліны|:count хвілін', + 'min' => ':count хв', + 'second' => ':count секунда|:count секунды|:count секунд', + 'a_second' => '{1}некалькі секунд|:count секунда|:count секунды|:count секунд', + 's' => ':count сек', + + 'hour_ago' => ':count гадзіну|:count гадзіны|:count гадзін', + 'a_hour_ago' => '{1}гадзіну|:count гадзіну|:count гадзіны|:count гадзін', + 'h_ago' => ':count гадзіну|:count гадзіны|:count гадзін', + 'minute_ago' => ':count хвіліну|:count хвіліны|:count хвілін', + 'a_minute_ago' => '{1}хвіліну|:count хвіліну|:count хвіліны|:count хвілін', + 'min_ago' => ':count хвіліну|:count хвіліны|:count хвілін', + 'second_ago' => ':count секунду|:count секунды|:count секунд', + 'a_second_ago' => '{1}некалькі секунд|:count секунду|:count секунды|:count секунд', + 's_ago' => ':count секунду|:count секунды|:count секунд', + + 'hour_from_now' => ':count гадзіну|:count гадзіны|:count гадзін', + 'a_hour_from_now' => '{1}гадзіну|:count гадзіну|:count гадзіны|:count гадзін', + 'h_from_now' => ':count гадзіну|:count гадзіны|:count гадзін', + 'minute_from_now' => ':count хвіліну|:count хвіліны|:count хвілін', + 'a_minute_from_now' => '{1}хвіліну|:count хвіліну|:count хвіліны|:count хвілін', + 'min_from_now' => ':count хвіліну|:count хвіліны|:count хвілін', + 'second_from_now' => ':count секунду|:count секунды|:count секунд', + 'a_second_from_now' => '{1}некалькі секунд|:count секунду|:count секунды|:count секунд', + 's_from_now' => ':count секунду|:count секунды|:count секунд', + + 'hour_after' => ':count гадзіну|:count гадзіны|:count гадзін', + 'a_hour_after' => '{1}гадзіну|:count гадзіну|:count гадзіны|:count гадзін', + 'h_after' => ':count гадзіну|:count гадзіны|:count гадзін', + 'minute_after' => ':count хвіліну|:count хвіліны|:count хвілін', + 'a_minute_after' => '{1}хвіліну|:count хвіліну|:count хвіліны|:count хвілін', + 'min_after' => ':count хвіліну|:count хвіліны|:count хвілін', + 'second_after' => ':count секунду|:count секунды|:count секунд', + 'a_second_after' => '{1}некалькі секунд|:count секунду|:count секунды|:count секунд', + 's_after' => ':count секунду|:count секунды|:count секунд', + + 'hour_before' => ':count гадзіну|:count гадзіны|:count гадзін', + 'a_hour_before' => '{1}гадзіну|:count гадзіну|:count гадзіны|:count гадзін', + 'h_before' => ':count гадзіну|:count гадзіны|:count гадзін', + 'minute_before' => ':count хвіліну|:count хвіліны|:count хвілін', + 'a_minute_before' => '{1}хвіліну|:count хвіліну|:count хвіліны|:count хвілін', + 'min_before' => ':count хвіліну|:count хвіліны|:count хвілін', + 'second_before' => ':count секунду|:count секунды|:count секунд', + 'a_second_before' => '{1}некалькі секунд|:count секунду|:count секунды|:count секунд', + 's_before' => ':count секунду|:count секунды|:count секунд', + + 'ago' => ':time таму', + 'from_now' => 'праз :time', + 'after' => ':time пасля', + 'before' => ':time да', + 'diff_now' => 'цяпер', + 'diff_today' => 'Сёння', + 'diff_today_regexp' => 'Сёння(?:\\s+ў)?', + 'diff_yesterday' => 'учора', + 'diff_yesterday_regexp' => 'Учора(?:\\s+ў)?', + 'diff_tomorrow' => 'заўтра', + 'diff_tomorrow_regexp' => 'Заўтра(?:\\s+ў)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMMM YYYY г.', + 'LLL' => 'D MMMM YYYY г., HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY г., HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Сёння ў] LT', + 'nextDay' => '[Заўтра ў] LT', + 'nextWeek' => '[У] dddd [ў] LT', + 'lastDay' => '[Учора ў] LT', + 'lastWeek' => function (CarbonInterface $current) { + switch ($current->dayOfWeek) { + case 1: + case 2: + case 4: + return '[У мінулы] dddd [ў] LT'; + default: + return '[У мінулую] dddd [ў] LT'; + } + }, + 'sameElse' => 'L', + ], + 'ordinal' => function ($number, $period) { + switch ($period) { + case 'M': + case 'd': + case 'DDD': + case 'w': + case 'W': + return ($number % 10 === 2 || $number % 10 === 3) && ($number % 100 !== 12 && $number % 100 !== 13) ? $number.'-і' : $number.'-ы'; + case 'D': + return $number.'-га'; + default: + return $number; + } + }, + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'ночы'; + } + if ($hour < 12) { + return 'раніцы'; + } + if ($hour < 17) { + return 'дня'; + } + + return 'вечара'; + }, + 'months' => ['студзеня', 'лютага', 'сакавіка', 'красавіка', 'траўня', 'чэрвеня', 'ліпеня', 'жніўня', 'верасня', 'кастрычніка', 'лістапада', 'снежня'], + 'months_standalone' => ['студзень', 'люты', 'сакавік', 'красавік', 'травень', 'чэрвень', 'ліпень', 'жнівень', 'верасень', 'кастрычнік', 'лістапад', 'снежань'], + 'months_short' => ['студ', 'лют', 'сак', 'крас', 'трав', 'чэрв', 'ліп', 'жнів', 'вер', 'каст', 'ліст', 'снеж'], + 'months_regexp' => '/(DD?o?\.?(\[[^\[\]]*\]|\s)+MMMM?|L{2,4}|l{2,4})/', + 'weekdays' => ['нядзелю', 'панядзелак', 'аўторак', 'сераду', 'чацвер', 'пятніцу', 'суботу'], + 'weekdays_standalone' => ['нядзеля', 'панядзелак', 'аўторак', 'серада', 'чацвер', 'пятніца', 'субота'], + 'weekdays_short' => ['нд', 'пн', 'ат', 'ср', 'чц', 'пт', 'сб'], + 'weekdays_min' => ['нд', 'пн', 'ат', 'ср', 'чц', 'пт', 'сб'], + 'weekdays_regexp' => '/\[ ?[Ууў] ?(?:мінулую|наступную)? ?\] ?dddd/', + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' і '], + 'months_short_standalone' => ['сту', 'лют', 'сак', 'кра', 'май', 'чэр', 'ліп', 'жні', 'вер', 'кас', 'ліс', 'сне'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/be_BY.php b/vendor/nesbot/carbon/src/Carbon/Lang/be_BY.php new file mode 100644 index 0000000..26684b4 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/be_BY.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/be.php', [ + 'months' => ['студзеня', 'лютага', 'сакавіка', 'красавіка', 'мая', 'чэрвеня', 'ліпеня', 'жніўня', 'верасня', 'кастрычніка', 'лістапада', 'снежня'], + 'months_short' => ['сту', 'лют', 'сак', 'кра', 'мая', 'чэр', 'ліп', 'жні', 'вер', 'кас', 'ліс', 'сне'], + 'weekdays' => ['Нядзеля', 'Панядзелак', 'Аўторак', 'Серада', 'Чацвер', 'Пятніца', 'Субота'], + 'weekdays_short' => ['Няд', 'Пан', 'Аўт', 'Срд', 'Чцв', 'Пят', 'Суб'], + 'weekdays_min' => ['Няд', 'Пан', 'Аўт', 'Срд', 'Чцв', 'Пят', 'Суб'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/be_BY@latin.php b/vendor/nesbot/carbon/src/Carbon/Lang/be_BY@latin.php new file mode 100644 index 0000000..517ce83 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/be_BY@latin.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['studzienia', 'lutaha', 'sakavika', 'krasavika', 'maja', 'červienia', 'lipienia', 'žniŭnia', 'vieraśnia', 'kastryčnika', 'listapada', 'śniežnia'], + 'months_short' => ['Stu', 'Lut', 'Sak', 'Kra', 'Maj', 'Čer', 'Lip', 'Žni', 'Vie', 'Kas', 'Lis', 'Śni'], + 'weekdays' => ['Niadziela', 'Paniadziełak', 'Aŭtorak', 'Sierada', 'Čaćvier', 'Piatnica', 'Subota'], + 'weekdays_short' => ['Nia', 'Pan', 'Aŭt', 'Sie', 'Čać', 'Pia', 'Sub'], + 'weekdays_min' => ['Nia', 'Pan', 'Aŭt', 'Sie', 'Čać', 'Pia', 'Sub'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/bem.php b/vendor/nesbot/carbon/src/Carbon/Lang/bem.php new file mode 100644 index 0000000..1c3ef03 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/bem.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/bem_ZM.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/bem_ZM.php b/vendor/nesbot/carbon/src/Carbon/Lang/bem_ZM.php new file mode 100644 index 0000000..620b579 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/bem_ZM.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - ANLoc Martin Benjamin locales@africanlocalization.net + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'MM/DD/YYYY', + ], + 'months' => ['Januari', 'Februari', 'Machi', 'Epreo', 'Mei', 'Juni', 'Julai', 'Ogasti', 'Septemba', 'Oktoba', 'Novemba', 'Disemba'], + 'months_short' => ['Jan', 'Feb', 'Mac', 'Epr', 'Mei', 'Jun', 'Jul', 'Oga', 'Sep', 'Okt', 'Nov', 'Dis'], + 'weekdays' => ['Pa Mulungu', 'Palichimo', 'Palichibuli', 'Palichitatu', 'Palichine', 'Palichisano', 'Pachibelushi'], + 'weekdays_short' => ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], + 'weekdays_min' => ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['uluchelo', 'akasuba'], + + 'year' => 'myaka :count', + 'y' => 'myaka :count', + 'a_year' => 'myaka :count', + + 'month' => 'myeshi :count', + 'm' => 'myeshi :count', + 'a_month' => 'myeshi :count', + + 'week' => 'umulungu :count', + 'w' => 'umulungu :count', + 'a_week' => 'umulungu :count', + + 'day' => 'inshiku :count', + 'd' => 'inshiku :count', + 'a_day' => 'inshiku :count', + + 'hour' => 'awala :count', + 'h' => 'awala :count', + 'a_hour' => 'awala :count', + + 'minute' => 'miniti :count', + 'min' => 'miniti :count', + 'a_minute' => 'miniti :count', + + 'second' => 'sekondi :count', + 's' => 'sekondi :count', + 'a_second' => 'sekondi :count', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ber.php b/vendor/nesbot/carbon/src/Carbon/Lang/ber.php new file mode 100644 index 0000000..685603c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ber.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/ber_DZ.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ber_DZ.php b/vendor/nesbot/carbon/src/Carbon/Lang/ber_DZ.php new file mode 100644 index 0000000..38de10a --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ber_DZ.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Pablo Saratxaga pablo@mandrakesoft.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['yanvar', 'fevral', 'mart', 'aprel', 'may', 'iyun', 'iyul', 'avqust', 'sentyabr', 'oktyabr', 'noyabr', 'dekabr'], + 'months_short' => ['Yan', 'Fev', 'Mar', 'Apr', 'May', 'İyn', 'İyl', 'Avq', 'Sen', 'Okt', 'Noy', 'Dek'], + 'weekdays' => ['bazar günü', 'birinci gün', 'ikinci gün', 'üçüncü gün', 'dördüncü gün', 'beşinci gün', 'altıncı gün'], + 'weekdays_short' => ['baz', 'bir', 'iki', 'üçü', 'dör', 'beş', 'alt'], + 'weekdays_min' => ['baz', 'bir', 'iki', 'üçü', 'dör', 'beş', 'alt'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ber_MA.php b/vendor/nesbot/carbon/src/Carbon/Lang/ber_MA.php new file mode 100644 index 0000000..38de10a --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ber_MA.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Pablo Saratxaga pablo@mandrakesoft.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['yanvar', 'fevral', 'mart', 'aprel', 'may', 'iyun', 'iyul', 'avqust', 'sentyabr', 'oktyabr', 'noyabr', 'dekabr'], + 'months_short' => ['Yan', 'Fev', 'Mar', 'Apr', 'May', 'İyn', 'İyl', 'Avq', 'Sen', 'Okt', 'Noy', 'Dek'], + 'weekdays' => ['bazar günü', 'birinci gün', 'ikinci gün', 'üçüncü gün', 'dördüncü gün', 'beşinci gün', 'altıncı gün'], + 'weekdays_short' => ['baz', 'bir', 'iki', 'üçü', 'dör', 'beş', 'alt'], + 'weekdays_min' => ['baz', 'bir', 'iki', 'üçü', 'dör', 'beş', 'alt'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/bez.php b/vendor/nesbot/carbon/src/Carbon/Lang/bez.php new file mode 100644 index 0000000..d59c5ef --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/bez.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['pamilau', 'pamunyi'], + 'weekdays' => ['pa mulungu', 'pa shahuviluha', 'pa hivili', 'pa hidatu', 'pa hitayi', 'pa hihanu', 'pa shahulembela'], + 'weekdays_short' => ['Mul', 'Vil', 'Hiv', 'Hid', 'Hit', 'Hih', 'Lem'], + 'weekdays_min' => ['Mul', 'Vil', 'Hiv', 'Hid', 'Hit', 'Hih', 'Lem'], + 'months' => ['pa mwedzi gwa hutala', 'pa mwedzi gwa wuvili', 'pa mwedzi gwa wudatu', 'pa mwedzi gwa wutai', 'pa mwedzi gwa wuhanu', 'pa mwedzi gwa sita', 'pa mwedzi gwa saba', 'pa mwedzi gwa nane', 'pa mwedzi gwa tisa', 'pa mwedzi gwa kumi', 'pa mwedzi gwa kumi na moja', 'pa mwedzi gwa kumi na mbili'], + 'months_short' => ['Hut', 'Vil', 'Dat', 'Tai', 'Han', 'Sit', 'Sab', 'Nan', 'Tis', 'Kum', 'Kmj', 'Kmb'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/bg.php b/vendor/nesbot/carbon/src/Carbon/Lang/bg.php new file mode 100644 index 0000000..f768074 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/bg.php @@ -0,0 +1,114 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - François B + * - Serhan Apaydın + * - JD Isaacks + * - Glavić + */ + +use Carbon\CarbonInterface; + +return [ + 'year' => ':count година|:count години', + 'a_year' => 'година|:count години', + 'y' => ':count година|:count години', + 'month' => ':count месец|:count месеца', + 'a_month' => 'месец|:count месеца', + 'm' => ':count месец|:count месеца', + 'week' => ':count седмица|:count седмици', + 'a_week' => 'седмица|:count седмици', + 'w' => ':count седмица|:count седмици', + 'day' => ':count ден|:count дни', + 'a_day' => 'ден|:count дни', + 'd' => ':count ден|:count дни', + 'hour' => ':count час|:count часа', + 'a_hour' => 'час|:count часа', + 'h' => ':count час|:count часа', + 'minute' => ':count минута|:count минути', + 'a_minute' => 'минута|:count минути', + 'min' => ':count минута|:count минути', + 'second' => ':count секунда|:count секунди', + 'a_second' => 'няколко секунди|:count секунди', + 's' => ':count секунда|:count секунди', + 'ago' => 'преди :time', + 'from_now' => 'след :time', + 'after' => 'след :time', + 'before' => 'преди :time', + 'diff_now' => 'сега', + 'diff_today' => 'Днес', + 'diff_today_regexp' => 'Днес(?:\\s+в)?', + 'diff_yesterday' => 'вчера', + 'diff_yesterday_regexp' => 'Вчера(?:\\s+в)?', + 'diff_tomorrow' => 'утре', + 'diff_tomorrow_regexp' => 'Утре(?:\\s+в)?', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'D.MM.YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY H:mm', + 'LLLL' => 'dddd, D MMMM YYYY H:mm', + ], + 'calendar' => [ + 'sameDay' => '[Днес в] LT', + 'nextDay' => '[Утре в] LT', + 'nextWeek' => 'dddd [в] LT', + 'lastDay' => '[Вчера в] LT', + 'lastWeek' => function (CarbonInterface $current) { + switch ($current->dayOfWeek) { + case 0: + case 3: + case 6: + return '[В изминалата] dddd [в] LT'; + default: + return '[В изминалия] dddd [в] LT'; + } + }, + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + $lastDigit = $number % 10; + $last2Digits = $number % 100; + if ($number === 0) { + return "$number-ев"; + } + if ($last2Digits === 0) { + return "$number-ен"; + } + if ($last2Digits > 10 && $last2Digits < 20) { + return "$number-ти"; + } + if ($lastDigit === 1) { + return "$number-ви"; + } + if ($lastDigit === 2) { + return "$number-ри"; + } + if ($lastDigit === 7 || $lastDigit === 8) { + return "$number-ми"; + } + + return "$number-ти"; + }, + 'months' => ['януари', 'февруари', 'март', 'април', 'май', 'юни', 'юли', 'август', 'септември', 'октомври', 'ноември', 'декември'], + 'months_short' => ['яну', 'фев', 'мар', 'апр', 'май', 'юни', 'юли', 'авг', 'сеп', 'окт', 'ное', 'дек'], + 'weekdays' => ['неделя', 'понеделник', 'вторник', 'сряда', 'четвъртък', 'петък', 'събота'], + 'weekdays_short' => ['нед', 'пон', 'вто', 'сря', 'чет', 'пет', 'съб'], + 'weekdays_min' => ['нд', 'пн', 'вт', 'ср', 'чт', 'пт', 'сб'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' и '], + 'meridiem' => ['преди обяд', 'следобед'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/bg_BG.php b/vendor/nesbot/carbon/src/Carbon/Lang/bg_BG.php new file mode 100644 index 0000000..b53874d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/bg_BG.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__.'/bg.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/bhb.php b/vendor/nesbot/carbon/src/Carbon/Lang/bhb.php new file mode 100644 index 0000000..49f0803 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/bhb.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/bhb_IN.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/bhb_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/bhb_IN.php new file mode 100644 index 0000000..ab557cb --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/bhb_IN.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Samsung Electronics Co., Ltd. alexey.merzlyakov@samsung.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], + 'months_short' => ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], + 'weekdays' => ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], + 'weekdays_short' => ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], + 'weekdays_min' => ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/bho.php b/vendor/nesbot/carbon/src/Carbon/Lang/bho.php new file mode 100644 index 0000000..e9ed0b6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/bho.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/bho_IN.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/bho_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/bho_IN.php new file mode 100644 index 0000000..bc54f36 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/bho_IN.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bhashaghar@googlegroups.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['जनवरी', 'फरवरी', 'मार्च', 'अप्रैल', 'मई', 'जून', 'जुलाई', 'अगस्त', 'सितम्बर', 'अक्टूबर', 'नवम्बर', 'दिसम्बर"'], + 'months_short' => ['जनवरी', 'फरवरी', 'मार्च', 'अप्रैल', 'मई', 'जून', 'जुलाई', 'अगस्त', 'सितम्बर', 'अक्टूबर', 'नवम्बर', 'दिसम्बर"'], + 'weekdays' => ['रविवार', 'सोमवार', 'मंगलवार', 'बुधवार', 'गुरुवार', 'शुक्रवार', 'शनिवार'], + 'weekdays_short' => ['रवि', 'सोम', 'मंगल', 'बुध', 'गुरु', 'शुक्र', 'शनि'], + 'weekdays_min' => ['रवि', 'सोम', 'मंगल', 'बुध', 'गुरु', 'शुक्र', 'शनि'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['पूर्वाह्न', 'अपराह्न'], + + 'hour' => ':count मौसम', + 'h' => ':count मौसम', + 'a_hour' => ':count मौसम', + + 'minute' => ':count कला', + 'min' => ':count कला', + 'a_minute' => ':count कला', + + 'second' => ':count सोमार', + 's' => ':count सोमार', + 'a_second' => ':count सोमार', + + 'year' => ':count साल', + 'y' => ':count साल', + 'a_year' => ':count साल', + + 'month' => ':count महिना', + 'm' => ':count महिना', + 'a_month' => ':count महिना', + + 'week' => ':count सप्ताह', + 'w' => ':count सप्ताह', + 'a_week' => ':count सप्ताह', + + 'day' => ':count दिन', + 'd' => ':count दिन', + 'a_day' => ':count दिन', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/bi.php b/vendor/nesbot/carbon/src/Carbon/Lang/bi.php new file mode 100644 index 0000000..dd08128 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/bi.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/bi_VU.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/bi_VU.php b/vendor/nesbot/carbon/src/Carbon/Lang/bi_VU.php new file mode 100644 index 0000000..1fe7770 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/bi_VU.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Samsung Electronics Co., Ltd. akhilesh.k@samsung.com & maninder1.s@samsung.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'dddd DD MMM YYYY', + ], + 'months' => ['jenuware', 'febwari', 'maj', 'epril', 'mei', 'jun', 'julae', 'ogis', 'septemba', 'oktoba', 'novemba', 'disemba'], + 'months_short' => ['jen', 'feb', 'maj', 'epr', 'mei', 'jun', 'jul', 'ogi', 'sep', 'okt', 'nov', 'dis'], + 'weekdays' => ['sande', 'mande', 'maj', 'wota', 'fraede', 'sarede'], + 'weekdays_short' => ['san', 'man', 'maj', 'wot', 'fra', 'sar'], + 'weekdays_min' => ['san', 'man', 'maj', 'wot', 'fra', 'sar'], + + 'year' => ':count seven', // less reliable + 'y' => ':count seven', // less reliable + 'a_year' => ':count seven', // less reliable + + 'month' => ':count mi', // less reliable + 'm' => ':count mi', // less reliable + 'a_month' => ':count mi', // less reliable + + 'week' => ':count sarede', // less reliable + 'w' => ':count sarede', // less reliable + 'a_week' => ':count sarede', // less reliable + + 'day' => ':count betde', // less reliable + 'd' => ':count betde', // less reliable + 'a_day' => ':count betde', // less reliable + + 'hour' => ':count klok', // less reliable + 'h' => ':count klok', // less reliable + 'a_hour' => ':count klok', // less reliable + + 'minute' => ':count smol', // less reliable + 'min' => ':count smol', // less reliable + 'a_minute' => ':count smol', // less reliable + + 'second' => ':count tu', // less reliable + 's' => ':count tu', // less reliable + 'a_second' => ':count tu', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/bm.php b/vendor/nesbot/carbon/src/Carbon/Lang/bm.php new file mode 100644 index 0000000..92822d2 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/bm.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Estelle Comment + */ +return [ + 'year' => 'san :count', + 'a_year' => '{1}san kelen|san :count', + 'y' => 'san :count', + 'month' => 'kalo :count', + 'a_month' => '{1}kalo kelen|kalo :count', + 'm' => 'k. :count', + 'week' => 'dɔgɔkun :count', + 'a_week' => 'dɔgɔkun kelen', + 'w' => 'd. :count', + 'day' => 'tile :count', + 'd' => 't. :count', + 'a_day' => '{1}tile kelen|tile :count', + 'hour' => 'lɛrɛ :count', + 'a_hour' => '{1}lɛrɛ kelen|lɛrɛ :count', + 'h' => 'l. :count', + 'minute' => 'miniti :count', + 'a_minute' => '{1}miniti kelen|miniti :count', + 'min' => 'm. :count', + 'second' => 'sekondi :count', + 'a_second' => '{1}sanga dama dama|sekondi :count', + 's' => 'sek. :count', + 'ago' => 'a bɛ :time bɔ', + 'from_now' => ':time kɔnɔ', + 'diff_today' => 'Bi', + 'diff_yesterday' => 'Kunu', + 'diff_yesterday_regexp' => 'Kunu(?:\\s+lɛrɛ)?', + 'diff_tomorrow' => 'Sini', + 'diff_tomorrow_regexp' => 'Sini(?:\\s+lɛrɛ)?', + 'diff_today_regexp' => 'Bi(?:\\s+lɛrɛ)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'MMMM [tile] D [san] YYYY', + 'LLL' => 'MMMM [tile] D [san] YYYY [lɛrɛ] HH:mm', + 'LLLL' => 'dddd MMMM [tile] D [san] YYYY [lɛrɛ] HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Bi lɛrɛ] LT', + 'nextDay' => '[Sini lɛrɛ] LT', + 'nextWeek' => 'dddd [don lɛrɛ] LT', + 'lastDay' => '[Kunu lɛrɛ] LT', + 'lastWeek' => 'dddd [tɛmɛnen lɛrɛ] LT', + 'sameElse' => 'L', + ], + 'months' => ['Zanwuyekalo', 'Fewuruyekalo', 'Marisikalo', 'Awirilikalo', 'Mɛkalo', 'Zuwɛnkalo', 'Zuluyekalo', 'Utikalo', 'Sɛtanburukalo', 'ɔkutɔburukalo', 'Nowanburukalo', 'Desanburukalo'], + 'months_short' => ['Zan', 'Few', 'Mar', 'Awi', 'Mɛ', 'Zuw', 'Zul', 'Uti', 'Sɛt', 'ɔku', 'Now', 'Des'], + 'weekdays' => ['Kari', 'Ntɛnɛn', 'Tarata', 'Araba', 'Alamisa', 'Juma', 'Sibiri'], + 'weekdays_short' => ['Kar', 'Ntɛ', 'Tar', 'Ara', 'Ala', 'Jum', 'Sib'], + 'weekdays_min' => ['Ka', 'Nt', 'Ta', 'Ar', 'Al', 'Ju', 'Si'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' ni '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/bn.php b/vendor/nesbot/carbon/src/Carbon/Lang/bn.php new file mode 100644 index 0000000..8e14789 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/bn.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - Shakib Hossain + * - Raju + * - Aniruddha Adhikary + * - JD Isaacks + * - Saiful Islam + * - Faisal Islam + */ +return [ + 'year' => ':count বছর', + 'a_year' => 'এক বছর|:count বছর', + 'y' => '১ বছর|:count বছর', + 'month' => ':count মাস', + 'a_month' => 'এক মাস|:count মাস', + 'm' => '১ মাস|:count মাস', + 'week' => ':count সপ্তাহ', + 'a_week' => '১ সপ্তাহ|:count সপ্তাহ', + 'w' => '১ সপ্তাহ|:count সপ্তাহ', + 'day' => ':count দিন', + 'a_day' => 'এক দিন|:count দিন', + 'd' => '১ দিন|:count দিন', + 'hour' => ':count ঘন্টা', + 'a_hour' => 'এক ঘন্টা|:count ঘন্টা', + 'h' => '১ ঘন্টা|:count ঘন্টা', + 'minute' => ':count মিনিট', + 'a_minute' => 'এক মিনিট|:count মিনিট', + 'min' => '১ মিনিট|:count মিনিট', + 'second' => ':count সেকেন্ড', + 'a_second' => 'কয়েক সেকেন্ড|:count সেকেন্ড', + 's' => '১ সেকেন্ড|:count সেকেন্ড', + 'ago' => ':time আগে', + 'from_now' => ':time পরে', + 'after' => ':time পরে', + 'before' => ':time আগে', + 'diff_now' => 'এখন', + 'diff_today' => 'আজ', + 'diff_yesterday' => 'গতকাল', + 'diff_tomorrow' => 'আগামীকাল', + 'period_recurrences' => ':count বার|:count বার', + 'period_interval' => 'প্রতি :interval', + 'period_start_date' => ':date থেকে', + 'period_end_date' => ':date পর্যন্ত', + 'formats' => [ + 'LT' => 'A Oh:Om সময়', + 'LTS' => 'A Oh:Om:Os সময়', + 'L' => 'OD/OM/OY', + 'LL' => 'OD MMMM OY', + 'LLL' => 'OD MMMM OY, A Oh:Om সময়', + 'LLLL' => 'dddd, OD MMMM OY, A Oh:Om সময়', + ], + 'calendar' => [ + 'sameDay' => '[আজ] LT', + 'nextDay' => '[আগামীকাল] LT', + 'nextWeek' => 'dddd, LT', + 'lastDay' => '[গতকাল] LT', + 'lastWeek' => '[গত] dddd, LT', + 'sameElse' => 'L', + ], + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'রাত'; + } + if ($hour < 10) { + return 'সকাল'; + } + if ($hour < 17) { + return 'দুপুর'; + } + if ($hour < 20) { + return 'বিকাল'; + } + + return 'রাত'; + }, + 'months' => ['জানুয়ারী', 'ফেব্রুয়ারি', 'মার্চ', 'এপ্রিল', 'মে', 'জুন', 'জুলাই', 'আগস্ট', 'সেপ্টেম্বর', 'অক্টোবর', 'নভেম্বর', 'ডিসেম্বর'], + 'months_short' => ['জানু', 'ফেব', 'মার্চ', 'এপ্র', 'মে', 'জুন', 'জুল', 'আগ', 'সেপ্ট', 'অক্টো', 'নভে', 'ডিসে'], + 'weekdays' => ['রবিবার', 'সোমবার', 'মঙ্গলবার', 'বুধবার', 'বৃহস্পতিবার', 'শুক্রবার', 'শনিবার'], + 'weekdays_short' => ['রবি', 'সোম', 'মঙ্গল', 'বুধ', 'বৃহস্পতি', 'শুক্র', 'শনি'], + 'weekdays_min' => ['রবি', 'সোম', 'মঙ্গ', 'বুধ', 'বৃহঃ', 'শুক্র', 'শনি'], + 'list' => [', ', ' এবং '], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, + 'weekdays_standalone' => ['রবিবার', 'সোমবার', 'মঙ্গলবার', 'বুধবার', 'বৃহষ্পতিবার', 'শুক্রবার', 'শনিবার'], + 'weekdays_min_standalone' => ['রঃ', 'সোঃ', 'মঃ', 'বুঃ', 'বৃঃ', 'শুঃ', 'শনি'], + 'months_short_standalone' => ['জানুয়ারী', 'ফেব্রুয়ারী', 'মার্চ', 'এপ্রিল', 'মে', 'জুন', 'জুলাই', 'আগস্ট', 'সেপ্টেম্বর', 'অক্টোবর', 'নভেম্বর', 'ডিসেম্বর'], + 'alt_numbers' => ['০', '১', '২', '৩', '৪', '৫', '৬', '৭', '৮', '৯'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/bn_BD.php b/vendor/nesbot/carbon/src/Carbon/Lang/bn_BD.php new file mode 100644 index 0000000..b5b28dd --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/bn_BD.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ankur Group, Taneem Ahmed, Jamil Ahmed + */ +return array_replace_recursive(require __DIR__.'/bn.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['জানুয়ারী', 'ফেব্রুয়ারী', 'মার্চ', 'এপ্রিল', 'মে', 'জুন', 'জুলাই', 'আগস্ট', 'সেপ্টেম্বর', 'অক্টোবর', 'নভেম্বর', 'ডিসেম্বর'], + 'months_short' => ['জানু', 'ফেব', 'মার্চ', 'এপ্রিল', 'মে', 'জুন', 'জুলাই', 'আগস্ট', 'সেপ্টেম্বর', 'অক্টোবর', 'নভেম্বর', 'ডিসেম্বর'], + 'weekdays' => ['রবিবার', 'সোমবার', 'মঙ্গলবার', 'বুধবার', 'বৃহস্পতিবার', 'শুক্রবার', 'শনিবার'], + 'weekdays_short' => ['রবি', 'সোম', 'মঙ্গল', 'বুধ', 'বৃহঃ', 'শুক্র', 'শনি'], + 'weekdays_min' => ['রবি', 'সোম', 'মঙ্গল', 'বুধ', 'বৃহঃ', 'শুক্র', 'শনি'], + 'first_day_of_week' => 5, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/bn_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/bn_IN.php new file mode 100644 index 0000000..8b3a50e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/bn_IN.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/bn.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['জানুয়ারী', 'ফেব্রুয়ারী', 'মার্চ', 'এপ্রিল', 'মে', 'জুন', 'জুলাই', 'আগস্ট', 'সেপ্টেম্বর', 'অক্টোবর', 'নভেম্বর', 'ডিসেম্বর'], + 'months_short' => ['জানু', 'ফেব', 'মার্চ', 'এপ্রিল', 'মে', 'জুন', 'জুলাই', 'আগস্ট', 'সেপ্টেম্বর', 'অক্টোবর', 'নভেম্বর', 'ডিসেম্বর'], + 'weekdays' => ['রবিবার', 'সোমবার', 'মঙ্গলবার', 'বুধবার', 'বৃহস্পতিবার', 'শুক্রবার', 'শনিবার'], + 'weekdays_short' => ['রবি', 'সোম', 'মঙ্গল', 'বুধ', 'বৃহস্পতি', 'শুক্র', 'শনি'], + 'weekdays_min' => ['রবি', 'সোম', 'মঙ্গল', 'বুধ', 'বৃহস্পতি', 'শুক্র', 'শনি'], + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/bo.php b/vendor/nesbot/carbon/src/Carbon/Lang/bo.php new file mode 100644 index 0000000..99e1bf4 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/bo.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - JD Isaacks + */ +return [ + 'year' => '{1}ལོ་གཅིག|]1,Inf[:count ལོ', + 'month' => '{1}ཟླ་བ་གཅིག|]1,Inf[:count ཟླ་བ', + 'week' => ':count བདུན་ཕྲག', + 'day' => '{1}ཉིན་གཅིག|]1,Inf[:count ཉིན་', + 'hour' => '{1}ཆུ་ཚོད་གཅིག|]1,Inf[:count ཆུ་ཚོད', + 'minute' => '{1}སྐར་མ་གཅིག|]1,Inf[:count སྐར་མ', + 'second' => '{1}ལམ་སང|]1,Inf[:count སྐར་ཆ།', + 'ago' => ':time སྔན་ལ', + 'from_now' => ':time ལ་', + 'diff_yesterday' => 'ཁ་སང', + 'diff_today' => 'དི་རིང', + 'diff_tomorrow' => 'སང་ཉིན', + 'formats' => [ + 'LT' => 'A h:mm', + 'LTS' => 'A h:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY, A h:mm', + 'LLLL' => 'dddd, D MMMM YYYY, A h:mm', + ], + 'calendar' => [ + 'sameDay' => '[དི་རིང] LT', + 'nextDay' => '[སང་ཉིན] LT', + 'nextWeek' => '[བདུན་ཕྲག་རྗེས་མ], LT', + 'lastDay' => '[ཁ་སང] LT', + 'lastWeek' => '[བདུན་ཕྲག་མཐའ་མ] dddd, LT', + 'sameElse' => 'L', + ], + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'མཚན་མོ'; + } + if ($hour < 10) { + return 'ཞོགས་ཀས'; + } + if ($hour < 17) { + return 'ཉིན་གུང'; + } + if ($hour < 20) { + return 'དགོང་དག'; + } + + return 'མཚན་མོ'; + }, + 'months' => ['ཟླ་བ་དང་པོ', 'ཟླ་བ་གཉིས་པ', 'ཟླ་བ་གསུམ་པ', 'ཟླ་བ་བཞི་པ', 'ཟླ་བ་ལྔ་པ', 'ཟླ་བ་དྲུག་པ', 'ཟླ་བ་བདུན་པ', 'ཟླ་བ་བརྒྱད་པ', 'ཟླ་བ་དགུ་པ', 'ཟླ་བ་བཅུ་པ', 'ཟླ་བ་བཅུ་གཅིག་པ', 'ཟླ་བ་བཅུ་གཉིས་པ'], + 'months_short' => ['ཟླ་བ་དང་པོ', 'ཟླ་བ་གཉིས་པ', 'ཟླ་བ་གསུམ་པ', 'ཟླ་བ་བཞི་པ', 'ཟླ་བ་ལྔ་པ', 'ཟླ་བ་དྲུག་པ', 'ཟླ་བ་བདུན་པ', 'ཟླ་བ་བརྒྱད་པ', 'ཟླ་བ་དགུ་པ', 'ཟླ་བ་བཅུ་པ', 'ཟླ་བ་བཅུ་གཅིག་པ', 'ཟླ་བ་བཅུ་གཉིས་པ'], + 'weekdays' => ['གཟའ་ཉི་མ་', 'གཟའ་ཟླ་བ་', 'གཟའ་མིག་དམར་', 'གཟའ་ལྷག་པ་', 'གཟའ་ཕུར་བུ', 'གཟའ་པ་སངས་', 'གཟའ་སྤེན་པ་'], + 'weekdays_short' => ['ཉི་མ་', 'ཟླ་བ་', 'མིག་དམར་', 'ལྷག་པ་', 'ཕུར་བུ', 'པ་སངས་', 'སྤེན་པ་'], + 'weekdays_min' => ['ཉི་མ་', 'ཟླ་བ་', 'མིག་དམར་', 'ལྷག་པ་', 'ཕུར་བུ', 'པ་སངས་', 'སྤེན་པ་'], + 'list' => [', ', ' ཨནད་ '], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, + 'months_standalone' => ['ཟླ་བ་དང་པོ་', 'ཟླ་བ་གཉིས་པ་', 'ཟླ་བ་གསུམ་པ་', 'ཟླ་བ་བཞི་པ་', 'ཟླ་བ་ལྔ་པ་', 'ཟླ་བ་དྲུག་པ་', 'ཟླ་བ་བདུན་པ་', 'ཟླ་བ་བརྒྱད་པ་', 'ཟླ་བ་དགུ་པ་', 'ཟླ་བ་བཅུ་པ་', 'ཟླ་བ་བཅུ་གཅིག་པ་', 'ཟླ་བ་བཅུ་གཉིས་པ་'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/bo_CN.php b/vendor/nesbot/carbon/src/Carbon/Lang/bo_CN.php new file mode 100644 index 0000000..380abb1 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/bo_CN.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__.'/bo.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/bo_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/bo_IN.php new file mode 100644 index 0000000..ca50d04 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/bo_IN.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_replace_recursive(require __DIR__.'/bo.php', [ + 'meridiem' => ['སྔ་དྲོ་', 'ཕྱི་དྲོ་'], + 'weekdays' => ['གཟའ་ཉི་མ་', 'གཟའ་ཟླ་བ་', 'གཟའ་མིག་དམར་', 'གཟའ་ལྷག་པ་', 'གཟའ་ཕུར་བུ་', 'གཟའ་པ་སངས་', 'གཟའ་སྤེན་པ་'], + 'weekdays_short' => ['ཉི་མ་', 'ཟླ་བ་', 'མིག་དམར་', 'ལྷག་པ་', 'ཕུར་བུ་', 'པ་སངས་', 'སྤེན་པ་'], + 'weekdays_min' => ['ཉི་མ་', 'ཟླ་བ་', 'མིག་དམར་', 'ལྷག་པ་', 'ཕུར་བུ་', 'པ་སངས་', 'སྤེན་པ་'], + 'months' => ['ཟླ་བ་དང་པོ', 'ཟླ་བ་གཉིས་པ', 'ཟླ་བ་གསུམ་པ', 'ཟླ་བ་བཞི་པ', 'ཟླ་བ་ལྔ་པ', 'ཟླ་བ་དྲུག་པ', 'ཟླ་བ་བདུན་པ', 'ཟླ་བ་བརྒྱད་པ', 'ཟླ་བ་དགུ་པ', 'ཟླ་བ་བཅུ་པ', 'ཟླ་བ་བཅུ་གཅིག་པ', 'ཟླ་བ་བཅུ་གཉིས་པ'], + 'months_short' => ['ཟླ་༡', 'ཟླ་༢', 'ཟླ་༣', 'ཟླ་༤', 'ཟླ་༥', 'ཟླ་༦', 'ཟླ་༧', 'ཟླ་༨', 'ཟླ་༩', 'ཟླ་༡༠', 'ཟླ་༡༡', 'ཟླ་༡༢'], + 'months_standalone' => ['ཟླ་བ་དང་པོ་', 'ཟླ་བ་གཉིས་པ་', 'ཟླ་བ་གསུམ་པ་', 'ཟླ་བ་བཞི་པ་', 'ཟླ་བ་ལྔ་པ་', 'ཟླ་བ་དྲུག་པ་', 'ཟླ་བ་བདུན་པ་', 'ཟླ་བ་བརྒྱད་པ་', 'ཟླ་བ་དགུ་པ་', 'ཟླ་བ་བཅུ་པ་', 'ཟླ་བ་བཅུ་གཅིག་པ་', 'ཟླ་བ་བཅུ་གཉིས་པ་'], + 'weekend' => [0, 0], + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'YYYY-MM-DD', + 'LL' => 'YYYY ལོའི་MMMཚེས་D', + 'LLL' => 'སྤྱི་ལོ་YYYY MMMMའི་ཚེས་D h:mm a', + 'LLLL' => 'YYYY MMMMའི་ཚེས་D, dddd h:mm a', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/br.php b/vendor/nesbot/carbon/src/Carbon/Lang/br.php new file mode 100644 index 0000000..583472f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/br.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - Serhan Apaydın + * - JD Isaacks + */ +return [ + 'year' => '{1}:count bloaz|{3,4,5,9}:count bloaz|[0,Inf[:count vloaz', + 'a_year' => '{1}ur bloaz|{3,4,5,9}:count bloaz|[0,Inf[:count vloaz', + 'month' => '{1}:count miz|{2}:count viz|[0,Inf[:count miz', + 'a_month' => '{1}ur miz|{2}:count viz|[0,Inf[:count miz', + 'week' => ':count sizhun', + 'a_week' => '{1}ur sizhun|:count sizhun', + 'day' => '{1}:count devezh|{2}:count zevezh|[0,Inf[:count devezh', + 'a_day' => '{1}un devezh|{2}:count zevezh|[0,Inf[:count devezh', + 'hour' => ':count eur', + 'a_hour' => '{1}un eur|:count eur', + 'minute' => '{1}:count vunutenn|{2}:count vunutenn|[0,Inf[:count munutenn', + 'a_minute' => '{1}ur vunutenn|{2}:count vunutenn|[0,Inf[:count munutenn', + 'second' => ':count eilenn', + 'a_second' => '{1}un nebeud segondennoù|[0,Inf[:count eilenn', + 'ago' => ':time \'zo', + 'from_now' => 'a-benn :time', + 'diff_now' => 'bremañ', + 'diff_today' => 'Hiziv', + 'diff_today_regexp' => 'Hiziv(?:\\s+da)?', + 'diff_yesterday' => 'decʼh', + 'diff_yesterday_regexp' => 'Dec\'h(?:\\s+da)?', + 'diff_tomorrow' => 'warcʼhoazh', + 'diff_tomorrow_regexp' => 'Warc\'hoazh(?:\\s+da)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D [a viz] MMMM YYYY', + 'LLL' => 'D [a viz] MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D [a viz] MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Hiziv da] LT', + 'nextDay' => '[Warc\'hoazh da] LT', + 'nextWeek' => 'dddd [da] LT', + 'lastDay' => '[Dec\'h da] LT', + 'lastWeek' => 'dddd [paset da] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + return $number.($number === 1 ? 'añ' : 'vet'); + }, + 'months' => ['Genver', 'C\'hwevrer', 'Meurzh', 'Ebrel', 'Mae', 'Mezheven', 'Gouere', 'Eost', 'Gwengolo', 'Here', 'Du', 'Kerzu'], + 'months_short' => ['Gen', 'C\'hwe', 'Meu', 'Ebr', 'Mae', 'Eve', 'Gou', 'Eos', 'Gwe', 'Her', 'Du', 'Ker'], + 'weekdays' => ['Sul', 'Lun', 'Meurzh', 'Merc\'her', 'Yaou', 'Gwener', 'Sadorn'], + 'weekdays_short' => ['Sul', 'Lun', 'Meu', 'Mer', 'Yao', 'Gwe', 'Sad'], + 'weekdays_min' => ['Su', 'Lu', 'Me', 'Mer', 'Ya', 'Gw', 'Sa'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' hag '], + 'meridiem' => ['A.M.', 'G.M.'], + + 'y' => ':count bl.', + 'd' => ':count d', + 'h' => ':count e', + 'min' => ':count min', + 's' => ':count s', +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/br_FR.php b/vendor/nesbot/carbon/src/Carbon/Lang/br_FR.php new file mode 100644 index 0000000..7f54185 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/br_FR.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__.'/br.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/brx.php b/vendor/nesbot/carbon/src/Carbon/Lang/brx.php new file mode 100644 index 0000000..a0a7bf9 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/brx.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/brx_IN.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/brx_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/brx_IN.php new file mode 100644 index 0000000..2d80ced --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/brx_IN.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Red Hat Pune bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'M/D/YY', + ], + 'months' => ['जानुवारी', 'फेब्रुवारी', 'मार्स', 'एफ्रिल', 'मे', 'जुन', 'जुलाइ', 'आगस्थ', 'सेबथेज्ब़र', 'अखथबर', 'नबेज्ब़र', 'दिसेज्ब़र'], + 'months_short' => ['जानुवारी', 'फेब्रुवारी', 'मार्स', 'एप्रिल', 'मे', 'जुन', 'जुलाइ', 'आगस्थ', 'सेबथेज्ब़र', 'अखथबर', 'नबेज्ब़र', 'दिसेज्ब़र'], + 'weekdays' => ['रबिबार', 'सोबार', 'मंगलबार', 'बुदबार', 'बिसथिबार', 'सुखुरबार', 'सुनिबार'], + 'weekdays_short' => ['रबि', 'सम', 'मंगल', 'बुद', 'बिसथि', 'सुखुर', 'सुनि'], + 'weekdays_min' => ['रबि', 'सम', 'मंगल', 'बुद', 'बिसथि', 'सुखुर', 'सुनि'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['फुं.', 'बेलासे.'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/bs.php b/vendor/nesbot/carbon/src/Carbon/Lang/bs.php new file mode 100644 index 0000000..e5d6808 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/bs.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bokideckonja + * - Josh Soref + * - François B + * - shaishavgandhi05 + * - Serhan Apaydın + * - JD Isaacks + */ + +use Carbon\CarbonInterface; + +return [ + '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 sedmice|:count sedmicu|:count sedmica', + 'w' => ':count sedmice|:count sedmicu|:count sedmica', + '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_now' => 'sada', + 'diff_today' => 'danas', + 'diff_today_regexp' => 'danas(?:\\s+u)?', + 'diff_yesterday' => 'jučer', + 'diff_yesterday_regexp' => 'jučer(?:\\s+u)?', + 'diff_tomorrow' => 'sutra', + 'diff_tomorrow_regexp' => 'sutra(?:\\s+u)?', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D. MMMM YYYY', + 'LLL' => 'D. MMMM YYYY H:mm', + 'LLLL' => 'dddd, D. MMMM YYYY H:mm', + ], + 'calendar' => [ + 'sameDay' => '[danas u] LT', + 'nextDay' => '[sutra u] LT', + 'nextWeek' => function (CarbonInterface $current) { + switch ($current->dayOfWeek) { + case 0: + return '[u] [nedjelju] [u] LT'; + case 3: + return '[u] [srijedu] [u] LT'; + case 6: + return '[u] [subotu] [u] LT'; + default: + return '[u] dddd [u] LT'; + } + }, + 'lastDay' => '[jučer u] LT', + 'lastWeek' => function (CarbonInterface $current) { + switch ($current->dayOfWeek) { + case 0: + case 3: + return '[prošlu] dddd [u] LT'; + case 6: + return '[prošle] [subote] [u] LT'; + default: + return '[prošli] dddd [u] LT'; + } + }, + 'sameElse' => 'L', + ], + 'ordinal' => ':number.', + 'months' => ['januar', 'februar', 'mart', 'april', 'maj', 'juni', 'juli', 'august', 'septembar', 'oktobar', 'novembar', 'decembar'], + 'months_short' => ['jan.', 'feb.', 'mar.', 'apr.', 'maj.', 'jun.', 'jul.', 'aug.', 'sep.', 'okt.', 'nov.', 'dec.'], + 'weekdays' => ['nedjelja', 'ponedjeljak', 'utorak', 'srijeda', 'četvrtak', 'petak', 'subota'], + 'weekdays_short' => ['ned.', 'pon.', 'uto.', 'sri.', 'čet.', 'pet.', 'sub.'], + 'weekdays_min' => ['ne', 'po', 'ut', 'sr', 'če', 'pe', 'su'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' i '], + 'meridiem' => ['prijepodne', 'popodne'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/bs_BA.php b/vendor/nesbot/carbon/src/Carbon/Lang/bs_BA.php new file mode 100644 index 0000000..0a59117 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/bs_BA.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__.'/bs.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/bs_Cyrl.php b/vendor/nesbot/carbon/src/Carbon/Lang/bs_Cyrl.php new file mode 100644 index 0000000..e1a1744 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/bs_Cyrl.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/bs.php', [ + 'meridiem' => ['пре подне', 'поподне'], + 'weekdays' => ['недјеља', 'понедјељак', 'уторак', 'сриједа', 'четвртак', 'петак', 'субота'], + 'weekdays_short' => ['нед', 'пон', 'уто', 'сри', 'чет', 'пет', 'суб'], + 'weekdays_min' => ['нед', 'пон', 'уто', 'сри', 'чет', 'пет', 'суб'], + 'months' => ['јануар', 'фебруар', 'март', 'април', 'мај', 'јуни', 'јули', 'аугуст', 'септембар', 'октобар', 'новембар', 'децембар'], + 'months_short' => ['јан', 'феб', 'мар', 'апр', 'мај', 'јун', 'јул', 'ауг', 'сеп', 'окт', 'нов', 'дец'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D.M.YYYY.', + 'LL' => 'DD.MM.YYYY.', + 'LLL' => 'DD. MMMM YYYY. HH:mm', + 'LLLL' => 'dddd, DD. MMMM YYYY. HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/bs_Latn.php b/vendor/nesbot/carbon/src/Carbon/Lang/bs_Latn.php new file mode 100644 index 0000000..b4e363e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/bs_Latn.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/bs.php', [ +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/byn.php b/vendor/nesbot/carbon/src/Carbon/Lang/byn.php new file mode 100644 index 0000000..7125f3d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/byn.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/byn_ER.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/byn_ER.php b/vendor/nesbot/carbon/src/Carbon/Lang/byn_ER.php new file mode 100644 index 0000000..ad67533 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/byn_ER.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['ልደትሪ', 'ካብኽብቲ', 'ክብላ', 'ፋጅኺሪ', 'ክቢቅሪ', 'ምኪኤል ትጓ̅ኒሪ', 'ኰርኩ', 'ማርያም ትሪ', 'ያኸኒ መሳቅለሪ', 'መተሉ', 'ምኪኤል መሽወሪ', 'ተሕሳስሪ'], + 'months_short' => ['ልደት', 'ካብኽ', 'ክብላ', 'ፋጅኺ', 'ክቢቅ', 'ም/ት', 'ኰር', 'ማርያ', 'ያኸኒ', 'መተሉ', 'ም/ም', 'ተሕሳ'], + 'weekdays' => ['ሰንበር ቅዳዅ', 'ሰኑ', 'ሰሊጝ', 'ለጓ ወሪ ለብዋ', 'ኣምድ', 'ኣርብ', 'ሰንበር ሽጓዅ'], + 'weekdays_short' => ['ሰ/ቅ', 'ሰኑ', 'ሰሊጝ', 'ለጓ', 'ኣምድ', 'ኣርብ', 'ሰ/ሽ'], + 'weekdays_min' => ['ሰ/ቅ', 'ሰኑ', 'ሰሊጝ', 'ለጓ', 'ኣምድ', 'ኣርብ', 'ሰ/ሽ'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['ፋዱስ ጃብ', 'ፋዱስ ደምቢ'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ca.php b/vendor/nesbot/carbon/src/Carbon/Lang/ca.php new file mode 100644 index 0000000..b8b1994 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ca.php @@ -0,0 +1,117 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - mestremuten + * - François B + * - Marc Ordinas i Llopis + * - Pere Orga + * - JD Isaacks + * - Quentí + * - Víctor Díaz + * - Xavi + * - qcardona + */ + +use Carbon\CarbonInterface; + +return [ + 'year' => ':count any|:count anys', + 'a_year' => 'un any|:count anys', + 'y' => ':count any|:count anys', + 'month' => ':count mes|:count mesos', + 'a_month' => 'un mes|:count mesos', + 'm' => ':count mes|:count mesos', + 'week' => ':count setmana|:count setmanes', + 'a_week' => 'una setmana|:count setmanes', + 'w' => ':count setmana|:count setmanes', + 'day' => ':count dia|:count dies', + 'a_day' => 'un dia|:count dies', + 'd' => ':count d', + 'hour' => ':count hora|:count hores', + 'a_hour' => 'una hora|:count hores', + 'h' => ':count h', + 'minute' => ':count minut|:count minuts', + 'a_minute' => 'un minut|:count minuts', + 'min' => ':count min', + 'second' => ':count segon|:count segons', + 'a_second' => 'uns segons|:count segons', + 's' => ':count s', + 'ago' => 'fa :time', + 'from_now' => 'd\'aquí a :time', + 'after' => ':time després', + 'before' => ':time abans', + 'diff_now' => 'ara mateix', + 'diff_today' => 'avui', + 'diff_today_regexp' => 'avui(?:\\s+a)?(?:\\s+les)?', + 'diff_yesterday' => 'ahir', + 'diff_yesterday_regexp' => 'ahir(?:\\s+a)?(?:\\s+les)?', + 'diff_tomorrow' => 'demà', + 'diff_tomorrow_regexp' => 'demà(?:\\s+a)?(?:\\s+les)?', + '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', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM [de] YYYY', + 'LLL' => 'D MMMM [de] YYYY [a les] H:mm', + 'LLLL' => 'dddd D MMMM [de] YYYY [a les] H:mm', + ], + 'calendar' => [ + 'sameDay' => function (CarbonInterface $current) { + return '[avui a '.($current->hour !== 1 ? 'les' : 'la').'] LT'; + }, + 'nextDay' => function (CarbonInterface $current) { + return '[demà a '.($current->hour !== 1 ? 'les' : 'la').'] LT'; + }, + 'nextWeek' => function (CarbonInterface $current) { + return 'dddd [a '.($current->hour !== 1 ? 'les' : 'la').'] LT'; + }, + 'lastDay' => function (CarbonInterface $current) { + return '[ahir a '.($current->hour !== 1 ? 'les' : 'la').'] LT'; + }, + 'lastWeek' => function (CarbonInterface $current) { + return '[el] dddd [passat a '.($current->hour !== 1 ? 'les' : 'la').'] LT'; + }, + 'sameElse' => 'L', + ], + 'ordinal' => function ($number, $period) { + return $number.( + ($period === 'w' || $period === 'W') ? 'a' : ( + ($number === 1) ? 'r' : ( + ($number === 2) ? 'n' : ( + ($number === 3) ? 'r' : ( + ($number === 4) ? 't' : 'è' + ) + ) + ) + ) + ); + }, + 'months' => ['de gener', 'de febrer', 'de març', 'd\'abril', 'de maig', 'de juny', 'de juliol', 'd\'agost', 'de setembre', 'd\'octubre', 'de novembre', 'de desembre'], + 'months_standalone' => ['gener', 'febrer', 'març', 'abril', 'maig', 'juny', 'juliol', 'agost', 'setembre', 'octubre', 'novembre', 'desembre'], + 'months_short' => ['de gen.', 'de febr.', 'de març', 'd\'abr.', 'de maig', 'de juny', 'de jul.', 'd\'ag.', 'de set.', 'd\'oct.', 'de nov.', 'de des.'], + 'months_short_standalone' => ['gen.', 'febr.', 'març', 'abr.', 'maig', 'juny', 'jul.', 'ag.', 'set.', 'oct.', 'nov.', 'des.'], + 'months_regexp' => '/(D[oD]?[\s,]+MMMM?|L{2,4}|l{2,4})/', + 'weekdays' => ['diumenge', 'dilluns', 'dimarts', 'dimecres', 'dijous', 'divendres', 'dissabte'], + 'weekdays_short' => ['dg.', 'dl.', 'dt.', 'dc.', 'dj.', 'dv.', 'ds.'], + 'weekdays_min' => ['dg', 'dl', 'dt', 'dc', 'dj', 'dv', 'ds'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' i '], + 'meridiem' => ['a. m.', 'p. m.'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ca_AD.php b/vendor/nesbot/carbon/src/Carbon/Lang/ca_AD.php new file mode 100644 index 0000000..861acd2 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ca_AD.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ca.php', [ +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ca_ES.php b/vendor/nesbot/carbon/src/Carbon/Lang/ca_ES.php new file mode 100644 index 0000000..5004978 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ca_ES.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__.'/ca.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ca_ES_Valencia.php b/vendor/nesbot/carbon/src/Carbon/Lang/ca_ES_Valencia.php new file mode 100644 index 0000000..1c16421 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ca_ES_Valencia.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Component\Translation\PluralizationRules; + +// @codeCoverageIgnoreStart +if (class_exists(PluralizationRules::class)) { + PluralizationRules::set(static function ($number) { + return PluralizationRules::get($number, 'ca'); + }, 'ca_ES_Valencia'); +} +// @codeCoverageIgnoreEnd + +return array_replace_recursive(require __DIR__.'/ca.php', [ +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ca_FR.php b/vendor/nesbot/carbon/src/Carbon/Lang/ca_FR.php new file mode 100644 index 0000000..861acd2 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ca_FR.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ca.php', [ +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ca_IT.php b/vendor/nesbot/carbon/src/Carbon/Lang/ca_IT.php new file mode 100644 index 0000000..861acd2 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ca_IT.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ca.php', [ +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ccp.php b/vendor/nesbot/carbon/src/Carbon/Lang/ccp.php new file mode 100644 index 0000000..99c1dca --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ccp.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'weekdays' => ['𑄢𑄧𑄝𑄨𑄝𑄢𑄴', '𑄥𑄧𑄟𑄴𑄝𑄢𑄴', '𑄟𑄧𑄁𑄉𑄧𑄣𑄴𑄝𑄢𑄴', '𑄝𑄪𑄖𑄴𑄝𑄢𑄴', '𑄝𑄳𑄢𑄨𑄥𑄪𑄛𑄴𑄝𑄢𑄴', '𑄥𑄪𑄇𑄴𑄇𑄮𑄢𑄴𑄝𑄢𑄴', '𑄥𑄧𑄚𑄨𑄝𑄢𑄴'], + 'weekdays_short' => ['𑄢𑄧𑄝𑄨', '𑄥𑄧𑄟𑄴', '𑄟𑄧𑄁𑄉𑄧𑄣𑄴', '𑄝𑄪𑄖𑄴', '𑄝𑄳𑄢𑄨𑄥𑄪𑄛𑄴', '𑄥𑄪𑄇𑄴𑄇𑄮𑄢𑄴', '𑄥𑄧𑄚𑄨'], + 'weekdays_min' => ['𑄢𑄧𑄝𑄨', '𑄥𑄧𑄟𑄴', '𑄟𑄧𑄁𑄉𑄧𑄣𑄴', '𑄝𑄪𑄖𑄴', '𑄝𑄳𑄢𑄨𑄥𑄪𑄛𑄴', '𑄥𑄪𑄇𑄴𑄇𑄮𑄢𑄴', '𑄥𑄧𑄚𑄨'], + 'months' => ['𑄎𑄚𑄪𑄠𑄢𑄨', '𑄜𑄬𑄛𑄴𑄝𑄳𑄢𑄪𑄠𑄢𑄨', '𑄟𑄢𑄴𑄌𑄧', '𑄃𑄬𑄛𑄳𑄢𑄨𑄣𑄴', '𑄟𑄬', '𑄎𑄪𑄚𑄴', '𑄎𑄪𑄣𑄭', '𑄃𑄉𑄧𑄌𑄴𑄑𑄴', '𑄥𑄬𑄛𑄴𑄑𑄬𑄟𑄴𑄝𑄧𑄢𑄴', '𑄃𑄧𑄇𑄴𑄑𑄬𑄝𑄧𑄢𑄴', '𑄚𑄧𑄞𑄬𑄟𑄴𑄝𑄧𑄢𑄴', '𑄓𑄨𑄥𑄬𑄟𑄴𑄝𑄧𑄢𑄴'], + 'months_short' => ['𑄎𑄚𑄪', '𑄜𑄬𑄛𑄴', '𑄟𑄢𑄴𑄌𑄧', '𑄃𑄬𑄛𑄳𑄢𑄨𑄣𑄴', '𑄟𑄬', '𑄎𑄪𑄚𑄴', '𑄎𑄪𑄣𑄭', '𑄃𑄉𑄧𑄌𑄴𑄑𑄴', '𑄥𑄬𑄛𑄴𑄑𑄬𑄟𑄴𑄝𑄧𑄢𑄴', '𑄃𑄧𑄇𑄴𑄑𑄮𑄝𑄧𑄢𑄴', '𑄚𑄧𑄞𑄬𑄟𑄴𑄝𑄧𑄢𑄴', '𑄓𑄨𑄥𑄬𑄟𑄴𑄝𑄢𑄴'], + 'months_short_standalone' => ['𑄎𑄚𑄪𑄠𑄢𑄨', '𑄜𑄬𑄛𑄴𑄝𑄳𑄢𑄪𑄠𑄢𑄨', '𑄟𑄢𑄴𑄌𑄧', '𑄃𑄬𑄛𑄳𑄢𑄨𑄣𑄴', '𑄟𑄬', '𑄎𑄪𑄚𑄴', '𑄎𑄪𑄣𑄭', '𑄃𑄉𑄧𑄌𑄴𑄑𑄴', '𑄥𑄬𑄛𑄴𑄑𑄬𑄟𑄴𑄝𑄧𑄢𑄴', '𑄃𑄧𑄇𑄴𑄑𑄮𑄝𑄧𑄢𑄴', '𑄚𑄧𑄞𑄬𑄟𑄴𑄝𑄧𑄢𑄴', '𑄓𑄨𑄥𑄬𑄟𑄴𑄝𑄧𑄢𑄴'], + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM, YYYY', + 'LLL' => 'D MMMM, YYYY h:mm a', + 'LLLL' => 'dddd, D MMMM, YYYY h:mm a', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ccp_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/ccp_IN.php new file mode 100644 index 0000000..c1fa8af --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ccp_IN.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ccp.php', [ + 'weekend' => [0, 0], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ce.php b/vendor/nesbot/carbon/src/Carbon/Lang/ce.php new file mode 100644 index 0000000..f99f6ff --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ce.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/ce_RU.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ce_RU.php b/vendor/nesbot/carbon/src/Carbon/Lang/ce_RU.php new file mode 100644 index 0000000..f769856 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ce_RU.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - ANCHR + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'YYYY.DD.MM', + ], + 'months' => ['Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь', 'Июль', 'Август', 'Сентябрь', 'Октябрь', 'Ноябрь', 'Декабрь'], + 'months_short' => ['янв', 'фев', 'мар', 'апр', 'май', 'июн', 'июл', 'авг', 'сен', 'окт', 'ноя', 'дек'], + 'weekdays' => ['КӀиранан де', 'Оршотан де', 'Шинарин де', 'Кхаарин де', 'Еарин де', 'ПӀераскан де', 'Шот де'], + 'weekdays_short' => ['КӀ', 'Ор', 'Ши', 'Кх', 'Еа', 'ПӀ', 'Шо'], + 'weekdays_min' => ['КӀ', 'Ор', 'Ши', 'Кх', 'Еа', 'ПӀ', 'Шо'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + + 'year' => ':count шо', + 'y' => ':count шо', + 'a_year' => ':count шо', + + 'month' => ':count бутт', + 'm' => ':count бутт', + 'a_month' => ':count бутт', + + 'week' => ':count кӏира', + 'w' => ':count кӏира', + 'a_week' => ':count кӏира', + + 'day' => ':count де', + 'd' => ':count де', + 'a_day' => ':count де', + + 'hour' => ':count сахьт', + 'h' => ':count сахьт', + 'a_hour' => ':count сахьт', + + 'minute' => ':count минот', + 'min' => ':count минот', + 'a_minute' => ':count минот', + + 'second' => ':count секунд', + 's' => ':count секунд', + 'a_second' => ':count секунд', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/cgg.php b/vendor/nesbot/carbon/src/Carbon/Lang/cgg.php new file mode 100644 index 0000000..09bcc1c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/cgg.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_replace_recursive(require __DIR__.'/en.php', [ + 'weekdays' => ['Sande', 'Orwokubanza', 'Orwakabiri', 'Orwakashatu', 'Orwakana', 'Orwakataano', 'Orwamukaaga'], + 'weekdays_short' => ['SAN', 'ORK', 'OKB', 'OKS', 'OKN', 'OKT', 'OMK'], + 'weekdays_min' => ['SAN', 'ORK', 'OKB', 'OKS', 'OKN', 'OKT', 'OMK'], + 'months' => ['Okwokubanza', 'Okwakabiri', 'Okwakashatu', 'Okwakana', 'Okwakataana', 'Okwamukaaga', 'Okwamushanju', 'Okwamunaana', 'Okwamwenda', 'Okwaikumi', 'Okwaikumi na kumwe', 'Okwaikumi na ibiri'], + 'months_short' => ['KBZ', 'KBR', 'KST', 'KKN', 'KTN', 'KMK', 'KMS', 'KMN', 'KMW', 'KKM', 'KNK', 'KNB'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + + 'day' => ':count ruhanga', // less reliable + 'd' => ':count ruhanga', // less reliable + 'a_day' => ':count ruhanga', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/chr.php b/vendor/nesbot/carbon/src/Carbon/Lang/chr.php new file mode 100644 index 0000000..e26190f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/chr.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/chr_US.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/chr_US.php b/vendor/nesbot/carbon/src/Carbon/Lang/chr_US.php new file mode 100644 index 0000000..371353e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/chr_US.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Cherokee Nation Joseph Erb josepherb7@gmail.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'MM/DD/YYYY', + ], + 'months' => ['ᎤᏃᎸᏔᏅ', 'ᎧᎦᎵ', 'ᎠᏅᏱ', 'ᎧᏬᏂ', 'ᎠᏂᏍᎬᏘ', 'ᏕᎭᎷᏱ', 'ᎫᏰᏉᏂ', 'ᎦᎶᏂ', 'ᏚᎵᏍᏗ', 'ᏚᏂᏅᏗ', 'ᏅᏓᏕᏆ', 'ᎥᏍᎩᏱ'], + 'months_short' => ['ᎤᏃ', 'ᎧᎦ', 'ᎠᏅ', 'ᎧᏬ', 'ᎠᏂ', 'ᏕᎭ', 'ᎫᏰ', 'ᎦᎶ', 'ᏚᎵ', 'ᏚᏂ', 'ᏅᏓ', 'ᎥᏍ'], + 'weekdays' => ['ᎤᎾᏙᏓᏆᏍᎬ', 'ᎤᎾᏙᏓᏉᏅᎯ', 'ᏔᎵᏁᎢᎦ', 'ᏦᎢᏁᎢᎦ', 'ᏅᎩᏁᎢᎦ', 'ᏧᎾᎩᎶᏍᏗ', 'ᎤᎾᏙᏓᏈᏕᎾ'], + 'weekdays_short' => ['ᏆᏍᎬ', 'ᏉᏅᎯ', 'ᏔᎵᏁ', 'ᏦᎢᏁ', 'ᏅᎩᏁ', 'ᏧᎾᎩ', 'ᏈᏕᎾ'], + 'weekdays_min' => ['ᏆᏍᎬ', 'ᏉᏅᎯ', 'ᏔᎵᏁ', 'ᏦᎢᏁ', 'ᏅᎩᏁ', 'ᏧᎾᎩ', 'ᏈᏕᎾ'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['ᏌᎾᎴ', 'ᏒᎯᏱᎢᏗᏢ', 'ꮜꮎꮄ', 'ꮢꭿᏹꭲꮧꮲ'], + + 'second' => ':count ᏐᎢ', // less reliable + 's' => ':count ᏐᎢ', // less reliable + 'a_second' => ':count ᏐᎢ', // less reliable + + 'year' => ':count ᏑᏕᏘᏴᏓ', + 'y' => ':count ᏑᏕᏘᏴᏓ', + 'a_year' => ':count ᏑᏕᏘᏴᏓ', + + 'month' => ':count ᏏᏅᏙ', + 'm' => ':count ᏏᏅᏙ', + 'a_month' => ':count ᏏᏅᏙ', + + 'week' => ':count ᏑᎾᏙᏓᏆᏍᏗ', + 'w' => ':count ᏑᎾᏙᏓᏆᏍᏗ', + 'a_week' => ':count ᏑᎾᏙᏓᏆᏍᏗ', + + 'day' => ':count ᎢᎦ', + 'd' => ':count ᎢᎦ', + 'a_day' => ':count ᎢᎦ', + + 'hour' => ':count ᏑᏟᎶᏛ', + 'h' => ':count ᏑᏟᎶᏛ', + 'a_hour' => ':count ᏑᏟᎶᏛ', + + 'minute' => ':count ᎢᏯᏔᏬᏍᏔᏅ', + 'min' => ':count ᎢᏯᏔᏬᏍᏔᏅ', + 'a_minute' => ':count ᎢᏯᏔᏬᏍᏔᏅ', + + 'ago' => ':time ᏥᎨᏒ', + 'from_now' => 'ᎾᎿ :time', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ckb.php b/vendor/nesbot/carbon/src/Carbon/Lang/ckb.php new file mode 100644 index 0000000..acf4dc2 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ckb.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Swara Mohammed + */ +$months = [ + 'ڕێبەندان', + 'ڕەشەمە', + 'نەورۆز', + 'گوڵان', + 'جۆزەردان', + 'پوشپەڕ', + 'گەلاوێژ', + 'خەرمانان', + 'ڕەزبەر', + 'گەڵاڕێزان', + 'سەرماوەرز', + 'بەفرانبار', +]; + +return [ + 'year' => implode('|', ['{0}:count ساڵێک', '{1}ساڵێک', '{2}دوو ساڵ', ']2,11[:count ساڵ', ']10,Inf[:count ساڵ']), + 'a_year' => implode('|', ['{0}:count ساڵێک', '{1}ساڵێک', '{2}دوو ساڵ', ']2,11[:count ساڵ', ']10,Inf[:count ساڵ']), + 'month' => implode('|', ['{0}:count مانگێک', '{1}مانگێک', '{2}دوو مانگ', ']2,11[:count مانگ', ']10,Inf[:count مانگ']), + 'a_month' => implode('|', ['{0}:count مانگێک', '{1}مانگێک', '{2}دوو مانگ', ']2,11[:count مانگ', ']10,Inf[:count مانگ']), + 'week' => implode('|', ['{0}:count هەفتەیەک', '{1}هەفتەیەک', '{2}دوو هەفتە', ']2,11[:count هەفتە', ']10,Inf[:count هەفتە']), + 'a_week' => implode('|', ['{0}:count هەفتەیەک', '{1}هەفتەیەک', '{2}دوو هەفتە', ']2,11[:count هەفتە', ']10,Inf[:count هەفتە']), + 'day' => implode('|', ['{0}:count ڕۆژێک', '{1}ڕۆژێک', '{2}دوو ڕۆژ', ']2,11[:count ڕۆژ', ']10,Inf[:count ڕۆژ']), + 'a_day' => implode('|', ['{0}:count ڕۆژێک', '{1}ڕۆژێک', '{2}دوو ڕۆژ', ']2,11[:count ڕۆژ', ']10,Inf[:count ڕۆژ']), + 'hour' => implode('|', ['{0}:count کاتژمێرێک', '{1}کاتژمێرێک', '{2}دوو کاتژمێر', ']2,11[:count کاتژمێر', ']10,Inf[:count کاتژمێر']), + 'a_hour' => implode('|', ['{0}:count کاتژمێرێک', '{1}کاتژمێرێک', '{2}دوو کاتژمێر', ']2,11[:count کاتژمێر', ']10,Inf[:count کاتژمێر']), + 'minute' => implode('|', ['{0}:count خولەکێک', '{1}خولەکێک', '{2}دوو خولەک', ']2,11[:count خولەک', ']10,Inf[:count خولەک']), + 'a_minute' => implode('|', ['{0}:count خولەکێک', '{1}خولەکێک', '{2}دوو خولەک', ']2,11[:count خولەک', ']10,Inf[:count خولەک']), + 'second' => implode('|', ['{0}:count چرکەیەک', '{1}چرکەیەک', '{2}دوو چرکە', ']2,11[:count چرکە', ']10,Inf[:count چرکە']), + 'a_second' => implode('|', ['{0}:count چرکەیەک', '{1}چرکەیەک', '{2}دوو چرکە', ']2,11[:count چرکە', ']10,Inf[:count چرکە']), + 'ago' => 'پێش :time', + 'from_now' => ':time لە ئێستاوە', + 'after' => 'دوای :time', + 'before' => 'پێش :time', + 'diff_now' => 'ئێستا', + 'diff_today' => 'ئەمڕۆ', + 'diff_today_regexp' => 'ڕۆژ(?:\\s+لە)?(?:\\s+کاتژمێر)?', + 'diff_yesterday' => 'دوێنێ', + 'diff_yesterday_regexp' => 'دوێنێ(?:\\s+لە)?(?:\\s+کاتژمێر)?', + 'diff_tomorrow' => 'سبەینێ', + 'diff_tomorrow_regexp' => 'سبەینێ(?:\\s+لە)?(?:\\s+کاتژمێر)?', + 'diff_before_yesterday' => 'پێش دوێنێ', + 'diff_after_tomorrow' => 'دوای سبەینێ', + 'period_recurrences' => implode('|', ['{0}جار', '{1}جار', '{2}:count دووجار', ']2,11[:count جار', ']10,Inf[:count جار']), + 'period_interval' => 'هەموو :interval', + 'period_start_date' => 'لە :date', + 'period_end_date' => 'بۆ :date', + 'months' => $months, + 'months_short' => $months, + 'weekdays' => ['یەکشەممە', 'دووشەممە', 'سێشەممە', 'چوارشەممە', 'پێنجشەممە', 'هەینی', 'شەممە'], + 'weekdays_short' => ['یەکشەممە', 'دووشەممە', 'سێشەممە', 'چوارشەممە', 'پێنجشەممە', 'هەینی', 'شەممە'], + 'weekdays_min' => ['یەکشەممە', 'دووشەممە', 'سێشەممە', 'چوارشەممە', 'پێنجشەممە', 'هەینی', 'شەممە'], + 'list' => ['، ', ' و '], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[ئەمڕۆ لە کاتژمێر] LT', + 'nextDay' => '[سبەینێ لە کاتژمێر] LT', + 'nextWeek' => 'dddd [لە کاتژمێر] LT', + 'lastDay' => '[دوێنێ لە کاتژمێر] LT', + 'lastWeek' => 'dddd [لە کاتژمێر] LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['پ.ن', 'د.ن'], + 'weekend' => [5, 6], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/cmn.php b/vendor/nesbot/carbon/src/Carbon/Lang/cmn.php new file mode 100644 index 0000000..80b1d69 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/cmn.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/cmn_TW.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/cmn_TW.php b/vendor/nesbot/carbon/src/Carbon/Lang/cmn_TW.php new file mode 100644 index 0000000..7e43f9d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/cmn_TW.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'YYYY年MM月DD號', + ], + 'months' => ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'], + 'months_short' => [' 1月', ' 2月', ' 3月', ' 4月', ' 5月', ' 6月', ' 7月', ' 8月', ' 9月', '10月', '11月', '12月'], + 'weekdays' => ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'], + 'weekdays_short' => ['日', '一', '二', '三', '四', '五', '六'], + 'weekdays_min' => ['日', '一', '二', '三', '四', '五', '六'], + 'meridiem' => ['上午', '下午'], + + 'year' => ':count 年', + 'y' => ':count 年', + 'a_year' => ':count 年', + + 'month' => ':count 月', + 'm' => ':count 月', + 'a_month' => ':count 月', + + 'week' => ':count 周', + 'w' => ':count 周', + 'a_week' => ':count 周', + + 'day' => ':count 白天', + 'd' => ':count 白天', + 'a_day' => ':count 白天', + + 'hour' => ':count 小时', + 'h' => ':count 小时', + 'a_hour' => ':count 小时', + + 'minute' => ':count 分钟', + 'min' => ':count 分钟', + 'a_minute' => ':count 分钟', + + 'second' => ':count 秒', + 's' => ':count 秒', + 'a_second' => ':count 秒', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/crh.php b/vendor/nesbot/carbon/src/Carbon/Lang/crh.php new file mode 100644 index 0000000..a1d7ce6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/crh.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/crh_UA.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/crh_UA.php b/vendor/nesbot/carbon/src/Carbon/Lang/crh_UA.php new file mode 100644 index 0000000..0513933 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/crh_UA.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Reşat SABIQ tilde.birlik@gmail.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['Yanvar', 'Fevral', 'Mart', 'Aprel', 'Mayıs', 'İyun', 'İyul', 'Avgust', 'Sentâbr', 'Oktâbr', 'Noyabr', 'Dekabr'], + 'months_short' => ['Yan', 'Fev', 'Mar', 'Apr', 'May', 'İyn', 'İyl', 'Avg', 'Sen', 'Okt', 'Noy', 'Dek'], + 'weekdays' => ['Bazar', 'Bazarertesi', 'Salı', 'Çarşembe', 'Cumaaqşamı', 'Cuma', 'Cumaertesi'], + 'weekdays_short' => ['Baz', 'Ber', 'Sal', 'Çar', 'Caq', 'Cum', 'Cer'], + 'weekdays_min' => ['Baz', 'Ber', 'Sal', 'Çar', 'Caq', 'Cum', 'Cer'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['ÜE', 'ÜS'], + + 'year' => ':count yıl', + 'y' => ':count yıl', + 'a_year' => ':count yıl', + + 'month' => ':count ay', + 'm' => ':count ay', + 'a_month' => ':count ay', + + 'week' => ':count afta', + 'w' => ':count afta', + 'a_week' => ':count afta', + + 'day' => ':count kün', + 'd' => ':count kün', + 'a_day' => ':count kün', + + 'hour' => ':count saat', + 'h' => ':count saat', + 'a_hour' => ':count saat', + + 'minute' => ':count daqqa', + 'min' => ':count daqqa', + 'a_minute' => ':count daqqa', + + 'second' => ':count ekinci', + 's' => ':count ekinci', + 'a_second' => ':count ekinci', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/cs.php b/vendor/nesbot/carbon/src/Carbon/Lang/cs.php new file mode 100644 index 0000000..c01e3cc --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/cs.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Philippe Vaucher + * - Jakub Tesinsky + * - Martin Suja + * - Nikos Timiopulos + * - Bohuslav Blín + * - Tsutomu Kuroda + * - tjku + * - Lukas Svoboda + * - Max Melentiev + * - Juanito Fatas + * - Akira Matsuda + * - Christopher Dell + * - Václav Pávek + * - CodeSkills + * - Tlapi + * - newman101 + * - Petr Kadlec + * - tommaskraus + * - Karel Sommer (calvera) + */ +$za = function ($time) { + return 'za '.strtr($time, [ + 'hodina' => 'hodinu', + 'minuta' => 'minutu', + 'sekunda' => 'sekundu', + ]); +}; + +$pred = function ($time) { + $time = strtr($time, [ + 'hodina' => 'hodinou', + 'minuta' => 'minutou', + 'sekunda' => 'sekundou', + ]); + $time = preg_replace('/hodiny?(?!\w)/', 'hodinami', $time); + $time = preg_replace('/minuty?(?!\w)/', 'minutami', $time); + $time = preg_replace('/sekundy?(?!\w)/', 'sekundami', $time); + + return "před $time"; +}; + +return [ + 'year' => ':count rok|:count roky|:count let', + 'y' => ':count rok|:count roky|:count let', + 'a_year' => 'rok|:count roky|:count let', + 'month' => ':count měsíc|:count měsíce|:count měsíců', + 'm' => ':count měs.', + 'a_month' => 'měsíc|:count měsíce|:count měsíců', + 'week' => ':count týden|:count týdny|:count týdnů', + 'w' => ':count týd.', + 'a_week' => 'týden|:count týdny|:count týdnů', + 'day' => ':count den|:count dny|:count dní', + 'd' => ':count den|:count dny|:count dní', + 'a_day' => 'den|:count dny|:count dní', + 'hour' => ':count hodina|:count hodiny|:count hodin', + 'h' => ':count hod.', + 'a_hour' => 'hodina|:count hodiny|:count hodin', + 'minute' => ':count minuta|:count minuty|:count minut', + 'min' => ':count min.', + 'a_minute' => 'minuta|:count minuty|:count minut', + 'second' => ':count sekunda|:count sekundy|:count sekund', + 's' => ':count sek.', + 'a_second' => 'pár sekund|:count sekundy|:count sekund', + + 'month_ago' => ':count měsícem|:count měsíci|:count měsíci', + 'a_month_ago' => 'měsícem|:count měsíci|:count měsíci', + 'day_ago' => ':count dnem|:count dny|:count dny', + 'a_day_ago' => 'dnem|:count dny|:count dny', + 'week_ago' => ':count týdnem|:count týdny|:count týdny', + 'a_week_ago' => 'týdnem|:count týdny|:count týdny', + 'year_ago' => ':count rokem|:count roky|:count lety', + 'y_ago' => ':count rok.|:count rok.|:count let.', + 'a_year_ago' => 'rokem|:count roky|:count lety', + + 'month_before' => ':count měsícem|:count měsíci|:count měsíci', + 'a_month_before' => 'měsícem|:count měsíci|:count měsíci', + 'day_before' => ':count dnem|:count dny|:count dny', + 'a_day_before' => 'dnem|:count dny|:count dny', + 'week_before' => ':count týdnem|:count týdny|:count týdny', + 'a_week_before' => 'týdnem|:count týdny|:count týdny', + 'year_before' => ':count rokem|:count roky|:count lety', + 'y_before' => ':count rok.|:count rok.|:count let.', + 'a_year_before' => 'rokem|:count roky|:count lety', + + 'ago' => $pred, + 'from_now' => $za, + 'before' => $pred, + 'after' => $za, + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'months' => ['ledna', 'února', 'března', 'dubna', 'května', 'června', 'července', 'srpna', 'září', 'října', 'listopadu', 'prosince'], + 'months_standalone' => ['leden', 'únor', 'březen', 'duben', 'květen', 'červen', 'červenec', 'srpen', 'září', 'říjen', 'listopad', 'prosinec'], + 'months_short' => ['led', 'úno', 'bře', 'dub', 'kvě', 'čvn', 'čvc', 'srp', 'zář', 'říj', 'lis', 'pro'], + 'weekdays' => ['neděle', 'pondělí', 'úterý', 'středa', 'čtvrtek', 'pátek', 'sobota'], + 'weekdays_short' => ['ned', 'pon', 'úte', 'stř', 'čtv', 'pát', 'sob'], + 'weekdays_min' => ['ne', 'po', 'út', 'st', 'čt', 'pá', 'so'], + 'list' => [', ', ' a '], + 'diff_now' => 'nyní', + 'diff_yesterday' => 'včera', + 'diff_tomorrow' => 'zítra', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD. MM. YYYY', + 'LL' => 'D. MMMM YYYY', + 'LLL' => 'D. MMMM YYYY HH:mm', + 'LLLL' => 'dddd D. MMMM YYYY HH:mm', + ], + 'meridiem' => ['dopoledne', 'odpoledne'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/cs_CZ.php b/vendor/nesbot/carbon/src/Carbon/Lang/cs_CZ.php new file mode 100644 index 0000000..ea2517e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/cs_CZ.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__.'/cs.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/csb.php b/vendor/nesbot/carbon/src/Carbon/Lang/csb.php new file mode 100644 index 0000000..a35d281 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/csb.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/csb_PL.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/csb_PL.php b/vendor/nesbot/carbon/src/Carbon/Lang/csb_PL.php new file mode 100644 index 0000000..25e0ca8 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/csb_PL.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - csb_PL locale Michal Ostrowski bug-glibc-locales@gnu.org + */ +return [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-DD', + 'LL' => 'MMMM DD, YYYY', + 'LLL' => 'DD MMM HH:mm', + 'LLLL' => 'MMMM DD, YYYY HH:mm', + ], + 'months' => ['stëcznika', 'gromicznika', 'strëmiannika', 'łżëkwiata', 'maja', 'czerwińca', 'lëpińca', 'zélnika', 'séwnika', 'rujana', 'lëstopadnika', 'gòdnika'], + 'months_short' => ['stë', 'gro', 'str', 'łżë', 'maj', 'cze', 'lëp', 'zél', 'séw', 'ruj', 'lës', 'gòd'], + 'weekdays' => ['niedzela', 'pòniedzôłk', 'wtórk', 'strzoda', 'czwiôrtk', 'piątk', 'sobòta'], + 'weekdays_short' => ['nie', 'pòn', 'wtó', 'str', 'czw', 'pią', 'sob'], + 'weekdays_min' => ['nie', 'pòn', 'wtó', 'str', 'czw', 'pią', 'sob'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' a téż '], + 'two_words_connector' => ' a téż ', + 'year' => ':count rok', + 'month' => ':count miesiąc', + 'week' => ':count tidzéń', + 'day' => ':count dzéń', + 'hour' => ':count gòdzëna', + 'minute' => ':count minuta', + 'second' => ':count sekunda', +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/cu.php b/vendor/nesbot/carbon/src/Carbon/Lang/cu.php new file mode 100644 index 0000000..d6d1312 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/cu.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_replace_recursive(require __DIR__.'/en.php', [ + 'months' => ['M01', 'M02', 'M03', 'M04', 'M05', 'M06', 'M07', 'M08', 'M09', 'M10', 'M11', 'M12'], + 'months_short' => ['M01', 'M02', 'M03', 'M04', 'M05', 'M06', 'M07', 'M08', 'M09', 'M10', 'M11', 'M12'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-DD', + 'LL' => 'YYYY MMM D', + 'LLL' => 'YYYY MMMM D HH:mm', + 'LLLL' => 'YYYY MMMM D, dddd HH:mm', + ], + + 'year' => ':count лѣто', + 'y' => ':count лѣто', + 'a_year' => ':count лѣто', + + 'month' => ':count мѣсѧць', + 'm' => ':count мѣсѧць', + 'a_month' => ':count мѣсѧць', + + 'week' => ':count сєдмица', + 'w' => ':count сєдмица', + 'a_week' => ':count сєдмица', + + 'day' => ':count дьнь', + 'd' => ':count дьнь', + 'a_day' => ':count дьнь', + + 'hour' => ':count година', + 'h' => ':count година', + 'a_hour' => ':count година', + + 'minute' => ':count малъ', // less reliable + 'min' => ':count малъ', // less reliable + 'a_minute' => ':count малъ', // less reliable + + 'second' => ':count въторъ', + 's' => ':count въторъ', + 'a_second' => ':count въторъ', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/cv.php b/vendor/nesbot/carbon/src/Carbon/Lang/cv.php new file mode 100644 index 0000000..8aeb73a --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/cv.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - François B + * - JD Isaacks + */ +return [ + 'year' => ':count ҫул', + 'a_year' => '{1}пӗр ҫул|:count ҫул', + 'month' => ':count уйӑх', + 'a_month' => '{1}пӗр уйӑх|:count уйӑх', + 'week' => ':count эрне', + 'a_week' => '{1}пӗр эрне|:count эрне', + 'day' => ':count кун', + 'a_day' => '{1}пӗр кун|:count кун', + 'hour' => ':count сехет', + 'a_hour' => '{1}пӗр сехет|:count сехет', + 'minute' => ':count минут', + 'a_minute' => '{1}пӗр минут|:count минут', + 'second' => ':count ҫеккунт', + 'a_second' => '{1}пӗр-ик ҫеккунт|:count ҫеккунт', + 'ago' => ':time каялла', + 'from_now' => function ($time) { + return $time.(preg_match('/сехет$/u', $time) ? 'рен' : (preg_match('/ҫул/u', $time) ? 'тан' : 'ран')); + }, + 'diff_yesterday' => 'Ӗнер', + 'diff_today' => 'Паян', + 'diff_tomorrow' => 'Ыран', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD-MM-YYYY', + 'LL' => 'YYYY [ҫулхи] MMMM [уйӑхӗн] D[-мӗшӗ]', + 'LLL' => 'YYYY [ҫулхи] MMMM [уйӑхӗн] D[-мӗшӗ], HH:mm', + 'LLLL' => 'dddd, YYYY [ҫулхи] MMMM [уйӑхӗн] D[-мӗшӗ], HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Паян] LT [сехетре]', + 'nextDay' => '[Ыран] LT [сехетре]', + 'nextWeek' => '[Ҫитес] dddd LT [сехетре]', + 'lastDay' => '[Ӗнер] LT [сехетре]', + 'lastWeek' => '[Иртнӗ] dddd LT [сехетре]', + 'sameElse' => 'L', + ], + 'ordinal' => ':number-мӗш', + 'months' => ['кӑрлач', 'нарӑс', 'пуш', 'ака', 'май', 'ҫӗртме', 'утӑ', 'ҫурла', 'авӑн', 'юпа', 'чӳк', 'раштав'], + 'months_short' => ['кӑр', 'нар', 'пуш', 'ака', 'май', 'ҫӗр', 'утӑ', 'ҫур', 'авн', 'юпа', 'чӳк', 'раш'], + 'weekdays' => ['вырсарникун', 'тунтикун', 'ытларикун', 'юнкун', 'кӗҫнерникун', 'эрнекун', 'шӑматкун'], + 'weekdays_short' => ['выр', 'тун', 'ытл', 'юн', 'кӗҫ', 'эрн', 'шӑм'], + 'weekdays_min' => ['вр', 'тн', 'ыт', 'юн', 'кҫ', 'эр', 'шм'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' тата '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/cv_RU.php b/vendor/nesbot/carbon/src/Carbon/Lang/cv_RU.php new file mode 100644 index 0000000..197bd8d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/cv_RU.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__.'/cv.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/cy.php b/vendor/nesbot/carbon/src/Carbon/Lang/cy.php new file mode 100644 index 0000000..119274f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/cy.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - JD Isaacks + * - Daniel Monaghan + */ +return [ + 'year' => '{1}blwyddyn|]1,Inf[:count flynedd', + 'y' => ':countbl', + 'month' => '{1}mis|]1,Inf[:count mis', + 'm' => ':countmi', + 'week' => ':count wythnos', + 'w' => ':countw', + 'day' => '{1}diwrnod|]1,Inf[:count diwrnod', + 'd' => ':countd', + 'hour' => '{1}awr|]1,Inf[:count awr', + 'h' => ':counth', + 'minute' => '{1}munud|]1,Inf[:count munud', + 'min' => ':countm', + 'second' => '{1}ychydig eiliadau|]1,Inf[:count eiliad', + 's' => ':counts', + 'ago' => ':time yn ôl', + 'from_now' => 'mewn :time', + 'after' => ':time ar ôl', + 'before' => ':time o\'r blaen', + 'diff_now' => 'nawr', + 'diff_today' => 'Heddiw', + 'diff_today_regexp' => 'Heddiw(?:\\s+am)?', + 'diff_yesterday' => 'ddoe', + 'diff_yesterday_regexp' => 'Ddoe(?:\\s+am)?', + 'diff_tomorrow' => 'yfory', + 'diff_tomorrow_regexp' => 'Yfory(?:\\s+am)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Heddiw am] LT', + 'nextDay' => '[Yfory am] LT', + 'nextWeek' => 'dddd [am] LT', + 'lastDay' => '[Ddoe am] LT', + 'lastWeek' => 'dddd [diwethaf am] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + return $number.( + $number > 20 + ? (\in_array((int) $number, [40, 50, 60, 80, 100], true) ? 'fed' : 'ain') + : ([ + '', 'af', 'il', 'ydd', 'ydd', 'ed', 'ed', 'ed', 'fed', 'fed', 'fed', // 1af to 10fed + 'eg', 'fed', 'eg', 'eg', 'fed', 'eg', 'eg', 'fed', 'eg', 'fed', // 11eg to 20fed + ])[$number] ?? '' + ); + }, + 'months' => ['Ionawr', 'Chwefror', 'Mawrth', 'Ebrill', 'Mai', 'Mehefin', 'Gorffennaf', 'Awst', 'Medi', 'Hydref', 'Tachwedd', 'Rhagfyr'], + 'months_short' => ['Ion', 'Chwe', 'Maw', 'Ebr', 'Mai', 'Meh', 'Gor', 'Aws', 'Med', 'Hyd', 'Tach', 'Rhag'], + 'weekdays' => ['Dydd Sul', 'Dydd Llun', 'Dydd Mawrth', 'Dydd Mercher', 'Dydd Iau', 'Dydd Gwener', 'Dydd Sadwrn'], + 'weekdays_short' => ['Sul', 'Llun', 'Maw', 'Mer', 'Iau', 'Gwe', 'Sad'], + 'weekdays_min' => ['Su', 'Ll', 'Ma', 'Me', 'Ia', 'Gw', 'Sa'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' a '], + 'meridiem' => ['yb', 'yh'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/cy_GB.php b/vendor/nesbot/carbon/src/Carbon/Lang/cy_GB.php new file mode 100644 index 0000000..2c8148d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/cy_GB.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__.'/cy.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/da.php b/vendor/nesbot/carbon/src/Carbon/Lang/da.php new file mode 100644 index 0000000..322f91d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/da.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Rune Mønnike + * - François B + * - codenhagen + * - JD Isaacks + * - Jens Herlevsen + * - Ulrik McArdle (mcardle) + * - Frederik Sauer (FrittenKeeZ) + * - Janus Bahs Jacquet (kokoshneta) + */ +return [ + 'year' => ':count år|:count år', + 'a_year' => 'et år|:count år', + 'y' => ':count år|:count år', + 'month' => ':count måned|:count måneder', + 'a_month' => 'en måned|:count måneder', + 'm' => ':count mdr.', + 'week' => ':count uge|:count uger', + 'a_week' => 'en uge|:count uger', + 'w' => ':count u.', + 'day' => ':count dag|:count dage', + 'a_day' => ':count dag|:count dage', + 'd' => ':count d.', + 'hour' => ':count time|:count timer', + 'a_hour' => 'en time|:count timer', + 'h' => ':count t.', + 'minute' => ':count minut|:count minutter', + 'a_minute' => 'et minut|:count minutter', + 'min' => ':count min.', + 'second' => ':count sekund|:count sekunder', + 'a_second' => 'få sekunder|:count sekunder', + 's' => ':count s.', + 'ago' => 'for :time siden', + 'from_now' => 'om :time', + 'after' => ':time efter', + 'before' => ':time før', + 'diff_now' => 'nu', + 'diff_today' => 'i dag', + 'diff_today_regexp' => 'i dag(?:\\s+kl.)?', + 'diff_yesterday' => 'i går', + 'diff_yesterday_regexp' => 'i går(?:\\s+kl.)?', + 'diff_tomorrow' => 'i morgen', + 'diff_tomorrow_regexp' => 'i morgen(?:\\s+kl.)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D. MMMM YYYY', + 'LLL' => 'D. MMMM YYYY HH:mm', + 'LLLL' => 'dddd [d.] D. MMMM YYYY [kl.] HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[i dag kl.] LT', + 'nextDay' => '[i morgen kl.] LT', + 'nextWeek' => 'på dddd [kl.] LT', + 'lastDay' => '[i går kl.] LT', + 'lastWeek' => '[i] dddd[s kl.] LT', + 'sameElse' => 'L', + ], + 'ordinal' => ':number.', + 'months' => ['januar', 'februar', 'marts', 'april', 'maj', 'juni', 'juli', 'august', 'september', 'oktober', 'november', 'december'], + 'months_short' => ['jan.', 'feb.', 'mar.', 'apr.', 'maj.', 'jun.', 'jul.', 'aug.', 'sep.', 'okt.', 'nov.', 'dec.'], + 'weekdays' => ['søndag', 'mandag', 'tirsdag', 'onsdag', 'torsdag', 'fredag', 'lørdag'], + 'weekdays_short' => ['søn.', 'man.', 'tir.', 'ons.', 'tor.', 'fre.', 'lør.'], + 'weekdays_min' => ['sø', 'ma', 'ti', 'on', 'to', 'fr', 'lø'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' og '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/da_DK.php b/vendor/nesbot/carbon/src/Carbon/Lang/da_DK.php new file mode 100644 index 0000000..392c484 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/da_DK.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__.'/da.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/da_GL.php b/vendor/nesbot/carbon/src/Carbon/Lang/da_GL.php new file mode 100644 index 0000000..ea5698b --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/da_GL.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/da.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + 'LL' => 'D. MMM YYYY', + 'LLL' => 'D. MMMM YYYY HH.mm', + 'LLLL' => 'dddd [den] D. MMMM YYYY HH.mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/dav.php b/vendor/nesbot/carbon/src/Carbon/Lang/dav.php new file mode 100644 index 0000000..e95ec4b --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/dav.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['Luma lwa K', 'luma lwa p'], + 'weekdays' => ['Ituku ja jumwa', 'Kuramuka jimweri', 'Kuramuka kawi', 'Kuramuka kadadu', 'Kuramuka kana', 'Kuramuka kasanu', 'Kifula nguwo'], + 'weekdays_short' => ['Jum', 'Jim', 'Kaw', 'Kad', 'Kan', 'Kas', 'Ngu'], + 'weekdays_min' => ['Jum', 'Jim', 'Kaw', 'Kad', 'Kan', 'Kas', 'Ngu'], + 'months' => ['Mori ghwa imbiri', 'Mori ghwa kawi', 'Mori ghwa kadadu', 'Mori ghwa kana', 'Mori ghwa kasanu', 'Mori ghwa karandadu', 'Mori ghwa mfungade', 'Mori ghwa wunyanya', 'Mori ghwa ikenda', 'Mori ghwa ikumi', 'Mori ghwa ikumi na imweri', 'Mori ghwa ikumi na iwi'], + 'months_short' => ['Imb', 'Kaw', 'Kad', 'Kan', 'Kas', 'Kar', 'Mfu', 'Wun', 'Ike', 'Iku', 'Imw', 'Iwi'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/de.php b/vendor/nesbot/carbon/src/Carbon/Lang/de.php new file mode 100644 index 0000000..3b70750 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/de.php @@ -0,0 +1,117 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Michael Hohl + * - sheriffmarley + * - dennisoderwald + * - Timo + * - Karag2006 + * - Pete Scopes (pdscopes) + */ +return [ + 'year' => ':count Jahr|:count Jahre', + 'a_year' => 'ein Jahr|:count Jahre', + 'y' => ':count J.', + 'month' => ':count Monat|:count Monate', + 'a_month' => 'ein Monat|:count Monate', + 'm' => ':count Mon.', + 'week' => ':count Woche|:count Wochen', + 'a_week' => 'eine Woche|:count Wochen', + 'w' => ':count Wo.', + 'day' => ':count Tag|:count Tage', + 'a_day' => 'ein Tag|:count Tage', + 'd' => ':count Tg.', + 'hour' => ':count Stunde|:count Stunden', + 'a_hour' => 'eine Stunde|:count Stunden', + 'h' => ':count Std.', + 'minute' => ':count Minute|:count Minuten', + 'a_minute' => 'eine Minute|:count Minuten', + 'min' => ':count Min.', + 'second' => ':count Sekunde|:count Sekunden', + 'a_second' => 'ein paar Sekunden|:count Sekunden', + 's' => ':count Sek.', + 'millisecond' => ':count Millisekunde|:count Millisekunden', + 'a_millisecond' => 'eine Millisekunde|:count Millisekunden', + 'ms' => ':countms', + 'microsecond' => ':count Mikrosekunde|:count Mikrosekunden', + 'a_microsecond' => 'eine Mikrosekunde|:count Mikrosekunden', + 'µs' => ':countµs', + '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', + 'a_year_from_now' => 'ein Jahr|:count Jahren', + 'a_month_from_now' => 'ein Monat|:count Monaten', + 'a_week_from_now' => 'eine Woche|:count Wochen', + 'a_day_from_now' => 'ein Tag|:count Tagen', + 'a_year_ago' => 'ein Jahr|:count Jahren', + 'a_month_ago' => 'ein Monat|:count Monaten', + 'a_week_ago' => 'eine Woche|:count Wochen', + 'a_day_ago' => 'ein Tag|:count Tagen', + + 'diff_now' => 'Gerade eben', + 'diff_today' => 'heute', + 'diff_today_regexp' => 'heute(?:\\s+um)?', + 'diff_yesterday' => 'Gestern', + 'diff_yesterday_regexp' => 'gestern(?:\\s+um)?', + 'diff_tomorrow' => 'Morgen', + 'diff_tomorrow_regexp' => 'morgen(?:\\s+um)?', + 'diff_before_yesterday' => 'Vorgestern', + 'diff_after_tomorrow' => 'Übermorgen', + + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D. MMMM YYYY', + 'LLL' => 'D. MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D. MMMM YYYY HH:mm', + ], + + 'calendar' => [ + 'sameDay' => '[heute um] LT [Uhr]', + 'nextDay' => '[morgen um] LT [Uhr]', + 'nextWeek' => 'dddd [um] LT [Uhr]', + 'lastDay' => '[gestern um] LT [Uhr]', + 'lastWeek' => '[letzten] dddd [um] LT [Uhr]', + 'sameElse' => 'L', + ], + + 'months' => ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'], + 'months_short' => ['Jan', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'], + 'weekdays' => ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'], + 'weekdays_short' => ['So.', 'Mo.', 'Di.', 'Mi.', 'Do.', 'Fr.', 'Sa.'], + 'weekdays_min' => ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'], + 'ordinal' => ':number.', + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' und '], + 'ordinal_words' => [ + 'of' => 'im', + 'first' => 'erster', + 'second' => 'zweiter', + 'third' => 'dritter', + 'fourth' => 'vierten', + 'fifth' => 'fünfter', + 'last' => 'letzten', + ], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/de_AT.php b/vendor/nesbot/carbon/src/Carbon/Lang/de_AT.php new file mode 100644 index 0000000..a2ea4c0 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/de_AT.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - sheriffmarley + * - Timo + * - Michael Hohl + * - Namoshek + * - Bernhard Baumrock (BernhardBaumrock) + */ +return array_replace_recursive(require __DIR__.'/de.php', [ + 'months' => [ + 0 => 'Jänner', + ], + 'months_short' => [ + 0 => 'Jän', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/de_BE.php b/vendor/nesbot/carbon/src/Carbon/Lang/de_BE.php new file mode 100644 index 0000000..8ed8dc6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/de_BE.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/de.php', [ + 'formats' => [ + 'L' => 'YYYY-MM-DD', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/de_CH.php b/vendor/nesbot/carbon/src/Carbon/Lang/de_CH.php new file mode 100644 index 0000000..a869ab4 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/de_CH.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - sheriffmarley + * - Timo + * - Michael Hohl + */ +return array_replace_recursive(require __DIR__.'/de.php', [ + 'weekdays_short' => ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/de_DE.php b/vendor/nesbot/carbon/src/Carbon/Lang/de_DE.php new file mode 100644 index 0000000..fb1209d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/de_DE.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Free Software Foundation, Inc. bug-glibc-locales@gnu.org + */ +return require __DIR__.'/de.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/de_IT.php b/vendor/nesbot/carbon/src/Carbon/Lang/de_IT.php new file mode 100644 index 0000000..604a856 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/de_IT.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Matthias Dieter Wallno:fer libc-locales@sourceware.org + */ +return require __DIR__.'/de.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/de_LI.php b/vendor/nesbot/carbon/src/Carbon/Lang/de_LI.php new file mode 100644 index 0000000..03e606a --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/de_LI.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__.'/de.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/de_LU.php b/vendor/nesbot/carbon/src/Carbon/Lang/de_LU.php new file mode 100644 index 0000000..8ed8dc6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/de_LU.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/de.php', [ + 'formats' => [ + 'L' => 'YYYY-MM-DD', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/dje.php b/vendor/nesbot/carbon/src/Carbon/Lang/dje.php new file mode 100644 index 0000000..74b7ac1 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/dje.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_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['Subbaahi', 'Zaarikay b'], + 'weekdays' => ['Alhadi', 'Atinni', 'Atalaata', 'Alarba', 'Alhamisi', 'Alzuma', 'Asibti'], + 'weekdays_short' => ['Alh', 'Ati', 'Ata', 'Ala', 'Alm', 'Alz', 'Asi'], + 'weekdays_min' => ['Alh', 'Ati', 'Ata', 'Ala', 'Alm', 'Alz', 'Asi'], + 'months' => ['Žanwiye', 'Feewiriye', 'Marsi', 'Awiril', 'Me', 'Žuweŋ', 'Žuyye', 'Ut', 'Sektanbur', 'Oktoobur', 'Noowanbur', 'Deesanbur'], + 'months_short' => ['Žan', 'Fee', 'Mar', 'Awi', 'Me', 'Žuw', 'Žuy', 'Ut', 'Sek', 'Okt', 'Noo', 'Dee'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM, YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + + 'year' => ':count hari', // less reliable + 'y' => ':count hari', // less reliable + 'a_year' => ':count hari', // less reliable + + 'week' => ':count alzuma', // less reliable + 'w' => ':count alzuma', // less reliable + 'a_week' => ':count alzuma', // less reliable + + 'second' => ':count atinni', // less reliable + 's' => ':count atinni', // less reliable + 'a_second' => ':count atinni', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/doi.php b/vendor/nesbot/carbon/src/Carbon/Lang/doi.php new file mode 100644 index 0000000..cb679c5 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/doi.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/doi_IN.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/doi_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/doi_IN.php new file mode 100644 index 0000000..d359721 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/doi_IN.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Red Hat Pune libc-alpha@sourceware.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['जनवरी', 'फरवरी', 'मार्च', 'एप्रैल', 'मेई', 'जून', 'जूलै', 'अगस्त', 'सितंबर', 'अक्तूबर', 'नवंबर', 'दिसंबर'], + 'months_short' => ['जनवरी', 'फरवरी', 'मार्च', 'एप्रैल', 'मेई', 'जून', 'जूलै', 'अगस्त', 'सितंबर', 'अक्तूबर', 'नवंबर', 'दिसंबर'], + 'weekdays' => ['ऐतबार', 'सोमबार', 'मंगलबर', 'बुधबार', 'बीरबार', 'शुक्करबार', 'श्नीचरबार'], + 'weekdays_short' => ['ऐत', 'सोम', 'मंगल', 'बुध', 'बीर', 'शुक्कर', 'श्नीचर'], + 'weekdays_min' => ['ऐत', 'सोम', 'मंगल', 'बुध', 'बीर', 'शुक्कर', 'श्नीचर'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['सञं', 'सबेर'], + + 'second' => ':count सङार', // less reliable + 's' => ':count सङार', // less reliable + 'a_second' => ':count सङार', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/dsb.php b/vendor/nesbot/carbon/src/Carbon/Lang/dsb.php new file mode 100644 index 0000000..1d214d5 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/dsb.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/dsb_DE.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/dsb_DE.php b/vendor/nesbot/carbon/src/Carbon/Lang/dsb_DE.php new file mode 100644 index 0000000..1b94187 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/dsb_DE.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Information from Michael Wolf bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'DD. MMMM YYYY', + 'LLL' => 'DD. MMMM, HH:mm [góź.]', + 'LLLL' => 'dddd, DD. MMMM YYYY, HH:mm [góź.]', + ], + 'months' => ['januara', 'februara', 'měrca', 'apryla', 'maja', 'junija', 'julija', 'awgusta', 'septembra', 'oktobra', 'nowembra', 'decembra'], + 'months_short' => ['Jan', 'Feb', 'Měr', 'Apr', 'Maj', 'Jun', 'Jul', 'Awg', 'Sep', 'Okt', 'Now', 'Dec'], + 'weekdays' => ['Njeźela', 'Pónjeźele', 'Wałtora', 'Srjoda', 'Stwórtk', 'Pětk', 'Sobota'], + 'weekdays_short' => ['Nj', 'Pó', 'Wa', 'Sr', 'St', 'Pě', 'So'], + 'weekdays_min' => ['Nj', 'Pó', 'Wa', 'Sr', 'St', 'Pě', 'So'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'year' => ':count lěto', + 'y' => ':count lěto', + 'a_year' => ':count lěto', + + 'month' => ':count mjasec', + 'm' => ':count mjasec', + 'a_month' => ':count mjasec', + + 'week' => ':count tyźeń', + 'w' => ':count tyźeń', + 'a_week' => ':count tyźeń', + + 'day' => ':count źeń', + 'd' => ':count źeń', + 'a_day' => ':count źeń', + + 'hour' => ':count góźina', + 'h' => ':count góźina', + 'a_hour' => ':count góźina', + + 'minute' => ':count minuta', + 'min' => ':count minuta', + 'a_minute' => ':count minuta', + + 'second' => ':count drugi', + 's' => ':count drugi', + 'a_second' => ':count drugi', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/dua.php b/vendor/nesbot/carbon/src/Carbon/Lang/dua.php new file mode 100644 index 0000000..55e5c7c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/dua.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['idiɓa', 'ebyámu'], + 'weekdays' => ['éti', 'mɔ́sú', 'kwasú', 'mukɔ́sú', 'ŋgisú', 'ɗónɛsú', 'esaɓasú'], + 'weekdays_short' => ['ét', 'mɔ́s', 'kwa', 'muk', 'ŋgi', 'ɗón', 'esa'], + 'weekdays_min' => ['ét', 'mɔ́s', 'kwa', 'muk', 'ŋgi', 'ɗón', 'esa'], + 'months' => ['dimɔ́di', 'ŋgɔndɛ', 'sɔŋɛ', 'diɓáɓá', 'emiasele', 'esɔpɛsɔpɛ', 'madiɓɛ́díɓɛ́', 'diŋgindi', 'nyɛtɛki', 'mayésɛ́', 'tiníní', 'eláŋgɛ́'], + 'months_short' => ['di', 'ŋgɔn', 'sɔŋ', 'diɓ', 'emi', 'esɔ', 'mad', 'diŋ', 'nyɛt', 'may', 'tin', 'elá'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + + 'year' => ':count ma mbu', // less reliable + 'y' => ':count ma mbu', // less reliable + 'a_year' => ':count ma mbu', // less reliable + + 'month' => ':count myo̱di', // less reliable + 'm' => ':count myo̱di', // less reliable + 'a_month' => ':count myo̱di', // less reliable + + 'week' => ':count woki', // less reliable + 'w' => ':count woki', // less reliable + 'a_week' => ':count woki', // less reliable + + 'day' => ':count buńa', // less reliable + 'd' => ':count buńa', // less reliable + 'a_day' => ':count buńa', // less reliable + + 'hour' => ':count ma awa', // less reliable + 'h' => ':count ma awa', // less reliable + 'a_hour' => ':count ma awa', // less reliable + + 'minute' => ':count minuti', // less reliable + 'min' => ':count minuti', // less reliable + 'a_minute' => ':count minuti', // less reliable + + 'second' => ':count maba', // less reliable + 's' => ':count maba', // less reliable + 'a_second' => ':count maba', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/dv.php b/vendor/nesbot/carbon/src/Carbon/Lang/dv.php new file mode 100644 index 0000000..4b8d7e1 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/dv.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +$months = [ + 'ޖެނުއަރީ', + 'ފެބްރުއަރީ', + 'މާރިޗު', + 'އޭޕްރީލު', + 'މޭ', + 'ޖޫން', + 'ޖުލައި', + 'އޯގަސްޓު', + 'ސެޕްޓެމްބަރު', + 'އޮކްޓޯބަރު', + 'ނޮވެމްބަރު', + 'ޑިސެމްބަރު', +]; + +$weekdays = [ + 'އާދިއްތަ', + 'ހޯމަ', + 'އަންގާރަ', + 'ބުދަ', + 'ބުރާސްފަތި', + 'ހުކުރު', + 'ހޮނިހިރު', +]; + +/* + * Authors: + * - Josh Soref + * - Jawish Hameed + */ +return [ + 'year' => ':count '.'އަހަރު', + 'a_year' => '{1}'.'އަހަރެއް'.'|:count '.'އަހަރު', + 'month' => ':count '.'މަސް', + 'a_month' => '{1}'.'މަހެއް'.'|:count '.'މަސް', + 'week' => ':count '.'ހަފްތާ', + 'a_week' => '{1}'.'ސިކުންތުކޮޅެއް'.'|:count '.'ހަފްތާ', + 'day' => ':count '.'ދުވަސް', + 'a_day' => '{1}'.'ދުވަހެއް'.'|:count '.'ދުވަސް', + 'hour' => ':count '.'ގަޑިއިރު', + 'a_hour' => '{1}'.'ގަޑިއިރެއް'.'|:count '.'ގަޑިއިރު', + 'minute' => ':count '.'މިނިޓު', + 'a_minute' => '{1}'.'މިނިޓެއް'.'|:count '.'މިނިޓު', + 'second' => ':count '.'ސިކުންތު', + 'a_second' => '{1}'.'ސިކުންތުކޮޅެއް'.'|:count '.'ސިކުންތު', + 'ago' => 'ކުރިން :time', + 'from_now' => 'ތެރޭގައި :time', + 'after' => ':time ފަހުން', + 'before' => ':time ކުރި', + 'diff_yesterday' => 'އިއްޔެ', + 'diff_today' => 'މިއަދު', + 'diff_tomorrow' => 'މާދަމާ', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[މިއަދު] LT', + 'nextDay' => '[މާދަމާ] LT', + 'nextWeek' => 'dddd LT', + 'lastDay' => '[އިއްޔެ] LT', + 'lastWeek' => '[ފާއިތުވި] dddd LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['މކ', 'މފ'], + 'months' => $months, + 'months_short' => $months, + 'weekdays' => $weekdays, + 'weekdays_short' => $weekdays, + 'weekdays_min' => ['އާދި', 'ހޯމަ', 'އަން', 'ބުދަ', 'ބުރާ', 'ހުކު', 'ހޮނި'], + 'list' => [', ', ' އަދި '], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/dv_MV.php b/vendor/nesbot/carbon/src/Carbon/Lang/dv_MV.php new file mode 100644 index 0000000..2668d5b --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/dv_MV.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ahmed Ali + */ + +$months = [ + 'ޖެނުއަރީ', + 'ފެބްރުއަރީ', + 'މާރިޗު', + 'އޭޕްރީލު', + 'މޭ', + 'ޖޫން', + 'ޖުލައި', + 'އޯގަސްޓު', + 'ސެޕްޓެމްބަރު', + 'އޮކްޓޯބަރު', + 'ނޮވެމްބަރު', + 'ޑިސެމްބަރު', +]; + +$weekdays = [ + 'އާދިއްތަ', + 'ހޯމަ', + 'އަންގާރަ', + 'ބުދަ', + 'ބުރާސްފަތި', + 'ހުކުރު', + 'ހޮނިހިރު', +]; + +return [ + '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_yesterday' => 'އިއްޔެ', + 'diff_today' => 'މިއަދު', + 'diff_tomorrow' => 'މާދަމާ', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[މިއަދު] LT', + 'nextDay' => '[މާދަމާ] LT', + 'nextWeek' => 'dddd LT', + 'lastDay' => '[އިއްޔެ] LT', + 'lastWeek' => '[ފާއިތުވި] dddd LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['މކ', 'މފ'], + 'months' => $months, + 'months_short' => $months, + 'weekdays' => $weekdays, + 'weekdays_short' => $weekdays, + 'weekdays_min' => ['އާދި', 'ހޯމަ', 'އަން', 'ބުދަ', 'ބުރާ', 'ހުކު', 'ހޮނި'], + 'list' => [', ', ' އަދި '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/dyo.php b/vendor/nesbot/carbon/src/Carbon/Lang/dyo.php new file mode 100644 index 0000000..33082e6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/dyo.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'weekdays' => ['Dimas', 'Teneŋ', 'Talata', 'Alarbay', 'Aramisay', 'Arjuma', 'Sibiti'], + 'weekdays_short' => ['Dim', 'Ten', 'Tal', 'Ala', 'Ara', 'Arj', 'Sib'], + 'weekdays_min' => ['Dim', 'Ten', 'Tal', 'Ala', 'Ara', 'Arj', 'Sib'], + 'months' => ['Sanvie', 'Fébirie', 'Mars', 'Aburil', 'Mee', 'Sueŋ', 'Súuyee', 'Ut', 'Settembar', 'Oktobar', 'Novembar', 'Disambar'], + 'months_short' => ['Sa', 'Fe', 'Ma', 'Ab', 'Me', 'Su', 'Sú', 'Ut', 'Se', 'Ok', 'No', 'De'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/dz.php b/vendor/nesbot/carbon/src/Carbon/Lang/dz.php new file mode 100644 index 0000000..cc17e69 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/dz.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/dz_BT.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/dz_BT.php b/vendor/nesbot/carbon/src/Carbon/Lang/dz_BT.php new file mode 100644 index 0000000..bfbcaf4 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/dz_BT.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Sherubtse College bug-glibc@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'པསྱི་ལོYYཟལMMཚེསDD', + ], + 'months' => ['ཟླ་བ་དང་པ་', 'ཟླ་བ་གཉིས་པ་', 'ཟླ་བ་གསུམ་པ་', 'ཟླ་བ་བཞི་པ་', 'ཟླ་བ་ལྔ་ཕ་', 'ཟླ་བ་དྲུག་པ་', 'ཟླ་བ་བདུནཔ་', 'ཟླ་བ་བརྒྱད་པ་', 'ཟླ་བ་དགུ་པ་', 'ཟླ་བ་བཅུ་པ་', 'ཟླ་བ་བཅུ་གཅིག་པ་', 'ཟླ་བ་བཅུ་གཉིས་པ་'], + 'months_short' => ['ཟླ་༡', 'ཟླ་༢', 'ཟླ་༣', 'ཟླ་༤', 'ཟླ་༥', 'ཟླ་༦', 'ཟླ་༧', 'ཟླ་༨', 'ཟླ་༩', 'ཟླ་༡༠', 'ཟླ་༡༡', 'ཟླ་༡༢'], + 'weekdays' => ['གཟའ་ཟླ་བ་', 'གཟའ་མིག་དམར་', 'གཟའ་ལྷག་ཕ་', 'གཟའ་པུར་བུ་', 'གཟའ་པ་སངས་', 'གཟའ་སྤེན་ཕ་', 'གཟའ་ཉི་མ་'], + 'weekdays_short' => ['ཟླ་', 'མིར་', 'ལྷག་', 'པུར་', 'སངས་', 'སྤེན་', 'ཉི་'], + 'weekdays_min' => ['ཟླ་', 'མིར་', 'ལྷག་', 'པུར་', 'སངས་', 'སྤེན་', 'ཉི་'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['ངས་ཆ', 'ཕྱི་ཆ'], + + 'year' => ':count ཆརཔ', // less reliable + 'y' => ':count ཆརཔ', // less reliable + 'a_year' => ':count ཆརཔ', // less reliable + + 'month' => ':count ཟླ་བ', // less reliable + 'm' => ':count ཟླ་བ', // less reliable + 'a_month' => ':count ཟླ་བ', // less reliable + + 'day' => ':count ཉི', // less reliable + 'd' => ':count ཉི', // less reliable + 'a_day' => ':count ཉི', // less reliable + + 'second' => ':count ཆ', // less reliable + 's' => ':count ཆ', // less reliable + 'a_second' => ':count ཆ', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ebu.php b/vendor/nesbot/carbon/src/Carbon/Lang/ebu.php new file mode 100644 index 0000000..f60bc6f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ebu.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['KI', 'UT'], + 'weekdays' => ['Kiumia', 'Njumatatu', 'Njumaine', 'Njumatano', 'Aramithi', 'Njumaa', 'NJumamothii'], + 'weekdays_short' => ['Kma', 'Tat', 'Ine', 'Tan', 'Arm', 'Maa', 'NMM'], + 'weekdays_min' => ['Kma', 'Tat', 'Ine', 'Tan', 'Arm', 'Maa', 'NMM'], + 'months' => ['Mweri wa mbere', 'Mweri wa kaĩri', 'Mweri wa kathatũ', 'Mweri wa kana', 'Mweri wa gatano', 'Mweri wa gatantatũ', 'Mweri wa mũgwanja', 'Mweri wa kanana', 'Mweri wa kenda', 'Mweri wa ikũmi', 'Mweri wa ikũmi na ũmwe', 'Mweri wa ikũmi na Kaĩrĩ'], + 'months_short' => ['Mbe', 'Kai', 'Kat', 'Kan', 'Gat', 'Gan', 'Mug', 'Knn', 'Ken', 'Iku', 'Imw', 'Igi'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ee.php b/vendor/nesbot/carbon/src/Carbon/Lang/ee.php new file mode 100644 index 0000000..f96c5c9 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ee.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['ŋ', 'ɣ'], + 'weekdays' => ['kɔsiɖa', 'dzoɖa', 'blaɖa', 'kuɖa', 'yawoɖa', 'fiɖa', 'memleɖa'], + 'weekdays_short' => ['kɔs', 'dzo', 'bla', 'kuɖ', 'yaw', 'fiɖ', 'mem'], + 'weekdays_min' => ['kɔs', 'dzo', 'bla', 'kuɖ', 'yaw', 'fiɖ', 'mem'], + 'months' => ['dzove', 'dzodze', 'tedoxe', 'afɔfĩe', 'dama', 'masa', 'siamlɔm', 'deasiamime', 'anyɔnyɔ', 'kele', 'adeɛmekpɔxe', 'dzome'], + 'months_short' => ['dzv', 'dzd', 'ted', 'afɔ', 'dam', 'mas', 'sia', 'dea', 'any', 'kel', 'ade', 'dzm'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'a [ga] h:mm', + 'LTS' => 'a [ga] h:mm:ss', + 'L' => 'M/D/YYYY', + 'LL' => 'MMM D [lia], YYYY', + 'LLL' => 'a [ga] h:mm MMMM D [lia] YYYY', + 'LLLL' => 'a [ga] h:mm dddd, MMMM D [lia] YYYY', + ], + + 'year' => 'ƒe :count', + 'y' => 'ƒe :count', + 'a_year' => 'ƒe :count', + + 'month' => 'ɣleti :count', + 'm' => 'ɣleti :count', + 'a_month' => 'ɣleti :count', + + 'week' => 'kwasiɖa :count', + 'w' => 'kwasiɖa :count', + 'a_week' => 'kwasiɖa :count', + + 'day' => 'ŋkeke :count', + 'd' => 'ŋkeke :count', + 'a_day' => 'ŋkeke :count', + + 'hour' => 'gaƒoƒo :count', + 'h' => 'gaƒoƒo :count', + 'a_hour' => 'gaƒoƒo :count', + + 'minute' => 'miniti :count', // less reliable + 'min' => 'miniti :count', // less reliable + 'a_minute' => 'miniti :count', // less reliable + + 'second' => 'sɛkɛnd :count', // less reliable + 's' => 'sɛkɛnd :count', // less reliable + 'a_second' => 'sɛkɛnd :count', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ee_TG.php b/vendor/nesbot/carbon/src/Carbon/Lang/ee_TG.php new file mode 100644 index 0000000..7a8b36c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ee_TG.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ee.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'LLL' => 'HH:mm MMMM D [lia] YYYY', + 'LLLL' => 'HH:mm dddd, MMMM D [lia] YYYY', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/el.php b/vendor/nesbot/carbon/src/Carbon/Lang/el.php new file mode 100644 index 0000000..7c40f9c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/el.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Alessandro Di Felice + * - François B + * - Tim Fish + * - Gabriel Monteagudo + * - JD Isaacks + * - yiannisdesp + * - Ilias Kasmeridis (iliaskasm) + */ + +use Carbon\CarbonInterface; + +return [ + 'year' => ':count χρόνος|:count χρόνια', + 'a_year' => 'ένας χρόνος|:count χρόνια', + 'y' => ':count χρ.', + 'month' => ':count μήνας|:count μήνες', + 'a_month' => 'ένας μήνας|:count μήνες', + 'm' => ':count μήν.', + 'week' => ':count εβδομάδα|:count εβδομάδες', + 'a_week' => 'μια εβδομάδα|:count εβδομάδες', + 'w' => ':count εβδ.', + 'day' => ':count μέρα|:count μέρες', + 'a_day' => 'μία μέρα|:count μέρες', + 'd' => ':count μέρ.', + 'hour' => ':count ώρα|:count ώρες', + 'a_hour' => 'μία ώρα|:count ώρες', + 'h' => ':count ώρα|:count ώρες', + 'minute' => ':count λεπτό|:count λεπτά', + 'a_minute' => 'ένα λεπτό|:count λεπτά', + 'min' => ':count λεπ.', + 'second' => ':count δευτερόλεπτο|:count δευτερόλεπτα', + 'a_second' => 'λίγα δευτερόλεπτα|:count δευτερόλεπτα', + 's' => ':count δευ.', + 'ago' => 'πριν :time', + 'from_now' => 'σε :time', + 'after' => ':time μετά', + 'before' => ':time πριν', + 'diff_now' => 'τώρα', + 'diff_today' => 'Σήμερα', + 'diff_today_regexp' => 'Σήμερα(?:\\s+{})?', + 'diff_yesterday' => 'χθες', + 'diff_yesterday_regexp' => 'Χθες(?:\\s+{})?', + 'diff_tomorrow' => 'αύριο', + 'diff_tomorrow_regexp' => 'Αύριο(?:\\s+{})?', + 'formats' => [ + 'LT' => 'h:mm A', + 'LTS' => 'h:mm:ss A', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm A', + 'LLLL' => 'dddd, D MMMM YYYY h:mm A', + ], + 'calendar' => [ + 'sameDay' => '[Σήμερα {}] LT', + 'nextDay' => '[Αύριο {}] LT', + 'nextWeek' => 'dddd [{}] LT', + 'lastDay' => '[Χθες {}] LT', + 'lastWeek' => function (CarbonInterface $current) { + switch ($current->dayOfWeek) { + case 6: + return '[το προηγούμενο] dddd [{}] LT'; + default: + return '[την προηγούμενη] dddd [{}] LT'; + } + }, + 'sameElse' => 'L', + ], + 'ordinal' => ':numberη', + 'meridiem' => ['ΠΜ', 'ΜΜ', 'πμ', 'μμ'], + 'months' => ['Ιανουαρίου', 'Φεβρουαρίου', 'Μαρτίου', 'Απριλίου', 'Μαΐου', 'Ιουνίου', 'Ιουλίου', 'Αυγούστου', 'Σεπτεμβρίου', 'Οκτωβρίου', 'Νοεμβρίου', 'Δεκεμβρίου'], + 'months_standalone' => ['Ιανουάριος', 'Φεβρουάριος', 'Μάρτιος', 'Απρίλιος', 'Μάιος', 'Ιούνιος', 'Ιούλιος', 'Αύγουστος', 'Σεπτέμβριος', 'Οκτώβριος', 'Νοέμβριος', 'Δεκέμβριος'], + 'months_regexp' => '/(D[oD]?[\s,]+MMMM|L{2,4}|l{2,4})/', + 'months_short' => ['Ιαν', 'Φεβ', 'Μαρ', 'Απρ', 'Μαϊ', 'Ιουν', 'Ιουλ', 'Αυγ', 'Σεπ', 'Οκτ', 'Νοε', 'Δεκ'], + 'weekdays' => ['Κυριακή', 'Δευτέρα', 'Τρίτη', 'Τετάρτη', 'Πέμπτη', 'Παρασκευή', 'Σάββατο'], + 'weekdays_short' => ['Κυρ', 'Δευ', 'Τρι', 'Τετ', 'Πεμ', 'Παρ', 'Σαβ'], + 'weekdays_min' => ['Κυ', 'Δε', 'Τρ', 'Τε', 'Πε', 'Πα', 'Σα'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' και '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/el_CY.php b/vendor/nesbot/carbon/src/Carbon/Lang/el_CY.php new file mode 100644 index 0000000..8a693c1 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/el_CY.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Greek Debian Translation Team bug-glibc@gnu.org + */ +return array_replace_recursive(require __DIR__.'/el.php', [ + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/el_GR.php b/vendor/nesbot/carbon/src/Carbon/Lang/el_GR.php new file mode 100644 index 0000000..df196af --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/el_GR.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/el.php', [ + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en.php b/vendor/nesbot/carbon/src/Carbon/Lang/en.php new file mode 100644 index 0000000..f81f617 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Milos Sakovic + * - Paul + * - Pete Scopes (pdscopes) + */ +return [ + /* + * {1}, {0} and ]1,Inf[ are not needed as it's the default for English pluralization. + * But as some languages are using en.php as a fallback, it's better to specify it + * explicitly so those languages also fallback to English pluralization when a unit + * is missing. + */ + 'year' => '{1}:count year|{0}:count years|]1,Inf[:count years', + 'a_year' => '{1}a year|{0}:count years|]1,Inf[:count years', + 'y' => '{1}:countyr|{0}:countyrs|]1,Inf[:countyrs', + 'month' => '{1}:count month|{0}:count months|]1,Inf[:count months', + 'a_month' => '{1}a month|{0}:count months|]1,Inf[:count months', + 'm' => '{1}:countmo|{0}:countmos|]1,Inf[:countmos', + 'week' => '{1}:count week|{0}:count weeks|]1,Inf[:count weeks', + 'a_week' => '{1}a week|{0}:count weeks|]1,Inf[:count weeks', + 'w' => ':countw', + 'day' => '{1}:count day|{0}:count days|]1,Inf[:count days', + 'a_day' => '{1}a day|{0}:count days|]1,Inf[:count days', + 'd' => ':countd', + 'hour' => '{1}:count hour|{0}:count hours|]1,Inf[:count hours', + 'a_hour' => '{1}an hour|{0}:count hours|]1,Inf[:count hours', + 'h' => ':counth', + 'minute' => '{1}:count minute|{0}:count minutes|]1,Inf[:count minutes', + 'a_minute' => '{1}a minute|{0}:count minutes|]1,Inf[:count minutes', + 'min' => ':countm', + 'second' => '{1}:count second|{0}:count seconds|]1,Inf[:count seconds', + 'a_second' => '{1}a few seconds|{0}:count seconds|]1,Inf[:count seconds', + 's' => ':counts', + 'millisecond' => '{1}:count millisecond|{0}:count milliseconds|]1,Inf[:count milliseconds', + 'a_millisecond' => '{1}a millisecond|{0}:count milliseconds|]1,Inf[:count milliseconds', + 'ms' => ':countms', + 'microsecond' => '{1}:count microsecond|{0}:count microseconds|]1,Inf[:count microseconds', + 'a_microsecond' => '{1}a microsecond|{0}:count microseconds|]1,Inf[:count microseconds', + 'µs' => ':countµs', + 'ago' => ':time ago', + 'from_now' => ':time from now', + 'after' => ':time after', + 'before' => ':time before', + 'diff_now' => 'just now', + 'diff_today' => 'today', + 'diff_yesterday' => 'yesterday', + 'diff_tomorrow' => 'tomorrow', + 'diff_before_yesterday' => 'before yesterday', + 'diff_after_tomorrow' => 'after tomorrow', + 'period_recurrences' => '{1}once|{0}:count times|]1,Inf[:count times', + 'period_interval' => 'every :interval', + 'period_start_date' => 'from :date', + 'period_end_date' => 'to :date', + 'months' => ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], + 'months_short' => ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], + 'weekdays' => ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], + 'weekdays_short' => ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], + 'weekdays_min' => ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'], + 'ordinal' => function ($number) { + $lastDigit = $number % 10; + + return $number.( + ((int) ($number % 100 / 10) === 1) ? 'th' : ( + ($lastDigit === 1) ? 'st' : ( + ($lastDigit === 2) ? 'nd' : ( + ($lastDigit === 3) ? 'rd' : 'th' + ) + ) + ) + ); + }, + 'list' => [', ', ' and '], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_001.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_001.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_001.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_150.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_150.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_150.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_AG.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_AG.php new file mode 100644 index 0000000..2c1c64f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_AG.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Free Software Foundation, Inc. bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_AI.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_AI.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_AI.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_AS.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_AS.php new file mode 100644 index 0000000..f086dc6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_AS.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__.'/en.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_AT.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_AT.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_AT.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_AU.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_AU.php new file mode 100644 index 0000000..f16bd4f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_AU.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Kunal Marwaha + * - François B + * - Mayank Badola + * - JD Isaacks + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'from_now' => 'in :time', + 'formats' => [ + 'LT' => 'h:mm A', + 'LTS' => 'h:mm:ss A', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm A', + 'LLLL' => 'dddd, D MMMM YYYY h:mm A', + ], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_BB.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_BB.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_BB.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_BE.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_BE.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_BE.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_BI.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_BI.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_BI.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_BM.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_BM.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_BM.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_BS.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_BS.php new file mode 100644 index 0000000..f086dc6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_BS.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__.'/en.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_BW.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_BW.php new file mode 100644 index 0000000..f086dc6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_BW.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__.'/en.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_BZ.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_BZ.php new file mode 100644 index 0000000..f086dc6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_BZ.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__.'/en.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_CA.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_CA.php new file mode 100644 index 0000000..e656086 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_CA.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - Zhan Tong Zhang + * - Mayank Badola + * - JD Isaacks + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'from_now' => 'in :time', + 'formats' => [ + 'LT' => 'h:mm A', + 'LTS' => 'h:mm:ss A', + 'L' => 'YYYY-MM-DD', + 'LL' => 'MMMM D, YYYY', + 'LLL' => 'MMMM D, YYYY h:mm A', + 'LLLL' => 'dddd, MMMM D, YYYY h:mm A', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_CC.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_CC.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_CC.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_CH.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_CH.php new file mode 100644 index 0000000..10d9cd8 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_CH.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_CK.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_CK.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_CK.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_CM.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_CM.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_CM.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_CX.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_CX.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_CX.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_CY.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_CY.php new file mode 100644 index 0000000..a44c350 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_CY.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - NehaGautam + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'from_now' => 'in :time', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD-MM-YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_DE.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_DE.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_DE.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_DG.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_DG.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_DG.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_DK.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_DK.php new file mode 100644 index 0000000..9e8a8c6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_DK.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Danish Standards Association bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'YYYY-MM-DD', + ], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_DM.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_DM.php new file mode 100644 index 0000000..f086dc6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_DM.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__.'/en.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_ER.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_ER.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_ER.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_FI.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_FI.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_FI.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_FJ.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_FJ.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_FJ.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_FK.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_FK.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_FK.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_FM.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_FM.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_FM.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_GB.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_GB.php new file mode 100644 index 0000000..67d9fd6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_GB.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - Mayank Badola + * - JD Isaacks + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'from_now' => 'in :time', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_GD.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_GD.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_GD.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_GG.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_GG.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_GG.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_GH.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_GH.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_GH.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_GI.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_GI.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_GI.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_GM.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_GM.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_GM.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_GU.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_GU.php new file mode 100644 index 0000000..f086dc6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_GU.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__.'/en.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_GY.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_GY.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_GY.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_HK.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_HK.php new file mode 100644 index 0000000..34aae98 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_HK.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_IE.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_IE.php new file mode 100644 index 0000000..c8d3c2f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_IE.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Martin McWhorter + * - François B + * - Chris Cartlidge + * - JD Isaacks + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'from_now' => 'in :time', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD-MM-YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_IL.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_IL.php new file mode 100644 index 0000000..e607924 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_IL.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Yoav Amit + * - François B + * - Mayank Badola + * - JD Isaacks + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'from_now' => 'in :time', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_IM.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_IM.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_IM.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_IN.php new file mode 100644 index 0000000..00414e9 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_IN.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YY', + 'LL' => 'MMMM DD, YYYY', + 'LLL' => 'DD MMM HH:mm', + 'LLLL' => 'MMMM DD, YYYY HH:mm', + ], + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_IO.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_IO.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_IO.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_ISO.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_ISO.php new file mode 100644 index 0000000..11457b0 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_ISO.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-dd', + 'LL' => 'YYYY MMM D', + 'LLL' => 'YYYY MMMM D HH:mm', + 'LLLL' => 'dddd, YYYY MMMM DD HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_JE.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_JE.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_JE.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_JM.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_JM.php new file mode 100644 index 0000000..f086dc6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_JM.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__.'/en.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_KE.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_KE.php new file mode 100644 index 0000000..f086dc6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_KE.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__.'/en.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_KI.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_KI.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_KI.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_KN.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_KN.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_KN.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_KY.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_KY.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_KY.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_LC.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_LC.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_LC.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_LR.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_LR.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_LR.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_LS.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_LS.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_LS.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_MG.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_MG.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_MG.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_MH.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_MH.php new file mode 100644 index 0000000..f086dc6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_MH.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__.'/en.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_MO.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_MO.php new file mode 100644 index 0000000..f086dc6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_MO.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__.'/en.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_MP.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_MP.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_MP.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_MS.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_MS.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_MS.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_MT.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_MT.php new file mode 100644 index 0000000..f086dc6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_MT.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__.'/en.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_MU.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_MU.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_MU.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_MW.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_MW.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_MW.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_MY.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_MY.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_MY.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_NA.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_NA.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_NA.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_NF.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_NF.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_NF.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_NG.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_NG.php new file mode 100644 index 0000000..67bceaa --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_NG.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_NL.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_NL.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_NL.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_NR.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_NR.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_NR.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_NU.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_NU.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_NU.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_NZ.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_NZ.php new file mode 100644 index 0000000..6a206a0 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_NZ.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - Mayank Badola + * - Luke McGregor + * - JD Isaacks + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'from_now' => 'in :time', + 'formats' => [ + 'LT' => 'h:mm A', + 'LTS' => 'h:mm:ss A', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm A', + 'LLLL' => 'dddd, D MMMM YYYY h:mm A', + ], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_PG.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_PG.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_PG.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_PH.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_PH.php new file mode 100644 index 0000000..34aae98 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_PH.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_PK.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_PK.php new file mode 100644 index 0000000..f086dc6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_PK.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__.'/en.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_PN.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_PN.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_PN.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_PR.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_PR.php new file mode 100644 index 0000000..f086dc6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_PR.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__.'/en.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_PW.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_PW.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_PW.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_RW.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_RW.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_RW.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_SB.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_SB.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_SB.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_SC.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_SC.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_SC.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_SD.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_SD.php new file mode 100644 index 0000000..c4e2557 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_SD.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 6, + 'weekend' => [5, 6], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_SE.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_SE.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_SE.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_SG.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_SG.php new file mode 100644 index 0000000..5ee9524 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_SG.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_replace_recursive(require __DIR__.'/en.php', [ + 'from_now' => 'in :time', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_SH.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_SH.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_SH.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_SI.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_SI.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_SI.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_SL.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_SL.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_SL.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_SS.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_SS.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_SS.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_SX.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_SX.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_SX.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_SZ.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_SZ.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_SZ.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_TC.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_TC.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_TC.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_TK.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_TK.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_TK.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_TO.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_TO.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_TO.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_TT.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_TT.php new file mode 100644 index 0000000..f086dc6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_TT.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__.'/en.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_TV.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_TV.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_TV.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_TZ.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_TZ.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_TZ.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_UG.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_UG.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_UG.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_UM.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_UM.php new file mode 100644 index 0000000..f086dc6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_UM.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__.'/en.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_US.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_US.php new file mode 100644 index 0000000..f086dc6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_US.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__.'/en.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_US_Posix.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_US_Posix.php new file mode 100644 index 0000000..f086dc6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_US_Posix.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__.'/en.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_VC.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_VC.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_VC.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_VG.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_VG.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_VG.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_VI.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_VI.php new file mode 100644 index 0000000..f086dc6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_VI.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__.'/en.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_VU.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_VU.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_VU.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_WS.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_WS.php new file mode 100644 index 0000000..f086dc6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_WS.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__.'/en.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_ZA.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_ZA.php new file mode 100644 index 0000000..48ea947 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_ZA.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Zuza Software Foundation (Translate.org.za) Dwayne Bailey dwayne@translate.org.za + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YY', + 'LL' => 'MMMM DD, YYYY', + 'LLL' => 'DD MMM HH:mm', + 'LLLL' => 'MMMM DD, YYYY HH:mm', + ], + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_ZM.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_ZM.php new file mode 100644 index 0000000..d8a8cb5 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_ZM.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - ANLoc Martin Benjamin locales@africanlocalization.net + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_ZW.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_ZW.php new file mode 100644 index 0000000..f086dc6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_ZW.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__.'/en.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/eo.php b/vendor/nesbot/carbon/src/Carbon/Lang/eo.php new file mode 100644 index 0000000..7c2efba --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/eo.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - François B + * - Mia Nordentoft + * - JD Isaacks + */ +return [ + 'year' => ':count jaro|:count jaroj', + 'a_year' => 'jaro|:count jaroj', + 'y' => ':count j.', + 'month' => ':count monato|:count monatoj', + 'a_month' => 'monato|:count monatoj', + 'm' => ':count mo.', + 'week' => ':count semajno|:count semajnoj', + 'a_week' => 'semajno|:count semajnoj', + 'w' => ':count sem.', + 'day' => ':count tago|:count tagoj', + 'a_day' => 'tago|:count tagoj', + 'd' => ':count t.', + 'hour' => ':count horo|:count horoj', + 'a_hour' => 'horo|:count horoj', + 'h' => ':count h.', + 'minute' => ':count minuto|:count minutoj', + 'a_minute' => 'minuto|:count minutoj', + 'min' => ':count min.', + 'second' => ':count sekundo|:count sekundoj', + 'a_second' => 'sekundoj|:count sekundoj', + 's' => ':count sek.', + 'ago' => 'antaŭ :time', + 'from_now' => 'post :time', + 'after' => ':time poste', + 'before' => ':time antaŭe', + 'diff_yesterday' => 'Hieraŭ', + 'diff_yesterday_regexp' => 'Hieraŭ(?:\\s+je)?', + 'diff_today' => 'Hodiaŭ', + 'diff_today_regexp' => 'Hodiaŭ(?:\\s+je)?', + 'diff_tomorrow' => 'Morgaŭ', + 'diff_tomorrow_regexp' => 'Morgaŭ(?:\\s+je)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-DD', + 'LL' => 'D[-a de] MMMM, YYYY', + 'LLL' => 'D[-a de] MMMM, YYYY HH:mm', + 'LLLL' => 'dddd, [la] D[-a de] MMMM, YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Hodiaŭ je] LT', + 'nextDay' => '[Morgaŭ je] LT', + 'nextWeek' => 'dddd [je] LT', + 'lastDay' => '[Hieraŭ je] LT', + 'lastWeek' => '[pasinta] dddd [je] LT', + 'sameElse' => 'L', + ], + 'ordinal' => ':numbera', + 'meridiem' => ['a.t.m.', 'p.t.m.'], + 'months' => ['januaro', 'februaro', 'marto', 'aprilo', 'majo', 'junio', 'julio', 'aŭgusto', 'septembro', 'oktobro', 'novembro', 'decembro'], + 'months_short' => ['jan', 'feb', 'mar', 'apr', 'maj', 'jun', 'jul', 'aŭg', 'sep', 'okt', 'nov', 'dec'], + 'weekdays' => ['dimanĉo', 'lundo', 'mardo', 'merkredo', 'ĵaŭdo', 'vendredo', 'sabato'], + 'weekdays_short' => ['dim', 'lun', 'mard', 'merk', 'ĵaŭ', 'ven', 'sab'], + 'weekdays_min' => ['di', 'lu', 'ma', 'me', 'ĵa', 've', 'sa'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' kaj '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es.php b/vendor/nesbot/carbon/src/Carbon/Lang/es.php new file mode 100644 index 0000000..1c4fcfd --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es.php @@ -0,0 +1,121 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Kunal Marwaha + * - kostas + * - François B + * - Tim Fish + * - Claire Coloma + * - Steven Heinrich + * - JD Isaacks + * - Raphael Amorim + * - Jorge Y. Castillo + * - Víctor Díaz + * - Diego + * - Sebastian Thierer + * - quinterocesar + * - Daniel Commesse Liévanos (danielcommesse) + * - Pete Scopes (pdscopes) + * - gam04 + */ + +use Carbon\CarbonInterface; + +return [ + 'year' => ':count año|:count años', + 'a_year' => 'un año|:count años', + 'y' => ':count año|:count años', + 'month' => ':count mes|:count meses', + 'a_month' => 'un mes|:count meses', + 'm' => ':count mes|:count meses', + 'week' => ':count semana|:count semanas', + 'a_week' => 'una semana|:count semanas', + 'w' => ':countsem', + 'day' => ':count día|:count días', + 'a_day' => 'un día|:count días', + 'd' => ':countd', + 'hour' => ':count hora|:count horas', + 'a_hour' => 'una hora|:count horas', + 'h' => ':counth', + 'minute' => ':count minuto|:count minutos', + 'a_minute' => 'un minuto|:count minutos', + 'min' => ':countm', + 'second' => ':count segundo|:count segundos', + 'a_second' => 'unos segundos|:count segundos', + 's' => ':counts', + 'millisecond' => ':count milisegundo|:count milisegundos', + 'a_millisecond' => 'un milisegundo|:count milisegundos', + 'ms' => ':countms', + 'microsecond' => ':count microsegundo|:count microsegundos', + 'a_microsecond' => 'un microsegundo|:count microsegundos', + 'µs' => ':countµs', + 'ago' => 'hace :time', + 'from_now' => 'en :time', + 'after' => ':time después', + 'before' => ':time antes', + 'diff_now' => 'ahora mismo', + 'diff_today' => 'hoy', + 'diff_today_regexp' => 'hoy(?:\\s+a)?(?:\\s+las)?', + 'diff_yesterday' => 'ayer', + 'diff_yesterday_regexp' => 'ayer(?:\\s+a)?(?:\\s+las)?', + 'diff_tomorrow' => 'mañana', + 'diff_tomorrow_regexp' => 'mañana(?:\\s+a)?(?:\\s+las)?', + 'diff_before_yesterday' => 'anteayer', + 'diff_after_tomorrow' => 'pasado mañana', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D [de] MMMM [de] YYYY', + 'LLL' => 'D [de] MMMM [de] YYYY H:mm', + 'LLLL' => 'dddd, D [de] MMMM [de] YYYY H:mm', + ], + 'calendar' => [ + 'sameDay' => function (CarbonInterface $current) { + return '[hoy a la'.($current->hour !== 1 ? 's' : '').'] LT'; + }, + 'nextDay' => function (CarbonInterface $current) { + return '[mañana a la'.($current->hour !== 1 ? 's' : '').'] LT'; + }, + 'nextWeek' => function (CarbonInterface $current) { + return 'dddd [a la'.($current->hour !== 1 ? 's' : '').'] LT'; + }, + 'lastDay' => function (CarbonInterface $current) { + return '[ayer a la'.($current->hour !== 1 ? 's' : '').'] LT'; + }, + 'lastWeek' => function (CarbonInterface $current) { + return '[el] dddd [pasado a la'.($current->hour !== 1 ? 's' : '').'] LT'; + }, + 'sameElse' => 'L', + ], + 'months' => ['enero', 'febrero', 'marzo', 'abril', 'mayo', 'junio', 'julio', 'agosto', 'septiembre', 'octubre', 'noviembre', 'diciembre'], + 'months_short' => ['ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago', 'sep', 'oct', 'nov', 'dic'], + 'mmm_suffix' => '.', + 'ordinal' => ':numberº', + 'weekdays' => ['domingo', 'lunes', 'martes', 'miércoles', 'jueves', 'viernes', 'sábado'], + 'weekdays_short' => ['dom.', 'lun.', 'mar.', 'mié.', 'jue.', 'vie.', 'sáb.'], + 'weekdays_min' => ['do', 'lu', 'ma', 'mi', 'ju', 'vi', 'sá'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' y '], + 'meridiem' => ['a. m.', 'p. m.'], + 'ordinal_words' => [ + 'of' => 'de', + 'first' => 'primer', + 'second' => 'segundo', + 'third' => 'tercer', + 'fourth' => 'cuarto', + 'fifth' => 'quinto', + 'last' => 'último', + ], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es_419.php b/vendor/nesbot/carbon/src/Carbon/Lang/es_419.php new file mode 100644 index 0000000..a74806e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es_419.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es_AR.php b/vendor/nesbot/carbon/src/Carbon/Lang/es_AR.php new file mode 100644 index 0000000..a74806e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es_AR.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es_BO.php b/vendor/nesbot/carbon/src/Carbon/Lang/es_BO.php new file mode 100644 index 0000000..c9b8432 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es_BO.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es_BR.php b/vendor/nesbot/carbon/src/Carbon/Lang/es_BR.php new file mode 100644 index 0000000..378d054 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es_BR.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es_BZ.php b/vendor/nesbot/carbon/src/Carbon/Lang/es_BZ.php new file mode 100644 index 0000000..378d054 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es_BZ.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es_CL.php b/vendor/nesbot/carbon/src/Carbon/Lang/es_CL.php new file mode 100644 index 0000000..a74806e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es_CL.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es_CO.php b/vendor/nesbot/carbon/src/Carbon/Lang/es_CO.php new file mode 100644 index 0000000..a74806e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es_CO.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es_CR.php b/vendor/nesbot/carbon/src/Carbon/Lang/es_CR.php new file mode 100644 index 0000000..553fc09 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es_CR.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Free Software Foundation, Inc. bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es_CU.php b/vendor/nesbot/carbon/src/Carbon/Lang/es_CU.php new file mode 100644 index 0000000..f02e1a6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es_CU.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es_DO.php b/vendor/nesbot/carbon/src/Carbon/Lang/es_DO.php new file mode 100644 index 0000000..0f855ba --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es_DO.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - kostas + * - François B + * - Tim Fish + * - Chiel Robben + * - Claire Coloma + * - Steven Heinrich + * - JD Isaacks + * - Raphael Amorim + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'diff_before_yesterday' => 'anteayer', + 'formats' => [ + 'LT' => 'h:mm A', + 'LTS' => 'h:mm:ss A', + 'LLL' => 'D [de] MMMM [de] YYYY h:mm A', + 'LLLL' => 'dddd, D [de] MMMM [de] YYYY h:mm A', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es_EA.php b/vendor/nesbot/carbon/src/Carbon/Lang/es_EA.php new file mode 100644 index 0000000..f02e1a6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es_EA.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es_EC.php b/vendor/nesbot/carbon/src/Carbon/Lang/es_EC.php new file mode 100644 index 0000000..a74806e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es_EC.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es_ES.php b/vendor/nesbot/carbon/src/Carbon/Lang/es_ES.php new file mode 100644 index 0000000..19217c2 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es_ES.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return require __DIR__.'/es.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es_GQ.php b/vendor/nesbot/carbon/src/Carbon/Lang/es_GQ.php new file mode 100644 index 0000000..f02e1a6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es_GQ.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es_GT.php b/vendor/nesbot/carbon/src/Carbon/Lang/es_GT.php new file mode 100644 index 0000000..a74806e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es_GT.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es_HN.php b/vendor/nesbot/carbon/src/Carbon/Lang/es_HN.php new file mode 100644 index 0000000..a74806e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es_HN.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es_IC.php b/vendor/nesbot/carbon/src/Carbon/Lang/es_IC.php new file mode 100644 index 0000000..f02e1a6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es_IC.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es_MX.php b/vendor/nesbot/carbon/src/Carbon/Lang/es_MX.php new file mode 100644 index 0000000..61e14cf --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es_MX.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'diff_before_yesterday' => 'antier', + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es_NI.php b/vendor/nesbot/carbon/src/Carbon/Lang/es_NI.php new file mode 100644 index 0000000..6b964c1 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es_NI.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Free Software Foundation, Inc. bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es_PA.php b/vendor/nesbot/carbon/src/Carbon/Lang/es_PA.php new file mode 100644 index 0000000..a74806e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es_PA.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es_PE.php b/vendor/nesbot/carbon/src/Carbon/Lang/es_PE.php new file mode 100644 index 0000000..a74806e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es_PE.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es_PH.php b/vendor/nesbot/carbon/src/Carbon/Lang/es_PH.php new file mode 100644 index 0000000..deae06a --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es_PH.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'D/M/yy', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D [de] MMMM [de] YYYY h:mm a', + 'LLLL' => 'dddd, D [de] MMMM [de] YYYY h:mm a', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es_PR.php b/vendor/nesbot/carbon/src/Carbon/Lang/es_PR.php new file mode 100644 index 0000000..6b964c1 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es_PR.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Free Software Foundation, Inc. bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es_PY.php b/vendor/nesbot/carbon/src/Carbon/Lang/es_PY.php new file mode 100644 index 0000000..a74806e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es_PY.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es_SV.php b/vendor/nesbot/carbon/src/Carbon/Lang/es_SV.php new file mode 100644 index 0000000..00db08e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es_SV.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'months' => ['enero', 'febrero', 'marzo', 'abril', 'mayo', 'junio', 'julio', 'agosto', 'septiembre', 'octubre', 'noviembre', 'diciembre'], + 'months_short' => ['ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago', 'sep', 'oct', 'nov', 'dic'], + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es_US.php b/vendor/nesbot/carbon/src/Carbon/Lang/es_US.php new file mode 100644 index 0000000..f333136 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es_US.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Kunal Marwaha + * - Josh Soref + * - Jørn Ølmheim + * - Craig Patik + * - bustta + * - François B + * - Tim Fish + * - Claire Coloma + * - Steven Heinrich + * - JD Isaacks + * - Raphael Amorim + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'diff_before_yesterday' => 'anteayer', + 'formats' => [ + 'LT' => 'h:mm A', + 'LTS' => 'h:mm:ss A', + 'L' => 'MM/DD/YYYY', + 'LL' => 'MMMM [de] D [de] YYYY', + 'LLL' => 'MMMM [de] D [de] YYYY h:mm A', + 'LLLL' => 'dddd, MMMM [de] D [de] YYYY h:mm A', + ], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es_UY.php b/vendor/nesbot/carbon/src/Carbon/Lang/es_UY.php new file mode 100644 index 0000000..39baff8 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es_UY.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'months' => ['enero', 'febrero', 'marzo', 'abril', 'mayo', 'junio', 'julio', 'agosto', 'setiembre', 'octubre', 'noviembre', 'diciembre'], + 'months_short' => ['ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago', 'set', 'oct', 'nov', 'dic'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es_VE.php b/vendor/nesbot/carbon/src/Carbon/Lang/es_VE.php new file mode 100644 index 0000000..a74806e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es_VE.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/et.php b/vendor/nesbot/carbon/src/Carbon/Lang/et.php new file mode 100644 index 0000000..f49c880 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/et.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Philippe Vaucher + * - Andres Ivanov + * - Tsutomu Kuroda + * - tjku + * - Max Melentiev + * - Juanito Fatas + * - RM87 + * - Akira Matsuda + * - Christopher Dell + * - Enrique Vidal + * - Simone Carletti + * - Aaron Patterson + * - Esko Lehtme + * - Mart Karu + * - Nicolás Hock Isaza + * - Kevin Valdek + * - Zahhar Kirillov + * - João Magalhães + * - Ingmar + * - Illimar Tambek + * - Mihkel + */ +return [ + 'year' => ':count aasta|:count aastat', + 'y' => ':count a', + 'month' => ':count kuu|:count kuud', + 'm' => ':count k', + 'week' => ':count nädal|:count nädalat', + 'w' => ':count näd', + 'day' => ':count päev|:count päeva', + 'd' => ':count p', + 'hour' => ':count tund|:count tundi', + 'h' => ':count t', + 'minute' => ':count minut|:count minutit', + 'min' => ':count min', + 'second' => ':count sekund|:count sekundit', + 's' => ':count s', + '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', + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'diff_now' => 'nüüd', + 'diff_today' => 'täna', + 'diff_yesterday' => 'eile', + 'diff_tomorrow' => 'homme', + 'diff_before_yesterday' => 'üleeile', + 'diff_after_tomorrow' => 'ülehomme', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D. MMMM YYYY', + 'LLL' => 'D. MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D. MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[täna] LT', + 'nextDay' => '[homme] LT', + 'lastDay' => '[eile] LT', + 'nextWeek' => 'dddd LT', + 'lastWeek' => '[eelmine] dddd LT', + 'sameElse' => 'L', + ], + 'months' => ['jaanuar', 'veebruar', 'märts', 'aprill', 'mai', 'juuni', 'juuli', 'august', 'september', 'oktoober', 'november', 'detsember'], + 'months_short' => ['jaan', 'veebr', 'märts', 'apr', 'mai', 'juuni', 'juuli', 'aug', 'sept', 'okt', 'nov', 'dets'], + 'weekdays' => ['pühapäev', 'esmaspäev', 'teisipäev', 'kolmapäev', 'neljapäev', 'reede', 'laupäev'], + 'weekdays_short' => ['P', 'E', 'T', 'K', 'N', 'R', 'L'], + 'weekdays_min' => ['P', 'E', 'T', 'K', 'N', 'R', 'L'], + 'list' => [', ', ' ja '], + 'meridiem' => ['enne lõunat', 'pärast lõunat'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/et_EE.php b/vendor/nesbot/carbon/src/Carbon/Lang/et_EE.php new file mode 100644 index 0000000..0f112b3 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/et_EE.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__.'/et.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/eu.php b/vendor/nesbot/carbon/src/Carbon/Lang/eu.php new file mode 100644 index 0000000..a543f1a --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/eu.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - François B + * - JD Isaacks + */ +return [ + 'year' => 'urte bat|:count urte', + 'y' => 'Urte 1|:count urte', + 'month' => 'hilabete bat|:count hilabete', + 'm' => 'Hile 1|:count hile', + 'week' => 'Aste 1|:count aste', + 'w' => 'Aste 1|:count aste', + 'day' => 'egun bat|:count egun', + 'd' => 'Egun 1|:count egun', + 'hour' => 'ordu bat|:count ordu', + 'h' => 'Ordu 1|:count ordu', + 'minute' => 'minutu bat|:count minutu', + 'min' => 'Minutu 1|:count minutu', + 'second' => 'segundo batzuk|:count segundo', + 's' => 'Segundu 1|:count segundu', + 'ago' => 'duela :time', + 'from_now' => ':time barru', + 'after' => ':time geroago', + 'before' => ':time lehenago', + 'diff_now' => 'orain', + 'diff_today' => 'gaur', + 'diff_yesterday' => 'atzo', + 'diff_tomorrow' => 'bihar', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-DD', + 'LL' => 'YYYY[ko] MMMM[ren] D[a]', + 'LLL' => 'YYYY[ko] MMMM[ren] D[a] HH:mm', + 'LLLL' => 'dddd, YYYY[ko] MMMM[ren] D[a] HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[gaur] LT[etan]', + 'nextDay' => '[bihar] LT[etan]', + 'nextWeek' => 'dddd LT[etan]', + 'lastDay' => '[atzo] LT[etan]', + 'lastWeek' => '[aurreko] dddd LT[etan]', + 'sameElse' => 'L', + ], + 'ordinal' => ':number.', + 'months' => ['urtarrila', 'otsaila', 'martxoa', 'apirila', 'maiatza', 'ekaina', 'uztaila', 'abuztua', 'iraila', 'urria', 'azaroa', 'abendua'], + 'months_short' => ['urt.', 'ots.', 'mar.', 'api.', 'mai.', 'eka.', 'uzt.', 'abu.', 'ira.', 'urr.', 'aza.', 'abe.'], + 'weekdays' => ['igandea', 'astelehena', 'asteartea', 'asteazkena', 'osteguna', 'ostirala', 'larunbata'], + 'weekdays_short' => ['ig.', 'al.', 'ar.', 'az.', 'og.', 'ol.', 'lr.'], + 'weekdays_min' => ['ig', 'al', 'ar', 'az', 'og', 'ol', 'lr'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' eta '], + 'meridiem' => ['g', 'a'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/eu_ES.php b/vendor/nesbot/carbon/src/Carbon/Lang/eu_ES.php new file mode 100644 index 0000000..0d1e82a --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/eu_ES.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__.'/eu.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ewo.php b/vendor/nesbot/carbon/src/Carbon/Lang/ewo.php new file mode 100644 index 0000000..7808ab5 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ewo.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['kíkíríg', 'ngəgógəle'], + 'weekdays' => ['sɔ́ndɔ', 'mɔ́ndi', 'sɔ́ndɔ məlú mə́bɛ̌', 'sɔ́ndɔ məlú mə́lɛ́', 'sɔ́ndɔ məlú mə́nyi', 'fúladé', 'séradé'], + 'weekdays_short' => ['sɔ́n', 'mɔ́n', 'smb', 'sml', 'smn', 'fúl', 'sér'], + 'weekdays_min' => ['sɔ́n', 'mɔ́n', 'smb', 'sml', 'smn', 'fúl', 'sér'], + 'months' => ['ngɔn osú', 'ngɔn bɛ̌', 'ngɔn lála', 'ngɔn nyina', 'ngɔn tána', 'ngɔn saməna', 'ngɔn zamgbála', 'ngɔn mwom', 'ngɔn ebulú', 'ngɔn awóm', 'ngɔn awóm ai dziá', 'ngɔn awóm ai bɛ̌'], + 'months_short' => ['ngo', 'ngb', 'ngl', 'ngn', 'ngt', 'ngs', 'ngz', 'ngm', 'nge', 'nga', 'ngad', 'ngab'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + + // Too unreliable + /* + 'year' => ':count mbu', // less reliable + 'y' => ':count mbu', // less reliable + 'a_year' => ':count mbu', // less reliable + + 'month' => ':count ngòn', // less reliable + 'm' => ':count ngòn', // less reliable + 'a_month' => ':count ngòn', // less reliable + + 'week' => ':count mësë', // less reliable + 'w' => ':count mësë', // less reliable + 'a_week' => ':count mësë', // less reliable + + 'day' => ':count mësë', // less reliable + 'd' => ':count mësë', // less reliable + 'a_day' => ':count mësë', // less reliable + + 'hour' => ':count awola', // less reliable + 'h' => ':count awola', // less reliable + 'a_hour' => ':count awola', // less reliable + + 'minute' => ':count awola', // less reliable + 'min' => ':count awola', // less reliable + 'a_minute' => ':count awola', // less reliable + */ +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fa.php b/vendor/nesbot/carbon/src/Carbon/Lang/fa.php new file mode 100644 index 0000000..72e0308 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fa.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - François B + * - Nasser Ghiasi + * - JD Isaacks + * - Hossein Jabbari + * - nimamo + * - hafezdivandari + * - Hassan Pezeshk (hpez) + */ +return [ + 'year' => ':count سال', + 'a_year' => 'یک سال'.'|:count '.'سال', + 'y' => ':count سال', + 'month' => ':count ماه', + 'a_month' => 'یک ماه'.'|:count '.'ماه', + 'm' => ':count ماه', + 'week' => ':count هفته', + 'a_week' => 'یک هفته'.'|:count '.'هفته', + 'w' => ':count هفته', + 'day' => ':count روز', + 'a_day' => 'یک روز'.'|:count '.'روز', + 'd' => ':count روز', + 'hour' => ':count ساعت', + 'a_hour' => 'یک ساعت'.'|:count '.'ساعت', + 'h' => ':count ساعت', + 'minute' => ':count دقیقه', + 'a_minute' => 'یک دقیقه'.'|:count '.'دقیقه', + 'min' => ':count دقیقه', + 'second' => ':count ثانیه', + 's' => ':count ثانیه', + 'ago' => ':time پیش', + 'from_now' => ':time دیگر', + 'after' => ':time پس از', + 'before' => ':time پیش از', + 'diff_now' => 'اکنون', + 'diff_today' => 'امروز', + 'diff_today_regexp' => 'امروز(?:\\s+ساعت)?', + 'diff_yesterday' => 'دیروز', + 'diff_yesterday_regexp' => 'دیروز(?:\\s+ساعت)?', + 'diff_tomorrow' => 'فردا', + 'diff_tomorrow_regexp' => 'فردا(?:\\s+ساعت)?', + 'formats' => [ + 'LT' => 'OH:Om', + 'LTS' => 'OH:Om:Os', + 'L' => 'OD/OM/OY', + 'LL' => 'OD MMMM OY', + 'LLL' => 'OD MMMM OY OH:Om', + 'LLLL' => 'dddd, OD MMMM OY OH:Om', + ], + 'calendar' => [ + 'sameDay' => '[امروز ساعت] LT', + 'nextDay' => '[فردا ساعت] LT', + 'nextWeek' => 'dddd [ساعت] LT', + 'lastDay' => '[دیروز ساعت] LT', + 'lastWeek' => 'dddd [پیش] [ساعت] LT', + 'sameElse' => 'L', + ], + 'ordinal' => ':timeم', + 'meridiem' => ['قبل از ظهر', 'بعد از ظهر'], + 'months' => ['ژانویه', 'فوریه', 'مارس', 'آوریل', 'مه', 'ژوئن', 'ژوئیه', 'اوت', 'سپتامبر', 'اکتبر', 'نوامبر', 'دسامبر'], + 'months_short' => ['ژانویه', 'فوریه', 'مارس', 'آوریل', 'مه', 'ژوئن', 'ژوئیه', 'اوت', 'سپتامبر', 'اکتبر', 'نوامبر', 'دسامبر'], + 'weekdays' => ['یکشنبه', 'دوشنبه', 'سه‌شنبه', 'چهارشنبه', 'پنجشنبه', 'جمعه', 'شنبه'], + 'weekdays_short' => ['یکشنبه', 'دوشنبه', 'سه‌شنبه', 'چهارشنبه', 'پنجشنبه', 'جمعه', 'شنبه'], + 'weekdays_min' => ['ی', 'د', 'س', 'چ', 'پ', 'ج', 'ش'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'list' => ['، ', ' و '], + 'alt_numbers' => ['۰۰', '۰۱', '۰۲', '۰۳', '۰۴', '۰۵', '۰۶', '۰۷', '۰۸', '۰۹', '۱۰', '۱۱', '۱۲', '۱۳', '۱۴', '۱۵', '۱۶', '۱۷', '۱۸', '۱۹', '۲۰', '۲۱', '۲۲', '۲۳', '۲۴', '۲۵', '۲۶', '۲۷', '۲۸', '۲۹', '۳۰', '۳۱', '۳۲', '۳۳', '۳۴', '۳۵', '۳۶', '۳۷', '۳۸', '۳۹', '۴۰', '۴۱', '۴۲', '۴۳', '۴۴', '۴۵', '۴۶', '۴۷', '۴۸', '۴۹', '۵۰', '۵۱', '۵۲', '۵۳', '۵۴', '۵۵', '۵۶', '۵۷', '۵۸', '۵۹', '۶۰', '۶۱', '۶۲', '۶۳', '۶۴', '۶۵', '۶۶', '۶۷', '۶۸', '۶۹', '۷۰', '۷۱', '۷۲', '۷۳', '۷۴', '۷۵', '۷۶', '۷۷', '۷۸', '۷۹', '۸۰', '۸۱', '۸۲', '۸۳', '۸۴', '۸۵', '۸۶', '۸۷', '۸۸', '۸۹', '۹۰', '۹۱', '۹۲', '۹۳', '۹۴', '۹۵', '۹۶', '۹۷', '۹۸', '۹۹'], + 'months_short_standalone' => ['ژانویه', 'فوریه', 'مارس', 'آوریل', 'مه', 'ژوئن', 'ژوئیه', 'اوت', 'سپتامبر', 'اکتبر', 'نوامبر', 'دسامبر'], + 'weekend' => [5, 5], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fa_AF.php b/vendor/nesbot/carbon/src/Carbon/Lang/fa_AF.php new file mode 100644 index 0000000..6947100 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fa_AF.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/fa.php', [ + 'meridiem' => ['ق', 'ب'], + 'weekend' => [4, 5], + 'formats' => [ + 'L' => 'OY/OM/OD', + 'LL' => 'OD MMM OY', + 'LLL' => 'OD MMMM OY،‏ H:mm', + 'LLLL' => 'dddd OD MMMM OY،‏ H:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fa_IR.php b/vendor/nesbot/carbon/src/Carbon/Lang/fa_IR.php new file mode 100644 index 0000000..08d0182 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fa_IR.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__.'/fa.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ff.php b/vendor/nesbot/carbon/src/Carbon/Lang/ff.php new file mode 100644 index 0000000..9525c95 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ff.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM, YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'months' => ['siilo', 'colte', 'mbooy', 'seeɗto', 'duujal', 'korse', 'morso', 'juko', 'siilto', 'yarkomaa', 'jolal', 'bowte'], + 'months_short' => ['sii', 'col', 'mbo', 'see', 'duu', 'kor', 'mor', 'juk', 'slt', 'yar', 'jol', 'bow'], + 'weekdays' => ['dewo', 'aaɓnde', 'mawbaare', 'njeslaare', 'naasaande', 'mawnde', 'hoore-biir'], + 'weekdays_short' => ['dew', 'aaɓ', 'maw', 'nje', 'naa', 'mwd', 'hbi'], + 'weekdays_min' => ['dew', 'aaɓ', 'maw', 'nje', 'naa', 'mwd', 'hbi'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['subaka', 'kikiiɗe'], + + 'year' => ':count baret', // less reliable + 'y' => ':count baret', // less reliable + 'a_year' => ':count baret', // less reliable + + 'month' => ':count lewru', // less reliable + 'm' => ':count lewru', // less reliable + 'a_month' => ':count lewru', // less reliable + + 'week' => ':count naange', // less reliable + 'w' => ':count naange', // less reliable + 'a_week' => ':count naange', // less reliable + + 'day' => ':count dian', // less reliable + 'd' => ':count dian', // less reliable + 'a_day' => ':count dian', // less reliable + + 'hour' => ':count montor', // less reliable + 'h' => ':count montor', // less reliable + 'a_hour' => ':count montor', // less reliable + + 'minute' => ':count tokossuoum', // less reliable + 'min' => ':count tokossuoum', // less reliable + 'a_minute' => ':count tokossuoum', // less reliable + + 'second' => ':count tenen', // less reliable + 's' => ':count tenen', // less reliable + 'a_second' => ':count tenen', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ff_CM.php b/vendor/nesbot/carbon/src/Carbon/Lang/ff_CM.php new file mode 100644 index 0000000..b797ac0 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ff_CM.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__.'/ff.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ff_GN.php b/vendor/nesbot/carbon/src/Carbon/Lang/ff_GN.php new file mode 100644 index 0000000..b797ac0 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ff_GN.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__.'/ff.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ff_MR.php b/vendor/nesbot/carbon/src/Carbon/Lang/ff_MR.php new file mode 100644 index 0000000..2f4c29f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ff_MR.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ff.php', [ + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM, YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'dddd D MMMM YYYY h:mm a', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ff_SN.php b/vendor/nesbot/carbon/src/Carbon/Lang/ff_SN.php new file mode 100644 index 0000000..1e4c8b6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ff_SN.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Pular-Fulfulde.org Ibrahima Sarr admin@pulaar-fulfulde.org + */ +return require __DIR__.'/ff.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fi.php b/vendor/nesbot/carbon/src/Carbon/Lang/fi.php new file mode 100644 index 0000000..edf2d6d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fi.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Philippe Vaucher + * - Janne Warén + * - digitalfrost + * - Tsutomu Kuroda + * - Roope Salmi + * - tjku + * - Max Melentiev + * - Sami Haahtinen + * - Teemu Leisti + * - Artem Ignatyev + * - Akira Matsuda + * - Christopher Dell + * - Enrique Vidal + * - Simone Carletti + * - Robert Bjarnason + * - Aaron Patterson + * - Nicolás Hock Isaza + * - Tom Hughes + * - Sven Fuchs + * - Petri Kivikangas + * - Nizar Jouini + * - Marko Seppae + * - Tomi Mynttinen (Pikseli) + * - Petteri (powergrip) + */ +return [ + 'year' => ':count vuosi|:count vuotta', + 'y' => ':count v', + 'month' => ':count kuukausi|:count kuukautta', + 'm' => ':count kk', + 'week' => ':count viikko|:count viikkoa', + 'w' => ':count vk', + 'day' => ':count päivä|:count päivää', + 'd' => ':count pv', + 'hour' => ':count tunti|:count tuntia', + 'h' => ':count t', + 'minute' => ':count minuutti|:count minuuttia', + 'min' => ':count min', + 'second' => ':count sekunti|:count sekuntia', + 'a_second' => 'muutama sekunti|:count sekuntia', + 's' => ':count s', + 'ago' => ':time sitten', + 'from_now' => ':time päästä', + 'year_from_now' => ':count vuoden', + 'month_from_now' => ':count kuukauden', + 'week_from_now' => ':count viikon', + 'day_from_now' => ':count päivän', + 'hour_from_now' => ':count tunnin', + 'minute_from_now' => ':count minuutin', + 'second_from_now' => ':count sekunnin', + 'after' => ':time sen jälkeen', + 'before' => ':time ennen', + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' ja '], + 'diff_now' => 'nyt', + 'diff_yesterday' => 'eilen', + 'diff_tomorrow' => 'huomenna', + 'formats' => [ + 'LT' => 'HH.mm', + 'LTS' => 'HH.mm:ss', + 'L' => 'D.M.YYYY', + 'LL' => 'dddd D. MMMM[ta] YYYY', + 'll' => 'ddd D. MMM YYYY', + 'LLL' => 'D.MM. HH.mm', + 'LLLL' => 'D. MMMM[ta] YYYY HH.mm', + 'llll' => 'D. MMM YY HH.mm', + ], + 'weekdays' => ['sunnuntai', 'maanantai', 'tiistai', 'keskiviikko', 'torstai', 'perjantai', 'lauantai'], + 'weekdays_short' => ['su', 'ma', 'ti', 'ke', 'to', 'pe', 'la'], + 'weekdays_min' => ['su', 'ma', 'ti', 'ke', 'to', 'pe', 'la'], + 'months' => ['tammikuu', 'helmikuu', 'maaliskuu', 'huhtikuu', 'toukokuu', 'kesäkuu', 'heinäkuu', 'elokuu', 'syyskuu', 'lokakuu', 'marraskuu', 'joulukuu'], + 'months_short' => ['tammi', 'helmi', 'maalis', 'huhti', 'touko', 'kesä', 'heinä', 'elo', 'syys', 'loka', 'marras', 'joulu'], + 'meridiem' => ['aamupäivä', 'iltapäivä'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fi_FI.php b/vendor/nesbot/carbon/src/Carbon/Lang/fi_FI.php new file mode 100644 index 0000000..920f1ca --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fi_FI.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__.'/fi.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fil.php b/vendor/nesbot/carbon/src/Carbon/Lang/fil.php new file mode 100644 index 0000000..61114e3 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fil.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/fil_PH.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fil_PH.php b/vendor/nesbot/carbon/src/Carbon/Lang/fil_PH.php new file mode 100644 index 0000000..bcf1580 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fil_PH.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Rene Torres Rene Torres, Pablo Saratxaga rgtorre@rocketmail.com, pablo@mandrakesoft.com + * - Jaycee Mariano (alohajaycee) + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'MM/DD/YY', + ], + 'months' => ['Enero', 'Pebrero', 'Marso', 'Abril', 'Mayo', 'Hunyo', 'Hulyo', 'Agosto', 'Setyembre', 'Oktubre', 'Nobyembre', 'Disyembre'], + 'months_short' => ['Ene', 'Peb', 'Mar', 'Abr', 'May', 'Hun', 'Hul', 'Ago', 'Set', 'Okt', 'Nob', 'Dis'], + 'weekdays' => ['Linggo', 'Lunes', 'Martes', 'Miyerkoles', 'Huwebes', 'Biyernes', 'Sabado'], + 'weekdays_short' => ['Lin', 'Lun', 'Mar', 'Miy', 'Huw', 'Biy', 'Sab'], + 'weekdays_min' => ['Lin', 'Lun', 'Mar', 'Miy', 'Huw', 'Biy', 'Sab'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['N.U.', 'N.H.'], + + 'before' => ':time bago', + 'after' => ':time pagkatapos', + + 'year' => ':count taon', + 'y' => ':count taon', + 'a_year' => ':count taon', + + 'month' => ':count buwan', + 'm' => ':count buwan', + 'a_month' => ':count buwan', + + 'week' => ':count linggo', + 'w' => ':count linggo', + 'a_week' => ':count linggo', + + 'day' => ':count araw', + 'd' => ':count araw', + 'a_day' => ':count araw', + + 'hour' => ':count oras', + 'h' => ':count oras', + 'a_hour' => ':count oras', + + 'minute' => ':count minuto', + 'min' => ':count minuto', + 'a_minute' => ':count minuto', + + 'second' => ':count segundo', + 's' => ':count segundo', + 'a_second' => ':count segundo', + + 'ago' => ':time ang nakalipas', + 'from_now' => 'sa :time', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fo.php b/vendor/nesbot/carbon/src/Carbon/Lang/fo.php new file mode 100644 index 0000000..6a14a6f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fo.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Kristian Sakarisson + * - François B + * - JD Isaacks + * - Sverri Mohr Olsen + */ +return [ + 'year' => 'eitt ár|:count ár', + 'y' => ':count ár|:count ár', + 'month' => 'ein mánaði|:count mánaðir', + 'm' => ':count mánaður|:count mánaðir', + 'week' => ':count vika|:count vikur', + 'w' => ':count vika|:count vikur', + 'day' => 'ein dagur|:count dagar', + 'd' => ':count dag|:count dagar', + 'hour' => 'ein tími|:count tímar', + 'h' => ':count tími|:count tímar', + 'minute' => 'ein minutt|:count minuttir', + 'min' => ':count minutt|:count minuttir', + 'second' => 'fá sekund|:count sekundir', + 's' => ':count sekund|:count sekundir', + 'ago' => ':time síðani', + 'from_now' => 'um :time', + 'after' => ':time aftaná', + 'before' => ':time áðrenn', + 'diff_today' => 'Í', + 'diff_yesterday' => 'Í', + 'diff_yesterday_regexp' => 'Í(?:\\s+gjár)?(?:\\s+kl.)?', + 'diff_tomorrow' => 'Í', + 'diff_tomorrow_regexp' => 'Í(?:\\s+morgin)?(?:\\s+kl.)?', + 'diff_today_regexp' => 'Í(?:\\s+dag)?(?:\\s+kl.)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D. MMMM, YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Í dag kl.] LT', + 'nextDay' => '[Í morgin kl.] LT', + 'nextWeek' => 'dddd [kl.] LT', + 'lastDay' => '[Í gjár kl.] LT', + 'lastWeek' => '[síðstu] dddd [kl] LT', + 'sameElse' => 'L', + ], + 'ordinal' => ':number.', + 'months' => ['januar', 'februar', 'mars', 'apríl', 'mai', 'juni', 'juli', 'august', 'september', 'oktober', 'november', 'desember'], + 'months_short' => ['jan', 'feb', 'mar', 'apr', 'mai', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'des'], + 'weekdays' => ['sunnudagur', 'mánadagur', 'týsdagur', 'mikudagur', 'hósdagur', 'fríggjadagur', 'leygardagur'], + 'weekdays_short' => ['sun', 'mán', 'týs', 'mik', 'hós', 'frí', 'ley'], + 'weekdays_min' => ['su', 'má', 'tý', 'mi', 'hó', 'fr', 'le'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' og '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fo_DK.php b/vendor/nesbot/carbon/src/Carbon/Lang/fo_DK.php new file mode 100644 index 0000000..657f2c5 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fo_DK.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/fo.php', [ + 'formats' => [ + 'L' => 'DD.MM.yy', + 'LL' => 'DD.MM.YYYY', + 'LLL' => 'D. MMMM YYYY, HH:mm', + 'LLLL' => 'dddd, D. MMMM YYYY, HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fo_FO.php b/vendor/nesbot/carbon/src/Carbon/Lang/fo_FO.php new file mode 100644 index 0000000..6d73616 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fo_FO.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__.'/fo.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr.php new file mode 100644 index 0000000..f4c7247 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Dieter Sting + * - François B + * - Maxime VALY + * - JD Isaacks + * - Dieter Sting + * - François B + * - JD Isaacks + * - Sebastian Thierer + * - Fastfuel + * - Pete Scopes (pdscopes) + */ +return [ + 'year' => ':count an|:count ans', + 'a_year' => 'un an|:count ans', + 'y' => ':count an|:count ans', + 'month' => ':count mois|:count mois', + 'a_month' => 'un mois|:count mois', + 'm' => ':count mois', + 'week' => ':count semaine|:count semaines', + 'a_week' => 'une semaine|:count semaines', + 'w' => ':count sem.', + 'day' => ':count jour|:count jours', + 'a_day' => 'un jour|:count jours', + 'd' => ':count j', + 'hour' => ':count heure|:count heures', + 'a_hour' => 'une heure|:count heures', + 'h' => ':count h', + 'minute' => ':count minute|:count minutes', + 'a_minute' => 'une minute|:count minutes', + 'min' => ':count min', + 'second' => ':count seconde|:count secondes', + 'a_second' => 'quelques secondes|:count secondes', + 's' => ':count s', + 'millisecond' => ':count milliseconde|:count millisecondes', + 'a_millisecond' => 'une milliseconde|:count millisecondes', + 'ms' => ':countms', + 'microsecond' => ':count microseconde|:count microsecondes', + 'a_microsecond' => 'une microseconde|:count microsecondes', + 'µs' => ':countµs', + 'ago' => 'il y a :time', + 'from_now' => 'dans :time', + 'after' => ':time après', + 'before' => ':time avant', + 'diff_now' => "à l'instant", + 'diff_today' => "aujourd'hui", + 'diff_today_regexp' => "aujourd'hui(?:\s+à)?", + 'diff_yesterday' => 'hier', + 'diff_yesterday_regexp' => 'hier(?:\s+à)?', + 'diff_tomorrow' => 'demain', + 'diff_tomorrow_regexp' => 'demain(?:\s+à)?', + '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', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Aujourd’hui à] LT', + 'nextDay' => '[Demain à] LT', + 'nextWeek' => 'dddd [à] LT', + 'lastDay' => '[Hier à] LT', + 'lastWeek' => 'dddd [dernier à] LT', + 'sameElse' => 'L', + ], + 'months' => ['janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet', 'août', 'septembre', 'octobre', 'novembre', 'décembre'], + 'months_short' => ['janv.', 'févr.', 'mars', 'avr.', 'mai', 'juin', 'juil.', 'août', 'sept.', 'oct.', 'nov.', 'déc.'], + 'weekdays' => ['dimanche', 'lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi'], + 'weekdays_short' => ['dim.', 'lun.', 'mar.', 'mer.', 'jeu.', 'ven.', 'sam.'], + 'weekdays_min' => ['di', 'lu', 'ma', 'me', 'je', 've', 'sa'], + 'ordinal' => function ($number, $period) { + switch ($period) { + // In French, only the first has to be ordinal, other number remains cardinal + // @link https://fr.wikihow.com/%C3%A9crire-la-date-en-fran%C3%A7ais + case 'D': + return $number.($number === 1 ? 'er' : ''); + + default: + case 'M': + case 'Q': + case 'DDD': + case 'd': + return $number.($number === 1 ? 'er' : 'e'); + + // Words with feminine grammatical gender: semaine + case 'w': + case 'W': + return $number.($number === 1 ? 're' : 'e'); + } + }, + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' et '], + 'ordinal_words' => [ + 'of' => 'de', + 'first' => 'premier', + 'second' => 'deuxième', + 'third' => 'troisième', + 'fourth' => 'quatrième', + 'fifth' => 'cinquième', + 'last' => 'dernier', + ], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_BE.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_BE.php new file mode 100644 index 0000000..f6cafe8 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_BE.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/fr.php', [ + 'months_short' => ['jan', 'fév', 'mar', 'avr', 'mai', 'jun', 'jui', 'aoû', 'sep', 'oct', 'nov', 'déc'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_BF.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_BF.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_BF.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__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_BI.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_BI.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_BI.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__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_BJ.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_BJ.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_BJ.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__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_BL.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_BL.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_BL.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__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_CA.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_CA.php new file mode 100644 index 0000000..c9f6346 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_CA.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Dieter Sting + * - François B + * - Maxime VALY + * - JD Isaacks + */ +return array_replace_recursive(require __DIR__.'/fr.php', [ + 'formats' => [ + 'L' => 'YYYY-MM-DD', + ], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_CD.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_CD.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_CD.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__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_CF.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_CF.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_CF.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__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_CG.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_CG.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_CG.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__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_CH.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_CH.php new file mode 100644 index 0000000..8674c27 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_CH.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Dieter Sting + * - François B + * - Gaspard Bucher + * - Maxime VALY + * - JD Isaacks + */ +return array_replace_recursive(require __DIR__.'/fr.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_CI.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_CI.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_CI.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__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_CM.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_CM.php new file mode 100644 index 0000000..67d3787 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_CM.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/fr.php', [ + 'meridiem' => ['mat.', 'soir'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_DJ.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_DJ.php new file mode 100644 index 0000000..2f06086 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_DJ.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/fr.php', [ + 'first_day_of_week' => 6, + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'dddd D MMMM YYYY h:mm a', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_DZ.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_DZ.php new file mode 100644 index 0000000..ae8db5f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_DZ.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/fr.php', [ + 'first_day_of_week' => 6, + 'weekend' => [5, 6], + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'dddd D MMMM YYYY h:mm a', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_FR.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_FR.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_FR.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__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_GA.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_GA.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_GA.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__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_GF.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_GF.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_GF.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__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_GN.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_GN.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_GN.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__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_GP.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_GP.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_GP.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__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_GQ.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_GQ.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_GQ.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__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_HT.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_HT.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_HT.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__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_KM.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_KM.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_KM.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__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_LU.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_LU.php new file mode 100644 index 0000000..8e37d85 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_LU.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/fr.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months_short' => ['jan', 'fév', 'mar', 'avr', 'mai', 'jun', 'jui', 'aoû', 'sep', 'oct', 'nov', 'déc'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_MA.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_MA.php new file mode 100644 index 0000000..1bf034d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_MA.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/fr.php', [ + 'first_day_of_week' => 6, + 'weekend' => [5, 6], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_MC.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_MC.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_MC.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__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_MF.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_MF.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_MF.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__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_MG.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_MG.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_MG.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__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_ML.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_ML.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_ML.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__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_MQ.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_MQ.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_MQ.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__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_MR.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_MR.php new file mode 100644 index 0000000..37cf83f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_MR.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/fr.php', [ + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'dddd D MMMM YYYY h:mm a', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_MU.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_MU.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_MU.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__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_NC.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_NC.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_NC.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__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_NE.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_NE.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_NE.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__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_PF.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_PF.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_PF.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__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_PM.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_PM.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_PM.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__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_RE.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_RE.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_RE.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__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_RW.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_RW.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_RW.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__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_SC.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_SC.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_SC.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__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_SN.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_SN.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_SN.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__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_SY.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_SY.php new file mode 100644 index 0000000..ae8db5f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_SY.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/fr.php', [ + 'first_day_of_week' => 6, + 'weekend' => [5, 6], + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'dddd D MMMM YYYY h:mm a', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_TD.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_TD.php new file mode 100644 index 0000000..37cf83f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_TD.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/fr.php', [ + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'dddd D MMMM YYYY h:mm a', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_TG.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_TG.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_TG.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__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_TN.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_TN.php new file mode 100644 index 0000000..6905e7a --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_TN.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/fr.php', [ + 'weekend' => [5, 6], + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'dddd D MMMM YYYY h:mm a', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_VU.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_VU.php new file mode 100644 index 0000000..37cf83f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_VU.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/fr.php', [ + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'dddd D MMMM YYYY h:mm a', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_WF.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_WF.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_WF.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__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_YT.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_YT.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_YT.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__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fur.php b/vendor/nesbot/carbon/src/Carbon/Lang/fur.php new file mode 100644 index 0000000..36c2564 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fur.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/fur_IT.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fur_IT.php b/vendor/nesbot/carbon/src/Carbon/Lang/fur_IT.php new file mode 100644 index 0000000..0147a59 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fur_IT.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Pablo Saratxaga pablo@mandrakesoft.com + */ +return [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD. MM. YY', + 'LL' => 'DD di MMMM dal YYYY', + 'LLL' => 'DD di MMM HH:mm', + 'LLLL' => 'DD di MMMM dal YYYY HH:mm', + ], + 'months' => ['zenâr', 'fevrâr', 'març', 'avrîl', 'mai', 'jugn', 'lui', 'avost', 'setembar', 'otubar', 'novembar', 'dicembar'], + 'months_short' => ['zen', 'fev', 'mar', 'avr', 'mai', 'jug', 'lui', 'avo', 'set', 'otu', 'nov', 'dic'], + 'weekdays' => ['domenie', 'lunis', 'martars', 'miercus', 'joibe', 'vinars', 'sabide'], + 'weekdays_short' => ['dom', 'lun', 'mar', 'mie', 'joi', 'vin', 'sab'], + 'weekdays_min' => ['dom', 'lun', 'mar', 'mie', 'joi', 'vin', 'sab'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'year' => ':count an', + 'month' => ':count mês', + 'week' => ':count setemane', + 'day' => ':count zornade', + 'hour' => ':count ore', + 'minute' => ':count minût', + 'second' => ':count secont', +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fy.php b/vendor/nesbot/carbon/src/Carbon/Lang/fy.php new file mode 100644 index 0000000..c1b5439 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fy.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - Tim Fish + * - JD Isaacks + */ +return [ + 'year' => ':count jier|:count jierren', + 'a_year' => 'ien jier|:count jierren', + 'y' => ':count j', + 'month' => ':count moanne|:count moannen', + 'a_month' => 'ien moanne|:count moannen', + 'm' => ':count moa.', + 'week' => ':count wike|:count wiken', + 'a_week' => 'in wike|:count wiken', + 'a' => ':count w.', + 'day' => ':count dei|:count dagen', + 'a_day' => 'ien dei|:count dagen', + 'd' => ':count d.', + 'hour' => ':count oere|:count oeren', + 'a_hour' => 'ien oere|:count oeren', + 'h' => ':count o.', + 'minute' => ':count minút|:count minuten', + 'a_minute' => 'ien minút|:count minuten', + 'min' => ':count min.', + 'second' => ':count sekonde|:count sekonden', + 'a_second' => 'in pear sekonden|:count sekonden', + 's' => ':count s.', + 'ago' => ':time lyn', + 'from_now' => 'oer :time', + 'diff_yesterday' => 'juster', + 'diff_yesterday_regexp' => 'juster(?:\\s+om)?', + 'diff_today' => 'hjoed', + 'diff_today_regexp' => 'hjoed(?:\\s+om)?', + 'diff_tomorrow' => 'moarn', + 'diff_tomorrow_regexp' => 'moarn(?:\\s+om)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD-MM-YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[hjoed om] LT', + 'nextDay' => '[moarn om] LT', + 'nextWeek' => 'dddd [om] LT', + 'lastDay' => '[juster om] LT', + 'lastWeek' => '[ôfrûne] dddd [om] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + return $number.(($number === 1 || $number === 8 || $number >= 20) ? 'ste' : 'de'); + }, + 'months' => ['jannewaris', 'febrewaris', 'maart', 'april', 'maaie', 'juny', 'july', 'augustus', 'septimber', 'oktober', 'novimber', 'desimber'], + 'months_short' => ['jan', 'feb', 'mrt', 'apr', 'mai', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'des'], + 'mmm_suffix' => '.', + 'weekdays' => ['snein', 'moandei', 'tiisdei', 'woansdei', 'tongersdei', 'freed', 'sneon'], + 'weekdays_short' => ['si.', 'mo.', 'ti.', 'wo.', 'to.', 'fr.', 'so.'], + 'weekdays_min' => ['Si', 'Mo', 'Ti', 'Wo', 'To', 'Fr', 'So'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' en '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fy_DE.php b/vendor/nesbot/carbon/src/Carbon/Lang/fy_DE.php new file mode 100644 index 0000000..8559d5c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fy_DE.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - information from Kenneth Christiansen Kenneth Christiansen, Pablo Saratxaga kenneth@gnu.org, pablo@mandriva.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['Jaunuwoa', 'Februwoa', 'Moaz', 'Aprell', 'Mai', 'Juni', 'Juli', 'August', 'Septamba', 'Oktoba', 'Nowamba', 'Dezamba'], + 'months_short' => ['Jan', 'Feb', 'Moz', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Now', 'Dez'], + 'weekdays' => ['Sinndag', 'Mondag', 'Dingsdag', 'Meddwäakj', 'Donnadag', 'Friedag', 'Sinnowend'], + 'weekdays_short' => ['Sdg', 'Mdg', 'Dsg', 'Mwk', 'Ddg', 'Fdg', 'Swd'], + 'weekdays_min' => ['Sdg', 'Mdg', 'Dsg', 'Mwk', 'Ddg', 'Fdg', 'Swd'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fy_NL.php b/vendor/nesbot/carbon/src/Carbon/Lang/fy_NL.php new file mode 100644 index 0000000..01cc96c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fy_NL.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Free Software Foundation, Inc. bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/fy.php', [ + 'formats' => [ + 'L' => 'DD-MM-YY', + ], + 'months' => ['Jannewaris', 'Febrewaris', 'Maart', 'April', 'Maaie', 'Juny', 'July', 'Augustus', 'Septimber', 'Oktober', 'Novimber', 'Desimber'], + 'months_short' => ['Jan', 'Feb', 'Mrt', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Des'], + 'weekdays' => ['Snein', 'Moandei', 'Tiisdei', 'Woansdei', 'Tongersdei', 'Freed', 'Sneon'], + 'weekdays_short' => ['Sn', 'Mo', 'Ti', 'Wo', 'To', 'Fr', 'Sn'], + 'weekdays_min' => ['Sn', 'Mo', 'Ti', 'Wo', 'To', 'Fr', 'Sn'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ga.php b/vendor/nesbot/carbon/src/Carbon/Lang/ga.php new file mode 100644 index 0000000..9f07a26 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ga.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Thanks to André Silva : https://github.com/askpt + */ + +return [ + 'year' => ':count bliain', + 'a_year' => '{1}bliain|:count bliain', + 'y' => ':countb', + 'month' => ':count mí', + 'a_month' => '{1}mí|:count mí', + 'm' => ':countm', + 'week' => ':count sheachtain', + 'a_week' => '{1}sheachtain|:count sheachtain', + 'w' => ':countsh', + 'day' => ':count lá', + 'a_day' => '{1}lá|:count lá', + 'd' => ':countl', + 'hour' => ':count uair an chloig', + 'a_hour' => '{1}uair an chloig|:count uair an chloig', + 'h' => ':countu', + 'minute' => ':count nóiméad', + 'a_minute' => '{1}nóiméad|:count nóiméad', + 'min' => ':countn', + 'second' => ':count soicind', + 'a_second' => '{1}cúpla soicind|:count soicind', + 's' => ':countso', + 'ago' => ':time ó shin', + 'from_now' => 'i :time', + 'after' => ':time tar éis', + 'before' => ':time roimh', + 'diff_now' => 'anois', + 'diff_today' => 'Inniu', + 'diff_today_regexp' => 'Inniu(?:\\s+ag)?', + 'diff_yesterday' => 'inné', + 'diff_yesterday_regexp' => 'Inné(?:\\s+aig)?', + 'diff_tomorrow' => 'amárach', + 'diff_tomorrow_regexp' => 'Amárach(?:\\s+ag)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Inniu ag] LT', + 'nextDay' => '[Amárach ag] LT', + 'nextWeek' => 'dddd [ag] LT', + 'lastDay' => '[Inné aig] LT', + 'lastWeek' => 'dddd [seo caite] [ag] LT', + 'sameElse' => 'L', + ], + 'months' => ['Eanáir', 'Feabhra', 'Márta', 'Aibreán', 'Bealtaine', 'Méitheamh', 'Iúil', 'Lúnasa', 'Meán Fómhair', 'Deaireadh Fómhair', 'Samhain', 'Nollaig'], + 'months_short' => ['Eaná', 'Feab', 'Márt', 'Aibr', 'Beal', 'Méit', 'Iúil', 'Lúna', 'Meán', 'Deai', 'Samh', 'Noll'], + 'weekdays' => ['Dé Domhnaigh', 'Dé Luain', 'Dé Máirt', 'Dé Céadaoin', 'Déardaoin', 'Dé hAoine', 'Dé Satharn'], + 'weekdays_short' => ['Dom', 'Lua', 'Mái', 'Céa', 'Déa', 'hAo', 'Sat'], + 'weekdays_min' => ['Do', 'Lu', 'Má', 'Ce', 'Dé', 'hA', 'Sa'], + 'ordinal' => function ($number) { + return $number.($number === 1 ? 'd' : ($number % 10 === 2 ? 'na' : 'mh')); + }, + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' agus '], + 'meridiem' => ['r.n.', 'i.n.'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ga_IE.php b/vendor/nesbot/carbon/src/Carbon/Lang/ga_IE.php new file mode 100644 index 0000000..57b0c4f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ga_IE.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__.'/ga.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/gd.php b/vendor/nesbot/carbon/src/Carbon/Lang/gd.php new file mode 100644 index 0000000..63d064d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/gd.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - Jon Ashdown + */ +return [ + 'year' => ':count bliadhna', + 'a_year' => '{1}bliadhna|:count bliadhna', + 'y' => ':count b.', + 'month' => ':count mìosan', + 'a_month' => '{1}mìos|:count mìosan', + 'm' => ':count ms.', + 'week' => ':count seachdainean', + 'a_week' => '{1}seachdain|:count seachdainean', + 'w' => ':count s.', + 'day' => ':count latha', + 'a_day' => '{1}latha|:count latha', + 'd' => ':count l.', + 'hour' => ':count uairean', + 'a_hour' => '{1}uair|:count uairean', + 'h' => ':count u.', + 'minute' => ':count mionaidean', + 'a_minute' => '{1}mionaid|:count mionaidean', + 'min' => ':count md.', + 'second' => ':count diogan', + 'a_second' => '{1}beagan diogan|:count diogan', + 's' => ':count d.', + 'ago' => 'bho chionn :time', + 'from_now' => 'ann an :time', + 'diff_yesterday' => 'An-dè', + 'diff_yesterday_regexp' => 'An-dè(?:\\s+aig)?', + 'diff_today' => 'An-diugh', + 'diff_today_regexp' => 'An-diugh(?:\\s+aig)?', + 'diff_tomorrow' => 'A-màireach', + 'diff_tomorrow_regexp' => 'A-màireach(?:\\s+aig)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[An-diugh aig] LT', + 'nextDay' => '[A-màireach aig] LT', + 'nextWeek' => 'dddd [aig] LT', + 'lastDay' => '[An-dè aig] LT', + 'lastWeek' => 'dddd [seo chaidh] [aig] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + return $number.($number === 1 ? 'd' : ($number % 10 === 2 ? 'na' : 'mh')); + }, + 'months' => ['Am Faoilleach', 'An Gearran', 'Am Màrt', 'An Giblean', 'An Cèitean', 'An t-Ògmhios', 'An t-Iuchar', 'An Lùnastal', 'An t-Sultain', 'An Dàmhair', 'An t-Samhain', 'An Dùbhlachd'], + 'months_short' => ['Faoi', 'Gear', 'Màrt', 'Gibl', 'Cèit', 'Ògmh', 'Iuch', 'Lùn', 'Sult', 'Dàmh', 'Samh', 'Dùbh'], + 'weekdays' => ['Didòmhnaich', 'Diluain', 'Dimàirt', 'Diciadain', 'Diardaoin', 'Dihaoine', 'Disathairne'], + 'weekdays_short' => ['Did', 'Dil', 'Dim', 'Dic', 'Dia', 'Dih', 'Dis'], + 'weekdays_min' => ['Dò', 'Lu', 'Mà', 'Ci', 'Ar', 'Ha', 'Sa'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' agus '], + 'meridiem' => ['m', 'f'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/gd_GB.php b/vendor/nesbot/carbon/src/Carbon/Lang/gd_GB.php new file mode 100644 index 0000000..4fc26b3 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/gd_GB.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__.'/gd.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/gez.php b/vendor/nesbot/carbon/src/Carbon/Lang/gez.php new file mode 100644 index 0000000..b8a2f0e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/gez.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/gez_ER.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/gez_ER.php b/vendor/nesbot/carbon/src/Carbon/Lang/gez_ER.php new file mode 100644 index 0000000..f19d1df --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/gez_ER.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['ጠሐረ', 'ከተተ', 'መገበ', 'አኀዘ', 'ግንባት', 'ሠንየ', 'ሐመለ', 'ነሐሰ', 'ከረመ', 'ጠቀመ', 'ኀደረ', 'ኀሠሠ'], + 'months_short' => ['ጠሐረ', 'ከተተ', 'መገበ', 'አኀዘ', 'ግንባ', 'ሠንየ', 'ሐመለ', 'ነሐሰ', 'ከረመ', 'ጠቀመ', 'ኀደረ', 'ኀሠሠ'], + 'weekdays' => ['እኁድ', 'ሰኑይ', 'ሠሉስ', 'ራብዕ', 'ሐሙስ', 'ዓርበ', 'ቀዳሚት'], + 'weekdays_short' => ['እኁድ', 'ሰኑይ', 'ሠሉስ', 'ራብዕ', 'ሐሙስ', 'ዓርበ', 'ቀዳሚ'], + 'weekdays_min' => ['እኁድ', 'ሰኑይ', 'ሠሉስ', 'ራብዕ', 'ሐሙስ', 'ዓርበ', 'ቀዳሚ'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['ጽባሕ', 'ምሴት'], + + 'month' => ':count ወርሕ', // less reliable + 'm' => ':count ወርሕ', // less reliable + 'a_month' => ':count ወርሕ', // less reliable + + 'week' => ':count ሰብዑ', // less reliable + 'w' => ':count ሰብዑ', // less reliable + 'a_week' => ':count ሰብዑ', // less reliable + + 'hour' => ':count አንትሙ', // less reliable + 'h' => ':count አንትሙ', // less reliable + 'a_hour' => ':count አንትሙ', // less reliable + + 'minute' => ':count ንኡስ', // less reliable + 'min' => ':count ንኡስ', // less reliable + 'a_minute' => ':count ንኡስ', // less reliable + + 'year' => ':count ዓመት', + 'y' => ':count ዓመት', + 'a_year' => ':count ዓመት', + + 'day' => ':count ዕለት', + 'd' => ':count ዕለት', + 'a_day' => ':count ዕለት', + + 'second' => ':count ካልእ', + 's' => ':count ካልእ', + 'a_second' => ':count ካልእ', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/gez_ET.php b/vendor/nesbot/carbon/src/Carbon/Lang/gez_ET.php new file mode 100644 index 0000000..3933009 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/gez_ET.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['ጃንዩወሪ', 'ፌብሩወሪ', 'ማርች', 'ኤፕረል', 'ሜይ', 'ጁን', 'ጁላይ', 'ኦገስት', 'ሴፕቴምበር', 'ኦክተውበር', 'ኖቬምበር', 'ዲሴምበር'], + 'months_short' => ['ጃንዩ', 'ፌብሩ', 'ማርች', 'ኤፕረ', 'ሜይ ', 'ጁን ', 'ጁላይ', 'ኦገስ', 'ሴፕቴ', 'ኦክተ', 'ኖቬም', 'ዲሴም'], + 'weekdays' => ['እኁድ', 'ሰኑይ', 'ሠሉስ', 'ራብዕ', 'ሐሙስ', 'ዓርበ', 'ቀዳሚት'], + 'weekdays_short' => ['እኁድ', 'ሰኑይ', 'ሠሉስ', 'ራብዕ', 'ሐሙስ', 'ዓርበ', 'ቀዳሚ'], + 'weekdays_min' => ['እኁድ', 'ሰኑይ', 'ሠሉስ', 'ራብዕ', 'ሐሙስ', 'ዓርበ', 'ቀዳሚ'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['ጽባሕ', 'ምሴት'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/gl.php b/vendor/nesbot/carbon/src/Carbon/Lang/gl.php new file mode 100644 index 0000000..088b0f2 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/gl.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - Fidel Pita + * - JD Isaacks + * - Diego Vilariño + * - Sebastian Thierer + */ + +use Carbon\CarbonInterface; + +return [ + 'year' => ':count ano|:count anos', + 'a_year' => 'un ano|:count anos', + 'y' => ':count a.', + 'month' => ':count mes|:count meses', + 'a_month' => 'un mes|:count meses', + 'm' => ':count mes.', + 'week' => ':count semana|:count semanas', + 'a_week' => 'unha semana|:count semanas', + 'w' => ':count sem.', + 'day' => ':count día|:count días', + 'a_day' => 'un día|:count días', + 'd' => ':count d.', + 'hour' => ':count hora|:count horas', + 'a_hour' => 'unha hora|:count horas', + 'h' => ':count h.', + 'minute' => ':count minuto|:count minutos', + 'a_minute' => 'un minuto|:count minutos', + 'min' => ':count min.', + 'second' => ':count segundo|:count segundos', + 'a_second' => 'uns segundos|:count segundos', + 's' => ':count seg.', + 'ago' => 'hai :time', + 'from_now' => function ($time) { + if (str_starts_with($time, 'un')) { + return "n$time"; + } + + return "en $time"; + }, + 'diff_now' => 'agora', + 'diff_today' => 'hoxe', + 'diff_today_regexp' => 'hoxe(?:\\s+ás)?', + 'diff_yesterday' => 'onte', + 'diff_yesterday_regexp' => 'onte(?:\\s+á)?', + 'diff_tomorrow' => 'mañá', + 'diff_tomorrow_regexp' => 'mañá(?:\\s+ás)?', + 'after' => ':time despois', + 'before' => ':time antes', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D [de] MMMM [de] YYYY', + 'LLL' => 'D [de] MMMM [de] YYYY H:mm', + 'LLLL' => 'dddd, D [de] MMMM [de] YYYY H:mm', + ], + 'calendar' => [ + 'sameDay' => function (CarbonInterface $current) { + return '[hoxe '.($current->hour !== 1 ? 'ás' : 'á').'] LT'; + }, + 'nextDay' => function (CarbonInterface $current) { + return '[mañá '.($current->hour !== 1 ? 'ás' : 'á').'] LT'; + }, + 'nextWeek' => function (CarbonInterface $current) { + return 'dddd ['.($current->hour !== 1 ? 'ás' : 'á').'] LT'; + }, + 'lastDay' => function (CarbonInterface $current) { + return '[onte '.($current->hour !== 1 ? 'á' : 'a').'] LT'; + }, + 'lastWeek' => function (CarbonInterface $current) { + return '[o] dddd [pasado '.($current->hour !== 1 ? 'ás' : 'á').'] LT'; + }, + 'sameElse' => 'L', + ], + 'ordinal' => ':numberº', + 'months' => ['xaneiro', 'febreiro', 'marzo', 'abril', 'maio', 'xuño', 'xullo', 'agosto', 'setembro', 'outubro', 'novembro', 'decembro'], + 'months_short' => ['xan.', 'feb.', 'mar.', 'abr.', 'mai.', 'xuñ.', 'xul.', 'ago.', 'set.', 'out.', 'nov.', 'dec.'], + 'weekdays' => ['domingo', 'luns', 'martes', 'mércores', 'xoves', 'venres', 'sábado'], + 'weekdays_short' => ['dom.', 'lun.', 'mar.', 'mér.', 'xov.', 'ven.', 'sáb.'], + 'weekdays_min' => ['do', 'lu', 'ma', 'mé', 'xo', 've', 'sá'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' e '], + 'meridiem' => ['a.m.', 'p.m.'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/gl_ES.php b/vendor/nesbot/carbon/src/Carbon/Lang/gl_ES.php new file mode 100644 index 0000000..9d6c1d9 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/gl_ES.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__.'/gl.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/gom.php b/vendor/nesbot/carbon/src/Carbon/Lang/gom.php new file mode 100644 index 0000000..2a0584f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/gom.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/gom_Latn.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/gom_Latn.php b/vendor/nesbot/carbon/src/Carbon/Lang/gom_Latn.php new file mode 100644 index 0000000..612bb88 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/gom_Latn.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'year' => ':count voros|:count vorsam', + 'y' => ':countv', + 'month' => ':count mhoino|:count mhoine', + 'm' => ':countmh', + 'week' => ':count satolleacho|:count satolleache', + 'w' => ':countsa|:countsa', + 'day' => ':count dis', + 'd' => ':countd', + 'hour' => ':count hor|:count horam', + 'h' => ':counth', + 'minute' => ':count minute|:count mintam', + 'min' => ':countm', + 'second' => ':count second', + 's' => ':counts', + + 'diff_today' => 'Aiz', + 'diff_yesterday' => 'Kal', + 'diff_tomorrow' => 'Faleam', + 'formats' => [ + 'LT' => 'A h:mm [vazta]', + 'LTS' => 'A h:mm:ss [vazta]', + 'L' => 'DD-MM-YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY A h:mm [vazta]', + 'LLLL' => 'dddd, MMMM[achea] Do, YYYY, A h:mm [vazta]', + 'llll' => 'ddd, D MMM YYYY, A h:mm [vazta]', + ], + + 'calendar' => [ + 'sameDay' => '[Aiz] LT', + 'nextDay' => '[Faleam] LT', + 'nextWeek' => '[Ieta to] dddd[,] LT', + 'lastDay' => '[Kal] LT', + 'lastWeek' => '[Fatlo] dddd[,] LT', + 'sameElse' => 'L', + ], + + 'months' => ['Janer', 'Febrer', 'Mars', 'Abril', 'Mai', 'Jun', 'Julai', 'Agost', 'Setembr', 'Otubr', 'Novembr', 'Dezembr'], + 'months_short' => ['Jan.', 'Feb.', 'Mars', 'Abr.', 'Mai', 'Jun', 'Jul.', 'Ago.', 'Set.', 'Otu.', 'Nov.', 'Dez.'], + 'weekdays' => ['Aitar', 'Somar', 'Mongllar', 'Budvar', 'Brestar', 'Sukrar', 'Son\'var'], + 'weekdays_short' => ['Ait.', 'Som.', 'Mon.', 'Bud.', 'Bre.', 'Suk.', 'Son.'], + 'weekdays_min' => ['Ai', 'Sm', 'Mo', 'Bu', 'Br', 'Su', 'Sn'], + + 'ordinal' => function ($number, $period) { + return $number.($period === 'D' ? 'er' : ''); + }, + + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'rati'; + } + if ($hour < 12) { + return 'sokalli'; + } + if ($hour < 16) { + return 'donparam'; + } + if ($hour < 20) { + return 'sanje'; + } + + return 'rati'; + }, + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' ani '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/gsw.php b/vendor/nesbot/carbon/src/Carbon/Lang/gsw.php new file mode 100644 index 0000000..c5c850e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/gsw.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Christopher Dell + * - Akira Matsuda + * - Enrique Vidal + * - Simone Carletti + * - Henning Kiel + * - Aaron Patterson + * - Florian Hanke + */ +return [ + 'year' => ':count Johr', + 'month' => ':count Monet', + 'week' => ':count Woche', + 'day' => ':count Tag', + 'hour' => ':count Schtund', + 'minute' => ':count Minute', + 'second' => ':count Sekunde', + 'weekdays' => ['Sunntig', 'Mäntig', 'Ziischtig', 'Mittwuch', 'Dunschtig', 'Friitig', 'Samschtig'], + 'weekdays_short' => ['Su', 'Mä', 'Zi', 'Mi', 'Du', 'Fr', 'Sa'], + 'weekdays_min' => ['Su', 'Mä', 'Zi', 'Mi', 'Du', 'Fr', 'Sa'], + 'months' => ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'Auguscht', 'September', 'Oktober', 'November', 'Dezember'], + 'months_short' => ['Jan', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'], + 'meridiem' => ['am Vormittag', 'am Namittag'], + 'ordinal' => ':number.', + 'list' => [', ', ' und '], + 'diff_now' => 'now', + 'diff_yesterday' => 'geschter', + 'diff_tomorrow' => 'moorn', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'Do MMMM YYYY', + 'LLL' => 'Do MMMM, HH:mm [Uhr]', + 'LLLL' => 'dddd, Do MMMM YYYY, HH:mm [Uhr]', + ], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/gsw_CH.php b/vendor/nesbot/carbon/src/Carbon/Lang/gsw_CH.php new file mode 100644 index 0000000..594eb25 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/gsw_CH.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__.'/gsw.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/gsw_FR.php b/vendor/nesbot/carbon/src/Carbon/Lang/gsw_FR.php new file mode 100644 index 0000000..3581dcf --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/gsw_FR.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/gsw.php', [ + 'meridiem' => ['vorm.', 'nam.'], + 'months' => ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'Auguscht', 'Septämber', 'Oktoober', 'Novämber', 'Dezämber'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LLL' => 'Do MMMM YYYY HH:mm', + 'LLLL' => 'dddd, Do MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/gsw_LI.php b/vendor/nesbot/carbon/src/Carbon/Lang/gsw_LI.php new file mode 100644 index 0000000..3581dcf --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/gsw_LI.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/gsw.php', [ + 'meridiem' => ['vorm.', 'nam.'], + 'months' => ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'Auguscht', 'Septämber', 'Oktoober', 'Novämber', 'Dezämber'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LLL' => 'Do MMMM YYYY HH:mm', + 'LLLL' => 'dddd, Do MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/gu.php b/vendor/nesbot/carbon/src/Carbon/Lang/gu.php new file mode 100644 index 0000000..8bc4311 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/gu.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - Kaushik Thanki + * - Josh Soref + */ +return [ + 'year' => 'એક વર્ષ|:count વર્ષ', + 'y' => ':countવર્ષ|:countવર્ષો', + 'month' => 'એક મહિનો|:count મહિના', + 'm' => ':countમહિનો|:countમહિના', + 'week' => ':count અઠવાડિયું|:count અઠવાડિયા', + 'w' => ':countઅઠ.|:countઅઠ.', + 'day' => 'એક દિવસ|:count દિવસ', + 'd' => ':countદિ.|:countદિ.', + 'hour' => 'એક કલાક|:count કલાક', + 'h' => ':countક.|:countક.', + 'minute' => 'એક મિનિટ|:count મિનિટ', + 'min' => ':countમિ.|:countમિ.', + 'second' => 'અમુક પળો|:count સેકંડ', + 's' => ':countસે.|:countસે.', + 'ago' => ':time પેહલા', + 'from_now' => ':time મા', + 'after' => ':time પછી', + 'before' => ':time પહેલા', + 'diff_now' => 'હમણાં', + 'diff_today' => 'આજ', + 'diff_yesterday' => 'ગઇકાલે', + 'diff_tomorrow' => 'કાલે', + 'formats' => [ + 'LT' => 'A h:mm વાગ્યે', + 'LTS' => 'A h:mm:ss વાગ્યે', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY, A h:mm વાગ્યે', + 'LLLL' => 'dddd, D MMMM YYYY, A h:mm વાગ્યે', + ], + 'calendar' => [ + 'sameDay' => '[આજ] LT', + 'nextDay' => '[કાલે] LT', + 'nextWeek' => 'dddd, LT', + 'lastDay' => '[ગઇકાલે] LT', + 'lastWeek' => '[પાછલા] dddd, LT', + 'sameElse' => 'L', + ], + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'રાત'; + } + if ($hour < 10) { + return 'સવાર'; + } + if ($hour < 17) { + return 'બપોર'; + } + if ($hour < 20) { + return 'સાંજ'; + } + + return 'રાત'; + }, + 'months' => ['જાન્યુઆરી', 'ફેબ્રુઆરી', 'માર્ચ', 'એપ્રિલ', 'મે', 'જૂન', 'જુલાઈ', 'ઑગસ્ટ', 'સપ્ટેમ્બર', 'ઑક્ટ્બર', 'નવેમ્બર', 'ડિસેમ્બર'], + 'months_short' => ['જાન્યુ.', 'ફેબ્રુ.', 'માર્ચ', 'એપ્રિ.', 'મે', 'જૂન', 'જુલા.', 'ઑગ.', 'સપ્ટે.', 'ઑક્ટ્.', 'નવે.', 'ડિસે.'], + 'weekdays' => ['રવિવાર', 'સોમવાર', 'મંગળવાર', 'બુધ્વાર', 'ગુરુવાર', 'શુક્રવાર', 'શનિવાર'], + 'weekdays_short' => ['રવિ', 'સોમ', 'મંગળ', 'બુધ્', 'ગુરુ', 'શુક્ર', 'શનિ'], + 'weekdays_min' => ['ર', 'સો', 'મં', 'બુ', 'ગુ', 'શુ', 'શ'], + 'list' => [', ', ' અને '], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, + 'weekend' => [0, 0], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/gu_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/gu_IN.php new file mode 100644 index 0000000..02654b1 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/gu_IN.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__.'/gu.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/guz.php b/vendor/nesbot/carbon/src/Carbon/Lang/guz.php new file mode 100644 index 0000000..6230165 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/guz.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_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['Ma', 'Mo'], + 'weekdays' => ['Chumapiri', 'Chumatato', 'Chumaine', 'Chumatano', 'Aramisi', 'Ichuma', 'Esabato'], + 'weekdays_short' => ['Cpr', 'Ctt', 'Cmn', 'Cmt', 'Ars', 'Icm', 'Est'], + 'weekdays_min' => ['Cpr', 'Ctt', 'Cmn', 'Cmt', 'Ars', 'Icm', 'Est'], + 'months' => ['Chanuari', 'Feburari', 'Machi', 'Apiriri', 'Mei', 'Juni', 'Chulai', 'Agosti', 'Septemba', 'Okitoba', 'Nobemba', 'Disemba'], + 'months_short' => ['Can', 'Feb', 'Mac', 'Apr', 'Mei', 'Jun', 'Cul', 'Agt', 'Sep', 'Okt', 'Nob', 'Dis'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + + 'month' => ':count omotunyi', // less reliable + 'm' => ':count omotunyi', // less reliable + 'a_month' => ':count omotunyi', // less reliable + + 'week' => ':count isano naibere', // less reliable + 'w' => ':count isano naibere', // less reliable + 'a_week' => ':count isano naibere', // less reliable + + 'second' => ':count ibere', // less reliable + 's' => ':count ibere', // less reliable + 'a_second' => ':count ibere', // less reliable + + 'year' => ':count omwaka', + 'y' => ':count omwaka', + 'a_year' => ':count omwaka', + + 'day' => ':count rituko', + 'd' => ':count rituko', + 'a_day' => ':count rituko', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/gv.php b/vendor/nesbot/carbon/src/Carbon/Lang/gv.php new file mode 100644 index 0000000..7c52b94 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/gv.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/gv_GB.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/gv_GB.php b/vendor/nesbot/carbon/src/Carbon/Lang/gv_GB.php new file mode 100644 index 0000000..6b1168f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/gv_GB.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Alastair McKinstry bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['Jerrey-geuree', 'Toshiaght-arree', 'Mayrnt', 'Averil', 'Boaldyn', 'Mean-souree', 'Jerrey-souree', 'Luanistyn', 'Mean-fouyir', 'Jerrey-fouyir', 'Mee Houney', 'Mee ny Nollick'], + 'months_short' => ['J-guer', 'T-arree', 'Mayrnt', 'Avrril', 'Boaldyn', 'M-souree', 'J-souree', 'Luanistyn', 'M-fouyir', 'J-fouyir', 'M.Houney', 'M.Nollick'], + 'weekdays' => ['Jedoonee', 'Jelhein', 'Jemayrt', 'Jercean', 'Jerdein', 'Jeheiney', 'Jesarn'], + 'weekdays_short' => ['Jed', 'Jel', 'Jem', 'Jerc', 'Jerd', 'Jeh', 'Jes'], + 'weekdays_min' => ['Jed', 'Jel', 'Jem', 'Jerc', 'Jerd', 'Jeh', 'Jes'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'year' => ':count blein', + 'y' => ':count blein', + 'a_year' => ':count blein', + + 'month' => ':count mee', + 'm' => ':count mee', + 'a_month' => ':count mee', + + 'week' => ':count shiaghtin', + 'w' => ':count shiaghtin', + 'a_week' => ':count shiaghtin', + + 'day' => ':count laa', + 'd' => ':count laa', + 'a_day' => ':count laa', + + 'hour' => ':count oor', + 'h' => ':count oor', + 'a_hour' => ':count oor', + + 'minute' => ':count feer veg', + 'min' => ':count feer veg', + 'a_minute' => ':count feer veg', + + 'second' => ':count derrey', + 's' => ':count derrey', + 'a_second' => ':count derrey', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ha.php b/vendor/nesbot/carbon/src/Carbon/Lang/ha.php new file mode 100644 index 0000000..cd8e34d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ha.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - pablo@mandriva.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM, YYYY', + 'LLL' => 'D MMMM, YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM, YYYY HH:mm', + ], + 'months' => ['Janairu', 'Faburairu', 'Maris', 'Afirilu', 'Mayu', 'Yuni', 'Yuli', 'Agusta', 'Satumba', 'Oktoba', 'Nuwamba', 'Disamba'], + 'months_short' => ['Jan', 'Fab', 'Mar', 'Afi', 'May', 'Yun', 'Yul', 'Agu', 'Sat', 'Okt', 'Nuw', 'Dis'], + 'weekdays' => ['Lahadi', 'Litini', 'Talata', 'Laraba', 'Alhamis', 'Jumaʼa', 'Asabar'], + 'weekdays_short' => ['Lah', 'Lit', 'Tal', 'Lar', 'Alh', 'Jum', 'Asa'], + 'weekdays_min' => ['Lh', 'Li', 'Ta', 'Lr', 'Al', 'Ju', 'As'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + + 'year' => 'shekara :count', + 'y' => 'shekara :count', + 'a_year' => 'shekara :count', + + 'month' => ':count wátàa', + 'm' => ':count wátàa', + 'a_month' => ':count wátàa', + + 'week' => ':count mako', + 'w' => ':count mako', + 'a_week' => ':count mako', + + 'day' => ':count rana', + 'd' => ':count rana', + 'a_day' => ':count rana', + + 'hour' => ':count áwàa', + 'h' => ':count áwàa', + 'a_hour' => ':count áwàa', + + 'minute' => 'minti :count', + 'min' => 'minti :count', + 'a_minute' => 'minti :count', + + 'second' => ':count ná bíyú', + 's' => ':count ná bíyú', + 'a_second' => ':count ná bíyú', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ha_GH.php b/vendor/nesbot/carbon/src/Carbon/Lang/ha_GH.php new file mode 100644 index 0000000..f9f99a7 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ha_GH.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__.'/ha.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ha_NE.php b/vendor/nesbot/carbon/src/Carbon/Lang/ha_NE.php new file mode 100644 index 0000000..f9f99a7 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ha_NE.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__.'/ha.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ha_NG.php b/vendor/nesbot/carbon/src/Carbon/Lang/ha_NG.php new file mode 100644 index 0000000..f9f99a7 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ha_NG.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__.'/ha.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/hak.php b/vendor/nesbot/carbon/src/Carbon/Lang/hak.php new file mode 100644 index 0000000..6c3260e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/hak.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/hak_TW.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/hak_TW.php b/vendor/nesbot/carbon/src/Carbon/Lang/hak_TW.php new file mode 100644 index 0000000..fe23986 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/hak_TW.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'YYYY年MM月DD日', + ], + 'months' => ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'], + 'months_short' => [' 1月', ' 2月', ' 3月', ' 4月', ' 5月', ' 6月', ' 7月', ' 8月', ' 9月', '10月', '11月', '12月'], + 'weekdays' => ['禮拜日', '禮拜一', '禮拜二', '禮拜三', '禮拜四', '禮拜五', '禮拜六'], + 'weekdays_short' => ['日', '一', '二', '三', '四', '五', '六'], + 'weekdays_min' => ['日', '一', '二', '三', '四', '五', '六'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['上晝', '下晝'], + + 'year' => ':count ngien11', + 'y' => ':count ngien11', + 'a_year' => ':count ngien11', + + 'month' => ':count ngie̍t', + 'm' => ':count ngie̍t', + 'a_month' => ':count ngie̍t', + + 'week' => ':count lî-pai', + 'w' => ':count lî-pai', + 'a_week' => ':count lî-pai', + + 'day' => ':count ngit', + 'd' => ':count ngit', + 'a_day' => ':count ngit', + + 'hour' => ':count sṳ̀', + 'h' => ':count sṳ̀', + 'a_hour' => ':count sṳ̀', + + 'minute' => ':count fûn', + 'min' => ':count fûn', + 'a_minute' => ':count fûn', + + 'second' => ':count miéu', + 's' => ':count miéu', + 'a_second' => ':count miéu', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/haw.php b/vendor/nesbot/carbon/src/Carbon/Lang/haw.php new file mode 100644 index 0000000..cdd3686 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/haw.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'months' => ['Ianuali', 'Pepeluali', 'Malaki', 'ʻApelila', 'Mei', 'Iune', 'Iulai', 'ʻAukake', 'Kepakemapa', 'ʻOkakopa', 'Nowemapa', 'Kekemapa'], + 'months_short' => ['Ian.', 'Pep.', 'Mal.', 'ʻAp.', 'Mei', 'Iun.', 'Iul.', 'ʻAu.', 'Kep.', 'ʻOk.', 'Now.', 'Kek.'], + 'weekdays' => ['Lāpule', 'Poʻakahi', 'Poʻalua', 'Poʻakolu', 'Poʻahā', 'Poʻalima', 'Poʻaono'], + 'weekdays_short' => ['LP', 'P1', 'P2', 'P3', 'P4', 'P5', 'P6'], + 'weekdays_min' => ['S', 'M', 'T', 'W', 'T', 'F', 'S'], + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'dddd, D MMMM YYYY h:mm a', + ], + + 'year' => ':count makahiki', + 'y' => ':count makahiki', + 'a_year' => ':count makahiki', + + 'month' => ':count mahina', + 'm' => ':count mahina', + 'a_month' => ':count mahina', + + 'week' => ':count pule', + 'w' => ':count pule', + 'a_week' => ':count pule', + + 'day' => ':count lā', + 'd' => ':count lā', + 'a_day' => ':count lā', + + 'hour' => ':count hola', + 'h' => ':count hola', + 'a_hour' => ':count hola', + + 'minute' => ':count minuke', + 'min' => ':count minuke', + 'a_minute' => ':count minuke', + + 'second' => ':count lua', + 's' => ':count lua', + 'a_second' => ':count lua', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/he.php b/vendor/nesbot/carbon/src/Carbon/Lang/he.php new file mode 100644 index 0000000..c3fb3e9 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/he.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Daniel Cohen Gindi + * - JD Isaacks + * - Itai Nathaniel + * - GabMic + * - Yaakov Dahan (yakidahan) + */ +return [ + 'year' => 'שנה|{2}שנתיים|:count שנים', + 'y' => 'שנה|:count שנ׳', + 'month' => 'חודש|{2}חודשיים|:count חודשים', + 'm' => 'חודש|:count חו׳', + 'week' => 'שבוע|{2}שבועיים|:count שבועות', + 'w' => 'שבוע|:count שב׳', + 'day' => 'יום|{2}יומיים|:count ימים', + 'd' => 'יום|:count ימ׳', + 'hour' => 'שעה|{2}שעתיים|:count שעות', + 'h' => 'שעה|:count שע׳', + 'minute' => 'דקה|{2}שתי דקות|:count דקות', + 'min' => 'דקה|:count דק׳', + 'second' => 'שנייה|:count שניות', + 'a_second' => 'כמה שניות|:count שניות', + 's' => 'שניה|:count שנ׳', + 'ago' => 'לפני :time', + 'from_now' => 'בעוד :time מעכשיו', + 'after' => 'אחרי :time', + 'before' => 'לפני :time', + 'diff_now' => 'עכשיו', + 'diff_today' => 'היום', + 'diff_today_regexp' => 'היום(?:\\s+ב־)?', + 'diff_yesterday' => 'אתמול', + 'diff_yesterday_regexp' => 'אתמול(?:\\s+ב־)?', + 'diff_tomorrow' => 'מחר', + 'diff_tomorrow_regexp' => 'מחר(?:\\s+ב־)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D [ב]MMMM YYYY', + 'LLL' => 'D [ב]MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D [ב]MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[היום ב־]LT', + 'nextDay' => '[מחר ב־]LT', + 'nextWeek' => 'dddd [בשעה] LT', + 'lastDay' => '[אתמול ב־]LT', + 'lastWeek' => '[ביום] dddd [האחרון בשעה] LT', + 'sameElse' => 'L', + ], + 'meridiem' => function ($hour, $minute, $isLower) { + if ($hour < 5) { + return 'לפנות בוקר'; + } + if ($hour < 10) { + return 'בבוקר'; + } + if ($hour < 12) { + return $isLower ? 'לפנה"צ' : 'לפני הצהריים'; + } + if ($hour < 18) { + return $isLower ? 'אחה"צ' : 'אחרי הצהריים'; + } + + return 'בערב'; + }, + 'months' => ['ינואר', 'פברואר', 'מרץ', 'אפריל', 'מאי', 'יוני', 'יולי', 'אוגוסט', 'ספטמבר', 'אוקטובר', 'נובמבר', 'דצמבר'], + 'months_short' => ['ינו׳', 'פבר׳', 'מרץ', 'אפר׳', 'מאי', 'יוני', 'יולי', 'אוג׳', 'ספט׳', 'אוק׳', 'נוב׳', 'דצמ׳'], + 'weekdays' => ['ראשון', 'שני', 'שלישי', 'רביעי', 'חמישי', 'שישי', 'שבת'], + 'weekdays_short' => ['א׳', 'ב׳', 'ג׳', 'ד׳', 'ה׳', 'ו׳', 'ש׳'], + 'weekdays_min' => ['א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ש'], + 'list' => [', ', ' ו -'], + 'weekend' => [5, 6], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/he_IL.php b/vendor/nesbot/carbon/src/Carbon/Lang/he_IL.php new file mode 100644 index 0000000..14fab3e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/he_IL.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__.'/he.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/hi.php b/vendor/nesbot/carbon/src/Carbon/Lang/hi.php new file mode 100644 index 0000000..70c57a2 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/hi.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - abhimanyu003 + * - Josh Soref + * - JD Isaacks + */ +return [ + 'year' => 'एक वर्ष|:count वर्ष', + 'y' => '1 वर्ष|:count वर्षों', + 'month' => 'एक महीने|:count महीने', + 'm' => '1 माह|:count महीने', + 'week' => '1 सप्ताह|:count सप्ताह', + 'w' => '1 सप्ताह|:count सप्ताह', + 'day' => 'एक दिन|:count दिन', + 'd' => '1 दिन|:count दिनों', + 'hour' => 'एक घंटा|:count घंटे', + 'h' => '1 घंटा|:count घंटे', + 'minute' => 'एक मिनट|:count मिनट', + 'min' => '1 मिनट|:count मिनटों', + 'second' => 'कुछ ही क्षण|:count सेकंड', + 's' => '1 सेकंड|:count सेकंड', + 'ago' => ':time पहले', + 'from_now' => ':time में', + 'after' => ':time के बाद', + 'before' => ':time के पहले', + 'diff_now' => 'अब', + 'diff_today' => 'आज', + 'diff_yesterday' => 'कल', + 'diff_tomorrow' => 'कल', + 'formats' => [ + 'LT' => 'A h:mm बजे', + 'LTS' => 'A h:mm:ss बजे', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY, A h:mm बजे', + 'LLLL' => 'dddd, D MMMM YYYY, A h:mm बजे', + ], + 'calendar' => [ + 'sameDay' => '[आज] LT', + 'nextDay' => '[कल] LT', + 'nextWeek' => 'dddd, LT', + 'lastDay' => '[कल] LT', + 'lastWeek' => '[पिछले] dddd, LT', + 'sameElse' => 'L', + ], + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'रात'; + } + if ($hour < 10) { + return 'सुबह'; + } + if ($hour < 17) { + return 'दोपहर'; + } + if ($hour < 20) { + return 'शाम'; + } + + return 'रात'; + }, + 'months' => ['जनवरी', 'फ़रवरी', 'मार्च', 'अप्रैल', 'मई', 'जून', 'जुलाई', 'अगस्त', 'सितम्बर', 'अक्टूबर', 'नवम्बर', 'दिसम्बर'], + 'months_short' => ['जन.', 'फ़र.', 'मार्च', 'अप्रै.', 'मई', 'जून', 'जुल.', 'अग.', 'सित.', 'अक्टू.', 'नव.', 'दिस.'], + 'weekdays' => ['रविवार', 'सोमवार', 'मंगलवार', 'बुधवार', 'गुरूवार', 'शुक्रवार', 'शनिवार'], + 'weekdays_short' => ['रवि', 'सोम', 'मंगल', 'बुध', 'गुरू', 'शुक्र', 'शनि'], + 'weekdays_min' => ['र', 'सो', 'मं', 'बु', 'गु', 'शु', 'श'], + 'list' => [', ', ' और '], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, + 'weekend' => [0, 0], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/hi_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/hi_IN.php new file mode 100644 index 0000000..749dd97 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/hi_IN.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__.'/hi.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/hif.php b/vendor/nesbot/carbon/src/Carbon/Lang/hif.php new file mode 100644 index 0000000..65791dd --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/hif.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/hif_FJ.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/hif_FJ.php b/vendor/nesbot/carbon/src/Carbon/Lang/hif_FJ.php new file mode 100644 index 0000000..30ad5e7 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/hif_FJ.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Samsung Electronics Co., Ltd. akhilesh.k@samsung.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'dddd DD MMM YYYY', + ], + 'months' => ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], + 'months_short' => ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], + 'weekdays' => ['Ravivar', 'Somvar', 'Mangalvar', 'Budhvar', 'Guruvar', 'Shukravar', 'Shanivar'], + 'weekdays_short' => ['Ravi', 'Som', 'Mangal', 'Budh', 'Guru', 'Shukra', 'Shani'], + 'weekdays_min' => ['Ravi', 'Som', 'Mangal', 'Budh', 'Guru', 'Shukra', 'Shani'], + 'meridiem' => ['Purvahan', 'Aparaahna'], + + 'hour' => ':count minit', // less reliable + 'h' => ':count minit', // less reliable + 'a_hour' => ':count minit', // less reliable + + 'year' => ':count saal', + 'y' => ':count saal', + 'a_year' => ':count saal', + + 'month' => ':count Mahina', + 'm' => ':count Mahina', + 'a_month' => ':count Mahina', + + 'week' => ':count Hafta', + 'w' => ':count Hafta', + 'a_week' => ':count Hafta', + + 'day' => ':count Din', + 'd' => ':count Din', + 'a_day' => ':count Din', + + 'minute' => ':count Minit', + 'min' => ':count Minit', + 'a_minute' => ':count Minit', + + 'second' => ':count Second', + 's' => ':count Second', + 'a_second' => ':count Second', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/hne.php b/vendor/nesbot/carbon/src/Carbon/Lang/hne.php new file mode 100644 index 0000000..4bcb05c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/hne.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/hne_IN.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/hne_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/hne_IN.php new file mode 100644 index 0000000..a5ca758 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/hne_IN.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Red Hat, Pune bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['जनवरी', 'फरवरी', 'मार्च', 'अपरेल', 'मई', 'जून', 'जुलाई', 'अगस्त', 'सितमबर', 'अकटूबर', 'नवमबर', 'दिसमबर'], + 'months_short' => ['जन', 'फर', 'मार्च', 'अप', 'मई', 'जून', 'जुला', 'अग', 'सित', 'अकटू', 'नव', 'दिस'], + 'weekdays' => ['इतवार', 'सोमवार', 'मंगलवार', 'बुधवार', 'बिरसपत', 'सुकरवार', 'सनिवार'], + 'weekdays_short' => ['इत', 'सोम', 'मंग', 'बुध', 'बिर', 'सुक', 'सनि'], + 'weekdays_min' => ['इत', 'सोम', 'मंग', 'बुध', 'बिर', 'सुक', 'सनि'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['बिहिनियाँ', 'मंझनियाँ'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/hr.php b/vendor/nesbot/carbon/src/Carbon/Lang/hr.php new file mode 100644 index 0000000..cfd85fd --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/hr.php @@ -0,0 +1,111 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - François B + * - Tim Fish + * - shaishavgandhi05 + * - Serhan Apaydın + * - JD Isaacks + * - tomhorvat + * - Josh Soref + * - François B + * - shaishavgandhi05 + * - Serhan Apaydın + * - JD Isaacks + * - tomhorvat + * - Stjepan Majdak + * - Vanja Retkovac (vr00) + */ + +use Carbon\CarbonInterface; + +return [ + 'year' => ':count godinu|:count godine|:count godina', + 'y' => ':count god.|:count god.|:count god.', + 'month' => ':count mjesec|:count mjeseca|:count mjeseci', + 'm' => ':count mj.|:count mj.|:count mj.', + 'week' => ':count tjedan|:count tjedna|:count tjedana', + 'w' => ':count tj.|:count tj.|:count tj.', + 'day' => ':count dan|:count dana|:count dana', + 'd' => ':count d.|:count d.|:count d.', + 'hour' => ':count sat|:count sata|:count sati', + 'h' => ':count sat|:count sata|:count sati', + 'minute' => ':count minutu|:count minute|:count minuta', + 'min' => ':count min.|:count min.|:count min.', + 'second' => ':count sekundu|:count sekunde|:count sekundi', + 'a_second' => 'nekoliko sekundi|:count sekunde|:count sekundi', + 's' => ':count sek.|:count sek.|:count sek.', + 'ago' => 'prije :time', + 'from_now' => 'za :time', + 'after' => ':time poslije', + 'before' => ':time prije', + 'diff_now' => 'sad', + 'diff_today' => 'danas', + 'diff_today_regexp' => 'danas(?:\\s+u)?', + 'diff_yesterday' => 'jučer', + 'diff_yesterday_regexp' => 'jučer(?:\\s+u)?', + 'diff_tomorrow' => 'sutra', + 'diff_tomorrow_regexp' => 'sutra(?:\\s+u)?', + 'diff_before_yesterday' => 'prekjučer', + 'diff_after_tomorrow' => 'prekosutra', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'D. M. YYYY.', + 'LL' => 'D. MMMM YYYY.', + 'LLL' => 'D. MMMM YYYY. H:mm', + 'LLLL' => 'dddd, D. MMMM YYYY. H:mm', + ], + 'calendar' => [ + 'sameDay' => '[danas u] LT', + 'nextDay' => '[sutra u] LT', + 'nextWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + return '[u] [nedjelju] [u] LT'; + case 3: + return '[u] [srijedu] [u] LT'; + case 6: + return '[u] [subotu] [u] LT'; + default: + return '[u] dddd [u] LT'; + } + }, + 'lastDay' => '[jučer u] LT', + 'lastWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + case 3: + return '[prošlu] dddd [u] LT'; + case 6: + return '[prošle] [subote] [u] LT'; + default: + return '[prošli] dddd [u] LT'; + } + }, + 'sameElse' => 'L', + ], + 'ordinal' => ':number.', + 'months' => ['siječnja', 'veljače', 'ožujka', 'travnja', 'svibnja', 'lipnja', 'srpnja', 'kolovoza', 'rujna', 'listopada', 'studenoga', 'prosinca'], + 'months_standalone' => ['siječanj', 'veljača', 'ožujak', 'travanj', 'svibanj', 'lipanj', 'srpanj', 'kolovoz', 'rujan', 'listopad', 'studeni', 'prosinac'], + 'months_short' => ['sij.', 'velj.', 'ožu.', 'tra.', 'svi.', 'lip.', 'srp.', 'kol.', 'ruj.', 'lis.', 'stu.', 'pro.'], + 'months_regexp' => '/(D[oD]?(\[[^\[\]]*\]|\s)+MMMM?|L{2,4}|l{2,4})/', + 'weekdays' => ['nedjelju', 'ponedjeljak', 'utorak', 'srijedu', 'četvrtak', 'petak', 'subotu'], + 'weekdays_standalone' => ['nedjelja', 'ponedjeljak', 'utorak', 'srijeda', 'četvrtak', 'petak', 'subota'], + 'weekdays_short' => ['ned.', 'pon.', 'uto.', 'sri.', 'čet.', 'pet.', 'sub.'], + 'weekdays_min' => ['ne', 'po', 'ut', 'sr', 'če', 'pe', 'su'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' i '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/hr_BA.php b/vendor/nesbot/carbon/src/Carbon/Lang/hr_BA.php new file mode 100644 index 0000000..7763a45 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/hr_BA.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - DarkoDevelop + */ +return array_replace_recursive(require __DIR__.'/hr.php', [ + 'weekdays' => ['nedjelja', 'ponedjeljak', 'utorak', 'srijeda', 'četvrtak', 'petak', 'subota'], + 'weekdays_short' => ['ned', 'pon', 'uto', 'sri', 'čet', 'pet', 'sub'], + 'weekdays_min' => ['ned', 'pon', 'uto', 'sri', 'čet', 'pet', 'sub'], + 'months' => ['siječnja', 'veljače', 'ožujka', 'travnja', 'svibnja', 'lipnja', 'srpnja', 'kolovoza', 'rujna', 'listopada', 'studenoga', 'prosinca'], + 'months_short' => ['sij', 'velj', 'ožu', 'tra', 'svi', 'lip', 'srp', 'kol', 'ruj', 'lis', 'stu', 'pro'], + 'months_standalone' => ['siječanj', 'veljača', 'ožujak', 'travanj', 'svibanj', 'lipanj', 'srpanj', 'kolovoz', 'rujan', 'listopad', 'studeni', 'prosinac'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D. M. yy.', + 'LL' => 'D. MMM YYYY.', + 'LLL' => 'D. MMMM YYYY. HH:mm', + 'LLLL' => 'dddd, D. MMMM YYYY. HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/hr_HR.php b/vendor/nesbot/carbon/src/Carbon/Lang/hr_HR.php new file mode 100644 index 0000000..db74d8c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/hr_HR.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__.'/hr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/hsb.php b/vendor/nesbot/carbon/src/Carbon/Lang/hsb.php new file mode 100644 index 0000000..3537b8b --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/hsb.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/hsb_DE.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/hsb_DE.php b/vendor/nesbot/carbon/src/Carbon/Lang/hsb_DE.php new file mode 100644 index 0000000..6ba2271 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/hsb_DE.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Information from Michael Wolf Andrzej Krzysztofowicz ankry@mif.pg.gda.pl + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'DD. MMMM YYYY', + 'LLL' => 'DD. MMMM, HH:mm [hodź.]', + 'LLLL' => 'dddd, DD. MMMM YYYY, HH:mm [hodź.]', + ], + 'months' => ['januara', 'februara', 'měrca', 'apryla', 'meje', 'junija', 'julija', 'awgusta', 'septembra', 'oktobra', 'nowembra', 'decembra'], + 'months_short' => ['Jan', 'Feb', 'Měr', 'Apr', 'Mej', 'Jun', 'Jul', 'Awg', 'Sep', 'Okt', 'Now', 'Dec'], + 'weekdays' => ['Njedźela', 'Póndźela', 'Wutora', 'Srjeda', 'Štvórtk', 'Pjatk', 'Sobota'], + 'weekdays_short' => ['Nj', 'Pó', 'Wu', 'Sr', 'Št', 'Pj', 'So'], + 'weekdays_min' => ['Nj', 'Pó', 'Wu', 'Sr', 'Št', 'Pj', 'So'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'year' => ':count lěto', + 'y' => ':count lěto', + 'a_year' => ':count lěto', + + 'month' => ':count měsac', + 'm' => ':count měsac', + 'a_month' => ':count měsac', + + 'week' => ':count tydźeń', + 'w' => ':count tydźeń', + 'a_week' => ':count tydźeń', + + 'day' => ':count dźeń', + 'd' => ':count dźeń', + 'a_day' => ':count dźeń', + + 'hour' => ':count hodźina', + 'h' => ':count hodźina', + 'a_hour' => ':count hodźina', + + 'minute' => ':count chwila', + 'min' => ':count chwila', + 'a_minute' => ':count chwila', + + 'second' => ':count druhi', + 's' => ':count druhi', + 'a_second' => ':count druhi', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ht.php b/vendor/nesbot/carbon/src/Carbon/Lang/ht.php new file mode 100644 index 0000000..ebd12ad --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ht.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/ht_HT.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ht_HT.php b/vendor/nesbot/carbon/src/Carbon/Lang/ht_HT.php new file mode 100644 index 0000000..139b813 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ht_HT.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Sugar Labs // OLPC sugarlabs.org libc-alpha@sourceware.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['janvye', 'fevriye', 'mas', 'avril', 'me', 'jen', 'jiyè', 'out', 'septanm', 'oktòb', 'novanm', 'desanm'], + 'months_short' => ['jan', 'fev', 'mas', 'avr', 'me', 'jen', 'jiy', 'out', 'sep', 'okt', 'nov', 'des'], + 'weekdays' => ['dimanch', 'lendi', 'madi', 'mèkredi', 'jedi', 'vandredi', 'samdi'], + 'weekdays_short' => ['dim', 'len', 'mad', 'mèk', 'jed', 'van', 'sam'], + 'weekdays_min' => ['dim', 'len', 'mad', 'mèk', 'jed', 'van', 'sam'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + + 'year' => ':count lane', + 'y' => ':count lane', + 'a_year' => ':count lane', + + 'month' => 'mwa :count', + 'm' => 'mwa :count', + 'a_month' => 'mwa :count', + + 'week' => 'semèn :count', + 'w' => 'semèn :count', + 'a_week' => 'semèn :count', + + 'day' => ':count jou', + 'd' => ':count jou', + 'a_day' => ':count jou', + + 'hour' => ':count lè', + 'h' => ':count lè', + 'a_hour' => ':count lè', + + 'minute' => ':count minit', + 'min' => ':count minit', + 'a_minute' => ':count minit', + + 'second' => ':count segonn', + 's' => ':count segonn', + 'a_second' => ':count segonn', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/hu.php b/vendor/nesbot/carbon/src/Carbon/Lang/hu.php new file mode 100644 index 0000000..b2d2ac1 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/hu.php @@ -0,0 +1,118 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Adam Brunner + * - Brett Johnson + * - balping + */ + +use Carbon\CarbonInterface; + +$huWeekEndings = ['vasárnap', 'hétfőn', 'kedden', 'szerdán', 'csütörtökön', 'pénteken', 'szombaton']; + +return [ + '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', + 'y_ago' => ':count éve', + 'month_ago' => ':count hónapja', + 'm_ago' => ':count hónapja', + 'week_ago' => ':count hete', + 'w_ago' => ':count hete', + 'day_ago' => ':count napja', + 'd_ago' => ':count napja', + 'hour_ago' => ':count órája', + 'h_ago' => ':count órája', + 'minute_ago' => ':count perce', + 'min_ago' => ':count perce', + 'second_ago' => ':count másodperce', + 's_ago' => ':count másodperce', + 'year_after' => ':count évvel', + 'y_after' => ':count évvel', + 'month_after' => ':count hónappal', + 'm_after' => ':count hónappal', + 'week_after' => ':count héttel', + 'w_after' => ':count héttel', + 'day_after' => ':count nappal', + 'd_after' => ':count nappal', + 'hour_after' => ':count órával', + 'h_after' => ':count órával', + 'minute_after' => ':count perccel', + 'min_after' => ':count perccel', + 'second_after' => ':count másodperccel', + 's_after' => ':count másodperccel', + 'year_before' => ':count évvel', + 'y_before' => ':count évvel', + 'month_before' => ':count hónappal', + 'm_before' => ':count hónappal', + 'week_before' => ':count héttel', + 'w_before' => ':count héttel', + 'day_before' => ':count nappal', + 'd_before' => ':count nappal', + 'hour_before' => ':count órával', + 'h_before' => ':count órával', + 'minute_before' => ':count perccel', + 'min_before' => ':count perccel', + 'second_before' => ':count másodperccel', + 's_before' => ':count másodperccel', + 'months' => ['január', 'február', 'március', 'április', 'május', 'június', 'július', 'augusztus', 'szeptember', 'október', 'november', 'december'], + 'months_short' => ['jan.', 'feb.', 'márc.', 'ápr.', 'máj.', 'jún.', 'júl.', 'aug.', 'szept.', 'okt.', 'nov.', 'dec.'], + 'weekdays' => ['vasárnap', 'hétfő', 'kedd', 'szerda', 'csütörtök', 'péntek', 'szombat'], + 'weekdays_short' => ['vas', 'hét', 'kedd', 'sze', 'csüt', 'pén', 'szo'], + 'weekdays_min' => ['v', 'h', 'k', 'sze', 'cs', 'p', 'sz'], + 'ordinal' => ':number.', + 'diff_now' => 'most', + 'diff_today' => 'ma', + 'diff_yesterday' => 'tegnap', + 'diff_tomorrow' => 'holnap', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'YYYY.MM.DD.', + 'LL' => 'YYYY. MMMM D.', + 'LLL' => 'YYYY. MMMM D. H:mm', + 'LLLL' => 'YYYY. MMMM D., dddd H:mm', + ], + 'calendar' => [ + 'sameDay' => '[ma] LT[-kor]', + 'nextDay' => '[holnap] LT[-kor]', + 'nextWeek' => function (CarbonInterface $date) use ($huWeekEndings) { + return '['.$huWeekEndings[$date->dayOfWeek].'] LT[-kor]'; + }, + 'lastDay' => '[tegnap] LT[-kor]', + 'lastWeek' => function (CarbonInterface $date) use ($huWeekEndings) { + return '[múlt '.$huWeekEndings[$date->dayOfWeek].'] LT[-kor]'; + }, + 'sameElse' => 'L', + ], + 'meridiem' => ['DE', 'DU'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' és '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/hu_HU.php b/vendor/nesbot/carbon/src/Carbon/Lang/hu_HU.php new file mode 100644 index 0000000..b1c4854 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/hu_HU.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__.'/hu.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/hy.php b/vendor/nesbot/carbon/src/Carbon/Lang/hy.php new file mode 100644 index 0000000..8b12994 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/hy.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - mhamlet + */ +return [ + 'year' => ':count տարի', + 'a_year' => 'տարի|:count տարի', + 'y' => ':countտ', + 'month' => ':count ամիս', + 'a_month' => 'ամիս|:count ամիս', + 'm' => ':countամ', + 'week' => ':count շաբաթ', + 'a_week' => 'շաբաթ|:count շաբաթ', + 'w' => ':countշ', + 'day' => ':count օր', + 'a_day' => 'օր|:count օր', + 'd' => ':countօր', + 'hour' => ':count ժամ', + 'a_hour' => 'ժամ|:count ժամ', + 'h' => ':countժ', + 'minute' => ':count րոպե', + 'a_minute' => 'րոպե|:count րոպե', + 'min' => ':countր', + 'second' => ':count վայրկյան', + 'a_second' => 'մի քանի վայրկյան|:count վայրկյան', + 's' => ':countվրկ', + 'ago' => ':time առաջ', + 'from_now' => ':timeից', + 'after' => ':time հետո', + 'before' => ':time առաջ', + 'diff_now' => 'հիմա', + 'diff_today' => 'այսօր', + 'diff_yesterday' => 'երեկ', + 'diff_tomorrow' => 'վաղը', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMMM YYYY թ.', + 'LLL' => 'D MMMM YYYY թ., HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY թ., HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[այսօր] LT', + 'nextDay' => '[վաղը] LT', + 'nextWeek' => 'dddd [օրը ժամը] LT', + 'lastDay' => '[երեկ] LT', + 'lastWeek' => '[անցած] dddd [օրը ժամը] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number, $period) { + switch ($period) { + case 'DDD': + case 'w': + case 'W': + case 'DDDo': + return $number.($number === 1 ? '-ին' : '-րդ'); + default: + return $number; + } + }, + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'գիշերվա'; + } + if ($hour < 12) { + return 'առավոտվա'; + } + if ($hour < 17) { + return 'ցերեկվա'; + } + + return 'երեկոյան'; + }, + 'months' => ['հունվարի', 'փետրվարի', 'մարտի', 'ապրիլի', 'մայիսի', 'հունիսի', 'հուլիսի', 'օգոստոսի', 'սեպտեմբերի', 'հոկտեմբերի', 'նոյեմբերի', 'դեկտեմբերի'], + 'months_standalone' => ['հունվար', 'փետրվար', 'մարտ', 'ապրիլ', 'մայիս', 'հունիս', 'հուլիս', 'օգոստոս', 'սեպտեմբեր', 'հոկտեմբեր', 'նոյեմբեր', 'դեկտեմբեր'], + 'months_short' => ['հնվ', 'փտր', 'մրտ', 'ապր', 'մյս', 'հնս', 'հլս', 'օգս', 'սպտ', 'հկտ', 'նմբ', 'դկտ'], + 'months_regexp' => '/(D[oD]?(\[[^\[\]]*\]|\s)+MMMM?|L{2,4}|l{2,4})/', + 'weekdays' => ['կիրակի', 'երկուշաբթի', 'երեքշաբթի', 'չորեքշաբթի', 'հինգշաբթի', 'ուրբաթ', 'շաբաթ'], + 'weekdays_short' => ['կրկ', 'երկ', 'երք', 'չրք', 'հնգ', 'ուրբ', 'շբթ'], + 'weekdays_min' => ['կրկ', 'երկ', 'երք', 'չրք', 'հնգ', 'ուրբ', 'շբթ'], + 'list' => [', ', ' եւ '], + 'first_day_of_week' => 1, +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/hy_AM.php b/vendor/nesbot/carbon/src/Carbon/Lang/hy_AM.php new file mode 100644 index 0000000..4587df5 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/hy_AM.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - François B + * - Tim Fish + * - Serhan Apaydın + * - JD Isaacks + */ +return array_replace_recursive(require __DIR__.'/hy.php', [ + 'from_now' => ':time հետո', + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/i18n.php b/vendor/nesbot/carbon/src/Carbon/Lang/i18n.php new file mode 100644 index 0000000..e65449b --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/i18n.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'YYYY-MM-DD', + ], + 'months' => ['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12'], + 'months_short' => ['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12'], + 'weekdays' => ['1', '2', '3', '4', '5', '6', '7'], + 'weekdays_short' => ['1', '2', '3', '4', '5', '6', '7'], + 'weekdays_min' => ['1', '2', '3', '4', '5', '6', '7'], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 4, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ia.php b/vendor/nesbot/carbon/src/Carbon/Lang/ia.php new file mode 100644 index 0000000..0a0d5e6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ia.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/ia_FR.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ia_FR.php b/vendor/nesbot/carbon/src/Carbon/Lang/ia_FR.php new file mode 100644 index 0000000..de4b2fa --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ia_FR.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Fedora Project Nik Kalach nikka@fedoraproject.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['januario', 'februario', 'martio', 'april', 'maio', 'junio', 'julio', 'augusto', 'septembre', 'octobre', 'novembre', 'decembre'], + 'months_short' => ['jan', 'feb', 'mar', 'apr', 'mai', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'], + 'weekdays' => ['dominica', 'lunedi', 'martedi', 'mercuridi', 'jovedi', 'venerdi', 'sabbato'], + 'weekdays_short' => ['dom', 'lun', 'mar', 'mer', 'jov', 'ven', 'sab'], + 'weekdays_min' => ['dom', 'lun', 'mar', 'mer', 'jov', 'ven', 'sab'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'year' => 'anno :count', + 'y' => 'anno :count', + 'a_year' => 'anno :count', + + 'month' => ':count mense', + 'm' => ':count mense', + 'a_month' => ':count mense', + + 'week' => ':count septimana', + 'w' => ':count septimana', + 'a_week' => ':count septimana', + + 'day' => ':count die', + 'd' => ':count die', + 'a_day' => ':count die', + + 'hour' => ':count hora', + 'h' => ':count hora', + 'a_hour' => ':count hora', + + 'minute' => ':count minuscule', + 'min' => ':count minuscule', + 'a_minute' => ':count minuscule', + + 'second' => ':count secunda', + 's' => ':count secunda', + 'a_second' => ':count secunda', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/id.php b/vendor/nesbot/carbon/src/Carbon/Lang/id.php new file mode 100644 index 0000000..afaf78f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/id.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - du + * - JD Isaacks + * - Nafies Luthfi + * - Raymundus Jati Primanda (mundusjp) + * - diankur313 + * - a-wip0 + */ +return [ + 'year' => ':count tahun', + 'a_year' => '{1}setahun|]1,Inf[:count tahun', + 'y' => ':countthn', + 'month' => ':count bulan', + 'a_month' => '{1}sebulan|]1,Inf[:count bulan', + 'm' => ':countbln', + 'week' => ':count minggu', + 'a_week' => '{1}seminggu|]1,Inf[:count minggu', + 'w' => ':countmgg', + 'day' => ':count hari', + 'a_day' => '{1}sehari|]1,Inf[:count hari', + 'd' => ':counthr', + 'hour' => ':count jam', + 'a_hour' => '{1}sejam|]1,Inf[:count jam', + 'h' => ':countj', + 'minute' => ':count menit', + 'a_minute' => '{1}semenit|]1,Inf[:count menit', + 'min' => ':countmnt', + 'second' => ':count detik', + 'a_second' => '{1}beberapa detik|]1,Inf[:count detik', + 's' => ':countdt', + 'ago' => ':time yang lalu', + 'from_now' => ':time dari sekarang', + 'after' => ':time setelahnya', + 'before' => ':time sebelumnya', + 'diff_now' => 'sekarang', + 'diff_today' => 'Hari', + 'diff_today_regexp' => 'Hari(?:\\s+ini)?(?:\\s+pukul)?', + 'diff_yesterday' => 'kemarin', + 'diff_yesterday_regexp' => 'Kemarin(?:\\s+pukul)?', + 'diff_tomorrow' => 'besok', + 'diff_tomorrow_regexp' => 'Besok(?:\\s+pukul)?', + 'formats' => [ + 'LT' => 'HH.mm', + 'LTS' => 'HH.mm.ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY [pukul] HH.mm', + 'LLLL' => 'dddd, D MMMM YYYY [pukul] HH.mm', + ], + 'calendar' => [ + 'sameDay' => '[Hari ini pukul] LT', + 'nextDay' => '[Besok pukul] LT', + 'nextWeek' => 'dddd [pukul] LT', + 'lastDay' => '[Kemarin pukul] LT', + 'lastWeek' => 'dddd [lalu pukul] LT', + 'sameElse' => 'L', + ], + 'meridiem' => function ($hour) { + if ($hour < 11) { + return 'pagi'; + } + if ($hour < 15) { + return 'siang'; + } + if ($hour < 19) { + return 'sore'; + } + + return 'malam'; + }, + 'months' => ['Januari', 'Februari', 'Maret', 'April', 'Mei', 'Juni', 'Juli', 'Agustus', 'September', 'Oktober', 'November', 'Desember'], + 'months_short' => ['Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun', 'Jul', 'Agt', 'Sep', 'Okt', 'Nov', 'Des'], + 'weekdays' => ['Minggu', 'Senin', 'Selasa', 'Rabu', 'Kamis', 'Jumat', 'Sabtu'], + 'weekdays_short' => ['Min', 'Sen', 'Sel', 'Rab', 'Kam', 'Jum', 'Sab'], + 'weekdays_min' => ['Mg', 'Sn', 'Sl', 'Rb', 'Km', 'Jm', 'Sb'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' dan '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/id_ID.php b/vendor/nesbot/carbon/src/Carbon/Lang/id_ID.php new file mode 100644 index 0000000..d5953a1 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/id_ID.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__.'/id.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ig.php b/vendor/nesbot/carbon/src/Carbon/Lang/ig.php new file mode 100644 index 0000000..de51e9c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ig.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/ig_NG.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ig_NG.php b/vendor/nesbot/carbon/src/Carbon/Lang/ig_NG.php new file mode 100644 index 0000000..0034e35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ig_NG.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - pablo@mandriva.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['Jenụwarị', 'Febrụwarị', 'Maachị', 'Eprel', 'Mee', 'Juun', 'Julaị', 'Ọgọọst', 'Septemba', 'Ọktoba', 'Novemba', 'Disemba'], + 'months_short' => ['Jen', 'Feb', 'Maa', 'Epr', 'Mee', 'Juu', 'Jul', 'Ọgọ', 'Sep', 'Ọkt', 'Nov', 'Dis'], + 'weekdays' => ['sọnde', 'mọnde', 'tuzde', 'wenzde', 'tọsde', 'fraịde', 'satọde'], + 'weekdays_short' => ['sọn', 'mọn', 'tuz', 'wen', 'tọs', 'fra', 'sat'], + 'weekdays_min' => ['sọn', 'mọn', 'tuz', 'wen', 'tọs', 'fra', 'sat'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + + 'year' => 'afo :count', + 'y' => 'afo :count', + 'a_year' => 'afo :count', + + 'month' => 'önwa :count', + 'm' => 'önwa :count', + 'a_month' => 'önwa :count', + + 'week' => 'izu :count', + 'w' => 'izu :count', + 'a_week' => 'izu :count', + + 'day' => 'ụbọchị :count', + 'd' => 'ụbọchị :count', + 'a_day' => 'ụbọchị :count', + + 'hour' => 'awa :count', + 'h' => 'awa :count', + 'a_hour' => 'awa :count', + + 'minute' => 'minit :count', + 'min' => 'minit :count', + 'a_minute' => 'minit :count', + + 'second' => 'sekọnd :count', + 's' => 'sekọnd :count', + 'a_second' => 'sekọnd :count', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ii.php b/vendor/nesbot/carbon/src/Carbon/Lang/ii.php new file mode 100644 index 0000000..a4246c2 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ii.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['ꎸꄑ', 'ꁯꋒ'], + 'weekdays' => ['ꑭꆏꑍ', 'ꆏꊂꋍ', 'ꆏꊂꑍ', 'ꆏꊂꌕ', 'ꆏꊂꇖ', 'ꆏꊂꉬ', 'ꆏꊂꃘ'], + 'weekdays_short' => ['ꑭꆏ', 'ꆏꋍ', 'ꆏꑍ', 'ꆏꌕ', 'ꆏꇖ', 'ꆏꉬ', 'ꆏꃘ'], + 'weekdays_min' => ['ꑭꆏ', 'ꆏꋍ', 'ꆏꑍ', 'ꆏꌕ', 'ꆏꇖ', 'ꆏꉬ', 'ꆏꃘ'], + 'months' => null, + 'months_short' => ['ꋍꆪ', 'ꑍꆪ', 'ꌕꆪ', 'ꇖꆪ', 'ꉬꆪ', 'ꃘꆪ', 'ꏃꆪ', 'ꉆꆪ', 'ꈬꆪ', 'ꊰꆪ', 'ꊰꊪꆪ', 'ꊰꑋꆪ'], + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'YYYY-MM-dd', + 'LL' => 'YYYY MMM D', + 'LLL' => 'YYYY MMMM D h:mm a', + 'LLLL' => 'YYYY MMMM D, dddd h:mm a', + ], + + 'year' => ':count ꒉ', // less reliable + 'y' => ':count ꒉ', // less reliable + 'a_year' => ':count ꒉ', // less reliable + + 'month' => ':count ꆪ', + 'm' => ':count ꆪ', + 'a_month' => ':count ꆪ', + + 'week' => ':count ꏃ', // less reliable + 'w' => ':count ꏃ', // less reliable + 'a_week' => ':count ꏃ', // less reliable + + 'day' => ':count ꏜ', // less reliable + 'd' => ':count ꏜ', // less reliable + 'a_day' => ':count ꏜ', // less reliable + + 'hour' => ':count ꄮꈉ', + 'h' => ':count ꄮꈉ', + 'a_hour' => ':count ꄮꈉ', + + 'minute' => ':count ꀄꊭ', // less reliable + 'min' => ':count ꀄꊭ', // less reliable + 'a_minute' => ':count ꀄꊭ', // less reliable + + 'second' => ':count ꇅ', // less reliable + 's' => ':count ꇅ', // less reliable + 'a_second' => ':count ꇅ', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ik.php b/vendor/nesbot/carbon/src/Carbon/Lang/ik.php new file mode 100644 index 0000000..7a13aa2 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ik.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/ik_CA.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ik_CA.php b/vendor/nesbot/carbon/src/Carbon/Lang/ik_CA.php new file mode 100644 index 0000000..bb2a109 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ik_CA.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - pablo@mandriva.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['Siqiññaatchiaq', 'Siqiññaasrugruk', 'Paniqsiqsiivik', 'Qilġich Tatqiat', 'Suppivik', 'Iġñivik', 'Itchavik', 'Tiññivik', 'Amiġaiqsivik', 'Sikkuvik', 'Nippivik', 'Siqiñġiḷaq'], + 'months_short' => ['Sñt', 'Sñs', 'Pan', 'Qil', 'Sup', 'Iġñ', 'Itc', 'Tiñ', 'Ami', 'Sik', 'Nip', 'Siq'], + 'weekdays' => ['Minġuiqsioiq', 'Savałłiq', 'Ilaqtchiioiq', 'Qitchiioiq', 'Sisamiioiq', 'Tallimmiioiq', 'Maqinġuoiq'], + 'weekdays_short' => ['Min', 'Sav', 'Ila', 'Qit', 'Sis', 'Tal', 'Maq'], + 'weekdays_min' => ['Min', 'Sav', 'Ila', 'Qit', 'Sis', 'Tal', 'Maq'], + 'day_of_first_week_of_year' => 1, + + 'year' => ':count ukiuq', + 'y' => ':count ukiuq', + 'a_year' => ':count ukiuq', + + 'month' => ':count Tatqiat', + 'm' => ':count Tatqiat', + 'a_month' => ':count Tatqiat', + + 'week' => ':count tatqiat', // less reliable + 'w' => ':count tatqiat', // less reliable + 'a_week' => ':count tatqiat', // less reliable + + 'day' => ':count siqiñiq', // less reliable + 'd' => ':count siqiñiq', // less reliable + 'a_day' => ':count siqiñiq', // less reliable + + 'hour' => ':count Siḷa', // less reliable + 'h' => ':count Siḷa', // less reliable + 'a_hour' => ':count Siḷa', // less reliable + + 'second' => ':count iġñiq', // less reliable + 's' => ':count iġñiq', // less reliable + 'a_second' => ':count iġñiq', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/in.php b/vendor/nesbot/carbon/src/Carbon/Lang/in.php new file mode 100644 index 0000000..d5953a1 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/in.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__.'/id.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/is.php b/vendor/nesbot/carbon/src/Carbon/Lang/is.php new file mode 100644 index 0000000..9990168 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/is.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Kristján Ingi Geirsson + */ +return [ + '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', + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' og '], + 'meridiem' => ['fh', 'eh'], + 'diff_now' => 'núna', + 'diff_yesterday' => 'í gær', + 'diff_tomorrow' => 'á morgun', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D. MMMM YYYY', + 'LLL' => 'D. MMMM [kl.] HH:mm', + 'LLLL' => 'dddd D. MMMM YYYY [kl.] HH:mm', + ], + 'weekdays' => ['sunnudaginn', 'mánudaginn', 'þriðjudaginn', 'miðvikudaginn', 'fimmtudaginn', 'föstudaginn', 'laugardaginn'], + 'weekdays_short' => ['sun', 'mán', 'þri', 'mið', 'fim', 'fös', 'lau'], + 'weekdays_min' => ['sun', 'mán', 'þri', 'mið', 'fim', 'fös', 'lau'], + 'months' => ['janúar', 'febrúar', 'mars', 'apríl', 'maí', 'júní', 'júlí', 'ágúst', 'september', 'október', 'nóvember', 'desember'], + 'months_short' => ['jan', 'feb', 'mar', 'apr', 'maí', 'jún', 'júl', 'ágú', 'sep', 'okt', 'nóv', 'des'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/is_IS.php b/vendor/nesbot/carbon/src/Carbon/Lang/is_IS.php new file mode 100644 index 0000000..4d35c44 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/is_IS.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__.'/is.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/it.php b/vendor/nesbot/carbon/src/Carbon/Lang/it.php new file mode 100644 index 0000000..49875d7 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/it.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ash + * - François B + * - Marco Perrando + * - Massimiliano Caniparoli + * - JD Isaacks + * - Andrea Martini + * - Francesco Marasco + * - Tizianoz93 + * - Davide Casiraghi (davide-casiraghi) + * - Pete Scopes (pdscopes) + */ + +use Carbon\CarbonInterface; + +return [ + 'year' => ':count anno|:count anni', + 'a_year' => 'un anno|:count anni', + 'y' => ':count anno|:count anni', + 'month' => ':count mese|:count mesi', + 'a_month' => 'un mese|:count mesi', + 'm' => ':count mese|:count mesi', + 'week' => ':count settimana|:count settimane', + 'a_week' => 'una settimana|:count settimane', + 'w' => ':count set.', + 'day' => ':count giorno|:count giorni', + 'a_day' => 'un giorno|:count giorni', + 'd' => ':count g|:count gg', + 'hour' => ':count ora|:count ore', + 'a_hour' => 'un\'ora|:count ore', + 'h' => ':count h', + 'minute' => ':count minuto|:count minuti', + 'a_minute' => 'un minuto|:count minuti', + 'min' => ':count min.', + 'second' => ':count secondo|:count secondi', + 'a_second' => 'alcuni secondi|:count secondi', + 's' => ':count sec.', + 'millisecond' => ':count millisecondo|:count millisecondi', + 'a_millisecond' => 'un millisecondo|:count millisecondi', + 'ms' => ':countms', + 'microsecond' => ':count microsecondo|:count microsecondi', + 'a_microsecond' => 'un microsecondo|:count microsecondi', + 'µs' => ':countµs', + 'ago' => ':time fa', + 'from_now' => function ($time) { + return (preg_match('/^\d.+$/', $time) ? 'tra' : 'in')." $time"; + }, + 'after' => ':time dopo', + 'before' => ':time prima', + 'diff_now' => 'proprio ora', + 'diff_today' => 'Oggi', + 'diff_today_regexp' => 'Oggi(?:\\s+alle)?', + 'diff_yesterday' => 'ieri', + 'diff_yesterday_regexp' => 'Ieri(?:\\s+alle)?', + 'diff_tomorrow' => 'domani', + 'diff_tomorrow_regexp' => 'Domani(?:\\s+alle)?', + 'diff_before_yesterday' => 'l\'altro ieri', + 'diff_after_tomorrow' => 'dopodomani', + 'period_interval' => 'ogni :interval', + 'period_start_date' => 'dal :date', + 'period_end_date' => 'al :date', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Oggi alle] LT', + 'nextDay' => '[Domani alle] LT', + 'nextWeek' => 'dddd [alle] LT', + 'lastDay' => '[Ieri alle] LT', + 'lastWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + return '[la scorsa] dddd [alle] LT'; + default: + return '[lo scorso] dddd [alle] LT'; + } + }, + 'sameElse' => 'L', + ], + 'ordinal' => ':numberº', + 'months' => ['gennaio', 'febbraio', 'marzo', 'aprile', 'maggio', 'giugno', 'luglio', 'agosto', 'settembre', 'ottobre', 'novembre', 'dicembre'], + 'months_short' => ['gen', 'feb', 'mar', 'apr', 'mag', 'giu', 'lug', 'ago', 'set', 'ott', 'nov', 'dic'], + 'weekdays' => ['domenica', 'lunedì', 'martedì', 'mercoledì', 'giovedì', 'venerdì', 'sabato'], + 'weekdays_short' => ['dom', 'lun', 'mar', 'mer', 'gio', 'ven', 'sab'], + 'weekdays_min' => ['do', 'lu', 'ma', 'me', 'gi', 've', 'sa'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' e '], + 'ordinal_words' => [ + 'of' => 'di', + 'first' => 'primo', + 'second' => 'secondo', + 'third' => 'terzo', + 'fourth' => 'quarto', + 'fifth' => 'quinto', + 'last' => 'ultimo', + ], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/it_CH.php b/vendor/nesbot/carbon/src/Carbon/Lang/it_CH.php new file mode 100644 index 0000000..c23cc50 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/it_CH.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Propaganistas + */ +return array_replace_recursive(require __DIR__.'/it.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/it_IT.php b/vendor/nesbot/carbon/src/Carbon/Lang/it_IT.php new file mode 100644 index 0000000..a5d1981 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/it_IT.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return require __DIR__.'/it.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/it_SM.php b/vendor/nesbot/carbon/src/Carbon/Lang/it_SM.php new file mode 100644 index 0000000..5e8fc92 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/it_SM.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__.'/it.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/it_VA.php b/vendor/nesbot/carbon/src/Carbon/Lang/it_VA.php new file mode 100644 index 0000000..5e8fc92 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/it_VA.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__.'/it.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/iu.php b/vendor/nesbot/carbon/src/Carbon/Lang/iu.php new file mode 100644 index 0000000..4fa9742 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/iu.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/iu_CA.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/iu_CA.php b/vendor/nesbot/carbon/src/Carbon/Lang/iu_CA.php new file mode 100644 index 0000000..6ab7e14 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/iu_CA.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Pablo Saratxaga pablo@mandriva.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'MM/DD/YY', + ], + 'months' => ['ᔮᓄᐊᓕ', 'ᕕᕗᐊᓕ', 'ᒪᔅᓯ', 'ᐃᐳᓗ', 'ᒪᐃ', 'ᔪᓂ', 'ᔪᓚᐃ', 'ᐊᒋᓯ', 'ᓯᑎᕙ', 'ᐊᑦᑐᕙ', 'ᓄᕕᕙ', 'ᑎᓯᕝᕙ'], + 'months_short' => ['ᔮᓄ', 'ᕕᕗ', 'ᒪᔅ', 'ᐃᐳ', 'ᒪᐃ', 'ᔪᓂ', 'ᔪᓚ', 'ᐊᒋ', 'ᓯᑎ', 'ᐊᑦ', 'ᓄᕕ', 'ᑎᓯ'], + 'weekdays' => ['ᓈᑦᑎᖑᔭᕐᕕᒃ', 'ᓇᒡᒐᔾᔭᐅ', 'ᓇᒡᒐᔾᔭᐅᓕᖅᑭᑦ', 'ᐱᖓᓲᓕᖅᓯᐅᑦ', 'ᕿᑎᖅᑰᑦ', 'ᐅᓪᓗᕈᓘᑐᐃᓇᖅ', 'ᓯᕙᑖᕕᒃ'], + 'weekdays_short' => ['ᓈ', 'ᓇ', 'ᓕ', 'ᐱ', 'ᕿ', 'ᐅ', 'ᓯ'], + 'weekdays_min' => ['ᓈ', 'ᓇ', 'ᓕ', 'ᐱ', 'ᕿ', 'ᐅ', 'ᓯ'], + 'day_of_first_week_of_year' => 1, + + 'year' => ':count ᐅᑭᐅᖅ', + 'y' => ':count ᐅᑭᐅᖅ', + 'a_year' => ':count ᐅᑭᐅᖅ', + + 'month' => ':count qaammat', + 'm' => ':count qaammat', + 'a_month' => ':count qaammat', + + 'week' => ':count sapaatip akunnera', + 'w' => ':count sapaatip akunnera', + 'a_week' => ':count sapaatip akunnera', + + 'day' => ':count ulloq', + 'd' => ':count ulloq', + 'a_day' => ':count ulloq', + + 'hour' => ':count ikarraq', + 'h' => ':count ikarraq', + 'a_hour' => ':count ikarraq', + + 'minute' => ':count titiqqaralaaq', // less reliable + 'min' => ':count titiqqaralaaq', // less reliable + 'a_minute' => ':count titiqqaralaaq', // less reliable + + 'second' => ':count marluk', // less reliable + 's' => ':count marluk', // less reliable + 'a_second' => ':count marluk', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/iw.php b/vendor/nesbot/carbon/src/Carbon/Lang/iw.php new file mode 100644 index 0000000..a26e350 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/iw.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'months' => ['ינואר', 'פברואר', 'מרץ', 'אפריל', 'מאי', 'יוני', 'יולי', 'אוגוסט', 'ספטמבר', 'אוקטובר', 'נובמבר', 'דצמבר'], + 'months_short' => ['ינו׳', 'פבר׳', 'מרץ', 'אפר׳', 'מאי', 'יוני', 'יולי', 'אוג׳', 'ספט׳', 'אוק׳', 'נוב׳', 'דצמ׳'], + 'weekdays' => ['יום ראשון', 'יום שני', 'יום שלישי', 'יום רביעי', 'יום חמישי', 'יום שישי', 'יום שבת'], + 'weekdays_short' => ['יום א׳', 'יום ב׳', 'יום ג׳', 'יום ד׳', 'יום ה׳', 'יום ו׳', 'שבת'], + 'weekdays_min' => ['א׳', 'ב׳', 'ג׳', 'ד׳', 'ה׳', 'ו׳', 'ש׳'], + 'meridiem' => ['לפנה״צ', 'אחה״צ'], + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'D.M.YYYY', + 'LL' => 'D בMMM YYYY', + 'LLL' => 'D בMMMM YYYY H:mm', + 'LLLL' => 'dddd, D בMMMM YYYY H:mm', + ], + + 'year' => ':count שנה', + 'y' => ':count שנה', + 'a_year' => ':count שנה', + + 'month' => ':count חודש', + 'm' => ':count חודש', + 'a_month' => ':count חודש', + + 'week' => ':count שבוע', + 'w' => ':count שבוע', + 'a_week' => ':count שבוע', + + 'day' => ':count יום', + 'd' => ':count יום', + 'a_day' => ':count יום', + + 'hour' => ':count שעה', + 'h' => ':count שעה', + 'a_hour' => ':count שעה', + + 'minute' => ':count דקה', + 'min' => ':count דקה', + 'a_minute' => ':count דקה', + + 'second' => ':count שניה', + 's' => ':count שניה', + 'a_second' => ':count שניה', + + 'ago' => 'לפני :time', + 'from_now' => 'בעוד :time', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ja.php b/vendor/nesbot/carbon/src/Carbon/Lang/ja.php new file mode 100644 index 0000000..1ca6751 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ja.php @@ -0,0 +1,102 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Takuya Sawada + * - Atsushi Tanaka + * - François B + * - Jason Katz-Brown + * - Serhan Apaydın + * - XueWei + * - JD Isaacks + * - toyama satoshi + * - atakigawa + */ + +use Carbon\CarbonInterface; + +return [ + '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秒', + 'a_second' => '{1}数秒|]1,Inf[:count秒', + 's' => ':count秒', + 'ago' => ':time前', + 'from_now' => ':time後', + 'after' => ':time後', + 'before' => ':time前', + 'diff_now' => '今', + 'diff_today' => '今日', + 'diff_yesterday' => '昨日', + 'diff_tomorrow' => '明日', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY/MM/DD', + 'LL' => 'YYYY年M月D日', + 'LLL' => 'YYYY年M月D日 HH:mm', + 'LLLL' => 'YYYY年M月D日 dddd HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[今日] LT', + 'nextDay' => '[明日] LT', + 'nextWeek' => function (CarbonInterface $current, CarbonInterface $other) { + if ($other->week !== $current->week) { + return '[来週]dddd LT'; + } + + return 'dddd LT'; + }, + 'lastDay' => '[昨日] LT', + 'lastWeek' => function (CarbonInterface $current, CarbonInterface $other) { + if ($other->week !== $current->week) { + return '[先週]dddd LT'; + } + + return 'dddd LT'; + }, + 'sameElse' => 'L', + ], + 'ordinal' => function ($number, $period) { + switch ($period) { + case 'd': + case 'D': + case 'DDD': + return $number.'日'; + default: + return $number; + } + }, + 'meridiem' => ['午前', '午後'], + 'months' => ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'], + 'months_short' => ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'], + 'weekdays' => ['日曜日', '月曜日', '火曜日', '水曜日', '木曜日', '金曜日', '土曜日'], + 'weekdays_short' => ['日', '月', '火', '水', '木', '金', '土'], + 'weekdays_min' => ['日', '月', '火', '水', '木', '金', '土'], + 'list' => '、', + 'alt_numbers' => ['〇', '一', '二', '三', '四', '五', '六', '七', '八', '九', '十', '十一', '十二', '十三', '十四', '十五', '十六', '十七', '十八', '十九', '二十', '二十一', '二十二', '二十三', '二十四', '二十五', '二十六', '二十七', '二十八', '二十九', '三十', '三十一', '三十二', '三十三', '三十四', '三十五', '三十六', '三十七', '三十八', '三十九', '四十', '四十一', '四十二', '四十三', '四十四', '四十五', '四十六', '四十七', '四十八', '四十九', '五十', '五十一', '五十二', '五十三', '五十四', '五十五', '五十六', '五十七', '五十八', '五十九', '六十', '六十一', '六十二', '六十三', '六十四', '六十五', '六十六', '六十七', '六十八', '六十九', '七十', '七十一', '七十二', '七十三', '七十四', '七十五', '七十六', '七十七', '七十八', '七十九', '八十', '八十一', '八十二', '八十三', '八十四', '八十五', '八十六', '八十七', '八十八', '八十九', '九十', '九十一', '九十二', '九十三', '九十四', '九十五', '九十六', '九十七', '九十八', '九十九'], + 'alt_numbers_pow' => [ + 10000 => '万', + 1000 => '千', + 100 => '百', + ], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ja_JP.php b/vendor/nesbot/carbon/src/Carbon/Lang/ja_JP.php new file mode 100644 index 0000000..c283625 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ja_JP.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__.'/ja.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/jgo.php b/vendor/nesbot/carbon/src/Carbon/Lang/jgo.php new file mode 100644 index 0000000..6a1e77a --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/jgo.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/jmc.php b/vendor/nesbot/carbon/src/Carbon/Lang/jmc.php new file mode 100644 index 0000000..ed92e8e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/jmc.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['utuko', 'kyiukonyi'], + 'weekdays' => ['Jumapilyi', 'Jumatatuu', 'Jumanne', 'Jumatanu', 'Alhamisi', 'Ijumaa', 'Jumamosi'], + 'weekdays_short' => ['Jpi', 'Jtt', 'Jnn', 'Jtn', 'Alh', 'Iju', 'Jmo'], + 'weekdays_min' => ['Jpi', 'Jtt', 'Jnn', 'Jtn', 'Alh', 'Iju', 'Jmo'], + 'months' => ['Januari', 'Februari', 'Machi', 'Aprilyi', 'Mei', 'Junyi', 'Julyai', 'Agusti', 'Septemba', 'Oktoba', 'Novemba', 'Desemba'], + 'months_short' => ['Jan', 'Feb', 'Mac', 'Apr', 'Mei', 'Jun', 'Jul', 'Ago', 'Sep', 'Okt', 'Nov', 'Des'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/jv.php b/vendor/nesbot/carbon/src/Carbon/Lang/jv.php new file mode 100644 index 0000000..bcbe044 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/jv.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - tgfjt + * - JD Isaacks + */ +return [ + 'year' => '{1}setaun|]1,Inf[:count taun', + 'month' => '{1}sewulan|]1,Inf[:count wulan', + 'week' => '{1}sakminggu|]1,Inf[:count minggu', + 'day' => '{1}sedinten|]1,Inf[:count dinten', + 'hour' => '{1}setunggal jam|]1,Inf[:count jam', + 'minute' => '{1}setunggal menit|]1,Inf[:count menit', + 'second' => '{1}sawetawis detik|]1,Inf[:count detik', + 'ago' => ':time ingkang kepengker', + 'from_now' => 'wonten ing :time', + 'diff_today' => 'Dinten', + 'diff_yesterday' => 'Kala', + 'diff_yesterday_regexp' => 'Kala(?:\\s+wingi)?(?:\\s+pukul)?', + 'diff_tomorrow' => 'Mbenjang', + 'diff_tomorrow_regexp' => 'Mbenjang(?:\\s+pukul)?', + 'diff_today_regexp' => 'Dinten(?:\\s+puniko)?(?:\\s+pukul)?', + 'formats' => [ + 'LT' => 'HH.mm', + 'LTS' => 'HH.mm.ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY [pukul] HH.mm', + 'LLLL' => 'dddd, D MMMM YYYY [pukul] HH.mm', + ], + 'calendar' => [ + 'sameDay' => '[Dinten puniko pukul] LT', + 'nextDay' => '[Mbenjang pukul] LT', + 'nextWeek' => 'dddd [pukul] LT', + 'lastDay' => '[Kala wingi pukul] LT', + 'lastWeek' => 'dddd [kepengker pukul] LT', + 'sameElse' => 'L', + ], + 'meridiem' => function ($hour) { + if ($hour < 11) { + return 'enjing'; + } + if ($hour < 15) { + return 'siyang'; + } + if ($hour < 19) { + return 'sonten'; + } + + return 'ndalu'; + }, + 'months' => ['Januari', 'Februari', 'Maret', 'April', 'Mei', 'Juni', 'Juli', 'Agustus', 'September', 'Oktober', 'Nopember', 'Desember'], + 'months_short' => ['Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun', 'Jul', 'Ags', 'Sep', 'Okt', 'Nop', 'Des'], + 'weekdays' => ['Minggu', 'Senen', 'Seloso', 'Rebu', 'Kemis', 'Jemuwah', 'Septu'], + 'weekdays_short' => ['Min', 'Sen', 'Sel', 'Reb', 'Kem', 'Jem', 'Sep'], + 'weekdays_min' => ['Mg', 'Sn', 'Sl', 'Rb', 'Km', 'Jm', 'Sp'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' lan '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ka.php b/vendor/nesbot/carbon/src/Carbon/Lang/ka.php new file mode 100644 index 0000000..a5d563d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ka.php @@ -0,0 +1,204 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Tornike Razmadze + * - François B + * - Lasha Dolidze + * - Tim Fish + * - JD Isaacks + * - Tornike Razmadze + * - François B + * - Lasha Dolidze + * - JD Isaacks + * - LONGMAN + * - Avtandil Kikabidze (akalongman) + * - Levan Velijanashvili (Stichoza) + */ + +use Carbon\CarbonInterface; + +return [ + 'year' => ':count წელი', + 'y' => ':count წელი', + 'a_year' => '{1}წელი|]1,Inf[:count წელი', + 'month' => ':count თვე', + 'm' => ':count თვე', + 'a_month' => '{1}თვე|]1,Inf[:count თვე', + 'week' => ':count კვირა', + 'w' => ':count კვირა', + 'a_week' => '{1}კვირა|]1,Inf[:count კვირა', + 'day' => ':count დღე', + 'd' => ':count დღე', + 'a_day' => '{1}დღე|]1,Inf[:count დღე', + 'hour' => ':count საათი', + 'h' => ':count საათი', + 'a_hour' => '{1}საათი|]1,Inf[:count საათი', + 'minute' => ':count წუთი', + 'min' => ':count წუთი', + 'a_minute' => '{1}წუთი|]1,Inf[:count წუთი', + 'second' => ':count წამი', + 's' => ':count წამი', + 'a_second' => '{1}რამდენიმე წამი|]1,Inf[:count წამი', + 'ago' => function ($time) { + $replacements = [ + // year + 'წელი' => 'წლის', + // month + 'თვე' => 'თვის', + // week + 'კვირა' => 'კვირის', + // day + 'დღე' => 'დღის', + // hour + 'საათი' => 'საათის', + // minute + 'წუთი' => 'წუთის', + // second + 'წამი' => 'წამის', + ]; + $time = strtr($time, array_flip($replacements)); + $time = strtr($time, $replacements); + + return "$time წინ"; + }, + 'from_now' => function ($time) { + $replacements = [ + // year + 'წელი' => 'წელიწადში', + // week + 'კვირა' => 'კვირაში', + // day + 'დღე' => 'დღეში', + // month + 'თვე' => 'თვეში', + // hour + 'საათი' => 'საათში', + // minute + 'წუთი' => 'წუთში', + // second + 'წამი' => 'წამში', + ]; + $time = strtr($time, array_flip($replacements)); + $time = strtr($time, $replacements); + + return $time; + }, + 'after' => function ($time) { + $replacements = [ + // year + 'წელი' => 'წლის', + // month + 'თვე' => 'თვის', + // week + 'კვირა' => 'კვირის', + // day + 'დღე' => 'დღის', + // hour + 'საათი' => 'საათის', + // minute + 'წუთი' => 'წუთის', + // second + 'წამი' => 'წამის', + ]; + $time = strtr($time, array_flip($replacements)); + $time = strtr($time, $replacements); + + return "$time შემდეგ"; + }, + 'before' => function ($time) { + $replacements = [ + // year + 'წელი' => 'წლით', + // month + 'თვე' => 'თვით', + // week + 'კვირა' => 'კვირით', + // day + 'დღე' => 'დღით', + // hour + 'საათი' => 'საათით', + // minute + 'წუთი' => 'წუთით', + // second + 'წამი' => 'წამით', + ]; + $time = strtr($time, array_flip($replacements)); + $time = strtr($time, $replacements); + + return "$time ადრე"; + }, + 'diff_now' => 'ახლა', + 'diff_today' => 'დღეს', + 'diff_yesterday' => 'გუშინ', + 'diff_tomorrow' => 'ხვალ', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[დღეს], LT[-ზე]', + 'nextDay' => '[ხვალ], LT[-ზე]', + 'nextWeek' => function (CarbonInterface $current, CarbonInterface $other) { + return ($current->isSameWeek($other) ? '' : '[შემდეგ] ').'dddd, LT[-ზე]'; + }, + 'lastDay' => '[გუშინ], LT[-ზე]', + 'lastWeek' => '[წინა] dddd, LT-ზე', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + if ($number === 0) { + return $number; + } + if ($number === 1) { + return $number.'-ლი'; + } + if (($number < 20) || ($number <= 100 && ($number % 20 === 0)) || ($number % 100 === 0)) { + return 'მე-'.$number; + } + + return $number.'-ე'; + }, + 'months' => ['იანვარი', 'თებერვალი', 'მარტი', 'აპრილი', 'მაისი', 'ივნისი', 'ივლისი', 'აგვისტო', 'სექტემბერი', 'ოქტომბერი', 'ნოემბერი', 'დეკემბერი'], + 'months_standalone' => ['იანვარს', 'თებერვალს', 'მარტს', 'აპრილს', 'მაისს', 'ივნისს', 'ივლისს', 'აგვისტოს', 'სექტემბერს', 'ოქტომბერს', 'ნოემბერს', 'დეკემბერს'], + 'months_short' => ['იან', 'თებ', 'მარ', 'აპრ', 'მაი', 'ივნ', 'ივლ', 'აგვ', 'სექ', 'ოქტ', 'ნოე', 'დეკ'], + 'months_regexp' => '/(D[oD]?(\[[^\[\]]*\]|\s)+MMMM?|L{2,4}|l{2,4})/', + 'weekdays' => ['კვირას', 'ორშაბათს', 'სამშაბათს', 'ოთხშაბათს', 'ხუთშაბათს', 'პარასკევს', 'შაბათს'], + 'weekdays_standalone' => ['კვირა', 'ორშაბათი', 'სამშაბათი', 'ოთხშაბათი', 'ხუთშაბათი', 'პარასკევი', 'შაბათი'], + 'weekdays_short' => ['კვი', 'ორშ', 'სამ', 'ოთხ', 'ხუთ', 'პარ', 'შაბ'], + 'weekdays_min' => ['კვ', 'ორ', 'სა', 'ოთ', 'ხუ', 'პა', 'შა'], + 'weekdays_regexp' => '/^([^d].*|.*[^d])$/', + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' და '], + 'meridiem' => function ($hour) { + if ($hour >= 4) { + if ($hour < 11) { + return 'დილის'; + } + + if ($hour < 16) { + return 'შუადღის'; + } + + if ($hour < 22) { + return 'საღამოს'; + } + } + + return 'ღამის'; + }, +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ka_GE.php b/vendor/nesbot/carbon/src/Carbon/Lang/ka_GE.php new file mode 100644 index 0000000..a26d930 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ka_GE.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__.'/ka.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/kab.php b/vendor/nesbot/carbon/src/Carbon/Lang/kab.php new file mode 100644 index 0000000..94d6473 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/kab.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/kab_DZ.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/kab_DZ.php b/vendor/nesbot/carbon/src/Carbon/Lang/kab_DZ.php new file mode 100644 index 0000000..796660b --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/kab_DZ.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - belkacem77@gmail.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Yennayer', 'Fuṛar', 'Meɣres', 'Yebrir', 'Mayyu', 'Yunyu', 'Yulyu', 'ɣuct', 'Ctembeṛ', 'Tubeṛ', 'Wambeṛ', 'Dujembeṛ'], + 'months_short' => ['Yen', 'Fur', 'Meɣ', 'Yeb', 'May', 'Yun', 'Yul', 'ɣuc', 'Cte', 'Tub', 'Wam', 'Duj'], + 'weekdays' => ['Acer', 'Arim', 'Aram', 'Ahad', 'Amhad', 'Sem', 'Sed'], + 'weekdays_short' => ['Ace', 'Ari', 'Ara', 'Aha', 'Amh', 'Sem', 'Sed'], + 'weekdays_min' => ['Ace', 'Ari', 'Ara', 'Aha', 'Amh', 'Sem', 'Sed'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['FT', 'MD'], + + 'year' => ':count n yiseggasen', + 'y' => ':count n yiseggasen', + 'a_year' => ':count n yiseggasen', + + 'month' => ':count n wayyuren', + 'm' => ':count n wayyuren', + 'a_month' => ':count n wayyuren', + + 'week' => ':count n ledwaṛ', // less reliable + 'w' => ':count n ledwaṛ', // less reliable + 'a_week' => ':count n ledwaṛ', // less reliable + + 'day' => ':count n wussan', + 'd' => ':count n wussan', + 'a_day' => ':count n wussan', + + 'hour' => ':count n tsaɛtin', + 'h' => ':count n tsaɛtin', + 'a_hour' => ':count n tsaɛtin', + + 'minute' => ':count n tedqiqin', + 'min' => ':count n tedqiqin', + 'a_minute' => ':count n tedqiqin', + + 'second' => ':count tasdidt', // less reliable + 's' => ':count tasdidt', // less reliable + 'a_second' => ':count tasdidt', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/kam.php b/vendor/nesbot/carbon/src/Carbon/Lang/kam.php new file mode 100644 index 0000000..0fc70d7 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/kam.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['Ĩyakwakya', 'Ĩyawĩoo'], + 'weekdays' => ['Wa kyumwa', 'Wa kwambĩlĩlya', 'Wa kelĩ', 'Wa katatũ', 'Wa kana', 'Wa katano', 'Wa thanthatũ'], + 'weekdays_short' => ['Wky', 'Wkw', 'Wkl', 'Wtũ', 'Wkn', 'Wtn', 'Wth'], + 'weekdays_min' => ['Wky', 'Wkw', 'Wkl', 'Wtũ', 'Wkn', 'Wtn', 'Wth'], + 'months' => ['Mwai wa mbee', 'Mwai wa kelĩ', 'Mwai wa katatũ', 'Mwai wa kana', 'Mwai wa katano', 'Mwai wa thanthatũ', 'Mwai wa muonza', 'Mwai wa nyaanya', 'Mwai wa kenda', 'Mwai wa ĩkumi', 'Mwai wa ĩkumi na ĩmwe', 'Mwai wa ĩkumi na ilĩ'], + 'months_short' => ['Mbe', 'Kel', 'Ktũ', 'Kan', 'Ktn', 'Tha', 'Moo', 'Nya', 'Knd', 'Ĩku', 'Ĩkm', 'Ĩkl'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + + // Too unreliable + /* + 'year' => ':count mbua', // less reliable + 'y' => ':count mbua', // less reliable + 'a_year' => ':count mbua', // less reliable + + 'month' => ':count ndakitali', // less reliable + 'm' => ':count ndakitali', // less reliable + 'a_month' => ':count ndakitali', // less reliable + + 'day' => ':count wia', // less reliable + 'd' => ':count wia', // less reliable + 'a_day' => ':count wia', // less reliable + + 'hour' => ':count orasan', // less reliable + 'h' => ':count orasan', // less reliable + 'a_hour' => ':count orasan', // less reliable + + 'minute' => ':count orasan', // less reliable + 'min' => ':count orasan', // less reliable + 'a_minute' => ':count orasan', // less reliable + */ +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/kde.php b/vendor/nesbot/carbon/src/Carbon/Lang/kde.php new file mode 100644 index 0000000..fbcc9f3 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/kde.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['Muhi', 'Chilo'], + 'weekdays' => ['Liduva lyapili', 'Liduva lyatatu', 'Liduva lyanchechi', 'Liduva lyannyano', 'Liduva lyannyano na linji', 'Liduva lyannyano na mavili', 'Liduva litandi'], + 'weekdays_short' => ['Ll2', 'Ll3', 'Ll4', 'Ll5', 'Ll6', 'Ll7', 'Ll1'], + 'weekdays_min' => ['Ll2', 'Ll3', 'Ll4', 'Ll5', 'Ll6', 'Ll7', 'Ll1'], + 'months' => ['Mwedi Ntandi', 'Mwedi wa Pili', 'Mwedi wa Tatu', 'Mwedi wa Nchechi', 'Mwedi wa Nnyano', 'Mwedi wa Nnyano na Umo', 'Mwedi wa Nnyano na Mivili', 'Mwedi wa Nnyano na Mitatu', 'Mwedi wa Nnyano na Nchechi', 'Mwedi wa Nnyano na Nnyano', 'Mwedi wa Nnyano na Nnyano na U', 'Mwedi wa Nnyano na Nnyano na M'], + 'months_short' => ['Jan', 'Feb', 'Mac', 'Apr', 'Mei', 'Jun', 'Jul', 'Ago', 'Sep', 'Okt', 'Nov', 'Des'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/kea.php b/vendor/nesbot/carbon/src/Carbon/Lang/kea.php new file mode 100644 index 0000000..8b6c21b --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/kea.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['a', 'p'], + 'weekdays' => ['dumingu', 'sigunda-fera', 'tersa-fera', 'kuarta-fera', 'kinta-fera', 'sesta-fera', 'sabadu'], + 'weekdays_short' => ['dum', 'sig', 'ter', 'kua', 'kin', 'ses', 'sab'], + 'weekdays_min' => ['du', 'si', 'te', 'ku', 'ki', 'se', 'sa'], + 'weekdays_standalone' => ['dumingu', 'sigunda-fera', 'tersa-fera', 'kuarta-fera', 'kinta-fera', 'sesta-fera', 'sábadu'], + 'months' => ['Janeru', 'Febreru', 'Marsu', 'Abril', 'Maiu', 'Junhu', 'Julhu', 'Agostu', 'Setenbru', 'Otubru', 'Nuvenbru', 'Dizenbru'], + 'months_short' => ['Jan', 'Feb', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Otu', 'Nuv', 'Diz'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D [di] MMMM [di] YYYY HH:mm', + 'LLLL' => 'dddd, D [di] MMMM [di] YYYY HH:mm', + ], + + 'year' => ':count otunu', // less reliable + 'y' => ':count otunu', // less reliable + 'a_year' => ':count otunu', // less reliable + + 'week' => ':count día dumingu', // less reliable + 'w' => ':count día dumingu', // less reliable + 'a_week' => ':count día dumingu', // less reliable + + 'day' => ':count diâ', // less reliable + 'd' => ':count diâ', // less reliable + 'a_day' => ':count diâ', // less reliable + + 'minute' => ':count sugundu', // less reliable + 'min' => ':count sugundu', // less reliable + 'a_minute' => ':count sugundu', // less reliable + + 'second' => ':count dós', // less reliable + 's' => ':count dós', // less reliable + 'a_second' => ':count dós', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/khq.php b/vendor/nesbot/carbon/src/Carbon/Lang/khq.php new file mode 100644 index 0000000..7a834cf --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/khq.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['Adduha', 'Aluula'], + 'weekdays' => ['Alhadi', 'Atini', 'Atalata', 'Alarba', 'Alhamiisa', 'Aljuma', 'Assabdu'], + 'weekdays_short' => ['Alh', 'Ati', 'Ata', 'Ala', 'Alm', 'Alj', 'Ass'], + 'weekdays_min' => ['Alh', 'Ati', 'Ata', 'Ala', 'Alm', 'Alj', 'Ass'], + 'months' => ['Žanwiye', 'Feewiriye', 'Marsi', 'Awiril', 'Me', 'Žuweŋ', 'Žuyye', 'Ut', 'Sektanbur', 'Oktoobur', 'Noowanbur', 'Deesanbur'], + 'months_short' => ['Žan', 'Fee', 'Mar', 'Awi', 'Me', 'Žuw', 'Žuy', 'Ut', 'Sek', 'Okt', 'Noo', 'Dee'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM, YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ki.php b/vendor/nesbot/carbon/src/Carbon/Lang/ki.php new file mode 100644 index 0000000..d86afc5 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ki.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['Kiroko', 'Hwaĩ-inĩ'], + 'weekdays' => ['Kiumia', 'Njumatatũ', 'Njumaine', 'Njumatana', 'Aramithi', 'Njumaa', 'Njumamothi'], + 'weekdays_short' => ['KMA', 'NTT', 'NMN', 'NMT', 'ART', 'NMA', 'NMM'], + 'weekdays_min' => ['KMA', 'NTT', 'NMN', 'NMT', 'ART', 'NMA', 'NMM'], + 'months' => ['Njenuarĩ', 'Mwere wa kerĩ', 'Mwere wa gatatũ', 'Mwere wa kana', 'Mwere wa gatano', 'Mwere wa gatandatũ', 'Mwere wa mũgwanja', 'Mwere wa kanana', 'Mwere wa kenda', 'Mwere wa ikũmi', 'Mwere wa ikũmi na ũmwe', 'Ndithemba'], + 'months_short' => ['JEN', 'WKR', 'WGT', 'WKN', 'WTN', 'WTD', 'WMJ', 'WNN', 'WKD', 'WIK', 'WMW', 'DIT'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + + 'year' => ':count mĩaka', // less reliable + 'y' => ':count mĩaka', // less reliable + 'a_year' => ':count mĩaka', // less reliable + + 'month' => ':count mweri', // less reliable + 'm' => ':count mweri', // less reliable + 'a_month' => ':count mweri', // less reliable + + 'week' => ':count kiumia', // less reliable + 'w' => ':count kiumia', // less reliable + 'a_week' => ':count kiumia', // less reliable + + 'day' => ':count mũthenya', // less reliable + 'd' => ':count mũthenya', // less reliable + 'a_day' => ':count mũthenya', // less reliable + + 'hour' => ':count thaa', // less reliable + 'h' => ':count thaa', // less reliable + 'a_hour' => ':count thaa', // less reliable + + 'minute' => ':count mundu', // less reliable + 'min' => ':count mundu', // less reliable + 'a_minute' => ':count mundu', // less reliable + + 'second' => ':count igego', // less reliable + 's' => ':count igego', // less reliable + 'a_second' => ':count igego', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/kk.php b/vendor/nesbot/carbon/src/Carbon/Lang/kk.php new file mode 100644 index 0000000..59fa9af --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/kk.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - François B + * - Talat Uspanov + * - Нурлан Рахимжанов + * - Toleugazy Kali + */ +return [ + 'year' => ':count жыл', + 'a_year' => '{1}бір жыл|:count жыл', + 'y' => ':count ж.', + 'month' => ':count ай', + 'a_month' => '{1}бір ай|:count ай', + 'm' => ':count ай', + 'week' => ':count апта', + 'a_week' => '{1}бір апта', + 'w' => ':count ап.', + 'day' => ':count күн', + 'a_day' => '{1}бір күн|:count күн', + 'd' => ':count к.', + 'hour' => ':count сағат', + 'a_hour' => '{1}бір сағат|:count сағат', + 'h' => ':count са.', + 'minute' => ':count минут', + 'a_minute' => '{1}бір минут|:count минут', + 'min' => ':count м.', + 'second' => ':count секунд', + 'a_second' => '{1}бірнеше секунд|:count секунд', + 's' => ':count се.', + 'ago' => ':time бұрын', + 'from_now' => ':time ішінде', + 'after' => ':time кейін', + 'before' => ':time бұрын', + 'diff_now' => 'қазір', + 'diff_today' => 'Бүгін', + 'diff_today_regexp' => 'Бүгін(?:\\s+сағат)?', + 'diff_yesterday' => 'кеше', + 'diff_yesterday_regexp' => 'Кеше(?:\\s+сағат)?', + 'diff_tomorrow' => 'ертең', + 'diff_tomorrow_regexp' => 'Ертең(?:\\s+сағат)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Бүгін сағат] LT', + 'nextDay' => '[Ертең сағат] LT', + 'nextWeek' => 'dddd [сағат] LT', + 'lastDay' => '[Кеше сағат] LT', + 'lastWeek' => '[Өткен аптаның] dddd [сағат] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + static $suffixes = [ + 0 => '-ші', + 1 => '-ші', + 2 => '-ші', + 3 => '-ші', + 4 => '-ші', + 5 => '-ші', + 6 => '-шы', + 7 => '-ші', + 8 => '-ші', + 9 => '-шы', + 10 => '-шы', + 20 => '-шы', + 30 => '-шы', + 40 => '-шы', + 50 => '-ші', + 60 => '-шы', + 70 => '-ші', + 80 => '-ші', + 90 => '-шы', + 100 => '-ші', + ]; + + return $number.($suffixes[$number] ?? $suffixes[$number % 10] ?? $suffixes[$number >= 100 ? 100 : -1] ?? ''); + }, + 'months' => ['қаңтар', 'ақпан', 'наурыз', 'сәуір', 'мамыр', 'маусым', 'шілде', 'тамыз', 'қыркүйек', 'қазан', 'қараша', 'желтоқсан'], + 'months_short' => ['қаң', 'ақп', 'нау', 'сәу', 'мам', 'мау', 'шіл', 'там', 'қыр', 'қаз', 'қар', 'жел'], + 'weekdays' => ['жексенбі', 'дүйсенбі', 'сейсенбі', 'сәрсенбі', 'бейсенбі', 'жұма', 'сенбі'], + 'weekdays_short' => ['жек', 'дүй', 'сей', 'сәр', 'бей', 'жұм', 'сен'], + 'weekdays_min' => ['жк', 'дй', 'сй', 'ср', 'бй', 'жм', 'сн'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' және '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/kk_KZ.php b/vendor/nesbot/carbon/src/Carbon/Lang/kk_KZ.php new file mode 100644 index 0000000..7dc5ebc --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/kk_KZ.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__.'/kk.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/kkj.php b/vendor/nesbot/carbon/src/Carbon/Lang/kkj.php new file mode 100644 index 0000000..6a1e77a --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/kkj.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/kl.php b/vendor/nesbot/carbon/src/Carbon/Lang/kl.php new file mode 100644 index 0000000..7329a07 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/kl.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/kl_GL.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/kl_GL.php b/vendor/nesbot/carbon/src/Carbon/Lang/kl_GL.php new file mode 100644 index 0000000..4fed720 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/kl_GL.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Danish Standards Association bug-glibc-locales@gnu.org + * - John Eyðstein Johannesen (mashema) + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D. MMMM YYYY', + 'LLL' => 'D. MMMM YYYY HH:mm', + 'LLLL' => 'dddd [d.] D. MMMM YYYY [kl.] HH:mm', + ], + 'months' => ['januaarip', 'februaarip', 'marsip', 'apriilip', 'maajip', 'juunip', 'juulip', 'aggustip', 'septembarip', 'oktobarip', 'novembarip', 'decembarip'], + 'months_short' => ['jan', 'feb', 'mar', 'apr', 'maj', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'], + 'weekdays' => ['sapaat', 'ataasinngorneq', 'marlunngorneq', 'pingasunngorneq', 'sisamanngorneq', 'tallimanngorneq', 'arfininngorneq'], + 'weekdays_short' => ['sap', 'ata', 'mar', 'pin', 'sis', 'tal', 'arf'], + 'weekdays_min' => ['sap', 'ata', 'mar', 'pin', 'sis', 'tal', 'arf'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + + 'year' => '{1}ukioq :count|{0}:count ukiut|]1,Inf[ukiut :count', + 'a_year' => '{1}ukioq|{0}:count ukiut|]1,Inf[ukiut :count', + 'y' => '{1}:countyr|{0}:countyrs|]1,Inf[:countyrs', + + 'month' => '{1}qaammat :count|{0}:count qaammatit|]1,Inf[qaammatit :count', + 'a_month' => '{1}qaammat|{0}:count qaammatit|]1,Inf[qaammatit :count', + 'm' => '{1}:countmo|{0}:countmos|]1,Inf[:countmos', + + 'week' => '{1}:count sap. ak.|{0}:count sap. ak.|]1,Inf[:count sap. ak.', + 'a_week' => '{1}a sap. ak.|{0}:count sap. ak.|]1,Inf[:count sap. ak.', + 'w' => ':countw', + + 'day' => '{1}:count ulloq|{0}:count ullut|]1,Inf[:count ullut', + 'a_day' => '{1}a ulloq|{0}:count ullut|]1,Inf[:count ullut', + 'd' => ':countd', + + 'hour' => '{1}:count tiimi|{0}:count tiimit|]1,Inf[:count tiimit', + 'a_hour' => '{1}tiimi|{0}:count tiimit|]1,Inf[:count tiimit', + 'h' => ':counth', + + 'minute' => '{1}:count minutsi|{0}:count minutsit|]1,Inf[:count minutsit', + 'a_minute' => '{1}a minutsi|{0}:count minutsit|]1,Inf[:count minutsit', + 'min' => ':countm', + + 'second' => '{1}:count sikunti|{0}:count sikuntit|]1,Inf[:count sikuntit', + 'a_second' => '{1}sikunti|{0}:count sikuntit|]1,Inf[:count sikuntit', + 's' => ':counts', + + 'ago' => ':time matuma siorna', + +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/kln.php b/vendor/nesbot/carbon/src/Carbon/Lang/kln.php new file mode 100644 index 0000000..b9c3996 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/kln.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_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['krn', 'koosk'], + 'weekdays' => ['Kotisap', 'Kotaai', 'Koaeng’', 'Kosomok', 'Koang’wan', 'Komuut', 'Kolo'], + 'weekdays_short' => ['Kts', 'Kot', 'Koo', 'Kos', 'Koa', 'Kom', 'Kol'], + 'weekdays_min' => ['Kts', 'Kot', 'Koo', 'Kos', 'Koa', 'Kom', 'Kol'], + 'months' => ['Mulgul', 'Ng’atyaato', 'Kiptaamo', 'Iwootkuut', 'Mamuut', 'Paagi', 'Ng’eiyeet', 'Rooptui', 'Bureet', 'Epeeso', 'Kipsuunde ne taai', 'Kipsuunde nebo aeng’'], + 'months_short' => ['Mul', 'Ngat', 'Taa', 'Iwo', 'Mam', 'Paa', 'Nge', 'Roo', 'Bur', 'Epe', 'Kpt', 'Kpa'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + + 'year' => ':count maghatiat', // less reliable + 'y' => ':count maghatiat', // less reliable + 'a_year' => ':count maghatiat', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/km.php b/vendor/nesbot/carbon/src/Carbon/Lang/km.php new file mode 100644 index 0000000..da790ac --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/km.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Kruy Vanna + * - Sereysethy Touch + * - JD Isaacks + * - Sovichet Tep + */ +return [ + 'year' => '{1}មួយឆ្នាំ|]1,Inf[:count ឆ្នាំ', + 'y' => ':count ឆ្នាំ', + 'month' => '{1}មួយខែ|]1,Inf[:count ខែ', + 'm' => ':count ខែ', + 'week' => ':count សប្ដាហ៍', + 'w' => ':count សប្ដាហ៍', + 'day' => '{1}មួយថ្ងៃ|]1,Inf[:count ថ្ងៃ', + 'd' => ':count ថ្ងៃ', + 'hour' => '{1}មួយម៉ោង|]1,Inf[:count ម៉ោង', + 'h' => ':count ម៉ោង', + 'minute' => '{1}មួយនាទី|]1,Inf[:count នាទី', + 'min' => ':count នាទី', + 'second' => '{1}ប៉ុន្មានវិនាទី|]1,Inf[:count វិនាទី', + 's' => ':count វិនាទី', + 'ago' => ':timeមុន', + 'from_now' => ':timeទៀត', + 'after' => 'នៅ​ក្រោយ :time', + 'before' => 'នៅ​មុន :time', + 'diff_now' => 'ឥឡូវ', + 'diff_today' => 'ថ្ងៃនេះ', + 'diff_today_regexp' => 'ថ្ងៃនេះ(?:\\s+ម៉ោង)?', + 'diff_yesterday' => 'ម្សិលមិញ', + 'diff_yesterday_regexp' => 'ម្សិលមិញ(?:\\s+ម៉ោង)?', + 'diff_tomorrow' => 'ថ្ងៃ​ស្អែក', + 'diff_tomorrow_regexp' => 'ស្អែក(?:\\s+ម៉ោង)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[ថ្ងៃនេះ ម៉ោង] LT', + 'nextDay' => '[ស្អែក ម៉ោង] LT', + 'nextWeek' => 'dddd [ម៉ោង] LT', + 'lastDay' => '[ម្សិលមិញ ម៉ោង] LT', + 'lastWeek' => 'dddd [សប្តាហ៍មុន] [ម៉ោង] LT', + 'sameElse' => 'L', + ], + 'ordinal' => 'ទី:number', + 'meridiem' => ['ព្រឹក', 'ល្ងាច'], + 'months' => ['មករា', 'កុម្ភៈ', 'មីនា', 'មេសា', 'ឧសភា', 'មិថុនា', 'កក្កដា', 'សីហា', 'កញ្ញា', 'តុលា', 'វិច្ឆិកា', 'ធ្នូ'], + 'months_short' => ['មករា', 'កុម្ភៈ', 'មីនា', 'មេសា', 'ឧសភា', 'មិថុនា', 'កក្កដា', 'សីហា', 'កញ្ញា', 'តុលា', 'វិច្ឆិកា', 'ធ្នូ'], + 'weekdays' => ['អាទិត្យ', 'ច័ន្ទ', 'អង្គារ', 'ពុធ', 'ព្រហស្បតិ៍', 'សុក្រ', 'សៅរ៍'], + 'weekdays_short' => ['អា', 'ច', 'អ', 'ព', 'ព្រ', 'សុ', 'ស'], + 'weekdays_min' => ['អា', 'ច', 'អ', 'ព', 'ព្រ', 'សុ', 'ស'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', 'និង '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/km_KH.php b/vendor/nesbot/carbon/src/Carbon/Lang/km_KH.php new file mode 100644 index 0000000..92e5fdb --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/km_KH.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__.'/km.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/kn.php b/vendor/nesbot/carbon/src/Carbon/Lang/kn.php new file mode 100644 index 0000000..0d2ad08 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/kn.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - MOHAN M U + * - François B + * - rajeevnaikte + */ +return [ + 'year' => '{1}ಒಂದು ವರ್ಷ|]1,Inf[:count ವರ್ಷ', + 'month' => '{1}ಒಂದು ತಿಂಗಳು|]1,Inf[:count ತಿಂಗಳು', + 'week' => '{1}ಒಂದು ವಾರ|]1,Inf[:count ವಾರಗಳು', + 'day' => '{1}ಒಂದು ದಿನ|]1,Inf[:count ದಿನ', + 'hour' => '{1}ಒಂದು ಗಂಟೆ|]1,Inf[:count ಗಂಟೆ', + 'minute' => '{1}ಒಂದು ನಿಮಿಷ|]1,Inf[:count ನಿಮಿಷ', + 'second' => '{1}ಕೆಲವು ಕ್ಷಣಗಳು|]1,Inf[:count ಸೆಕೆಂಡುಗಳು', + 'ago' => ':time ಹಿಂದೆ', + 'from_now' => ':time ನಂತರ', + 'diff_now' => 'ಈಗ', + 'diff_today' => 'ಇಂದು', + 'diff_yesterday' => 'ನಿನ್ನೆ', + 'diff_tomorrow' => 'ನಾಳೆ', + 'formats' => [ + 'LT' => 'A h:mm', + 'LTS' => 'A h:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY, A h:mm', + 'LLLL' => 'dddd, D MMMM YYYY, A h:mm', + ], + 'calendar' => [ + 'sameDay' => '[ಇಂದು] LT', + 'nextDay' => '[ನಾಳೆ] LT', + 'nextWeek' => 'dddd, LT', + 'lastDay' => '[ನಿನ್ನೆ] LT', + 'lastWeek' => '[ಕೊನೆಯ] dddd, LT', + 'sameElse' => 'L', + ], + 'ordinal' => ':numberನೇ', + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'ರಾತ್ರಿ'; + } + if ($hour < 10) { + return 'ಬೆಳಿಗ್ಗೆ'; + } + if ($hour < 17) { + return 'ಮಧ್ಯಾಹ್ನ'; + } + if ($hour < 20) { + return 'ಸಂಜೆ'; + } + + return 'ರಾತ್ರಿ'; + }, + 'months' => ['ಜನವರಿ', 'ಫೆಬ್ರವರಿ', 'ಮಾರ್ಚ್', 'ಏಪ್ರಿಲ್', 'ಮೇ', 'ಜೂನ್', 'ಜುಲೈ', 'ಆಗಸ್ಟ್', 'ಸೆಪ್ಟೆಂಬರ್', 'ಅಕ್ಟೋಬರ್', 'ನವೆಂಬರ್', 'ಡಿಸೆಂಬರ್'], + 'months_short' => ['ಜನ', 'ಫೆಬ್ರ', 'ಮಾರ್ಚ್', 'ಏಪ್ರಿಲ್', 'ಮೇ', 'ಜೂನ್', 'ಜುಲೈ', 'ಆಗಸ್ಟ್', 'ಸೆಪ್ಟೆಂ', 'ಅಕ್ಟೋ', 'ನವೆಂ', 'ಡಿಸೆಂ'], + 'weekdays' => ['ಭಾನುವಾರ', 'ಸೋಮವಾರ', 'ಮಂಗಳವಾರ', 'ಬುಧವಾರ', 'ಗುರುವಾರ', 'ಶುಕ್ರವಾರ', 'ಶನಿವಾರ'], + 'weekdays_short' => ['ಭಾನು', 'ಸೋಮ', 'ಮಂಗಳ', 'ಬುಧ', 'ಗುರು', 'ಶುಕ್ರ', 'ಶನಿ'], + 'weekdays_min' => ['ಭಾ', 'ಸೋ', 'ಮಂ', 'ಬು', 'ಗು', 'ಶು', 'ಶ'], + 'list' => ', ', + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, + 'weekend' => [0, 0], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/kn_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/kn_IN.php new file mode 100644 index 0000000..30e3d88 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/kn_IN.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__.'/kn.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ko.php b/vendor/nesbot/carbon/src/Carbon/Lang/ko.php new file mode 100644 index 0000000..4fa6237 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ko.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Kunal Marwaha + * - FourwingsY + * - François B + * - Jason Katz-Brown + * - Seokjun Kim + * - Junho Kim + * - JD Isaacks + * - Juwon Kim + */ +return [ + 'year' => ':count년', + 'a_year' => '{1}일년|]1,Inf[:count년', + 'y' => ':count년', + 'month' => ':count개월', + 'a_month' => '{1}한달|]1,Inf[:count개월', + 'm' => ':count개월', + 'week' => ':count주', + 'a_week' => '{1}일주일|]1,Inf[:count 주', + 'w' => ':count주일', + 'day' => ':count일', + 'a_day' => '{1}하루|]1,Inf[:count일', + 'd' => ':count일', + 'hour' => ':count시간', + 'a_hour' => '{1}한시간|]1,Inf[:count시간', + 'h' => ':count시간', + 'minute' => ':count분', + 'a_minute' => '{1}일분|]1,Inf[:count분', + 'min' => ':count분', + 'second' => ':count초', + 'a_second' => '{1}몇초|]1,Inf[:count초', + 's' => ':count초', + 'ago' => ':time 전', + 'from_now' => ':time 후', + 'after' => ':time 후', + 'before' => ':time 전', + 'diff_now' => '지금', + 'diff_today' => '오늘', + 'diff_yesterday' => '어제', + 'diff_tomorrow' => '내일', + 'formats' => [ + 'LT' => 'A h:mm', + 'LTS' => 'A h:mm:ss', + 'L' => 'YYYY.MM.DD.', + 'LL' => 'YYYY년 MMMM D일', + 'LLL' => 'YYYY년 MMMM D일 A h:mm', + 'LLLL' => 'YYYY년 MMMM D일 dddd A h:mm', + ], + 'calendar' => [ + 'sameDay' => '오늘 LT', + 'nextDay' => '내일 LT', + 'nextWeek' => 'dddd LT', + 'lastDay' => '어제 LT', + 'lastWeek' => '지난주 dddd LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number, $period) { + switch ($period) { + case 'd': + case 'D': + case 'DDD': + return $number.'일'; + case 'M': + return $number.'월'; + case 'w': + case 'W': + return $number.'주'; + default: + return $number; + } + }, + 'meridiem' => ['오전', '오후'], + 'months' => ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'], + 'months_short' => ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'], + 'weekdays' => ['일요일', '월요일', '화요일', '수요일', '목요일', '금요일', '토요일'], + 'weekdays_short' => ['일', '월', '화', '수', '목', '금', '토'], + 'weekdays_min' => ['일', '월', '화', '수', '목', '금', '토'], + 'list' => ' ', +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ko_KP.php b/vendor/nesbot/carbon/src/Carbon/Lang/ko_KP.php new file mode 100644 index 0000000..4ba802b --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ko_KP.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ko.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ko_KR.php b/vendor/nesbot/carbon/src/Carbon/Lang/ko_KR.php new file mode 100644 index 0000000..9d873a2 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ko_KR.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__.'/ko.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/kok.php b/vendor/nesbot/carbon/src/Carbon/Lang/kok.php new file mode 100644 index 0000000..4adcddc --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/kok.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/kok_IN.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/kok_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/kok_IN.php new file mode 100644 index 0000000..92ba844 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/kok_IN.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Red Hat, Pune bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D-M-YY', + ], + 'months' => ['जानेवारी', 'फेब्रुवारी', 'मार्च', 'एप्रिल', 'मे', 'जून', 'जुलै', 'ओगस्ट', 'सेप्टेंबर', 'ओक्टोबर', 'नोव्हेंबर', 'डिसेंबर'], + 'months_short' => ['जानेवारी', 'फेब्रुवारी', 'मार्च', 'एप्रिल', 'मे', 'जून', 'जुलै', 'ओगस्ट', 'सेप्टेंबर', 'ओक्टोबर', 'नोव्हेंबर', 'डिसेंबर'], + 'weekdays' => ['आयतार', 'सोमार', 'मंगळवार', 'बुधवार', 'बेरेसतार', 'शुकरार', 'शेनवार'], + 'weekdays_short' => ['आयतार', 'सोमार', 'मंगळवार', 'बुधवार', 'बेरेसतार', 'शुकरार', 'शेनवार'], + 'weekdays_min' => ['आयतार', 'सोमार', 'मंगळवार', 'बुधवार', 'बेरेसतार', 'शुकरार', 'शेनवार'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['म.पू.', 'म.नं.'], + + 'year' => ':count वैशाकु', // less reliable + 'y' => ':count वैशाकु', // less reliable + 'a_year' => ':count वैशाकु', // less reliable + + 'week' => ':count आदित्यवार', // less reliable + 'w' => ':count आदित्यवार', // less reliable + 'a_week' => ':count आदित्यवार', // less reliable + + 'minute' => ':count नोंद', // less reliable + 'min' => ':count नोंद', // less reliable + 'a_minute' => ':count नोंद', // less reliable + + 'second' => ':count तेंको', // less reliable + 's' => ':count तेंको', // less reliable + 'a_second' => ':count तेंको', // less reliable + + 'month' => ':count मैनो', + 'm' => ':count मैनो', + 'a_month' => ':count मैनो', + + 'day' => ':count दिवसु', + 'd' => ':count दिवसु', + 'a_day' => ':count दिवसु', + + 'hour' => ':count घंते', + 'h' => ':count घंते', + 'a_hour' => ':count घंते', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ks.php b/vendor/nesbot/carbon/src/Carbon/Lang/ks.php new file mode 100644 index 0000000..9876079 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ks.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/ks_IN.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ks_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/ks_IN.php new file mode 100644 index 0000000..ce9d5d4 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ks_IN.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Red Hat, Pune bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'M/D/YY', + ], + 'months' => ['جنؤری', 'فرؤری', 'مارٕچ', 'اپریل', 'میٔ', 'جوٗن', 'جوٗلایی', 'اگست', 'ستمبر', 'اکتوٗبر', 'نومبر', 'دسمبر'], + 'months_short' => ['جنؤری', 'فرؤری', 'مارٕچ', 'اپریل', 'میٔ', 'جوٗن', 'جوٗلایی', 'اگست', 'ستمبر', 'اکتوٗبر', 'نومبر', 'دسمبر'], + 'weekdays' => ['آتهوار', 'ژءندروار', 'بوءںوار', 'بودهوار', 'برىسوار', 'جمع', 'بٹوار'], + 'weekdays_short' => ['آتهوار', 'ژءنتروار', 'بوءںوار', 'بودهوار', 'برىسوار', 'جمع', 'بٹوار'], + 'weekdays_min' => ['آتهوار', 'ژءنتروار', 'بوءںوار', 'بودهوار', 'برىسوار', 'جمع', 'بٹوار'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['دوپھربرونھ', 'دوپھرپتھ'], + + 'year' => ':count آب', // less reliable + 'y' => ':count آب', // less reliable + 'a_year' => ':count آب', // less reliable + + 'month' => ':count रान्', // less reliable + 'm' => ':count रान्', // less reliable + 'a_month' => ':count रान्', // less reliable + + 'week' => ':count آتھٕوار', // less reliable + 'w' => ':count آتھٕوار', // less reliable + 'a_week' => ':count آتھٕوار', // less reliable + + 'hour' => ':count سۄن', // less reliable + 'h' => ':count سۄن', // less reliable + 'a_hour' => ':count سۄن', // less reliable + + 'minute' => ':count فَن', // less reliable + 'min' => ':count فَن', // less reliable + 'a_minute' => ':count فَن', // less reliable + + 'second' => ':count दोʼयुम', // less reliable + 's' => ':count दोʼयुम', // less reliable + 'a_second' => ':count दोʼयुम', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ks_IN@devanagari.php b/vendor/nesbot/carbon/src/Carbon/Lang/ks_IN@devanagari.php new file mode 100644 index 0000000..a2ae8b6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ks_IN@devanagari.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - ks-gnome-trans-commits@lists.code.indlinux.net + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'M/D/YY', + ], + 'months' => ['जनवरी', 'फ़रवरी', 'मार्च', 'अप्रेल', 'मई', 'जून', 'जुलाई', 'अगस्त', 'सितम्बर', 'अक्टूबर', 'नवम्बर', 'दिसम्बर'], + 'months_short' => ['जनवरी', 'फ़रवरी', 'मार्च', 'अप्रेल', 'मई', 'जून', 'जुलाई', 'अगस्त', 'सितम्बर', 'अक्टूबर', 'नवम्बर', 'दिसम्बर'], + 'weekdays' => ['आथवार', 'चॅ़दुरवार', 'बोमवार', 'ब्वदवार', 'ब्रसवार', 'शोकुरवार', 'बटुवार'], + 'weekdays_short' => ['आथ ', 'चॅ़दुर', 'बोम', 'ब्वद', 'ब्रस', 'शोकुर', 'बटु'], + 'weekdays_min' => ['आथ ', 'चॅ़दुर', 'बोम', 'ब्वद', 'ब्रस', 'शोकुर', 'बटु'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['पूर्वाह्न', 'अपराह्न'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ksb.php b/vendor/nesbot/carbon/src/Carbon/Lang/ksb.php new file mode 100644 index 0000000..aaa0061 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ksb.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['makeo', 'nyiaghuo'], + 'weekdays' => ['Jumaapii', 'Jumaatatu', 'Jumaane', 'Jumaatano', 'Alhamisi', 'Ijumaa', 'Jumaamosi'], + 'weekdays_short' => ['Jpi', 'Jtt', 'Jmn', 'Jtn', 'Alh', 'Iju', 'Jmo'], + 'weekdays_min' => ['Jpi', 'Jtt', 'Jmn', 'Jtn', 'Alh', 'Iju', 'Jmo'], + 'months' => ['Januali', 'Febluali', 'Machi', 'Aplili', 'Mei', 'Juni', 'Julai', 'Agosti', 'Septemba', 'Oktoba', 'Novemba', 'Desemba'], + 'months_short' => ['Jan', 'Feb', 'Mac', 'Apr', 'Mei', 'Jun', 'Jul', 'Ago', 'Sep', 'Okt', 'Nov', 'Des'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ksf.php b/vendor/nesbot/carbon/src/Carbon/Lang/ksf.php new file mode 100644 index 0000000..84a5967 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ksf.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['sárúwá', 'cɛɛ́nko'], + 'weekdays' => ['sɔ́ndǝ', 'lǝndí', 'maadí', 'mɛkrɛdí', 'jǝǝdí', 'júmbá', 'samdí'], + 'weekdays_short' => ['sɔ́n', 'lǝn', 'maa', 'mɛk', 'jǝǝ', 'júm', 'sam'], + 'weekdays_min' => ['sɔ́n', 'lǝn', 'maa', 'mɛk', 'jǝǝ', 'júm', 'sam'], + 'months' => ['ŋwíí a ntɔ́ntɔ', 'ŋwíí akǝ bɛ́ɛ', 'ŋwíí akǝ ráá', 'ŋwíí akǝ nin', 'ŋwíí akǝ táan', 'ŋwíí akǝ táafɔk', 'ŋwíí akǝ táabɛɛ', 'ŋwíí akǝ táaraa', 'ŋwíí akǝ táanin', 'ŋwíí akǝ ntɛk', 'ŋwíí akǝ ntɛk di bɔ́k', 'ŋwíí akǝ ntɛk di bɛ́ɛ'], + 'months_short' => ['ŋ1', 'ŋ2', 'ŋ3', 'ŋ4', 'ŋ5', 'ŋ6', 'ŋ7', 'ŋ8', 'ŋ9', 'ŋ10', 'ŋ11', 'ŋ12'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ksh.php b/vendor/nesbot/carbon/src/Carbon/Lang/ksh.php new file mode 100644 index 0000000..95457e2 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ksh.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['v.M.', 'n.M.'], + 'weekdays' => ['Sunndaach', 'Mohndaach', 'Dinnsdaach', 'Metwoch', 'Dunnersdaach', 'Friidaach', 'Samsdaach'], + 'weekdays_short' => ['Su.', 'Mo.', 'Di.', 'Me.', 'Du.', 'Fr.', 'Sa.'], + 'weekdays_min' => ['Su', 'Mo', 'Di', 'Me', 'Du', 'Fr', 'Sa'], + 'months' => ['Jannewa', 'Fäbrowa', 'Määz', 'Aprell', 'Mai', 'Juuni', 'Juuli', 'Oujoß', 'Septämber', 'Oktohber', 'Novämber', 'Dezämber'], + 'months_short' => ['Jan', 'Fäb', 'Mäz', 'Apr', 'Mai', 'Jun', 'Jul', 'Ouj', 'Säp', 'Okt', 'Nov', 'Dez'], + 'months_short_standalone' => ['Jan.', 'Fäb.', 'Mäz.', 'Apr.', 'Mai', 'Jun.', 'Jul.', 'Ouj.', 'Säp.', 'Okt.', 'Nov.', 'Dez.'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D. M. YYYY', + 'LL' => 'D. MMM. YYYY', + 'LLL' => 'D. MMMM YYYY HH:mm', + 'LLLL' => 'dddd, [dä] D. MMMM YYYY HH:mm', + ], + + 'year' => ':count Johr', + 'y' => ':count Johr', + 'a_year' => ':count Johr', + + 'month' => ':count Moohnd', + 'm' => ':count Moohnd', + 'a_month' => ':count Moohnd', + + 'week' => ':count woch', + 'w' => ':count woch', + 'a_week' => ':count woch', + + 'day' => ':count Daach', + 'd' => ':count Daach', + 'a_day' => ':count Daach', + + 'hour' => ':count Uhr', + 'h' => ':count Uhr', + 'a_hour' => ':count Uhr', + + 'minute' => ':count Menutt', + 'min' => ':count Menutt', + 'a_minute' => ':count Menutt', + + 'second' => ':count Sekůndt', + 's' => ':count Sekůndt', + 'a_second' => ':count Sekůndt', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ku.php b/vendor/nesbot/carbon/src/Carbon/Lang/ku.php new file mode 100644 index 0000000..189960c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ku.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Unicode, Inc. + */ + +return [ + 'ago' => 'berî :time', + 'from_now' => 'di :time de', + 'after' => ':time piştî', + 'before' => ':time berê', + 'year' => ':count sal', + 'year_ago' => ':count salê|:count salan', + 'year_from_now' => 'salekê|:count salan', + 'month' => ':count meh', + 'week' => ':count hefte', + 'day' => ':count roj', + 'hour' => ':count saet', + 'minute' => ':count deqîqe', + 'second' => ':count saniye', + 'months' => ['rêbendanê', 'reşemiyê', 'adarê', 'avrêlê', 'gulanê', 'pûşperê', 'tîrmehê', 'gelawêjê', 'rezberê', 'kewçêrê', 'sermawezê', 'berfanbarê'], + 'months_standalone' => ['rêbendan', 'reşemî', 'adar', 'avrêl', 'gulan', 'pûşper', 'tîrmeh', 'gelawêj', 'rezber', 'kewçêr', 'sermawez', 'berfanbar'], + 'months_short' => ['rêb', 'reş', 'ada', 'avr', 'gul', 'pûş', 'tîr', 'gel', 'rez', 'kew', 'ser', 'ber'], + 'weekdays' => ['yekşem', 'duşem', 'sêşem', 'çarşem', 'pêncşem', 'în', 'şemî'], + 'weekdays_short' => ['yş', 'dş', 'sş', 'çş', 'pş', 'în', 'ş'], + 'weekdays_min' => ['Y', 'D', 'S', 'Ç', 'P', 'Î', 'Ş'], + 'list' => [', ', ' û '], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ku_TR.php b/vendor/nesbot/carbon/src/Carbon/Lang/ku_TR.php new file mode 100644 index 0000000..4243a82 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ku_TR.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__.'/ku.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/kw.php b/vendor/nesbot/carbon/src/Carbon/Lang/kw.php new file mode 100644 index 0000000..26e242e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/kw.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/kw_GB.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/kw_GB.php b/vendor/nesbot/carbon/src/Carbon/Lang/kw_GB.php new file mode 100644 index 0000000..00bf52b --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/kw_GB.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Alastair McKinstry bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['mis Genver', 'mis Hwevrer', 'mis Meurth', 'mis Ebrel', 'mis Me', 'mis Metheven', 'mis Gortheren', 'mis Est', 'mis Gwynngala', 'mis Hedra', 'mis Du', 'mis Kevardhu'], + 'months_short' => ['Gen', 'Hwe', 'Meu', 'Ebr', 'Me', 'Met', 'Gor', 'Est', 'Gwn', 'Hed', 'Du', 'Kev'], + 'weekdays' => ['De Sul', 'De Lun', 'De Merth', 'De Merher', 'De Yow', 'De Gwener', 'De Sadorn'], + 'weekdays_short' => ['Sul', 'Lun', 'Mth', 'Mhr', 'Yow', 'Gwe', 'Sad'], + 'weekdays_min' => ['Sul', 'Lun', 'Mth', 'Mhr', 'Yow', 'Gwe', 'Sad'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'year' => ':count bledhen', + 'y' => ':count bledhen', + 'a_year' => ':count bledhen', + + 'month' => ':count mis', + 'm' => ':count mis', + 'a_month' => ':count mis', + + 'week' => ':count seythen', + 'w' => ':count seythen', + 'a_week' => ':count seythen', + + 'day' => ':count dydh', + 'd' => ':count dydh', + 'a_day' => ':count dydh', + + 'hour' => ':count eur', + 'h' => ':count eur', + 'a_hour' => ':count eur', + + 'minute' => ':count mynysen', + 'min' => ':count mynysen', + 'a_minute' => ':count mynysen', + + 'second' => ':count pryjwyth', + 's' => ':count pryjwyth', + 'a_second' => ':count pryjwyth', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ky.php b/vendor/nesbot/carbon/src/Carbon/Lang/ky.php new file mode 100644 index 0000000..e0d1af1 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ky.php @@ -0,0 +1,106 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - acutexyz + * - Josh Soref + * - François B + * - Chyngyz Arystan uulu + * - Chyngyz + * - acutexyz + * - Josh Soref + * - François B + * - Chyngyz Arystan uulu + */ +return [ + 'year' => ':count жыл', + 'a_year' => '{1}бир жыл|:count жыл', + 'y' => ':count жыл', + 'month' => ':count ай', + 'a_month' => '{1}бир ай|:count ай', + 'm' => ':count ай', + 'week' => ':count апта', + 'a_week' => '{1}бир апта|:count апта', + 'w' => ':count апт.', + 'day' => ':count күн', + 'a_day' => '{1}бир күн|:count күн', + 'd' => ':count күн', + 'hour' => ':count саат', + 'a_hour' => '{1}бир саат|:count саат', + 'h' => ':count саат.', + 'minute' => ':count мүнөт', + 'a_minute' => '{1}бир мүнөт|:count мүнөт', + 'min' => ':count мүн.', + 'second' => ':count секунд', + 'a_second' => '{1}бирнече секунд|:count секунд', + 's' => ':count сек.', + 'ago' => ':time мурун', + 'from_now' => ':time ичинде', + 'diff_now' => 'азыр', + 'diff_today' => 'Бүгүн', + 'diff_today_regexp' => 'Бүгүн(?:\\s+саат)?', + 'diff_yesterday' => 'кечээ', + 'diff_yesterday_regexp' => 'Кече(?:\\s+саат)?', + 'diff_tomorrow' => 'эртең', + 'diff_tomorrow_regexp' => 'Эртең(?:\\s+саат)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Бүгүн саат] LT', + 'nextDay' => '[Эртең саат] LT', + 'nextWeek' => 'dddd [саат] LT', + 'lastDay' => '[Кече саат] LT', + 'lastWeek' => '[Өткен аптанын] dddd [күнү] [саат] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + static $suffixes = [ + 0 => '-чү', + 1 => '-чи', + 2 => '-чи', + 3 => '-чү', + 4 => '-чү', + 5 => '-чи', + 6 => '-чы', + 7 => '-чи', + 8 => '-чи', + 9 => '-чу', + 10 => '-чу', + 20 => '-чы', + 30 => '-чу', + 40 => '-чы', + 50 => '-чү', + 60 => '-чы', + 70 => '-чи', + 80 => '-чи', + 90 => '-чу', + 100 => '-чү', + ]; + + return $number.($suffixes[$number] ?? $suffixes[$number % 10] ?? $suffixes[$number >= 100 ? 100 : -1] ?? ''); + }, + 'months' => ['январь', 'февраль', 'март', 'апрель', 'май', 'июнь', 'июль', 'август', 'сентябрь', 'октябрь', 'ноябрь', 'декабрь'], + 'months_short' => ['янв', 'фев', 'март', 'апр', 'май', 'июнь', 'июль', 'авг', 'сен', 'окт', 'ноя', 'дек'], + 'weekdays' => ['Жекшемби', 'Дүйшөмбү', 'Шейшемби', 'Шаршемби', 'Бейшемби', 'Жума', 'Ишемби'], + 'weekdays_short' => ['Жек', 'Дүй', 'Шей', 'Шар', 'Бей', 'Жум', 'Ише'], + 'weekdays_min' => ['Жк', 'Дй', 'Шй', 'Шр', 'Бй', 'Жм', 'Иш'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => ' ', + 'meridiem' => ['таңкы', 'түштөн кийинки'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ky_KG.php b/vendor/nesbot/carbon/src/Carbon/Lang/ky_KG.php new file mode 100644 index 0000000..9923a31 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ky_KG.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__.'/ky.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/lag.php b/vendor/nesbot/carbon/src/Carbon/Lang/lag.php new file mode 100644 index 0000000..f3f57f6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/lag.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['TOO', 'MUU'], + 'weekdays' => ['Jumapíiri', 'Jumatátu', 'Jumaíne', 'Jumatáano', 'Alamíisi', 'Ijumáa', 'Jumamóosi'], + 'weekdays_short' => ['Píili', 'Táatu', 'Íne', 'Táano', 'Alh', 'Ijm', 'Móosi'], + 'weekdays_min' => ['Píili', 'Táatu', 'Íne', 'Táano', 'Alh', 'Ijm', 'Móosi'], + 'months' => ['Kʉfúngatɨ', 'Kʉnaanɨ', 'Kʉkeenda', 'Kwiikumi', 'Kwiinyambála', 'Kwiidwaata', 'Kʉmʉʉnchɨ', 'Kʉvɨɨrɨ', 'Kʉsaatʉ', 'Kwiinyi', 'Kʉsaano', 'Kʉsasatʉ'], + 'months_short' => ['Fúngatɨ', 'Naanɨ', 'Keenda', 'Ikúmi', 'Inyambala', 'Idwaata', 'Mʉʉnchɨ', 'Vɨɨrɨ', 'Saatʉ', 'Inyi', 'Saano', 'Sasatʉ'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/lb.php b/vendor/nesbot/carbon/src/Carbon/Lang/lb.php new file mode 100644 index 0000000..7636655 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/lb.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Philippe Vaucher + * - Tsutomu Kuroda + * - dan-nl + * - Simon Lelorrain (slelorrain) + */ + +use Carbon\CarbonInterface; + +return [ + 'year' => ':count Joer', + 'y' => ':countJ', + 'month' => ':count Mount|:count Méint', + 'm' => ':countMo', + 'week' => ':count Woch|:count Wochen', + 'w' => ':countWo|:countWo', + 'day' => ':count Dag|:count Deeg', + 'd' => ':countD', + 'hour' => ':count Stonn|:count Stonnen', + 'h' => ':countSto', + 'minute' => ':count Minutt|:count Minutten', + 'min' => ':countM', + 'second' => ':count Sekonn|:count Sekonnen', + 's' => ':countSek', + + 'ago' => 'virun :time', + 'from_now' => 'an :time', + 'before' => ':time virdrun', + 'after' => ':time duerno', + + 'diff_today' => 'Haut', + 'diff_yesterday' => 'Gëschter', + 'diff_yesterday_regexp' => 'Gëschter(?:\\s+um)?', + 'diff_tomorrow' => 'Muer', + 'diff_tomorrow_regexp' => 'Muer(?:\\s+um)?', + 'diff_today_regexp' => 'Haut(?:\\s+um)?', + 'formats' => [ + 'LT' => 'H:mm [Auer]', + 'LTS' => 'H:mm:ss [Auer]', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D. MMMM YYYY', + 'LLL' => 'D. MMMM YYYY H:mm [Auer]', + 'LLLL' => 'dddd, D. MMMM YYYY H:mm [Auer]', + ], + + 'calendar' => [ + 'sameDay' => '[Haut um] LT', + 'nextDay' => '[Muer um] LT', + 'nextWeek' => 'dddd [um] LT', + 'lastDay' => '[Gëschter um] LT', + 'lastWeek' => function (CarbonInterface $date) { + // Different date string for 'Dënschdeg' (Tuesday) and 'Donneschdeg' (Thursday) due to phonological rule + switch ($date->dayOfWeek) { + case 2: + case 4: + return '[Leschten] dddd [um] LT'; + default: + return '[Leschte] dddd [um] LT'; + } + }, + 'sameElse' => 'L', + ], + + 'months' => ['Januar', 'Februar', 'Mäerz', 'Abrëll', 'Mee', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'], + 'months_short' => ['Jan.', 'Febr.', 'Mrz.', 'Abr.', 'Mee', 'Jun.', 'Jul.', 'Aug.', 'Sept.', 'Okt.', 'Nov.', 'Dez.'], + 'weekdays' => ['Sonndeg', 'Méindeg', 'Dënschdeg', 'Mëttwoch', 'Donneschdeg', 'Freideg', 'Samschdeg'], + 'weekdays_short' => ['So.', 'Mé.', 'Dë.', 'Më.', 'Do.', 'Fr.', 'Sa.'], + 'weekdays_min' => ['So', 'Mé', 'Dë', 'Më', 'Do', 'Fr', 'Sa'], + 'ordinal' => ':number.', + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' an '], + 'meridiem' => ['moies', 'mëttes'], + 'weekdays_short_standalone' => ['Son', 'Méi', 'Dën', 'Mët', 'Don', 'Fre', 'Sam'], + 'months_short_standalone' => ['Jan', 'Feb', 'Mäe', 'Abr', 'Mee', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/lb_LU.php b/vendor/nesbot/carbon/src/Carbon/Lang/lb_LU.php new file mode 100644 index 0000000..414bd4d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/lb_LU.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__.'/lb.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/lg.php b/vendor/nesbot/carbon/src/Carbon/Lang/lg.php new file mode 100644 index 0000000..48bc68b --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/lg.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/lg_UG.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/lg_UG.php b/vendor/nesbot/carbon/src/Carbon/Lang/lg_UG.php new file mode 100644 index 0000000..aa02214 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/lg_UG.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Akademe ya Luganda Kizito Birabwa kompyuta@kizito.uklinux.net + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['Janwaliyo', 'Febwaliyo', 'Marisi', 'Apuli', 'Maayi', 'Juuni', 'Julaayi', 'Agusito', 'Sebuttemba', 'Okitobba', 'Novemba', 'Desemba'], + 'months_short' => ['Jan', 'Feb', 'Mar', 'Apu', 'Maa', 'Juu', 'Jul', 'Agu', 'Seb', 'Oki', 'Nov', 'Des'], + 'weekdays' => ['Sabiiti', 'Balaza', 'Lwakubiri', 'Lwakusatu', 'Lwakuna', 'Lwakutaano', 'Lwamukaaga'], + 'weekdays_short' => ['Sab', 'Bal', 'Lw2', 'Lw3', 'Lw4', 'Lw5', 'Lw6'], + 'weekdays_min' => ['Sab', 'Bal', 'Lw2', 'Lw3', 'Lw4', 'Lw5', 'Lw6'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + + 'month' => ':count njuba', // less reliable + 'm' => ':count njuba', // less reliable + 'a_month' => ':count njuba', // less reliable + + 'year' => ':count mwaaka', + 'y' => ':count mwaaka', + 'a_year' => ':count mwaaka', + + 'week' => ':count sabbiiti', + 'w' => ':count sabbiiti', + 'a_week' => ':count sabbiiti', + + 'day' => ':count lunaku', + 'd' => ':count lunaku', + 'a_day' => ':count lunaku', + + 'hour' => 'saawa :count', + 'h' => 'saawa :count', + 'a_hour' => 'saawa :count', + + 'minute' => 'ddakiika :count', + 'min' => 'ddakiika :count', + 'a_minute' => 'ddakiika :count', + + 'second' => ':count kyʼokubiri', + 's' => ':count kyʼokubiri', + 'a_second' => ':count kyʼokubiri', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/li.php b/vendor/nesbot/carbon/src/Carbon/Lang/li.php new file mode 100644 index 0000000..86c3009 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/li.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/li_NL.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/li_NL.php b/vendor/nesbot/carbon/src/Carbon/Lang/li_NL.php new file mode 100644 index 0000000..6c5feb7 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/li_NL.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - information from Kenneth Christiansen Kenneth Christiansen, Pablo Saratxaga kenneth@gnu.org, pablo@mandriva.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['jannewarie', 'fibberwarie', 'miert', 'eprèl', 'meij', 'junie', 'julie', 'augustus', 'september', 'oktober', 'november', 'desember'], + 'months_short' => ['jan', 'fib', 'mie', 'epr', 'mei', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'des'], + 'weekdays' => ['zóndig', 'maondig', 'daensdig', 'goonsdig', 'dónderdig', 'vriedig', 'zaoterdig'], + 'weekdays_short' => ['zón', 'mao', 'dae', 'goo', 'dón', 'vri', 'zao'], + 'weekdays_min' => ['zón', 'mao', 'dae', 'goo', 'dón', 'vri', 'zao'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'minute' => ':count momênt', // less reliable + 'min' => ':count momênt', // less reliable + 'a_minute' => ':count momênt', // less reliable + + 'year' => ':count jaor', + 'y' => ':count jaor', + 'a_year' => ':count jaor', + + 'month' => ':count maond', + 'm' => ':count maond', + 'a_month' => ':count maond', + + 'week' => ':count waek', + 'w' => ':count waek', + 'a_week' => ':count waek', + + 'day' => ':count daag', + 'd' => ':count daag', + 'a_day' => ':count daag', + + 'hour' => ':count oer', + 'h' => ':count oer', + 'a_hour' => ':count oer', + + 'second' => ':count Secónd', + 's' => ':count Secónd', + 'a_second' => ':count Secónd', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/lij.php b/vendor/nesbot/carbon/src/Carbon/Lang/lij.php new file mode 100644 index 0000000..45732b5 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/lij.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/lij_IT.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/lij_IT.php b/vendor/nesbot/carbon/src/Carbon/Lang/lij_IT.php new file mode 100644 index 0000000..f8726fd --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/lij_IT.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Gastaldi alessio.gastaldi@libero.it + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['zenâ', 'fevrâ', 'marzo', 'avrî', 'mazzo', 'zûgno', 'lûggio', 'agosto', 'settembre', 'ottobre', 'novembre', 'dixembre'], + 'months_short' => ['zen', 'fev', 'mar', 'arv', 'maz', 'zûg', 'lûg', 'ago', 'set', 'ött', 'nov', 'dix'], + 'weekdays' => ['domenega', 'lûnedì', 'martedì', 'mercUrdì', 'zêggia', 'venardì', 'sabbo'], + 'weekdays_short' => ['dom', 'lûn', 'mar', 'mer', 'zêu', 'ven', 'sab'], + 'weekdays_min' => ['dom', 'lûn', 'mar', 'mer', 'zêu', 'ven', 'sab'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'year' => ':count etæ', // less reliable + 'y' => ':count etæ', // less reliable + 'a_year' => ':count etæ', // less reliable + + 'month' => ':count meize', + 'm' => ':count meize', + 'a_month' => ':count meize', + + 'week' => ':count settemannha', + 'w' => ':count settemannha', + 'a_week' => ':count settemannha', + + 'day' => ':count giorno', + 'd' => ':count giorno', + 'a_day' => ':count giorno', + + 'hour' => ':count reléuio', // less reliable + 'h' => ':count reléuio', // less reliable + 'a_hour' => ':count reléuio', // less reliable + + 'minute' => ':count menûo', + 'min' => ':count menûo', + 'a_minute' => ':count menûo', + + 'second' => ':count segondo', + 's' => ':count segondo', + 'a_second' => ':count segondo', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/lkt.php b/vendor/nesbot/carbon/src/Carbon/Lang/lkt.php new file mode 100644 index 0000000..ae73a97 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/lkt.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + + 'month' => ':count haŋwí', // less reliable + 'm' => ':count haŋwí', // less reliable + 'a_month' => ':count haŋwí', // less reliable + + 'week' => ':count šakówiŋ', // less reliable + 'w' => ':count šakówiŋ', // less reliable + 'a_week' => ':count šakówiŋ', // less reliable + + 'hour' => ':count maza škaŋškaŋ', // less reliable + 'h' => ':count maza škaŋškaŋ', // less reliable + 'a_hour' => ':count maza škaŋškaŋ', // less reliable + + 'minute' => ':count číkʼala', // less reliable + 'min' => ':count číkʼala', // less reliable + 'a_minute' => ':count číkʼala', // less reliable + + 'year' => ':count waníyetu', + 'y' => ':count waníyetu', + 'a_year' => ':count waníyetu', + + 'day' => ':count aŋpétu', + 'd' => ':count aŋpétu', + 'a_day' => ':count aŋpétu', + + 'second' => ':count icinuŋpa', + 's' => ':count icinuŋpa', + 'a_second' => ':count icinuŋpa', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ln.php b/vendor/nesbot/carbon/src/Carbon/Lang/ln.php new file mode 100644 index 0000000..9d5c35d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ln.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ubuntu René Manassé GALEKWA renemanasse@gmail.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'months' => ['sánzá ya yambo', 'sánzá ya míbalé', 'sánzá ya mísáto', 'sánzá ya mínei', 'sánzá ya mítáno', 'sánzá ya motóbá', 'sánzá ya nsambo', 'sánzá ya mwambe', 'sánzá ya libwa', 'sánzá ya zómi', 'sánzá ya zómi na mɔ̌kɔ́', 'sánzá ya zómi na míbalé'], + 'months_short' => ['yan', 'fbl', 'msi', 'apl', 'mai', 'yun', 'yul', 'agt', 'stb', 'ɔtb', 'nvb', 'dsb'], + 'weekdays' => ['Lomíngo', 'Mosálá mɔ̌kɔ́', 'Misálá míbalé', 'Misálá mísáto', 'Misálá mínei', 'Misálá mítáno', 'Mpɔ́sɔ'], + 'weekdays_short' => ['m1.', 'm2.', 'm3.', 'm4.', 'm5.', 'm6.', 'm7.'], + 'weekdays_min' => ['m1.', 'm2.', 'm3.', 'm4.', 'm5.', 'm6.', 'm7.'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + + 'year' => 'mbula :count', + 'y' => 'mbula :count', + 'a_year' => 'mbula :count', + + 'month' => 'sánzá :count', + 'm' => 'sánzá :count', + 'a_month' => 'sánzá :count', + + 'week' => 'mpɔ́sɔ :count', + 'w' => 'mpɔ́sɔ :count', + 'a_week' => 'mpɔ́sɔ :count', + + 'day' => 'mokɔlɔ :count', + 'd' => 'mokɔlɔ :count', + 'a_day' => 'mokɔlɔ :count', + + 'hour' => 'ngonga :count', + 'h' => 'ngonga :count', + 'a_hour' => 'ngonga :count', + + 'minute' => 'miniti :count', + 'min' => 'miniti :count', + 'a_minute' => 'miniti :count', + + 'second' => 'segɔnde :count', + 's' => 'segɔnde :count', + 'a_second' => 'segɔnde :count', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ln_AO.php b/vendor/nesbot/carbon/src/Carbon/Lang/ln_AO.php new file mode 100644 index 0000000..7fdb7f1 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ln_AO.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ln.php', [ + 'weekdays' => ['eyenga', 'mokɔlɔ mwa yambo', 'mokɔlɔ mwa míbalé', 'mokɔlɔ mwa mísáto', 'mokɔlɔ ya mínéi', 'mokɔlɔ ya mítáno', 'mpɔ́sɔ'], + 'weekdays_short' => ['eye', 'ybo', 'mbl', 'mst', 'min', 'mtn', 'mps'], + 'weekdays_min' => ['eye', 'ybo', 'mbl', 'mst', 'min', 'mtn', 'mps'], + 'meridiem' => ['ntɔ́ngɔ́', 'mpókwa'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ln_CD.php b/vendor/nesbot/carbon/src/Carbon/Lang/ln_CD.php new file mode 100644 index 0000000..13635fc --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ln_CD.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ubuntu René Manassé GALEKWA renemanasse@gmail.com + */ +return require __DIR__.'/ln.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ln_CF.php b/vendor/nesbot/carbon/src/Carbon/Lang/ln_CF.php new file mode 100644 index 0000000..7fdb7f1 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ln_CF.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ln.php', [ + 'weekdays' => ['eyenga', 'mokɔlɔ mwa yambo', 'mokɔlɔ mwa míbalé', 'mokɔlɔ mwa mísáto', 'mokɔlɔ ya mínéi', 'mokɔlɔ ya mítáno', 'mpɔ́sɔ'], + 'weekdays_short' => ['eye', 'ybo', 'mbl', 'mst', 'min', 'mtn', 'mps'], + 'weekdays_min' => ['eye', 'ybo', 'mbl', 'mst', 'min', 'mtn', 'mps'], + 'meridiem' => ['ntɔ́ngɔ́', 'mpókwa'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ln_CG.php b/vendor/nesbot/carbon/src/Carbon/Lang/ln_CG.php new file mode 100644 index 0000000..7fdb7f1 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ln_CG.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ln.php', [ + 'weekdays' => ['eyenga', 'mokɔlɔ mwa yambo', 'mokɔlɔ mwa míbalé', 'mokɔlɔ mwa mísáto', 'mokɔlɔ ya mínéi', 'mokɔlɔ ya mítáno', 'mpɔ́sɔ'], + 'weekdays_short' => ['eye', 'ybo', 'mbl', 'mst', 'min', 'mtn', 'mps'], + 'weekdays_min' => ['eye', 'ybo', 'mbl', 'mst', 'min', 'mtn', 'mps'], + 'meridiem' => ['ntɔ́ngɔ́', 'mpókwa'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/lo.php b/vendor/nesbot/carbon/src/Carbon/Lang/lo.php new file mode 100644 index 0000000..48715f5 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/lo.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - ryanhart2 + */ +return [ + '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' => '{1}ບໍ່ເທົ່າໃດວິນາທີ|]1,Inf[:count ວິນາທີ', + 's' => ':count ວິ. ', + 'ago' => ':timeຜ່ານມາ', + 'from_now' => 'ອີກ :time', + 'diff_now' => 'ຕອນນີ້', + 'diff_today' => 'ມື້ນີ້ເວລາ', + 'diff_yesterday' => 'ມື້ວານນີ້ເວລາ', + 'diff_tomorrow' => 'ມື້ອື່ນເວລາ', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'ວັນdddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[ມື້ນີ້ເວລາ] LT', + 'nextDay' => '[ມື້ອື່ນເວລາ] LT', + 'nextWeek' => '[ວັນ]dddd[ໜ້າເວລາ] LT', + 'lastDay' => '[ມື້ວານນີ້ເວລາ] LT', + 'lastWeek' => '[ວັນ]dddd[ແລ້ວນີ້ເວລາ] LT', + 'sameElse' => 'L', + ], + 'ordinal' => 'ທີ່:number', + 'meridiem' => ['ຕອນເຊົ້າ', 'ຕອນແລງ'], + 'months' => ['ມັງກອນ', 'ກຸມພາ', 'ມີນາ', 'ເມສາ', 'ພຶດສະພາ', 'ມິຖຸນາ', 'ກໍລະກົດ', 'ສິງຫາ', 'ກັນຍາ', 'ຕຸລາ', 'ພະຈິກ', 'ທັນວາ'], + 'months_short' => ['ມັງກອນ', 'ກຸມພາ', 'ມີນາ', 'ເມສາ', 'ພຶດສະພາ', 'ມິຖຸນາ', 'ກໍລະກົດ', 'ສິງຫາ', 'ກັນຍາ', 'ຕຸລາ', 'ພະຈິກ', 'ທັນວາ'], + 'weekdays' => ['ອາທິດ', 'ຈັນ', 'ອັງຄານ', 'ພຸດ', 'ພະຫັດ', 'ສຸກ', 'ເສົາ'], + 'weekdays_short' => ['ທິດ', 'ຈັນ', 'ອັງຄານ', 'ພຸດ', 'ພະຫັດ', 'ສຸກ', 'ເສົາ'], + 'weekdays_min' => ['ທ', 'ຈ', 'ອຄ', 'ພ', 'ພຫ', 'ສກ', 'ສ'], + 'list' => [', ', 'ແລະ '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/lo_LA.php b/vendor/nesbot/carbon/src/Carbon/Lang/lo_LA.php new file mode 100644 index 0000000..9b7fd9b --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/lo_LA.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__.'/lo.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/lrc.php b/vendor/nesbot/carbon/src/Carbon/Lang/lrc.php new file mode 100644 index 0000000..546e679 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/lrc.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + + 'minute' => ':count هنر', // less reliable + 'min' => ':count هنر', // less reliable + 'a_minute' => ':count هنر', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/lrc_IQ.php b/vendor/nesbot/carbon/src/Carbon/Lang/lrc_IQ.php new file mode 100644 index 0000000..d42f5e9 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/lrc_IQ.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/lrc.php', [ +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/lt.php b/vendor/nesbot/carbon/src/Carbon/Lang/lt.php new file mode 100644 index 0000000..7d1b6f7 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/lt.php @@ -0,0 +1,135 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Philippe Vaucher + * - Tsutomu Kuroda + * - tjku + * - valdas406 + * - Justas Palumickas + * - Max Melentiev + * - Andrius Janauskas + * - Juanito Fatas + * - Akira Matsuda + * - Christopher Dell + * - Enrique Vidal + * - Simone Carletti + * - Aaron Patterson + * - Nicolás Hock Isaza + * - Laurynas Butkus + * - Sven Fuchs + * - Dominykas Tijūnaitis + * - Justinas Bolys + * - Ričardas + * - Kirill Chalkin + * - Rolandas + * - Justinas (Gamesh) + */ +return [ + 'year' => ':count metai|:count metai|:count metų', + 'y' => ':count m.', + 'month' => ':count mėnuo|:count mėnesiai|:count mėnesį', + 'm' => ':count mėn.', + 'week' => ':count savaitė|:count savaitės|:count savaitę', + 'w' => ':count sav.', + 'day' => ':count diena|:count dienos|:count dienų', + 'd' => ':count d.', + 'hour' => ':count valanda|:count valandos|:count valandų', + 'h' => ':count val.', + 'minute' => ':count minutė|:count minutės|:count minutę', + 'min' => ':count min.', + 'second' => ':count sekundė|:count sekundės|:count sekundžių', + 's' => ':count sek.', + + 'year_ago' => ':count metus|:count metus|:count metų', + 'month_ago' => ':count mėnesį|:count mėnesius|:count mėnesių', + 'week_ago' => ':count savaitę|:count savaites|:count savaičių', + 'day_ago' => ':count dieną|:count dienas|:count dienų', + 'hour_ago' => ':count valandą|:count valandas|:count valandų', + 'minute_ago' => ':count minutę|:count minutes|:count minučių', + 'second_ago' => ':count sekundę|:count sekundes|:count sekundžių', + + 'year_from_now' => ':count metų', + 'month_from_now' => ':count mėnesio|:count mėnesių|:count mėnesių', + 'week_from_now' => ':count savaitės|:count savaičių|:count savaičių', + 'day_from_now' => ':count dienos|:count dienų|:count dienų', + 'hour_from_now' => ':count valandos|:count valandų|:count valandų', + 'minute_from_now' => ':count minutės|:count minučių|:count minučių', + 'second_from_now' => ':count sekundės|:count sekundžių|:count sekundžių', + + 'year_after' => ':count metų', + 'month_after' => ':count mėnesio|:count mėnesių|:count mėnesių', + 'week_after' => ':count savaitės|:count savaičių|:count savaičių', + 'day_after' => ':count dienos|:count dienų|:count dienų', + 'hour_after' => ':count valandos|:count valandų|:count valandų', + 'minute_after' => ':count minutės|:count minučių|:count minučių', + 'second_after' => ':count sekundės|:count sekundžių|:count sekundžių', + + 'ago' => 'prieš :time', + 'from_now' => ':time nuo dabar', + 'after' => 'po :time', + 'before' => 'už :time', + + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'diff_now' => 'ką tik', + 'diff_today' => 'Šiandien', + 'diff_yesterday' => 'vakar', + 'diff_yesterday_regexp' => 'Vakar', + 'diff_tomorrow' => 'rytoj', + 'diff_tomorrow_regexp' => 'Rytoj', + 'diff_before_yesterday' => 'užvakar', + 'diff_after_tomorrow' => 'poryt', + + 'period_recurrences' => 'kartą|:count kartų', + 'period_interval' => 'kiekvieną :interval', + 'period_start_date' => 'nuo :date', + 'period_end_date' => 'iki :date', + + 'months' => ['sausio', 'vasario', 'kovo', 'balandžio', 'gegužės', 'birželio', 'liepos', 'rugpjūčio', 'rugsėjo', 'spalio', 'lapkričio', 'gruodžio'], + 'months_standalone' => ['sausis', 'vasaris', 'kovas', 'balandis', 'gegužė', 'birželis', 'liepa', 'rugpjūtis', 'rugsėjis', 'spalis', 'lapkritis', 'gruodis'], + 'months_regexp' => '/(L{2,4}|D[oD]?(\[[^\[\]]*\]|\s)+MMMM?|MMMM?(\[[^\[\]]*\]|\s)+D[oD]?)/', + 'months_short' => ['sau', 'vas', 'kov', 'bal', 'geg', 'bir', 'lie', 'rgp', 'rgs', 'spa', 'lap', 'gru'], + 'weekdays' => ['sekmadienį', 'pirmadienį', 'antradienį', 'trečiadienį', 'ketvirtadienį', 'penktadienį', 'šeštadienį'], + 'weekdays_standalone' => ['sekmadienis', 'pirmadienis', 'antradienis', 'trečiadienis', 'ketvirtadienis', 'penktadienis', 'šeštadienis'], + 'weekdays_short' => ['sek', 'pir', 'ant', 'tre', 'ket', 'pen', 'šeš'], + 'weekdays_min' => ['se', 'pi', 'an', 'tr', 'ke', 'pe', 'še'], + 'list' => [', ', ' ir '], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-DD', + 'LL' => 'MMMM DD, YYYY', + 'LLL' => 'DD MMM HH:mm', + 'LLLL' => 'MMMM DD, YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Šiandien] LT', + 'nextDay' => '[Rytoj] LT', + 'nextWeek' => 'dddd LT', + 'lastDay' => '[Vakar] LT', + 'lastWeek' => '[Paskutinį] dddd LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + switch ($number) { + case 0: + return '0-is'; + case 3: + return '3-ias'; + default: + return "$number-as"; + } + }, + 'meridiem' => ['priešpiet', 'popiet'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/lt_LT.php b/vendor/nesbot/carbon/src/Carbon/Lang/lt_LT.php new file mode 100644 index 0000000..f772d38 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/lt_LT.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__.'/lt.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/lu.php b/vendor/nesbot/carbon/src/Carbon/Lang/lu.php new file mode 100644 index 0000000..c8cd83a --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/lu.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['Dinda', 'Dilolo'], + 'weekdays' => ['Lumingu', 'Nkodya', 'Ndàayà', 'Ndangù', 'Njòwa', 'Ngòvya', 'Lubingu'], + 'weekdays_short' => ['Lum', 'Nko', 'Ndy', 'Ndg', 'Njw', 'Ngv', 'Lub'], + 'weekdays_min' => ['Lum', 'Nko', 'Ndy', 'Ndg', 'Njw', 'Ngv', 'Lub'], + 'months' => ['Ciongo', 'Lùishi', 'Lusòlo', 'Mùuyà', 'Lumùngùlù', 'Lufuimi', 'Kabàlàshìpù', 'Lùshìkà', 'Lutongolo', 'Lungùdi', 'Kaswèkèsè', 'Ciswà'], + 'months_short' => ['Cio', 'Lui', 'Lus', 'Muu', 'Lum', 'Luf', 'Kab', 'Lush', 'Lut', 'Lun', 'Kas', 'Cis'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/luo.php b/vendor/nesbot/carbon/src/Carbon/Lang/luo.php new file mode 100644 index 0000000..b55af73 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/luo.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['OD', 'OT'], + 'weekdays' => ['Jumapil', 'Wuok Tich', 'Tich Ariyo', 'Tich Adek', 'Tich Ang’wen', 'Tich Abich', 'Ngeso'], + 'weekdays_short' => ['JMP', 'WUT', 'TAR', 'TAD', 'TAN', 'TAB', 'NGS'], + 'weekdays_min' => ['JMP', 'WUT', 'TAR', 'TAD', 'TAN', 'TAB', 'NGS'], + 'months' => ['Dwe mar Achiel', 'Dwe mar Ariyo', 'Dwe mar Adek', 'Dwe mar Ang’wen', 'Dwe mar Abich', 'Dwe mar Auchiel', 'Dwe mar Abiriyo', 'Dwe mar Aboro', 'Dwe mar Ochiko', 'Dwe mar Apar', 'Dwe mar gi achiel', 'Dwe mar Apar gi ariyo'], + 'months_short' => ['DAC', 'DAR', 'DAD', 'DAN', 'DAH', 'DAU', 'DAO', 'DAB', 'DOC', 'DAP', 'DGI', 'DAG'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + + 'year' => 'higni :count', + 'y' => 'higni :count', + 'a_year' => ':higni :count', + + 'month' => 'dweche :count', + 'm' => 'dweche :count', + 'a_month' => 'dweche :count', + + 'week' => 'jumbe :count', + 'w' => 'jumbe :count', + 'a_week' => 'jumbe :count', + + 'day' => 'ndalo :count', + 'd' => 'ndalo :count', + 'a_day' => 'ndalo :count', + + 'hour' => 'seche :count', + 'h' => 'seche :count', + 'a_hour' => 'seche :count', + + 'minute' => 'dakika :count', + 'min' => 'dakika :count', + 'a_minute' => 'dakika :count', + + 'second' => 'nus dakika :count', + 's' => 'nus dakika :count', + 'a_second' => 'nus dakika :count', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/luy.php b/vendor/nesbot/carbon/src/Carbon/Lang/luy.php new file mode 100644 index 0000000..2b37e3e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/luy.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'weekdays' => ['Jumapiri', 'Jumatatu', 'Jumanne', 'Jumatano', 'Murwa wa Kanne', 'Murwa wa Katano', 'Jumamosi'], + 'weekdays_short' => ['J2', 'J3', 'J4', 'J5', 'Al', 'Ij', 'J1'], + 'weekdays_min' => ['J2', 'J3', 'J4', 'J5', 'Al', 'Ij', 'J1'], + 'months' => ['Januari', 'Februari', 'Machi', 'Aprili', 'Mei', 'Juni', 'Julai', 'Agosti', 'Septemba', 'Oktoba', 'Novemba', 'Desemba'], + 'months_short' => ['Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun', 'Jul', 'Ago', 'Sep', 'Okt', 'Nov', 'Des'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + + // Too unreliable + /* + 'year' => ':count liliino', // less reliable + 'y' => ':count liliino', // less reliable + 'a_year' => ':count liliino', // less reliable + + 'month' => ':count kumwesi', // less reliable + 'm' => ':count kumwesi', // less reliable + 'a_month' => ':count kumwesi', // less reliable + + 'week' => ':count olutambi', // less reliable + 'w' => ':count olutambi', // less reliable + 'a_week' => ':count olutambi', // less reliable + + 'day' => ':count luno', // less reliable + 'd' => ':count luno', // less reliable + 'a_day' => ':count luno', // less reliable + + 'hour' => ':count ekengele', // less reliable + 'h' => ':count ekengele', // less reliable + 'a_hour' => ':count ekengele', // less reliable + + 'minute' => ':count omundu', // less reliable + 'min' => ':count omundu', // less reliable + 'a_minute' => ':count omundu', // less reliable + + 'second' => ':count liliino', // less reliable + 's' => ':count liliino', // less reliable + 'a_second' => ':count liliino', // less reliable + */ +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/lv.php b/vendor/nesbot/carbon/src/Carbon/Lang/lv.php new file mode 100644 index 0000000..d5cba7c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/lv.php @@ -0,0 +1,183 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Carbon\CarbonInterface; + +/** + * This file is part of the Carbon package. + * + * (c) Brian Nesbitt + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Philippe Vaucher + * - pirminis + * - Tsutomu Kuroda + * - tjku + * - Andris Zāģeris + * - Max Melentiev + * - Edgars Beigarts + * - Juanito Fatas + * - Vitauts Stočka + * - Akira Matsuda + * - Christopher Dell + * - Enrique Vidal + * - Simone Carletti + * - Aaron Patterson + * - Kaspars Bankovskis + * - Nicolás Hock Isaza + * - Viesturs Kavacs (Kavacky) + * - zakse + * - Janis Eglitis (janiseglitis) + * - Guntars + * - Juris Sudmalis + */ +$daysOfWeek = ['svētdiena', 'pirmdiena', 'otrdiena', 'trešdiena', 'ceturtdiena', 'piektdiena', 'sestdiena']; +$daysOfWeekLocativum = ['svētdien', 'pirmdien', 'otrdien', 'trešdien', 'ceturtdien', 'piektdien', 'sestdien']; + +$transformDiff = function ($input) { + return strtr($input, [ + // Nominative => "pirms/pēc" Dative + 'gads' => 'gada', + 'gadi' => 'gadiem', + 'gadu' => 'gadiem', + 'mēnesis' => 'mēneša', + 'mēneši' => 'mēnešiem', + 'mēnešu' => 'mēnešiem', + 'nedēļa' => 'nedēļas', + 'nedēļas' => 'nedēļām', + 'nedēļu' => 'nedēļām', + 'diena' => 'dienas', + 'dienas' => 'dienām', + 'dienu' => 'dienām', + 'stunda' => 'stundas', + 'stundas' => 'stundām', + 'stundu' => 'stundām', + 'minūte' => 'minūtes', + 'minūtes' => 'minūtēm', + 'minūšu' => 'minūtēm', + 'sekunde' => 'sekundes', + 'sekundes' => 'sekundēm', + 'sekunžu' => 'sekundēm', + ]); +}; + +return [ + 'ago' => function ($time) use ($transformDiff) { + return 'pirms '.$transformDiff($time); + }, + 'from_now' => function ($time) use ($transformDiff) { + return 'pēc '.$transformDiff($time); + }, + + 'year' => '0 gadu|:count gads|:count gadi', + 'y' => ':count g.', + 'a_year' => '{1}gads|0 gadu|:count gads|:count gadi', + 'month' => '0 mēnešu|:count mēnesis|:count mēneši', + 'm' => ':count mēn.', + 'a_month' => '{1}mēnesis|0 mēnešu|:count mēnesis|:count mēneši', + 'week' => '0 nedēļu|:count nedēļa|:count nedēļas', + 'w' => ':count ned.', + 'a_week' => '{1}nedēļa|0 nedēļu|:count nedēļa|:count nedēļas', + 'day' => '0 dienu|:count diena|:count dienas', + 'd' => ':count d.', + 'a_day' => '{1}diena|0 dienu|:count diena|:count dienas', + 'hour' => '0 stundu|:count stunda|:count stundas', + 'h' => ':count st.', + 'a_hour' => '{1}stunda|0 stundu|:count stunda|:count stundas', + 'minute' => '0 minūšu|:count minūte|:count minūtes', + 'min' => ':count min.', + 'a_minute' => '{1}minūte|0 minūšu|:count minūte|:count minūtes', + 'second' => '0 sekunžu|:count sekunde|:count sekundes', + 's' => ':count sek.', + 'a_second' => '{1}sekunde|0 sekunžu|:count sekunde|:count sekundes', + + 'after' => ':time vēlāk', + 'year_after' => '0 gadus|:count gadu|:count gadus', + 'a_year_after' => '{1}gadu|0 gadus|:count gadu|:count gadus', + 'month_after' => '0 mēnešus|:count mēnesi|:count mēnešus', + 'a_month_after' => '{1}mēnesi|0 mēnešus|:count mēnesi|:count mēnešus', + 'week_after' => '0 nedēļas|:count nedēļu|:count nedēļas', + 'a_week_after' => '{1}nedēļu|0 nedēļas|:count nedēļu|:count nedēļas', + 'day_after' => '0 dienas|:count dienu|:count dienas', + 'a_day_after' => '{1}dienu|0 dienas|:count dienu|:count dienas', + 'hour_after' => '0 stundas|:count stundu|:count stundas', + 'a_hour_after' => '{1}stundu|0 stundas|:count stundu|:count stundas', + 'minute_after' => '0 minūtes|:count minūti|:count minūtes', + 'a_minute_after' => '{1}minūti|0 minūtes|:count minūti|:count minūtes', + 'second_after' => '0 sekundes|:count sekundi|:count sekundes', + 'a_second_after' => '{1}sekundi|0 sekundes|:count sekundi|:count sekundes', + + 'before' => ':time agrāk', + 'year_before' => '0 gadus|:count gadu|:count gadus', + 'a_year_before' => '{1}gadu|0 gadus|:count gadu|:count gadus', + 'month_before' => '0 mēnešus|:count mēnesi|:count mēnešus', + 'a_month_before' => '{1}mēnesi|0 mēnešus|:count mēnesi|:count mēnešus', + 'week_before' => '0 nedēļas|:count nedēļu|:count nedēļas', + 'a_week_before' => '{1}nedēļu|0 nedēļas|:count nedēļu|:count nedēļas', + 'day_before' => '0 dienas|:count dienu|:count dienas', + 'a_day_before' => '{1}dienu|0 dienas|:count dienu|:count dienas', + 'hour_before' => '0 stundas|:count stundu|:count stundas', + 'a_hour_before' => '{1}stundu|0 stundas|:count stundu|:count stundas', + 'minute_before' => '0 minūtes|:count minūti|:count minūtes', + 'a_minute_before' => '{1}minūti|0 minūtes|:count minūti|:count minūtes', + 'second_before' => '0 sekundes|:count sekundi|:count sekundes', + 'a_second_before' => '{1}sekundi|0 sekundes|:count sekundi|:count sekundes', + + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' un '], + + 'diff_now' => 'tagad', + 'diff_today' => 'šodien', + 'diff_yesterday' => 'vakar', + 'diff_before_yesterday' => 'aizvakar', + 'diff_tomorrow' => 'rīt', + 'diff_after_tomorrow' => 'parīt', + + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY.', + 'LL' => 'YYYY. [gada] D. MMMM', + 'LLL' => 'DD.MM.YYYY., HH:mm', + 'LLLL' => 'YYYY. [gada] D. MMMM, HH:mm', + ], + + 'calendar' => [ + 'sameDay' => '[šodien] [plkst.] LT', + 'nextDay' => '[rīt] [plkst.] LT', + 'nextWeek' => function (CarbonInterface $current, CarbonInterface $other) use ($daysOfWeekLocativum) { + if ($current->week !== $other->week) { + return '[nākošo] ['.$daysOfWeekLocativum[$current->dayOfWeek].'] [plkst.] LT'; + } + + return '['.$daysOfWeekLocativum[$current->dayOfWeek].'] [plkst.] LT'; + }, + 'lastDay' => '[vakar] [plkst.] LT', + 'lastWeek' => function (CarbonInterface $current) use ($daysOfWeekLocativum) { + return '[pagājušo] ['.$daysOfWeekLocativum[$current->dayOfWeek].'] [plkst.] LT'; + }, + 'sameElse' => 'L', + ], + + 'weekdays' => $daysOfWeek, + 'weekdays_short' => ['Sv.', 'P.', 'O.', 'T.', 'C.', 'Pk.', 'S.'], + 'weekdays_min' => ['Sv.', 'P.', 'O.', 'T.', 'C.', 'Pk.', 'S.'], + 'months' => ['janvāris', 'februāris', 'marts', 'aprīlis', 'maijs', 'jūnijs', 'jūlijs', 'augusts', 'septembris', 'oktobris', 'novembris', 'decembris'], + 'months_standalone' => ['janvārī', 'februārī', 'martā', 'aprīlī', 'maijā', 'jūnijā', 'jūlijā', 'augustā', 'septembrī', 'oktobrī', 'novembrī', 'decembrī'], + 'months_short' => ['janv.', 'febr.', 'martā', 'apr.', 'maijā', 'jūn.', 'jūl.', 'aug.', 'sept.', 'okt.', 'nov.', 'dec.'], + 'meridiem' => ['priekšpusdiena', 'pēcpusdiena'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/lv_LV.php b/vendor/nesbot/carbon/src/Carbon/Lang/lv_LV.php new file mode 100644 index 0000000..ee91c36 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/lv_LV.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__.'/lv.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/lzh.php b/vendor/nesbot/carbon/src/Carbon/Lang/lzh.php new file mode 100644 index 0000000..1180c6b --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/lzh.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/lzh_TW.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/lzh_TW.php b/vendor/nesbot/carbon/src/Carbon/Lang/lzh_TW.php new file mode 100644 index 0000000..3b1493e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/lzh_TW.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'OY[年]MMMMOD[日]', + ], + 'months' => ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'], + 'months_short' => [' 一 ', ' 二 ', ' 三 ', ' 四 ', ' 五 ', ' 六 ', ' 七 ', ' 八 ', ' 九 ', ' 十 ', '十一', '十二'], + 'weekdays' => ['週日', '週一', '週二', '週三', '週四', '週五', '週六'], + 'weekdays_short' => ['日', '一', '二', '三', '四', '五', '六'], + 'weekdays_min' => ['日', '一', '二', '三', '四', '五', '六'], + 'day_of_first_week_of_year' => 1, + 'alt_numbers' => ['〇', '一', '二', '三', '四', '五', '六', '七', '八', '九', '十', '十一', '十二', '十三', '十四', '十五', '十六', '十七', '十八', '十九', '廿', '廿一', '廿二', '廿三', '廿四', '廿五', '廿六', '廿七', '廿八', '廿九', '卅', '卅一'], + 'meridiem' => ['朝', '暮'], + + 'year' => ':count 夏', // less reliable + 'y' => ':count 夏', // less reliable + 'a_year' => ':count 夏', // less reliable + + 'month' => ':count 月', // less reliable + 'm' => ':count 月', // less reliable + 'a_month' => ':count 月', // less reliable + + 'hour' => ':count 氧', // less reliable + 'h' => ':count 氧', // less reliable + 'a_hour' => ':count 氧', // less reliable + + 'minute' => ':count 點', // less reliable + 'min' => ':count 點', // less reliable + 'a_minute' => ':count 點', // less reliable + + 'second' => ':count 楚', // less reliable + 's' => ':count 楚', // less reliable + 'a_second' => ':count 楚', // less reliable + + 'week' => ':count 星期', + 'w' => ':count 星期', + 'a_week' => ':count 星期', + + 'day' => ':count 日(曆法)', + 'd' => ':count 日(曆法)', + 'a_day' => ':count 日(曆法)', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mag.php b/vendor/nesbot/carbon/src/Carbon/Lang/mag.php new file mode 100644 index 0000000..7532436 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mag.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/mag_IN.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mag_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/mag_IN.php new file mode 100644 index 0000000..193f67a --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mag_IN.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bhashaghar@googlegroups.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['जनवरी', 'फ़रवरी', 'मार्च', 'अप्रेल', 'मई', 'जून', 'जुलाई', 'अगस्त', 'सितम्बर', 'अक्टूबर', 'नवम्बर', 'दिसम्बर'], + 'months_short' => ['जनवरी', 'फ़रवरी', 'मार्च', 'अप्रेल', 'मई', 'जून', 'जुलाई', 'अगस्त', 'सितम्बर', 'अक्टूबर', 'नवम्बर', 'दिसम्बर'], + 'weekdays' => ['एतवार', 'सोमार', 'मंगर', 'बुध', 'बिफे', 'सूक', 'सनिचर'], + 'weekdays_short' => ['एतवार', 'सोमार', 'मंगर', 'बुध', 'बिफे', 'सूक', 'सनिचर'], + 'weekdays_min' => ['एतवार', 'सोमार', 'मंगर', 'बुध', 'बिफे', 'सूक', 'सनिचर'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['पूर्वाह्न', 'अपराह्न'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mai.php b/vendor/nesbot/carbon/src/Carbon/Lang/mai.php new file mode 100644 index 0000000..792b973 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mai.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/mai_IN.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mai_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/mai_IN.php new file mode 100644 index 0000000..03049d4 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mai_IN.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Maithili Computing Research Center, Pune, India rajeshkajha@yahoo.com,akhilesh.k@samusng.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['बैसाख', 'जेठ', 'अषाढ़', 'सावोन', 'भादो', 'आसिन', 'कातिक', 'अगहन', 'पूस', 'माघ', 'फागुन', 'चैति'], + 'months_short' => ['बैसाख', 'जेठ', 'अषाढ़', 'सावोन', 'भादो', 'आसिन', 'कातिक', 'अगहन', 'पूस', 'माघ', 'फागुन', 'चैति'], + 'weekdays' => ['रविदिन', 'सोमदिन', 'मंगलदिन', 'बुधदिन', 'बृहस्पतीदिन', 'शुक्रदिन', 'शनीदिन'], + 'weekdays_short' => ['रवि', 'सोम', 'मंगल', 'बुध', 'बृहस्पती', 'शुक्र', 'शनी'], + 'weekdays_min' => ['रवि', 'सोम', 'मंगल', 'बुध', 'बृहस्पती', 'शुक्र', 'शनी'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['पूर्वाह्न', 'अपराह्न'], + + 'year' => ':count ऋतु', // less reliable + 'y' => ':count ऋतु', // less reliable + 'a_year' => ':count ऋतु', // less reliable + + 'month' => ':count महिना', + 'm' => ':count महिना', + 'a_month' => ':count महिना', + + 'week' => ':count श्रेणी:क्यालेन्डर', // less reliable + 'w' => ':count श्रेणी:क्यालेन्डर', // less reliable + 'a_week' => ':count श्रेणी:क्यालेन्डर', // less reliable + + 'day' => ':count दिन', + 'd' => ':count दिन', + 'a_day' => ':count दिन', + + 'hour' => ':count घण्टा', + 'h' => ':count घण्टा', + 'a_hour' => ':count घण्टा', + + 'minute' => ':count समय', // less reliable + 'min' => ':count समय', // less reliable + 'a_minute' => ':count समय', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mas.php b/vendor/nesbot/carbon/src/Carbon/Lang/mas.php new file mode 100644 index 0000000..cbd610c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mas.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['Ɛnkakɛnyá', 'Ɛndámâ'], + 'weekdays' => ['Jumapílí', 'Jumatátu', 'Jumane', 'Jumatánɔ', 'Alaámisi', 'Jumáa', 'Jumamósi'], + 'weekdays_short' => ['Jpi', 'Jtt', 'Jnn', 'Jtn', 'Alh', 'Iju', 'Jmo'], + 'weekdays_min' => ['Jpi', 'Jtt', 'Jnn', 'Jtn', 'Alh', 'Iju', 'Jmo'], + 'months' => ['Oladalʉ́', 'Arát', 'Ɔɛnɨ́ɔɨŋɔk', 'Olodoyíóríê inkókúâ', 'Oloilépūnyīē inkókúâ', 'Kújúɔrɔk', 'Mórusásin', 'Ɔlɔ́ɨ́bɔ́rárɛ', 'Kúshîn', 'Olgísan', 'Pʉshʉ́ka', 'Ntʉ́ŋʉ́s'], + 'months_short' => ['Dal', 'Ará', 'Ɔɛn', 'Doy', 'Lép', 'Rok', 'Sás', 'Bɔ́r', 'Kús', 'Gís', 'Shʉ́', 'Ntʉ́'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + + 'year' => ':count olameyu', // less reliable + 'y' => ':count olameyu', // less reliable + 'a_year' => ':count olameyu', // less reliable + + 'week' => ':count engolongeare orwiki', // less reliable + 'w' => ':count engolongeare orwiki', // less reliable + 'a_week' => ':count engolongeare orwiki', // less reliable + + 'hour' => ':count esahabu', // less reliable + 'h' => ':count esahabu', // less reliable + 'a_hour' => ':count esahabu', // less reliable + + 'second' => ':count are', // less reliable + 's' => ':count are', // less reliable + 'a_second' => ':count are', // less reliable + + 'month' => ':count olapa', + 'm' => ':count olapa', + 'a_month' => ':count olapa', + + 'day' => ':count enkolongʼ', + 'd' => ':count enkolongʼ', + 'a_day' => ':count enkolongʼ', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mas_TZ.php b/vendor/nesbot/carbon/src/Carbon/Lang/mas_TZ.php new file mode 100644 index 0000000..56e2905 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mas_TZ.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/mas.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mer.php b/vendor/nesbot/carbon/src/Carbon/Lang/mer.php new file mode 100644 index 0000000..2e14597 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mer.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_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['RŨ', 'ŨG'], + 'weekdays' => ['Kiumia', 'Muramuko', 'Wairi', 'Wethatu', 'Wena', 'Wetano', 'Jumamosi'], + 'weekdays_short' => ['KIU', 'MRA', 'WAI', 'WET', 'WEN', 'WTN', 'JUM'], + 'weekdays_min' => ['KIU', 'MRA', 'WAI', 'WET', 'WEN', 'WTN', 'JUM'], + 'months' => ['Januarĩ', 'Feburuarĩ', 'Machi', 'Ĩpurũ', 'Mĩĩ', 'Njuni', 'Njuraĩ', 'Agasti', 'Septemba', 'Oktũba', 'Novemba', 'Dicemba'], + 'months_short' => ['JAN', 'FEB', 'MAC', 'ĨPU', 'MĨĨ', 'NJU', 'NJR', 'AGA', 'SPT', 'OKT', 'NOV', 'DEC'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + + 'year' => ':count murume', // less reliable + 'y' => ':count murume', // less reliable + 'a_year' => ':count murume', // less reliable + + 'month' => ':count muchaara', // less reliable + 'm' => ':count muchaara', // less reliable + 'a_month' => ':count muchaara', // less reliable + + 'minute' => ':count monto', // less reliable + 'min' => ':count monto', // less reliable + 'a_minute' => ':count monto', // less reliable + + 'second' => ':count gikeno', // less reliable + 's' => ':count gikeno', // less reliable + 'a_second' => ':count gikeno', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mfe.php b/vendor/nesbot/carbon/src/Carbon/Lang/mfe.php new file mode 100644 index 0000000..4d6e6b6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mfe.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/mfe_MU.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mfe_MU.php b/vendor/nesbot/carbon/src/Carbon/Lang/mfe_MU.php new file mode 100644 index 0000000..2d27b45 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mfe_MU.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Samsung Electronics Co., Ltd. akhilesh.k@samsung.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['zanvie', 'fevriye', 'mars', 'avril', 'me', 'zin', 'zilye', 'out', 'septam', 'oktob', 'novam', 'desam'], + 'months_short' => ['zan', 'fev', 'mar', 'avr', 'me', 'zin', 'zil', 'out', 'sep', 'okt', 'nov', 'des'], + 'weekdays' => ['dimans', 'lindi', 'mardi', 'merkredi', 'zedi', 'vandredi', 'samdi'], + 'weekdays_short' => ['dim', 'lin', 'mar', 'mer', 'ze', 'van', 'sam'], + 'weekdays_min' => ['dim', 'lin', 'mar', 'mer', 'ze', 'van', 'sam'], + + 'year' => ':count banané', + 'y' => ':count banané', + 'a_year' => ':count banané', + + 'month' => ':count mwa', + 'm' => ':count mwa', + 'a_month' => ':count mwa', + + 'week' => ':count sémenn', + 'w' => ':count sémenn', + 'a_week' => ':count sémenn', + + 'day' => ':count zour', + 'd' => ':count zour', + 'a_day' => ':count zour', + + 'hour' => ':count -er-tan', + 'h' => ':count -er-tan', + 'a_hour' => ':count -er-tan', + + 'minute' => ':count minitt', + 'min' => ':count minitt', + 'a_minute' => ':count minitt', + + 'second' => ':count déziém', + 's' => ':count déziém', + 'a_second' => ':count déziém', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mg.php b/vendor/nesbot/carbon/src/Carbon/Lang/mg.php new file mode 100644 index 0000000..40bc2a8 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mg.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/mg_MG.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mg_MG.php b/vendor/nesbot/carbon/src/Carbon/Lang/mg_MG.php new file mode 100644 index 0000000..6a14535 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mg_MG.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - The Debian Project modified by GNU//Linux Malagasy Rado Ramarotafika,Do-Risika RAFIEFERANTSIARONJY rado@linuxmg.org,dourix@free.fr + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['Janoary', 'Febroary', 'Martsa', 'Aprily', 'Mey', 'Jona', 'Jolay', 'Aogositra', 'Septambra', 'Oktobra', 'Novambra', 'Desambra'], + 'months_short' => ['Jan', 'Feb', 'Mar', 'Apr', 'Mey', 'Jon', 'Jol', 'Aog', 'Sep', 'Okt', 'Nov', 'Des'], + 'weekdays' => ['alahady', 'alatsinainy', 'talata', 'alarobia', 'alakamisy', 'zoma', 'sabotsy'], + 'weekdays_short' => ['lhd', 'lts', 'tlt', 'lrb', 'lkm', 'zom', 'sab'], + 'weekdays_min' => ['lhd', 'lts', 'tlt', 'lrb', 'lkm', 'zom', 'sab'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + + 'minute' => ':count minitra', // less reliable + 'min' => ':count minitra', // less reliable + 'a_minute' => ':count minitra', // less reliable + + 'year' => ':count taona', + 'y' => ':count taona', + 'a_year' => ':count taona', + + 'month' => ':count volana', + 'm' => ':count volana', + 'a_month' => ':count volana', + + 'week' => ':count herinandro', + 'w' => ':count herinandro', + 'a_week' => ':count herinandro', + + 'day' => ':count andro', + 'd' => ':count andro', + 'a_day' => ':count andro', + + 'hour' => ':count ora', + 'h' => ':count ora', + 'a_hour' => ':count ora', + + 'second' => ':count segondra', + 's' => ':count segondra', + 'a_second' => ':count segondra', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mgh.php b/vendor/nesbot/carbon/src/Carbon/Lang/mgh.php new file mode 100644 index 0000000..2a80960 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mgh.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['wichishu', 'mchochil’l'], + 'weekdays' => ['Sabato', 'Jumatatu', 'Jumanne', 'Jumatano', 'Arahamisi', 'Ijumaa', 'Jumamosi'], + 'weekdays_short' => ['Sab', 'Jtt', 'Jnn', 'Jtn', 'Ara', 'Iju', 'Jmo'], + 'weekdays_min' => ['Sab', 'Jtt', 'Jnn', 'Jtn', 'Ara', 'Iju', 'Jmo'], + 'months' => ['Mweri wo kwanza', 'Mweri wo unayeli', 'Mweri wo uneraru', 'Mweri wo unecheshe', 'Mweri wo unethanu', 'Mweri wo thanu na mocha', 'Mweri wo saba', 'Mweri wo nane', 'Mweri wo tisa', 'Mweri wo kumi', 'Mweri wo kumi na moja', 'Mweri wo kumi na yel’li'], + 'months_short' => ['Kwa', 'Una', 'Rar', 'Che', 'Tha', 'Moc', 'Sab', 'Nan', 'Tis', 'Kum', 'Moj', 'Yel'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mgo.php b/vendor/nesbot/carbon/src/Carbon/Lang/mgo.php new file mode 100644 index 0000000..a126c9f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mgo.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'weekdays' => ['Aneg 1', 'Aneg 2', 'Aneg 3', 'Aneg 4', 'Aneg 5', 'Aneg 6', 'Aneg 7'], + 'weekdays_short' => ['Aneg 1', 'Aneg 2', 'Aneg 3', 'Aneg 4', 'Aneg 5', 'Aneg 6', 'Aneg 7'], + 'weekdays_min' => ['1', '2', '3', '4', '5', '6', '7'], + 'months' => ['iməg mbegtug', 'imeg àbùbì', 'imeg mbəŋchubi', 'iməg ngwə̀t', 'iməg fog', 'iməg ichiibɔd', 'iməg àdùmbə̀ŋ', 'iməg ichika', 'iməg kud', 'iməg tèsiʼe', 'iməg zò', 'iməg krizmed'], + 'months_short' => ['mbegtug', 'imeg àbùbì', 'imeg mbəŋchubi', 'iməg ngwə̀t', 'iməg fog', 'iməg ichiibɔd', 'iməg àdùmbə̀ŋ', 'iməg ichika', 'iməg kud', 'iməg tèsiʼe', 'iməg zò', 'iməg krizmed'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-dd', + 'LL' => 'YYYY MMM D', + 'LLL' => 'YYYY MMMM D HH:mm', + 'LLLL' => 'dddd, YYYY MMMM DD HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mhr.php b/vendor/nesbot/carbon/src/Carbon/Lang/mhr.php new file mode 100644 index 0000000..6bbc9f6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mhr.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/mhr_RU.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mhr_RU.php b/vendor/nesbot/carbon/src/Carbon/Lang/mhr_RU.php new file mode 100644 index 0000000..309ead9 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mhr_RU.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - PeshSajSoft Ltd. Vyacheslav Kileev slavakileev@yandex.ru + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'YYYY.MM.DD', + ], + 'months' => ['Шорыкйол', 'Пургыж', 'Ӱярня', 'Вӱдшор', 'Ага', 'Пеледыш', 'Сӱрем', 'Сорла', 'Идым', 'Шыжа', 'Кылме', 'Теле'], + 'months_short' => ['Шрк', 'Пгж', 'Ӱрн', 'Вшр', 'Ага', 'Пдш', 'Срм', 'Срл', 'Идм', 'Шыж', 'Клм', 'Тел'], + 'weekdays' => ['Рушарня', 'Шочмо', 'Кушкыжмо', 'Вӱргече', 'Изарня', 'Кугарня', 'Шуматкече'], + 'weekdays_short' => ['Ршр', 'Шчм', 'Кжм', 'Вгч', 'Изр', 'Кгр', 'Шмт'], + 'weekdays_min' => ['Ршр', 'Шчм', 'Кжм', 'Вгч', 'Изр', 'Кгр', 'Шмт'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + + 'year' => ':count идалык', + 'y' => ':count идалык', + 'a_year' => ':count идалык', + + 'month' => ':count Тылзе', + 'm' => ':count Тылзе', + 'a_month' => ':count Тылзе', + + 'week' => ':count арня', + 'w' => ':count арня', + 'a_week' => ':count арня', + + 'day' => ':count кече', + 'd' => ':count кече', + 'a_day' => ':count кече', + + 'hour' => ':count час', + 'h' => ':count час', + 'a_hour' => ':count час', + + 'minute' => ':count минут', + 'min' => ':count минут', + 'a_minute' => ':count минут', + + 'second' => ':count кокымшан', + 's' => ':count кокымшан', + 'a_second' => ':count кокымшан', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mi.php b/vendor/nesbot/carbon/src/Carbon/Lang/mi.php new file mode 100644 index 0000000..b7f51ec --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mi.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - John Corrigan + * - François B + */ +return [ + 'year' => ':count tau', + 'a_year' => '{1}he tau|:count tau', + 'month' => ':count marama', + 'a_month' => '{1}he marama|:count marama', + 'week' => ':count wiki', + 'a_week' => '{1}he wiki|:count wiki', + 'day' => ':count ra', + 'a_day' => '{1}he ra|:count ra', + 'hour' => ':count haora', + 'a_hour' => '{1}te haora|:count haora', + 'minute' => ':count meneti', + 'a_minute' => '{1}he meneti|:count meneti', + 'second' => ':count hēkona', + 'a_second' => '{1}te hēkona ruarua|:count hēkona', + 'ago' => ':time i mua', + 'from_now' => 'i roto i :time', + 'diff_yesterday' => 'inanahi', + 'diff_yesterday_regexp' => 'inanahi(?:\\s+i)?', + 'diff_today' => 'i teie', + 'diff_today_regexp' => 'i teie(?:\\s+mahana,)?(?:\\s+i)?', + 'diff_tomorrow' => 'apopo', + 'diff_tomorrow_regexp' => 'apopo(?:\\s+i)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY [i] HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY [i] HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[i teie mahana, i] LT', + 'nextDay' => '[apopo i] LT', + 'nextWeek' => 'dddd [i] LT', + 'lastDay' => '[inanahi i] LT', + 'lastWeek' => 'dddd [whakamutunga i] LT', + 'sameElse' => 'L', + ], + 'ordinal' => ':numberº', + 'months' => ['Kohi-tāte', 'Hui-tanguru', 'Poutū-te-rangi', 'Paenga-whāwhā', 'Haratua', 'Pipiri', 'Hōngoingoi', 'Here-turi-kōkā', 'Mahuru', 'Whiringa-ā-nuku', 'Whiringa-ā-rangi', 'Hakihea'], + 'months_short' => ['Kohi', 'Hui', 'Pou', 'Pae', 'Hara', 'Pipi', 'Hōngoi', 'Here', 'Mahu', 'Whi-nu', 'Whi-ra', 'Haki'], + 'weekdays' => ['Rātapu', 'Mane', 'Tūrei', 'Wenerei', 'Tāite', 'Paraire', 'Hātarei'], + 'weekdays_short' => ['Ta', 'Ma', 'Tū', 'We', 'Tāi', 'Pa', 'Hā'], + 'weekdays_min' => ['Ta', 'Ma', 'Tū', 'We', 'Tāi', 'Pa', 'Hā'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' me te '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mi_NZ.php b/vendor/nesbot/carbon/src/Carbon/Lang/mi_NZ.php new file mode 100644 index 0000000..6b964e3 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mi_NZ.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__.'/mi.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/miq.php b/vendor/nesbot/carbon/src/Carbon/Lang/miq.php new file mode 100644 index 0000000..51e5a98 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/miq.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/miq_NI.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/miq_NI.php b/vendor/nesbot/carbon/src/Carbon/Lang/miq_NI.php new file mode 100644 index 0000000..57faa31 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/miq_NI.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['siakwa kati', 'kuswa kati', 'kakamuk kati', 'lî wainhka kati', 'lih mairin kati', 'lî kati', 'pastara kati', 'sikla kati', 'wîs kati', 'waupasa kati', 'yahbra kati', 'trisu kati'], + 'months_short' => ['siakwa kati', 'kuswa kati', 'kakamuk kati', 'lî wainhka kati', 'lih mairin kati', 'lî kati', 'pastara kati', 'sikla kati', 'wîs kati', 'waupasa kati', 'yahbra kati', 'trisu kati'], + 'weekdays' => ['sandi', 'mundi', 'tiusdi', 'wensde', 'tausde', 'praidi', 'satadi'], + 'weekdays_short' => ['san', 'mun', 'tius', 'wens', 'taus', 'prai', 'sat'], + 'weekdays_min' => ['san', 'mun', 'tius', 'wens', 'taus', 'prai', 'sat'], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 7, + 'meridiem' => ['VM', 'NM'], + + 'month' => ':count kati', // less reliable + 'm' => ':count kati', // less reliable + 'a_month' => ':count kati', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mjw.php b/vendor/nesbot/carbon/src/Carbon/Lang/mjw.php new file mode 100644 index 0000000..617154c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mjw.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/mjw_IN.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mjw_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/mjw_IN.php new file mode 100644 index 0000000..58ed0d1 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mjw_IN.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Jor Teron bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['Arkoi', 'Thangthang', 'There', 'Jangmi', 'Aru', 'Vosik', 'Jakhong', 'Paipai', 'Chiti', 'Phere', 'Phaikuni', 'Matijong'], + 'months_short' => ['Ark', 'Thang', 'The', 'Jang', 'Aru', 'Vos', 'Jak', 'Pai', 'Chi', 'Phe', 'Phai', 'Mati'], + 'weekdays' => ['Bhomkuru', 'Urmi', 'Durmi', 'Thelang', 'Theman', 'Bhomta', 'Bhomti'], + 'weekdays_short' => ['Bhom', 'Ur', 'Dur', 'Tkel', 'Tkem', 'Bhta', 'Bhti'], + 'weekdays_min' => ['Bhom', 'Ur', 'Dur', 'Tkel', 'Tkem', 'Bhta', 'Bhti'], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mk.php b/vendor/nesbot/carbon/src/Carbon/Lang/mk.php new file mode 100644 index 0000000..d822de0 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mk.php @@ -0,0 +1,116 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Sashko Todorov + * - Josh Soref + * - François B + * - Serhan Apaydın + * - Borislav Mickov + * - JD Isaacks + * - Tomi Atanasoski + */ + +use Carbon\CarbonInterface; + +return [ + 'year' => ':count година|:count години', + 'a_year' => 'година|:count години', + 'y' => ':count год.', + 'month' => ':count месец|:count месеци', + 'a_month' => 'месец|:count месеци', + 'm' => ':count месец|:count месеци', + 'week' => ':count седмица|:count седмици', + 'a_week' => 'седмица|:count седмици', + 'w' => ':count седмица|:count седмици', + 'day' => ':count ден|:count дена', + 'a_day' => 'ден|:count дена', + 'd' => ':count ден|:count дена', + 'hour' => ':count час|:count часа', + 'a_hour' => 'час|:count часа', + 'h' => ':count час|:count часа', + 'minute' => ':count минута|:count минути', + 'a_minute' => 'минута|:count минути', + 'min' => ':count мин.', + 'second' => ':count секунда|:count секунди', + 'a_second' => 'неколку секунди|:count секунди', + 's' => ':count сек.', + 'ago' => 'пред :time', + 'from_now' => 'после :time', + 'after' => 'по :time', + 'before' => 'пред :time', + 'diff_now' => 'сега', + 'diff_today' => 'Денес', + 'diff_today_regexp' => 'Денес(?:\\s+во)?', + 'diff_yesterday' => 'вчера', + 'diff_yesterday_regexp' => 'Вчера(?:\\s+во)?', + 'diff_tomorrow' => 'утре', + 'diff_tomorrow_regexp' => 'Утре(?:\\s+во)?', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'D.MM.YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY H:mm', + 'LLLL' => 'dddd, D MMMM YYYY H:mm', + ], + 'calendar' => [ + 'sameDay' => '[Денес во] LT', + 'nextDay' => '[Утре во] LT', + 'nextWeek' => '[Во] dddd [во] LT', + 'lastDay' => '[Вчера во] LT', + 'lastWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + case 3: + case 6: + return '[Изминатата] dddd [во] LT'; + default: + return '[Изминатиот] dddd [во] LT'; + } + }, + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + $lastDigit = $number % 10; + $last2Digits = $number % 100; + if ($number === 0) { + return $number.'-ев'; + } + if ($last2Digits === 0) { + return $number.'-ен'; + } + if ($last2Digits > 10 && $last2Digits < 20) { + return $number.'-ти'; + } + if ($lastDigit === 1) { + return $number.'-ви'; + } + if ($lastDigit === 2) { + return $number.'-ри'; + } + if ($lastDigit === 7 || $lastDigit === 8) { + return $number.'-ми'; + } + + return $number.'-ти'; + }, + 'months' => ['јануари', 'февруари', 'март', 'април', 'мај', 'јуни', 'јули', 'август', 'септември', 'октомври', 'ноември', 'декември'], + 'months_short' => ['јан', 'фев', 'мар', 'апр', 'мај', 'јун', 'јул', 'авг', 'сеп', 'окт', 'ное', 'дек'], + 'weekdays' => ['недела', 'понеделник', 'вторник', 'среда', 'четврток', 'петок', 'сабота'], + 'weekdays_short' => ['нед', 'пон', 'вто', 'сре', 'чет', 'пет', 'саб'], + 'weekdays_min' => ['нe', 'пo', 'вт', 'ср', 'че', 'пе', 'сa'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' и '], + 'meridiem' => ['АМ', 'ПМ'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mk_MK.php b/vendor/nesbot/carbon/src/Carbon/Lang/mk_MK.php new file mode 100644 index 0000000..95e2ff9 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mk_MK.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__.'/mk.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ml.php b/vendor/nesbot/carbon/src/Carbon/Lang/ml.php new file mode 100644 index 0000000..1abd6c4 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ml.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - JD Isaacks + */ +return [ + 'year' => ':count വർഷം', + 'a_year' => 'ഒരു വർഷം|:count വർഷം', + 'month' => ':count മാസം', + 'a_month' => 'ഒരു മാസം|:count മാസം', + 'week' => ':count ആഴ്ച', + 'a_week' => 'ഒരാഴ്ച|:count ആഴ്ച', + 'day' => ':count ദിവസം', + 'a_day' => 'ഒരു ദിവസം|:count ദിവസം', + 'hour' => ':count മണിക്കൂർ', + 'a_hour' => 'ഒരു മണിക്കൂർ|:count മണിക്കൂർ', + 'minute' => ':count മിനിറ്റ്', + 'a_minute' => 'ഒരു മിനിറ്റ്|:count മിനിറ്റ്', + 'second' => ':count സെക്കൻഡ്', + 'a_second' => 'അൽപ നിമിഷങ്ങൾ|:count സെക്കൻഡ്', + 'ago' => ':time മുൻപ്', + 'from_now' => ':time കഴിഞ്ഞ്', + 'diff_now' => 'ഇപ്പോൾ', + 'diff_today' => 'ഇന്ന്', + 'diff_yesterday' => 'ഇന്നലെ', + 'diff_tomorrow' => 'നാളെ', + 'formats' => [ + 'LT' => 'A h:mm -നു', + 'LTS' => 'A h:mm:ss -നു', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY, A h:mm -നു', + 'LLLL' => 'dddd, D MMMM YYYY, A h:mm -നു', + ], + 'calendar' => [ + 'sameDay' => '[ഇന്ന്] LT', + 'nextDay' => '[നാളെ] LT', + 'nextWeek' => 'dddd, LT', + 'lastDay' => '[ഇന്നലെ] LT', + 'lastWeek' => '[കഴിഞ്ഞ] dddd, LT', + 'sameElse' => 'L', + ], + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'രാത്രി'; + } + if ($hour < 12) { + return 'രാവിലെ'; + } + if ($hour < 17) { + return 'ഉച്ച കഴിഞ്ഞ്'; + } + if ($hour < 20) { + return 'വൈകുന്നേരം'; + } + + return 'രാത്രി'; + }, + 'months' => ['ജനുവരി', 'ഫെബ്രുവരി', 'മാർച്ച്', 'ഏപ്രിൽ', 'മേയ്', 'ജൂൺ', 'ജൂലൈ', 'ഓഗസ്റ്റ്', 'സെപ്റ്റംബർ', 'ഒക്ടോബർ', 'നവംബർ', 'ഡിസംബർ'], + 'months_short' => ['ജനു.', 'ഫെബ്രു.', 'മാർ.', 'ഏപ്രി.', 'മേയ്', 'ജൂൺ', 'ജൂലൈ.', 'ഓഗ.', 'സെപ്റ്റ.', 'ഒക്ടോ.', 'നവം.', 'ഡിസം.'], + 'weekdays' => ['ഞായറാഴ്ച', 'തിങ്കളാഴ്ച', 'ചൊവ്വാഴ്ച', 'ബുധനാഴ്ച', 'വ്യാഴാഴ്ച', 'വെള്ളിയാഴ്ച', 'ശനിയാഴ്ച'], + 'weekdays_short' => ['ഞായർ', 'തിങ്കൾ', 'ചൊവ്വ', 'ബുധൻ', 'വ്യാഴം', 'വെള്ളി', 'ശനി'], + 'weekdays_min' => ['ഞാ', 'തി', 'ചൊ', 'ബു', 'വ്യാ', 'വെ', 'ശ'], + 'list' => ', ', + 'weekend' => [0, 0], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ml_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/ml_IN.php new file mode 100644 index 0000000..000e795 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ml_IN.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__.'/ml.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mn.php b/vendor/nesbot/carbon/src/Carbon/Lang/mn.php new file mode 100644 index 0000000..38c6434 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mn.php @@ -0,0 +1,116 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Philippe Vaucher + * - Tsutomu Kuroda + * - tjku + * - Max Melentiev + * - Zolzaya Erdenebaatar + * - Tom Hughes + * - Akira Matsuda + * - Christopher Dell + * - Michael Kessler + * - Enrique Vidal + * - Simone Carletti + * - Aaron Patterson + * - Nicolás Hock Isaza + * - Ochirkhuyag + * - Batmandakh + * - lucifer-crybaby + */ +return [ + '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_mode' => 'last', + 'ago' => ':time өмнө', + 'year_ago' => ':count жилийн', + 'y_ago' => ':count жилийн', + 'month_ago' => ':count сарын', + 'm_ago' => ':count сарын', + 'day_ago' => ':count хоногийн', + 'd_ago' => ':count хоногийн', + 'week_ago' => ':count долоо хоногийн', + 'w_ago' => ':count долоо хоногийн', + 'hour_ago' => ':count цагийн', + 'minute_ago' => ':count минутын', + 'second_ago' => ':count секундын', + + 'from_now_mode' => 'last', + 'from_now' => 'одоогоос :time', + 'year_from_now' => ':count жилийн дараа', + 'y_from_now' => ':count жилийн дараа', + 'month_from_now' => ':count сарын дараа', + 'm_from_now' => ':count сарын дараа', + 'day_from_now' => ':count хоногийн дараа', + 'd_from_now' => ':count хоногийн дараа', + 'hour_from_now' => ':count цагийн дараа', + 'minute_from_now' => ':count минутын дараа', + 'second_from_now' => ':count секундын дараа', + + 'after_mode' => 'last', + 'after' => ':time дараа', + 'year_after' => ':count жилийн', + 'y_after' => ':count жилийн', + 'month_after' => ':count сарын', + 'm_after' => ':count сарын', + 'day_after' => ':count хоногийн', + 'd_after' => ':count хоногийн', + 'hour_after' => ':count цагийн', + 'minute_after' => ':count минутын', + 'second_after' => ':count секундын', + + 'before_mode' => 'last', + 'before' => ':time өмнө', + 'year_before' => ':count жилийн', + 'y_before' => ':count жилийн', + 'month_before' => ':count сарын', + 'm_before' => ':count сарын', + 'day_before' => ':count хоногийн', + 'd_before' => ':count хоногийн', + 'hour_before' => ':count цагийн', + 'minute_before' => ':count минутын', + 'second_before' => ':count секундын', + + 'list' => ', ', + 'diff_now' => 'одоо', + 'diff_yesterday' => 'өчигдөр', + 'diff_tomorrow' => 'маргааш', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-DD', + 'LL' => 'YYYY MMMM DD', + 'LLL' => 'YY-MM-DD, HH:mm', + 'LLLL' => 'YYYY MMMM DD, HH:mm', + ], + 'weekdays' => ['Ням', 'Даваа', 'Мягмар', 'Лхагва', 'Пүрэв', 'Баасан', 'Бямба'], + 'weekdays_short' => ['Ня', 'Да', 'Мя', 'Лх', 'Пү', 'Ба', 'Бя'], + 'weekdays_min' => ['Ня', 'Да', 'Мя', 'Лх', 'Пү', 'Ба', 'Бя'], + 'months' => ['1 сар', '2 сар', '3 сар', '4 сар', '5 сар', '6 сар', '7 сар', '8 сар', '9 сар', '10 сар', '11 сар', '12 сар'], + 'months_short' => ['1 сар', '2 сар', '3 сар', '4 сар', '5 сар', '6 сар', '7 сар', '8 сар', '9 сар', '10 сар', '11 сар', '12 сар'], + 'meridiem' => ['өглөө', 'орой'], + 'first_day_of_week' => 1, +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mn_MN.php b/vendor/nesbot/carbon/src/Carbon/Lang/mn_MN.php new file mode 100644 index 0000000..e5ce426 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mn_MN.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__.'/mn.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mni.php b/vendor/nesbot/carbon/src/Carbon/Lang/mni.php new file mode 100644 index 0000000..cafa2f8 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mni.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/mni_IN.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mni_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/mni_IN.php new file mode 100644 index 0000000..45d430e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mni_IN.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Red Hat Pune libc-alpha@sourceware.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['জানুৱারি', 'ফেব্রুৱারি', 'মার্চ', 'এপ্রিল', 'মে', 'জুন', 'জুলাই', 'আগষ্ট', 'সেপ্তেম্বর', 'ওক্তোবর', 'নবেম্বর', 'ডিসেম্বর'], + 'months_short' => ['জান', 'ফেব', 'মার', 'এপ্রি', 'মে', 'জুন', 'জুল', 'আগ', 'সেপ', 'ওক্ত', 'নবে', 'ডিস'], + 'weekdays' => ['নোংমাইজিং', 'নিংথৌকাবা', 'লৈবাকপোকপা', 'য়ুমশকৈশা', 'শগোলশেন', 'ইরাই', 'থাংজ'], + 'weekdays_short' => ['নোং', 'নিং', 'লৈবাক', 'য়ুম', 'শগোল', 'ইরা', 'থাং'], + 'weekdays_min' => ['নোং', 'নিং', 'লৈবাক', 'য়ুম', 'শগোল', 'ইরা', 'থাং'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['এ.ম.', 'প.ম.'], + + 'year' => ':count ইসিং', // less reliable + 'y' => ':count ইসিং', // less reliable + 'a_year' => ':count ইসিং', // less reliable + + 'second' => ':count ꯅꯤꯡꯊꯧꯀꯥꯕ', // less reliable + 's' => ':count ꯅꯤꯡꯊꯧꯀꯥꯕ', // less reliable + 'a_second' => ':count ꯅꯤꯡꯊꯧꯀꯥꯕ', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mo.php b/vendor/nesbot/carbon/src/Carbon/Lang/mo.php new file mode 100644 index 0000000..102afcd --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mo.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__.'/ro.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mr.php b/vendor/nesbot/carbon/src/Carbon/Lang/mr.php new file mode 100644 index 0000000..4aaeafd --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mr.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Vikram-enyota + */ +return [ + 'year' => ':count वर्ष', + 'y' => ':count वर्ष', + 'month' => ':count महिना|:count महिने', + 'm' => ':count महिना|:count महिने', + 'week' => ':count आठवडा|:count आठवडे', + 'w' => ':count आठवडा|:count आठवडे', + 'day' => ':count दिवस', + 'd' => ':count दिवस', + 'hour' => ':count तास', + 'h' => ':count तास', + 'minute' => ':count मिनिटे', + 'min' => ':count मिनिटे', + 'second' => ':count सेकंद', + 's' => ':count सेकंद', + + 'ago' => ':timeपूर्वी', + 'from_now' => ':timeमध्ये', + 'before' => ':timeपूर्वी', + 'after' => ':timeनंतर', + + 'diff_now' => 'आत्ता', + 'diff_today' => 'आज', + 'diff_yesterday' => 'काल', + 'diff_tomorrow' => 'उद्या', + + 'formats' => [ + 'LT' => 'A h:mm वाजता', + 'LTS' => 'A h:mm:ss वाजता', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY, A h:mm वाजता', + 'LLLL' => 'dddd, D MMMM YYYY, A h:mm वाजता', + ], + + 'calendar' => [ + 'sameDay' => '[आज] LT', + 'nextDay' => '[उद्या] LT', + 'nextWeek' => 'dddd, LT', + 'lastDay' => '[काल] LT', + 'lastWeek' => '[मागील] dddd, LT', + 'sameElse' => 'L', + ], + + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'रात्री'; + } + if ($hour < 10) { + return 'सकाळी'; + } + if ($hour < 17) { + return 'दुपारी'; + } + if ($hour < 20) { + return 'सायंकाळी'; + } + + return 'रात्री'; + }, + + 'months' => ['जानेवारी', 'फेब्रुवारी', 'मार्च', 'एप्रिल', 'मे', 'जून', 'जुलै', 'ऑगस्ट', 'सप्टेंबर', 'ऑक्टोबर', 'नोव्हेंबर', 'डिसेंबर'], + 'months_short' => ['जाने.', 'फेब्रु.', 'मार्च.', 'एप्रि.', 'मे.', 'जून.', 'जुलै.', 'ऑग.', 'सप्टें.', 'ऑक्टो.', 'नोव्हें.', 'डिसें.'], + 'weekdays' => ['रविवार', 'सोमवार', 'मंगळवार', 'बुधवार', 'गुरूवार', 'शुक्रवार', 'शनिवार'], + 'weekdays_short' => ['रवि', 'सोम', 'मंगळ', 'बुध', 'गुरू', 'शुक्र', 'शनि'], + 'weekdays_min' => ['र', 'सो', 'मं', 'बु', 'गु', 'शु', 'श'], + 'list' => [', ', ' आणि '], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, + 'weekend' => [0, 0], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mr_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/mr_IN.php new file mode 100644 index 0000000..7bca919 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mr_IN.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__.'/mr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ms.php b/vendor/nesbot/carbon/src/Carbon/Lang/ms.php new file mode 100644 index 0000000..36934ee --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ms.php @@ -0,0 +1,104 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - Azri Jamil + * - JD Isaacks + * - Josh Soref + * - Azri Jamil + * - Hariadi Hinta + * - Ashraf Kamarudin + */ +return [ + 'year' => ':count tahun', + 'a_year' => '{1}setahun|]1,Inf[:count tahun', + 'y' => ':count tahun', + 'month' => ':count bulan', + 'a_month' => '{1}sebulan|]1,Inf[:count bulan', + 'm' => ':count bulan', + 'week' => ':count minggu', + 'a_week' => '{1}seminggu|]1,Inf[:count minggu', + 'w' => ':count minggu', + 'day' => ':count hari', + 'a_day' => '{1}sehari|]1,Inf[:count hari', + 'd' => ':count hari', + 'hour' => ':count jam', + 'a_hour' => '{1}sejam|]1,Inf[:count jam', + 'h' => ':count jam', + 'minute' => ':count minit', + 'a_minute' => '{1}seminit|]1,Inf[:count minit', + 'min' => ':count minit', + 'second' => ':count saat', + 'a_second' => '{1}beberapa saat|]1,Inf[:count saat', + 'millisecond' => ':count milisaat', + 'a_millisecond' => '{1}semilisaat|]1,Inf[:count milliseconds', + 'microsecond' => ':count mikrodetik', + 'a_microsecond' => '{1}semikrodetik|]1,Inf[:count mikrodetik', + 's' => ':count saat', + 'ago' => ':time yang lepas', + 'from_now' => ':time dari sekarang', + 'after' => ':time kemudian', + 'before' => ':time lepas', + 'diff_now' => 'sekarang', + 'diff_today' => 'Hari', + 'diff_today_regexp' => 'Hari(?:\\s+ini)?(?:\\s+pukul)?', + 'diff_yesterday' => 'semalam', + 'diff_yesterday_regexp' => 'Semalam(?:\\s+pukul)?', + 'diff_tomorrow' => 'esok', + 'diff_tomorrow_regexp' => 'Esok(?:\\s+pukul)?', + 'diff_before_yesterday' => 'kelmarin', + 'diff_after_tomorrow' => 'lusa', + 'formats' => [ + 'LT' => 'HH.mm', + 'LTS' => 'HH.mm.ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY [pukul] HH.mm', + 'LLLL' => 'dddd, D MMMM YYYY [pukul] HH.mm', + ], + 'calendar' => [ + 'sameDay' => '[Hari ini pukul] LT', + 'nextDay' => '[Esok pukul] LT', + 'nextWeek' => 'dddd [pukul] LT', + 'lastDay' => '[Kelmarin pukul] LT', + 'lastWeek' => 'dddd [lepas pukul] LT', + 'sameElse' => 'L', + ], + 'meridiem' => function ($hour) { + if ($hour < 1) { + return 'tengah malam'; + } + + if ($hour < 12) { + return 'pagi'; + } + + if ($hour < 13) { + return 'tengah hari'; + } + + if ($hour < 19) { + return 'petang'; + } + + return 'malam'; + }, + 'months' => ['Januari', 'Februari', 'Mac', 'April', 'Mei', 'Jun', 'Julai', 'Ogos', 'September', 'Oktober', 'November', 'Disember'], + 'months_short' => ['Jan', 'Feb', 'Mac', 'Apr', 'Mei', 'Jun', 'Jul', 'Ogs', 'Sep', 'Okt', 'Nov', 'Dis'], + 'weekdays' => ['Ahad', 'Isnin', 'Selasa', 'Rabu', 'Khamis', 'Jumaat', 'Sabtu'], + 'weekdays_short' => ['Ahd', 'Isn', 'Sel', 'Rab', 'Kha', 'Jum', 'Sab'], + 'weekdays_min' => ['Ah', 'Is', 'Sl', 'Rb', 'Km', 'Jm', 'Sb'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' dan '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ms_BN.php b/vendor/nesbot/carbon/src/Carbon/Lang/ms_BN.php new file mode 100644 index 0000000..ef837a2 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ms_BN.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ms.php', [ + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'D/MM/yy', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY, h:mm a', + 'LLLL' => 'dd MMMM YYYY, h:mm a', + ], + 'meridiem' => ['a', 'p'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ms_MY.php b/vendor/nesbot/carbon/src/Carbon/Lang/ms_MY.php new file mode 100644 index 0000000..970d604 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ms_MY.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - Azri Jamil + * - JD Isaacks + */ +return require __DIR__.'/ms.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ms_SG.php b/vendor/nesbot/carbon/src/Carbon/Lang/ms_SG.php new file mode 100644 index 0000000..77cb83d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ms_SG.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ms.php', [ + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'D/MM/yy', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY, h:mm a', + 'LLLL' => 'dddd, D MMMM YYYY, h:mm a', + ], + 'meridiem' => ['a', 'p'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mt.php b/vendor/nesbot/carbon/src/Carbon/Lang/mt.php new file mode 100644 index 0000000..e8aadcc --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mt.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Alessandro Maruccia + */ +return [ + 'year' => 'sena|:count sni|:count sni|:count sni', + 'y' => 'sa sena|:count snin|:count snin|:count snin', + 'month' => 'xahar|:count xhur|:count xhur|:count xhur', + 'm' => ':count xahar|:count xhur|:count xhur|:count xhur', + 'week' => 'gimgħa|:count ġimgħat|:count ġimgħat|:count ġimgħat', + 'w' => 'ġimgħa|:count ġimgħat|:count ġimgħat|:count ġimgħat', + 'day' => 'ġurnata|:count ġranet|:count ġranet|:count ġranet', + 'd' => 'ġurnata|:count ġranet|:count ġranet|:count ġranet', + 'hour' => 'siegħa|:count siegħat|:count siegħat|:count siegħat', + 'h' => 'siegħa|:count sigħat|:count sigħat|:count sigħat', + 'minute' => 'minuta|:count minuti|:count minuti|:count minuti', + 'min' => 'min.|:count min.|:count min.|:count min.', + 'second' => 'ftit sekondi|:count sekondi|:count sekondi|:count sekondi', + 's' => 'sek.|:count sek.|:count sek.|:count sek.', + 'ago' => ':time ilu', + 'from_now' => 'f’ :time', + 'diff_now' => 'issa', + 'diff_today' => 'Illum', + 'diff_today_regexp' => 'Illum(?:\\s+fil-)?', + 'diff_yesterday' => 'lbieraħ', + 'diff_yesterday_regexp' => 'Il-bieraħ(?:\\s+fil-)?', + 'diff_tomorrow' => 'għada', + 'diff_tomorrow_regexp' => 'Għada(?:\\s+fil-)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Illum fil-]LT', + 'nextDay' => '[Għada fil-]LT', + 'nextWeek' => 'dddd [fil-]LT', + 'lastDay' => '[Il-bieraħ fil-]LT', + 'lastWeek' => 'dddd [li għadda] [fil-]LT', + 'sameElse' => 'L', + ], + 'ordinal' => ':numberº', + 'months' => ['Jannar', 'Frar', 'Marzu', 'April', 'Mejju', 'Ġunju', 'Lulju', 'Awwissu', 'Settembru', 'Ottubru', 'Novembru', 'Diċembru'], + 'months_short' => ['Jan', 'Fra', 'Mar', 'Apr', 'Mej', 'Ġun', 'Lul', 'Aww', 'Set', 'Ott', 'Nov', 'Diċ'], + 'weekdays' => ['Il-Ħadd', 'It-Tnejn', 'It-Tlieta', 'L-Erbgħa', 'Il-Ħamis', 'Il-Ġimgħa', 'Is-Sibt'], + 'weekdays_short' => ['Ħad', 'Tne', 'Tli', 'Erb', 'Ħam', 'Ġim', 'Sib'], + 'weekdays_min' => ['Ħa', 'Tn', 'Tl', 'Er', 'Ħa', 'Ġi', 'Si'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' u '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mt_MT.php b/vendor/nesbot/carbon/src/Carbon/Lang/mt_MT.php new file mode 100644 index 0000000..9534f68 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mt_MT.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__.'/mt.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mua.php b/vendor/nesbot/carbon/src/Carbon/Lang/mua.php new file mode 100644 index 0000000..a3a3c6f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mua.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['comme', 'lilli'], + 'weekdays' => ['Com’yakke', 'Comlaaɗii', 'Comzyiiɗii', 'Comkolle', 'Comkaldǝɓlii', 'Comgaisuu', 'Comzyeɓsuu'], + 'weekdays_short' => ['Cya', 'Cla', 'Czi', 'Cko', 'Cka', 'Cga', 'Cze'], + 'weekdays_min' => ['Cya', 'Cla', 'Czi', 'Cko', 'Cka', 'Cga', 'Cze'], + 'months' => ['Fĩi Loo', 'Cokcwaklaŋne', 'Cokcwaklii', 'Fĩi Marfoo', 'Madǝǝuutǝbijaŋ', 'Mamǝŋgwãafahbii', 'Mamǝŋgwãalii', 'Madǝmbii', 'Fĩi Dǝɓlii', 'Fĩi Mundaŋ', 'Fĩi Gwahlle', 'Fĩi Yuru'], + 'months_short' => ['FLO', 'CLA', 'CKI', 'FMF', 'MAD', 'MBI', 'MLI', 'MAM', 'FDE', 'FMU', 'FGW', 'FYU'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/my.php b/vendor/nesbot/carbon/src/Carbon/Lang/my.php new file mode 100644 index 0000000..bbdfba4 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/my.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - JD Isaacks + * - Nay Lin Aung + */ +return [ + 'year' => '{1}တစ်နှစ်|]1,Inf[:count နှစ်', + 'y' => ':count နှစ်', + 'month' => '{1}တစ်လ|]1,Inf[:count လ', + 'm' => ':count လ', + 'week' => ':count ပတ်', + 'w' => ':count ပတ်', + 'day' => '{1}တစ်ရက်|]1,Inf[:count ရက်', + 'd' => ':count ရက်', + 'hour' => '{1}တစ်နာရီ|]1,Inf[:count နာရီ', + 'h' => ':count နာရီ', + 'minute' => '{1}တစ်မိနစ်|]1,Inf[:count မိနစ်', + 'min' => ':count မိနစ်', + 'second' => '{1}စက္ကန်.အနည်းငယ်|]1,Inf[:count စက္ကန့်', + 's' => ':count စက္ကန့်', + 'ago' => 'လွန်ခဲ့သော :time က', + 'from_now' => 'လာမည့် :time မှာ', + 'after' => ':time ကြာပြီးနောက်', + 'before' => ':time မတိုင်ခင်', + 'diff_now' => 'အခုလေးတင်', + 'diff_today' => 'ယနေ.', + 'diff_yesterday' => 'မနေ့က', + 'diff_yesterday_regexp' => 'မနေ.က', + 'diff_tomorrow' => 'မနက်ဖြန်', + 'diff_before_yesterday' => 'တမြန်နေ့က', + 'diff_after_tomorrow' => 'တဘက်ခါ', + 'period_recurrences' => ':count ကြိမ်', + 'formats' => [ + 'LT' => 'Oh:Om A', + 'LTS' => 'Oh:Om:Os A', + 'L' => 'OD/OM/OY', + 'LL' => 'OD MMMM OY', + 'LLL' => 'OD MMMM OY Oh:Om A', + 'LLLL' => 'dddd OD MMMM OY Oh:Om A', + ], + 'calendar' => [ + 'sameDay' => '[ယနေ.] LT [မှာ]', + 'nextDay' => '[မနက်ဖြန်] LT [မှာ]', + 'nextWeek' => 'dddd LT [မှာ]', + 'lastDay' => '[မနေ.က] LT [မှာ]', + 'lastWeek' => '[ပြီးခဲ့သော] dddd LT [မှာ]', + 'sameElse' => 'L', + ], + 'months' => ['ဇန်နဝါရီ', 'ဖေဖော်ဝါရီ', 'မတ်', 'ဧပြီ', 'မေ', 'ဇွန်', 'ဇူလိုင်', 'သြဂုတ်', 'စက်တင်ဘာ', 'အောက်တိုဘာ', 'နိုဝင်ဘာ', 'ဒီဇင်ဘာ'], + 'months_short' => ['ဇန်', 'ဖေ', 'မတ်', 'ပြီ', 'မေ', 'ဇွန်', 'လိုင်', 'သြ', 'စက်', 'အောက်', 'နို', 'ဒီ'], + 'weekdays' => ['တနင်္ဂနွေ', 'တနင်္လာ', 'အင်္ဂါ', 'ဗုဒ္ဓဟူး', 'ကြာသပတေး', 'သောကြာ', 'စနေ'], + 'weekdays_short' => ['နွေ', 'လာ', 'ဂါ', 'ဟူး', 'ကြာ', 'သော', 'နေ'], + 'weekdays_min' => ['နွေ', 'လာ', 'ဂါ', 'ဟူး', 'ကြာ', 'သော', 'နေ'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'alt_numbers' => ['၀၀', '၀၁', '၀၂', '၀၃', '၀၄', '၀၅', '၀၆', '၀၇', '၀၈', '၀၉', '၁၀', '၁၁', '၁၂', '၁၃', '၁၄', '၁၅', '၁၆', '၁၇', '၁၈', '၁၉', '၂၀', '၂၁', '၂၂', '၂၃', '၂၄', '၂၅', '၂၆', '၂၇', '၂၈', '၂၉', '၃၀', '၃၁', '၃၂', '၃၃', '၃၄', '၃၅', '၃၆', '၃၇', '၃၈', '၃၉', '၄၀', '၄၁', '၄၂', '၄၃', '၄၄', '၄၅', '၄၆', '၄၇', '၄၈', '၄၉', '၅၀', '၅၁', '၅၂', '၅၃', '၅၄', '၅၅', '၅၆', '၅၇', '၅၈', '၅၉', '၆၀', '၆၁', '၆၂', '၆၃', '၆၄', '၆၅', '၆၆', '၆၇', '၆၈', '၆၉', '၇၀', '၇၁', '၇၂', '၇၃', '၇၄', '၇၅', '၇၆', '၇၇', '၇၈', '၇၉', '၈၀', '၈၁', '၈၂', '၈၃', '၈၄', '၈၅', '၈၆', '၈၇', '၈၈', '၈၉', '၉၀', '၉၁', '၉၂', '၉၃', '၉၄', '၉၅', '၉၆', '၉၇', '၉၈', '၉၉'], + 'meridiem' => ['နံနက်', 'ညနေ'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/my_MM.php b/vendor/nesbot/carbon/src/Carbon/Lang/my_MM.php new file mode 100644 index 0000000..a0108dd --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/my_MM.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__.'/my.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mzn.php b/vendor/nesbot/carbon/src/Carbon/Lang/mzn.php new file mode 100644 index 0000000..70f5f23 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mzn.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/fa.php', [ + 'months' => ['ژانویه', 'فوریه', 'مارس', 'آوریل', 'مه', 'ژوئن', 'ژوئیه', 'اوت', 'سپتامبر', 'اکتبر', 'نوامبر', 'دسامبر'], + 'months_short' => ['ژانویه', 'فوریه', 'مارس', 'آوریل', 'مه', 'ژوئن', 'ژوئیه', 'اوت', 'سپتامبر', 'اکتبر', 'نوامبر', 'دسامبر'], + 'first_day_of_week' => 6, + 'weekend' => [5, 5], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-dd', + 'LL' => 'YYYY MMM D', + 'LLL' => 'YYYY MMMM D HH:mm', + 'LLLL' => 'YYYY MMMM D, dddd HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nan.php b/vendor/nesbot/carbon/src/Carbon/Lang/nan.php new file mode 100644 index 0000000..0affece --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nan.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/nan_TW.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nan_TW.php b/vendor/nesbot/carbon/src/Carbon/Lang/nan_TW.php new file mode 100644 index 0000000..5c50aa4 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nan_TW.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'YYYY年MM月DD日', + ], + 'months' => ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'], + 'months_short' => [' 1月', ' 2月', ' 3月', ' 4月', ' 5月', ' 6月', ' 7月', ' 8月', ' 9月', '10月', '11月', '12月'], + 'weekdays' => ['禮拜日', '禮拜一', '禮拜二', '禮拜三', '禮拜四', '禮拜五', '禮拜六'], + 'weekdays_short' => ['日', '一', '二', '三', '四', '五', '六'], + 'weekdays_min' => ['日', '一', '二', '三', '四', '五', '六'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['頂晡', '下晡'], + + 'year' => ':count 年', + 'y' => ':count 年', + 'a_year' => ':count 年', + + 'month' => ':count goe̍h', + 'm' => ':count goe̍h', + 'a_month' => ':count goe̍h', + + 'week' => ':count lé-pài', + 'w' => ':count lé-pài', + 'a_week' => ':count lé-pài', + + 'day' => ':count 日', + 'd' => ':count 日', + 'a_day' => ':count 日', + + 'hour' => ':count tiám-cheng', + 'h' => ':count tiám-cheng', + 'a_hour' => ':count tiám-cheng', + + 'minute' => ':count Hun-cheng', + 'min' => ':count Hun-cheng', + 'a_minute' => ':count Hun-cheng', + + 'second' => ':count Bió', + 's' => ':count Bió', + 'a_second' => ':count Bió', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nan_TW@latin.php b/vendor/nesbot/carbon/src/Carbon/Lang/nan_TW@latin.php new file mode 100644 index 0000000..99ca2a4 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nan_TW@latin.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Arne Goetje arne@canonical.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'YYYY-MM-DD', + ], + 'months' => ['1goe̍h', '2goe̍h', '3goe̍h', '4goe̍h', '5goe̍h', '6goe̍h', '7goe̍h', '8goe̍h', '9goe̍h', '10goe̍h', '11goe̍h', '12goe̍h'], + 'months_short' => ['1g', '2g', '3g', '4g', '5g', '6g', '7g', '8g', '9g', '10g', '11g', '12g'], + 'weekdays' => ['lé-pài-ji̍t', 'pài-it', 'pài-jī', 'pài-saⁿ', 'pài-sì', 'pài-gō͘', 'pài-la̍k'], + 'weekdays_short' => ['lp', 'p1', 'p2', 'p3', 'p4', 'p5', 'p6'], + 'weekdays_min' => ['lp', 'p1', 'p2', 'p3', 'p4', 'p5', 'p6'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['téng-po͘', 'ē-po͘'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/naq.php b/vendor/nesbot/carbon/src/Carbon/Lang/naq.php new file mode 100644 index 0000000..fbd9be9 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/naq.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_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['ǁgoagas', 'ǃuias'], + 'weekdays' => ['Sontaxtsees', 'Mantaxtsees', 'Denstaxtsees', 'Wunstaxtsees', 'Dondertaxtsees', 'Fraitaxtsees', 'Satertaxtsees'], + 'weekdays_short' => ['Son', 'Ma', 'De', 'Wu', 'Do', 'Fr', 'Sat'], + 'weekdays_min' => ['Son', 'Ma', 'De', 'Wu', 'Do', 'Fr', 'Sat'], + 'months' => ['ǃKhanni', 'ǃKhanǀgôab', 'ǀKhuuǁkhâb', 'ǃHôaǂkhaib', 'ǃKhaitsâb', 'Gamaǀaeb', 'ǂKhoesaob', 'Aoǁkhuumûǁkhâb', 'Taraǀkhuumûǁkhâb', 'ǂNûǁnâiseb', 'ǀHooǂgaeb', 'Hôasoreǁkhâb'], + 'months_short' => ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'dddd, D MMMM YYYY h:mm a', + ], + + 'year' => ':count kurigu', + 'y' => ':count kurigu', + 'a_year' => ':count kurigu', + + 'month' => ':count ǁaub', // less reliable + 'm' => ':count ǁaub', // less reliable + 'a_month' => ':count ǁaub', // less reliable + + 'week' => ':count hû', // less reliable + 'w' => ':count hû', // less reliable + 'a_week' => ':count hû', // less reliable + + 'day' => ':count ǀhobas', // less reliable + 'd' => ':count ǀhobas', // less reliable + 'a_day' => ':count ǀhobas', // less reliable + + 'hour' => ':count ǂgaes', // less reliable + 'h' => ':count ǂgaes', // less reliable + 'a_hour' => ':count ǂgaes', // less reliable + + 'minute' => ':count minutga', // less reliable + 'min' => ':count minutga', // less reliable + 'a_minute' => ':count minutga', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nb.php b/vendor/nesbot/carbon/src/Carbon/Lang/nb.php new file mode 100644 index 0000000..371ee84 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nb.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - Alexander Tømmerås + * - Sigurd Gartmann + * - JD Isaacks + */ +return [ + 'year' => ':count år|:count år', + 'a_year' => 'ett år|:count år', + 'y' => ':count år|:count år', + 'month' => ':count måned|:count måneder', + 'a_month' => 'en måned|:count måneder', + 'm' => ':count md.', + 'week' => ':count uke|:count uker', + 'a_week' => 'en uke|:count uker', + 'w' => ':count u.', + 'day' => ':count dag|:count dager', + 'a_day' => 'en dag|:count dager', + 'd' => ':count d.', + 'hour' => ':count time|:count timer', + 'a_hour' => 'en time|:count timer', + 'h' => ':count t', + 'minute' => ':count minutt|:count minutter', + 'a_minute' => 'ett minutt|:count minutter', + 'min' => ':count min', + 'second' => ':count sekund|:count sekunder', + 'a_second' => 'noen sekunder|:count sekunder', + 's' => ':count sek', + 'ago' => ':time siden', + 'from_now' => 'om :time', + 'after' => ':time etter', + 'before' => ':time før', + 'diff_now' => 'akkurat nå', + 'diff_today' => 'i dag', + 'diff_today_regexp' => 'i dag(?:\\s+kl.)?', + 'diff_yesterday' => 'i går', + 'diff_yesterday_regexp' => 'i går(?:\\s+kl.)?', + 'diff_tomorrow' => 'i morgen', + 'diff_tomorrow_regexp' => 'i morgen(?:\\s+kl.)?', + 'diff_before_yesterday' => 'i forgårs', + 'diff_after_tomorrow' => 'i overmorgen', + 'period_recurrences' => 'en gang|:count ganger', + 'period_interval' => 'hver :interval', + 'period_start_date' => 'fra :date', + 'period_end_date' => 'til :date', + 'months' => ['januar', 'februar', 'mars', 'april', 'mai', 'juni', 'juli', 'august', 'september', 'oktober', 'november', 'desember'], + 'months_short' => ['jan', 'feb', 'mar', 'apr', 'mai', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'des'], + 'weekdays' => ['søndag', 'mandag', 'tirsdag', 'onsdag', 'torsdag', 'fredag', 'lørdag'], + 'weekdays_short' => ['søn', 'man', 'tir', 'ons', 'tor', 'fre', 'lør'], + 'weekdays_min' => ['sø', 'ma', 'ti', 'on', 'to', 'fr', 'lø'], + 'ordinal' => ':number.', + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D. MMMM YYYY', + 'LLL' => 'D. MMMM YYYY [kl.] HH:mm', + 'LLLL' => 'dddd D. MMMM YYYY [kl.] HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[i dag kl.] LT', + 'nextDay' => '[i morgen kl.] LT', + 'nextWeek' => 'dddd [kl.] LT', + 'lastDay' => '[i går kl.] LT', + 'lastWeek' => '[forrige] dddd [kl.] LT', + 'sameElse' => 'L', + ], + 'list' => [', ', ' og '], + 'meridiem' => ['a.m.', 'p.m.'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nb_NO.php b/vendor/nesbot/carbon/src/Carbon/Lang/nb_NO.php new file mode 100644 index 0000000..31678c5 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nb_NO.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__.'/nb.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nb_SJ.php b/vendor/nesbot/carbon/src/Carbon/Lang/nb_SJ.php new file mode 100644 index 0000000..ce0210b --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nb_SJ.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/nb.php', [ + 'formats' => [ + 'LL' => 'D. MMM YYYY', + 'LLL' => 'D. MMMM YYYY, HH:mm', + 'LLLL' => 'dddd D. MMMM YYYY, HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nd.php b/vendor/nesbot/carbon/src/Carbon/Lang/nd.php new file mode 100644 index 0000000..f75d9a7 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nd.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'weekdays' => ['Sonto', 'Mvulo', 'Sibili', 'Sithathu', 'Sine', 'Sihlanu', 'Mgqibelo'], + 'weekdays_short' => ['Son', 'Mvu', 'Sib', 'Sit', 'Sin', 'Sih', 'Mgq'], + 'weekdays_min' => ['Son', 'Mvu', 'Sib', 'Sit', 'Sin', 'Sih', 'Mgq'], + 'months' => ['Zibandlela', 'Nhlolanja', 'Mbimbitho', 'Mabasa', 'Nkwenkwezi', 'Nhlangula', 'Ntulikazi', 'Ncwabakazi', 'Mpandula', 'Mfumfu', 'Lwezi', 'Mpalakazi'], + 'months_short' => ['Zib', 'Nhlo', 'Mbi', 'Mab', 'Nkw', 'Nhla', 'Ntu', 'Ncw', 'Mpan', 'Mfu', 'Lwe', 'Mpal'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + + 'year' => 'okweminyaka engu-:count', // less reliable + 'y' => 'okweminyaka engu-:count', // less reliable + 'a_year' => 'okweminyaka engu-:count', // less reliable + + 'month' => 'inyanga ezingu-:count', + 'm' => 'inyanga ezingu-:count', + 'a_month' => 'inyanga ezingu-:count', + + 'week' => 'amaviki angu-:count', + 'w' => 'amaviki angu-:count', + 'a_week' => 'amaviki angu-:count', + + 'day' => 'kwamalanga angu-:count', + 'd' => 'kwamalanga angu-:count', + 'a_day' => 'kwamalanga angu-:count', + + 'hour' => 'amahola angu-:count', + 'h' => 'amahola angu-:count', + 'a_hour' => 'amahola angu-:count', + + 'minute' => 'imizuzu engu-:count', + 'min' => 'imizuzu engu-:count', + 'a_minute' => 'imizuzu engu-:count', + + 'second' => 'imizuzwana engu-:count', + 's' => 'imizuzwana engu-:count', + 'a_second' => 'imizuzwana engu-:count', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nds.php b/vendor/nesbot/carbon/src/Carbon/Lang/nds.php new file mode 100644 index 0000000..c0b3775 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nds.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/nds_DE.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nds_DE.php b/vendor/nesbot/carbon/src/Carbon/Lang/nds_DE.php new file mode 100644 index 0000000..a6c57a9 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nds_DE.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - information from Kenneth Christiansen Kenneth Christiansen, Pablo Saratxaga kenneth@gnu.org, pablo@mandrakesoft.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['Jannuaar', 'Feberwaar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'], + 'months_short' => ['Jan', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'], + 'weekdays' => ['Sünndag', 'Maandag', 'Dingsdag', 'Middeweek', 'Dunnersdag', 'Freedag', 'Sünnavend'], + 'weekdays_short' => ['Sdag', 'Maan', 'Ding', 'Midd', 'Dunn', 'Free', 'Svd.'], + 'weekdays_min' => ['Sd', 'Ma', 'Di', 'Mi', 'Du', 'Fr', 'Sa'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'year' => ':count Johr', + 'y' => ':countJ', + 'a_year' => '{1}een Johr|:count Johr', + + 'month' => ':count Maand', + 'm' => ':countM', + 'a_month' => '{1}een Maand|:count Maand', + + 'week' => ':count Week|:count Weken', + 'w' => ':countW', + 'a_week' => '{1}een Week|:count Week|:count Weken', + + 'day' => ':count Dag|:count Daag', + 'd' => ':countD', + 'a_day' => '{1}een Dag|:count Dag|:count Daag', + + 'hour' => ':count Stünn|:count Stünnen', + 'h' => ':countSt', + 'a_hour' => '{1}een Stünn|:count Stünn|:count Stünnen', + + 'minute' => ':count Minuut|:count Minuten', + 'min' => ':countm', + 'a_minute' => '{1}een Minuut|:count Minuut|:count Minuten', + + 'second' => ':count Sekunn|:count Sekunnen', + 's' => ':counts', + 'a_second' => 'en poor Sekunnen|:count Sekunn|:count Sekunnen', + + 'ago' => 'vör :time', + 'from_now' => 'in :time', + 'before' => ':time vörher', + 'after' => ':time later', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nds_NL.php b/vendor/nesbot/carbon/src/Carbon/Lang/nds_NL.php new file mode 100644 index 0000000..de2c57b --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nds_NL.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - information from Kenneth Christiansen Kenneth Christiansen, Pablo Saratxaga kenneth@gnu.org, pablo@mandrakesoft.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['Jaunuwoa', 'Februwoa', 'Moaz', 'Aprell', 'Mai', 'Juni', 'Juli', 'August', 'Septamba', 'Oktoba', 'Nowamba', 'Dezamba'], + 'months_short' => ['Jan', 'Feb', 'Moz', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Now', 'Dez'], + 'weekdays' => ['Sinndag', 'Mondag', 'Dingsdag', 'Meddwäakj', 'Donnadag', 'Friedag', 'Sinnowend'], + 'weekdays_short' => ['Sdg', 'Mdg', 'Dsg', 'Mwk', 'Ddg', 'Fdg', 'Swd'], + 'weekdays_min' => ['Sdg', 'Mdg', 'Dsg', 'Mwk', 'Ddg', 'Fdg', 'Swd'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ne.php b/vendor/nesbot/carbon/src/Carbon/Lang/ne.php new file mode 100644 index 0000000..d4caf0e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ne.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - nootanghimire + * - Josh Soref + * - Nj Subedi + * - JD Isaacks + */ +return [ + '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_today' => 'आज', + 'diff_yesterday' => 'हिजो', + 'diff_tomorrow' => 'भोलि', + 'formats' => [ + 'LT' => 'Aको h:mm बजे', + 'LTS' => 'Aको h:mm:ss बजे', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY, Aको h:mm बजे', + 'LLLL' => 'dddd, D MMMM YYYY, Aको h:mm बजे', + ], + 'calendar' => [ + 'sameDay' => '[आज] LT', + 'nextDay' => '[भोलि] LT', + 'nextWeek' => '[आउँदो] dddd[,] LT', + 'lastDay' => '[हिजो] LT', + 'lastWeek' => '[गएको] dddd[,] LT', + 'sameElse' => 'L', + ], + 'meridiem' => function ($hour) { + if ($hour < 3) { + return 'राति'; + } + if ($hour < 12) { + return 'बिहान'; + } + if ($hour < 16) { + return 'दिउँसो'; + } + if ($hour < 20) { + return 'साँझ'; + } + + return 'राति'; + }, + 'months' => ['जनवरी', 'फेब्रुवरी', 'मार्च', 'अप्रिल', 'मई', 'जुन', 'जुलाई', 'अगष्ट', 'सेप्टेम्बर', 'अक्टोबर', 'नोभेम्बर', 'डिसेम्बर'], + 'months_short' => ['जन.', 'फेब्रु.', 'मार्च', 'अप्रि.', 'मई', 'जुन', 'जुलाई.', 'अग.', 'सेप्ट.', 'अक्टो.', 'नोभे.', 'डिसे.'], + 'weekdays' => ['आइतबार', 'सोमबार', 'मङ्गलबार', 'बुधबार', 'बिहिबार', 'शुक्रबार', 'शनिबार'], + 'weekdays_short' => ['आइत.', 'सोम.', 'मङ्गल.', 'बुध.', 'बिहि.', 'शुक्र.', 'शनि.'], + 'weekdays_min' => ['आ.', 'सो.', 'मं.', 'बु.', 'बि.', 'शु.', 'श.'], + 'list' => [', ', ' र '], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ne_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/ne_IN.php new file mode 100644 index 0000000..f68d00e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ne_IN.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ne.php', [ + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'yy/M/d', + 'LL' => 'YYYY MMM D', + 'LLL' => 'YYYY MMMM D, h:mm a', + 'LLLL' => 'YYYY MMMM D, dddd, h:mm a', + ], + 'months' => ['जनवरी', 'फेब्रुअरी', 'मार्च', 'अप्रिल', 'मे', 'जुन', 'जुलाई', 'अगस्ट', 'सेप्टेम्बर', 'अक्टोबर', 'नोभेम्बर', 'डिसेम्बर'], + 'months_short' => ['जनवरी', 'फेब्रुअरी', 'मार्च', 'अप्रिल', 'मे', 'जुन', 'जुलाई', 'अगस्ट', 'सेप्टेम्बर', 'अक्टोबर', 'नोभेम्बर', 'डिसेम्बर'], + 'weekend' => [0, 0], + 'meridiem' => ['पूर्वाह्न', 'अपराह्न'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ne_NP.php b/vendor/nesbot/carbon/src/Carbon/Lang/ne_NP.php new file mode 100644 index 0000000..27840c0 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ne_NP.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__.'/ne.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nhn.php b/vendor/nesbot/carbon/src/Carbon/Lang/nhn.php new file mode 100644 index 0000000..5a85831 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nhn.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/nhn_MX.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nhn_MX.php b/vendor/nesbot/carbon/src/Carbon/Lang/nhn_MX.php new file mode 100644 index 0000000..9db88a1 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nhn_MX.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP libc-alpha@sourceware.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['enero', 'febrero', 'marzo', 'abril', 'mayo', 'junio', 'julio', 'agosto', 'septiembre', 'octubre', 'noviembre', 'diciembre'], + 'months_short' => ['ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago', 'sep', 'oct', 'nov', 'dic'], + 'weekdays' => ['teoilhuitl', 'ceilhuitl', 'omeilhuitl', 'yeilhuitl', 'nahuilhuitl', 'macuililhuitl', 'chicuaceilhuitl'], + 'weekdays_short' => ['teo', 'cei', 'ome', 'yei', 'nau', 'mac', 'chi'], + 'weekdays_min' => ['teo', 'cei', 'ome', 'yei', 'nau', 'mac', 'chi'], + 'day_of_first_week_of_year' => 1, + + 'month' => ':count metztli', // less reliable + 'm' => ':count metztli', // less reliable + 'a_month' => ':count metztli', // less reliable + + 'week' => ':count tonalli', // less reliable + 'w' => ':count tonalli', // less reliable + 'a_week' => ':count tonalli', // less reliable + + 'day' => ':count tonatih', // less reliable + 'd' => ':count tonatih', // less reliable + 'a_day' => ':count tonatih', // less reliable + + 'minute' => ':count toltecayotl', // less reliable + 'min' => ':count toltecayotl', // less reliable + 'a_minute' => ':count toltecayotl', // less reliable + + 'second' => ':count ome', // less reliable + 's' => ':count ome', // less reliable + 'a_second' => ':count ome', // less reliable + + 'year' => ':count xihuitl', + 'y' => ':count xihuitl', + 'a_year' => ':count xihuitl', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/niu.php b/vendor/nesbot/carbon/src/Carbon/Lang/niu.php new file mode 100644 index 0000000..bd9be8a --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/niu.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/niu_NU.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/niu_NU.php b/vendor/nesbot/carbon/src/Carbon/Lang/niu_NU.php new file mode 100644 index 0000000..6e7a697 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/niu_NU.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RockET Systems Emani Fakaotimanava-Lui emani@niue.nu + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['Ianuali', 'Fepuali', 'Masi', 'Apelila', 'Me', 'Iuni', 'Iulai', 'Aokuso', 'Sepetema', 'Oketopa', 'Novema', 'Tesemo'], + 'months_short' => ['Ian', 'Fep', 'Mas', 'Ape', 'Me', 'Iun', 'Iul', 'Aok', 'Sep', 'Oke', 'Nov', 'Tes'], + 'weekdays' => ['Aho Tapu', 'Aho Gofua', 'Aho Ua', 'Aho Lotu', 'Aho Tuloto', 'Aho Falaile', 'Aho Faiumu'], + 'weekdays_short' => ['Tapu', 'Gofua', 'Ua', 'Lotu', 'Tuloto', 'Falaile', 'Faiumu'], + 'weekdays_min' => ['Tapu', 'Gofua', 'Ua', 'Lotu', 'Tuloto', 'Falaile', 'Faiumu'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + + 'year' => ':count tau', + 'y' => ':count tau', + 'a_year' => ':count tau', + + 'month' => ':count mahina', + 'm' => ':count mahina', + 'a_month' => ':count mahina', + + 'week' => ':count faahi tapu', + 'w' => ':count faahi tapu', + 'a_week' => ':count faahi tapu', + + 'day' => ':count aho', + 'd' => ':count aho', + 'a_day' => ':count aho', + + 'hour' => ':count e tulā', + 'h' => ':count e tulā', + 'a_hour' => ':count e tulā', + + 'minute' => ':count minuti', + 'min' => ':count minuti', + 'a_minute' => ':count minuti', + + 'second' => ':count sekone', + 's' => ':count sekone', + 'a_second' => ':count sekone', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nl.php b/vendor/nesbot/carbon/src/Carbon/Lang/nl.php new file mode 100644 index 0000000..2d73770 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nl.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Roy + * - Stephan + * - François B + * - Tim Fish + * - Kevin Huang + * - Jacob Middag + * - JD Isaacks + * - Roy + * - Stephan + * - François B + * - Tim Fish + * - Jacob Middag + * - JD Isaacks + * - Propaganistas + * - MegaXLR + * - adriaanzon + * - MonkeyPhysics + * - JeroenG + * - RikSomers + * - proclame + * - Rik de Groot (hwdegroot) + */ +return [ + 'year' => ':count jaar|:count jaar', + 'a_year' => 'een jaar|:count jaar', + 'y' => ':countj', + 'month' => ':count maand|:count maanden', + 'a_month' => 'een maand|:count maanden', + 'm' => ':countmnd', + 'week' => ':count week|:count weken', + 'a_week' => 'een week|:count weken', + 'w' => ':countw', + 'day' => ':count dag|:count dagen', + 'a_day' => 'een dag|:count dagen', + 'd' => ':countd', + 'hour' => ':count uur|:count uur', + 'a_hour' => 'een uur|:count uur', + 'h' => ':countu', + 'minute' => ':count minuut|:count minuten', + 'a_minute' => 'een minuut|:count minuten', + 'min' => ':countmin', + 'second' => ':count seconde|:count seconden', + 'a_second' => 'een paar seconden|:count seconden', + 's' => ':counts', + 'ago' => ':time geleden', + 'from_now' => 'over :time', + 'after' => ':time later', + 'before' => ':time eerder', + 'diff_now' => 'nu', + 'diff_today' => 'vandaag', + 'diff_today_regexp' => 'vandaag(?:\\s+om)?', + 'diff_yesterday' => 'gisteren', + 'diff_yesterday_regexp' => 'gisteren(?:\\s+om)?', + 'diff_tomorrow' => 'morgen', + 'diff_tomorrow_regexp' => 'morgen(?:\\s+om)?', + 'diff_after_tomorrow' => 'overmorgen', + 'diff_before_yesterday' => 'eergisteren', + 'period_recurrences' => ':count keer', + 'period_interval' => function (string $interval = '') { + /** @var string $output */ + $output = preg_replace('/^(een|één|1)\s+/u', '', $interval); + + if (preg_match('/^(een|één|1)( jaar|j| uur|u)/u', $interval)) { + return "elk $output"; + } + + return "elke $output"; + }, + 'period_start_date' => 'van :date', + 'period_end_date' => 'tot :date', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD-MM-YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[vandaag om] LT', + 'nextDay' => '[morgen om] LT', + 'nextWeek' => 'dddd [om] LT', + 'lastDay' => '[gisteren om] LT', + 'lastWeek' => '[afgelopen] dddd [om] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + return $number.(($number === 1 || $number === 8 || $number >= 20) ? 'ste' : 'de'); + }, + 'months' => ['januari', 'februari', 'maart', 'april', 'mei', 'juni', 'juli', 'augustus', 'september', 'oktober', 'november', 'december'], + 'months_short' => ['jan', 'feb', 'mrt', 'apr', 'mei', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'], + 'mmm_suffix' => '.', + 'weekdays' => ['zondag', 'maandag', 'dinsdag', 'woensdag', 'donderdag', 'vrijdag', 'zaterdag'], + 'weekdays_short' => ['zo.', 'ma.', 'di.', 'wo.', 'do.', 'vr.', 'za.'], + 'weekdays_min' => ['zo', 'ma', 'di', 'wo', 'do', 'vr', 'za'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' en '], + 'meridiem' => ['\'s ochtends', '\'s middags'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nl_AW.php b/vendor/nesbot/carbon/src/Carbon/Lang/nl_AW.php new file mode 100644 index 0000000..5ec136d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nl_AW.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Free Software Foundation, Inc. bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/nl.php', [ + 'formats' => [ + 'L' => 'DD-MM-YY', + ], + 'months' => ['januari', 'februari', 'maart', 'april', 'mei', 'juni', 'juli', 'augustus', 'september', 'oktober', 'november', 'december'], + 'months_short' => ['jan', 'feb', 'mrt', 'apr', 'mei', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'], + 'weekdays' => ['zondag', 'maandag', 'dinsdag', 'woensdag', 'donderdag', 'vrijdag', 'zaterdag'], + 'weekdays_short' => ['zo', 'ma', 'di', 'wo', 'do', 'vr', 'za'], + 'weekdays_min' => ['zo', 'ma', 'di', 'wo', 'do', 'vr', 'za'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nl_BE.php b/vendor/nesbot/carbon/src/Carbon/Lang/nl_BE.php new file mode 100644 index 0000000..037f5b4 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nl_BE.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Roy + * - Stephan + * - François B + * - Tim Fish + * - Kevin Huang + * - Jacob Middag + * - JD Isaacks + * - Propaganistas + */ +return array_replace_recursive(require __DIR__.'/nl.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nl_BQ.php b/vendor/nesbot/carbon/src/Carbon/Lang/nl_BQ.php new file mode 100644 index 0000000..c269197 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nl_BQ.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__.'/nl.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nl_CW.php b/vendor/nesbot/carbon/src/Carbon/Lang/nl_CW.php new file mode 100644 index 0000000..c269197 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nl_CW.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__.'/nl.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nl_NL.php b/vendor/nesbot/carbon/src/Carbon/Lang/nl_NL.php new file mode 100644 index 0000000..14e4853 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nl_NL.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/nl.php', [ + 'months' => ['januari', 'februari', 'maart', 'april', 'mei', 'juni', 'juli', 'augustus', 'september', 'oktober', 'november', 'december'], + 'months_short' => ['jan', 'feb', 'mrt', 'apr', 'mei', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'], + 'weekdays' => ['zondag', 'maandag', 'dinsdag', 'woensdag', 'donderdag', 'vrijdag', 'zaterdag'], + 'weekdays_short' => ['zo', 'ma', 'di', 'wo', 'do', 'vr', 'za'], + 'weekdays_min' => ['zo', 'ma', 'di', 'wo', 'do', 'vr', 'za'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nl_SR.php b/vendor/nesbot/carbon/src/Carbon/Lang/nl_SR.php new file mode 100644 index 0000000..c269197 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nl_SR.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__.'/nl.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nl_SX.php b/vendor/nesbot/carbon/src/Carbon/Lang/nl_SX.php new file mode 100644 index 0000000..c269197 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nl_SX.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__.'/nl.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nmg.php b/vendor/nesbot/carbon/src/Carbon/Lang/nmg.php new file mode 100644 index 0000000..4d1df6e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nmg.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['maná', 'kugú'], + 'weekdays' => ['sɔ́ndɔ', 'mɔ́ndɔ', 'sɔ́ndɔ mafú mába', 'sɔ́ndɔ mafú málal', 'sɔ́ndɔ mafú mána', 'mabágá má sukul', 'sásadi'], + 'weekdays_short' => ['sɔ́n', 'mɔ́n', 'smb', 'sml', 'smn', 'mbs', 'sas'], + 'weekdays_min' => ['sɔ́n', 'mɔ́n', 'smb', 'sml', 'smn', 'mbs', 'sas'], + 'months' => ['ngwɛn matáhra', 'ngwɛn ńmba', 'ngwɛn ńlal', 'ngwɛn ńna', 'ngwɛn ńtan', 'ngwɛn ńtuó', 'ngwɛn hɛmbuɛrí', 'ngwɛn lɔmbi', 'ngwɛn rɛbvuâ', 'ngwɛn wum', 'ngwɛn wum navǔr', 'krísimin'], + 'months_short' => ['ng1', 'ng2', 'ng3', 'ng4', 'ng5', 'ng6', 'ng7', 'ng8', 'ng9', 'ng10', 'ng11', 'kris'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nn.php b/vendor/nesbot/carbon/src/Carbon/Lang/nn.php new file mode 100644 index 0000000..041f7b2 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nn.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - Alexander Tømmerås + * - Øystein + * - JD Isaacks + * - Gaute Hvoslef Kvalnes (gaute) + */ +return [ + 'year' => ':count år', + 'a_year' => 'eit år|:count år', + 'y' => ':count år', + 'month' => ':count månad|:count månader', + 'a_month' => 'ein månad|:count månader', + 'm' => ':count md', + 'week' => ':count veke|:count veker', + 'a_week' => 'ei veke|:count veker', + 'w' => ':countv', + 'day' => ':count dag|:count dagar', + 'a_day' => 'ein dag|:count dagar', + 'd' => ':countd', + 'hour' => ':count time|:count timar', + 'a_hour' => 'ein time|:count timar', + 'h' => ':countt', + 'minute' => ':count minutt', + 'a_minute' => 'eit minutt|:count minutt', + 'min' => ':countm', + 'second' => ':count sekund', + 'a_second' => 'nokre sekund|:count sekund', + 's' => ':counts', + 'ago' => ':time sidan', + 'from_now' => 'om :time', + 'after' => ':time etter', + 'before' => ':time før', + 'diff_today' => 'I dag', + 'diff_yesterday' => 'I går', + 'diff_yesterday_regexp' => 'I går(?:\\s+klokka)?', + 'diff_tomorrow' => 'I morgon', + 'diff_tomorrow_regexp' => 'I morgon(?:\\s+klokka)?', + 'diff_today_regexp' => 'I dag(?:\\s+klokka)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D. MMMM YYYY', + 'LLL' => 'D. MMMM YYYY [kl.] H:mm', + 'LLLL' => 'dddd D. MMMM YYYY [kl.] HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[I dag klokka] LT', + 'nextDay' => '[I morgon klokka] LT', + 'nextWeek' => 'dddd [klokka] LT', + 'lastDay' => '[I går klokka] LT', + 'lastWeek' => '[Føregåande] dddd [klokka] LT', + 'sameElse' => 'L', + ], + 'ordinal' => ':number.', + 'months' => ['januar', 'februar', 'mars', 'april', 'mai', 'juni', 'juli', 'august', 'september', 'oktober', 'november', 'desember'], + 'months_short' => ['jan', 'feb', 'mar', 'apr', 'mai', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'des'], + 'weekdays' => ['sundag', 'måndag', 'tysdag', 'onsdag', 'torsdag', 'fredag', 'laurdag'], + 'weekdays_short' => ['sun', 'mån', 'tys', 'ons', 'tor', 'fre', 'lau'], + 'weekdays_min' => ['su', 'må', 'ty', 'on', 'to', 'fr', 'la'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' og '], + 'meridiem' => ['f.m.', 'e.m.'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nn_NO.php b/vendor/nesbot/carbon/src/Carbon/Lang/nn_NO.php new file mode 100644 index 0000000..8e16871 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nn_NO.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__.'/nn.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nnh.php b/vendor/nesbot/carbon/src/Carbon/Lang/nnh.php new file mode 100644 index 0000000..007d239 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nnh.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['mbaʼámbaʼ', 'ncwònzém'], + 'weekdays' => null, + 'weekdays_short' => ['lyɛʼɛ́ sẅíŋtè', 'mvfò lyɛ̌ʼ', 'mbɔ́ɔntè mvfò lyɛ̌ʼ', 'tsètsɛ̀ɛ lyɛ̌ʼ', 'mbɔ́ɔntè tsetsɛ̀ɛ lyɛ̌ʼ', 'mvfò màga lyɛ̌ʼ', 'màga lyɛ̌ʼ'], + 'weekdays_min' => null, + 'months' => null, + 'months_short' => ['saŋ tsetsɛ̀ɛ lùm', 'saŋ kàg ngwóŋ', 'saŋ lepyè shúm', 'saŋ cÿó', 'saŋ tsɛ̀ɛ cÿó', 'saŋ njÿoláʼ', 'saŋ tyɛ̀b tyɛ̀b mbʉ̀ŋ', 'saŋ mbʉ̀ŋ', 'saŋ ngwɔ̀ʼ mbÿɛ', 'saŋ tàŋa tsetsáʼ', 'saŋ mejwoŋó', 'saŋ lùm'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/yy', + 'LL' => 'D MMM, YYYY', + 'LLL' => '[lyɛ]̌ʼ d [na] MMMM, YYYY HH:mm', + 'LLLL' => 'dddd , [lyɛ]̌ʼ d [na] MMMM, YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/no.php b/vendor/nesbot/carbon/src/Carbon/Lang/no.php new file mode 100644 index 0000000..f4497c7 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/no.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Daniel S. Billing + * - Paul + * - Jimmie Johansson + * - Jens Herlevsen + */ +return array_replace_recursive(require __DIR__.'/nb.php', [ + 'formats' => [ + 'LLL' => 'D. MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D. MMMM YYYY [kl.] HH:mm', + ], + 'calendar' => [ + 'nextWeek' => 'på dddd [kl.] LT', + 'lastWeek' => '[i] dddd[s kl.] LT', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nr.php b/vendor/nesbot/carbon/src/Carbon/Lang/nr.php new file mode 100644 index 0000000..1bc999f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nr.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/nr_ZA.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nr_ZA.php b/vendor/nesbot/carbon/src/Carbon/Lang/nr_ZA.php new file mode 100644 index 0000000..f9a7be8 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nr_ZA.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Zuza Software Foundation (Translate.org.za) Dwayne Bailey dwayne@translate.org.za + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Janabari', 'uFeberbari', 'uMatjhi', 'u-Apreli', 'Meyi', 'Juni', 'Julayi', 'Arhostosi', 'Septemba', 'Oktoba', 'Usinyikhaba', 'Disemba'], + 'months_short' => ['Jan', 'Feb', 'Mat', 'Apr', 'Mey', 'Jun', 'Jul', 'Arh', 'Sep', 'Okt', 'Usi', 'Dis'], + 'weekdays' => ['uSonto', 'uMvulo', 'uLesibili', 'lesithathu', 'uLesine', 'ngoLesihlanu', 'umGqibelo'], + 'weekdays_short' => ['Son', 'Mvu', 'Bil', 'Tha', 'Ne', 'Hla', 'Gqi'], + 'weekdays_min' => ['Son', 'Mvu', 'Bil', 'Tha', 'Ne', 'Hla', 'Gqi'], + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nso.php b/vendor/nesbot/carbon/src/Carbon/Lang/nso.php new file mode 100644 index 0000000..2a6cabb --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nso.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/nso_ZA.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nso_ZA.php b/vendor/nesbot/carbon/src/Carbon/Lang/nso_ZA.php new file mode 100644 index 0000000..b08fe6d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nso_ZA.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Zuza Software Foundation (Translate.org.za) Dwayne Bailey dwayne@translate.org.za + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Janaware', 'Febereware', 'Matšhe', 'Aprele', 'Mei', 'June', 'Julae', 'Agostose', 'Setemere', 'Oktobere', 'Nofemere', 'Disemere'], + 'months_short' => ['Jan', 'Feb', 'Mat', 'Apr', 'Mei', 'Jun', 'Jul', 'Ago', 'Set', 'Okt', 'Nof', 'Dis'], + 'weekdays' => ['LaMorena', 'Mošupologo', 'Labobedi', 'Laboraro', 'Labone', 'Labohlano', 'Mokibelo'], + 'weekdays_short' => ['Son', 'Moš', 'Bed', 'Rar', 'Ne', 'Hla', 'Mok'], + 'weekdays_min' => ['Son', 'Moš', 'Bed', 'Rar', 'Ne', 'Hla', 'Mok'], + 'day_of_first_week_of_year' => 1, + + 'year' => ':count ngwaga', + 'y' => ':count ngwaga', + 'a_year' => ':count ngwaga', + + 'month' => ':count Kgwedi', + 'm' => ':count Kgwedi', + 'a_month' => ':count Kgwedi', + + 'week' => ':count Beke', + 'w' => ':count Beke', + 'a_week' => ':count Beke', + + 'day' => ':count Letšatši', + 'd' => ':count Letšatši', + 'a_day' => ':count Letšatši', + + 'hour' => ':count Iri', + 'h' => ':count Iri', + 'a_hour' => ':count Iri', + + 'minute' => ':count Motsotso', + 'min' => ':count Motsotso', + 'a_minute' => ':count Motsotso', + + 'second' => ':count motsotswana', + 's' => ':count motsotswana', + 'a_second' => ':count motsotswana', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nus.php b/vendor/nesbot/carbon/src/Carbon/Lang/nus.php new file mode 100644 index 0000000..789bc39 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nus.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_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['RW', 'TŊ'], + 'weekdays' => ['Cäŋ kuɔth', 'Jiec la̱t', 'Rɛw lätni', 'Diɔ̱k lätni', 'Ŋuaan lätni', 'Dhieec lätni', 'Bäkɛl lätni'], + 'weekdays_short' => ['Cäŋ', 'Jiec', 'Rɛw', 'Diɔ̱k', 'Ŋuaan', 'Dhieec', 'Bäkɛl'], + 'weekdays_min' => ['Cäŋ', 'Jiec', 'Rɛw', 'Diɔ̱k', 'Ŋuaan', 'Dhieec', 'Bäkɛl'], + 'months' => ['Tiop thar pɛt', 'Pɛt', 'Duɔ̱ɔ̱ŋ', 'Guak', 'Duät', 'Kornyoot', 'Pay yie̱tni', 'Tho̱o̱r', 'Tɛɛr', 'Laath', 'Kur', 'Tio̱p in di̱i̱t'], + 'months_short' => ['Tiop', 'Pɛt', 'Duɔ̱ɔ̱', 'Guak', 'Duä', 'Kor', 'Pay', 'Thoo', 'Tɛɛ', 'Laa', 'Kur', 'Tid'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'D/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'dddd D MMMM YYYY h:mm a', + ], + + 'year' => ':count jiök', // less reliable + 'y' => ':count jiök', // less reliable + 'a_year' => ':count jiök', // less reliable + + 'month' => ':count pay', // less reliable + 'm' => ':count pay', // less reliable + 'a_month' => ':count pay', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nyn.php b/vendor/nesbot/carbon/src/Carbon/Lang/nyn.php new file mode 100644 index 0000000..8660ea4 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nyn.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'weekdays' => ['Sande', 'Orwokubanza', 'Orwakabiri', 'Orwakashatu', 'Orwakana', 'Orwakataano', 'Orwamukaaga'], + 'weekdays_short' => ['SAN', 'ORK', 'OKB', 'OKS', 'OKN', 'OKT', 'OMK'], + 'weekdays_min' => ['SAN', 'ORK', 'OKB', 'OKS', 'OKN', 'OKT', 'OMK'], + 'months' => ['Okwokubanza', 'Okwakabiri', 'Okwakashatu', 'Okwakana', 'Okwakataana', 'Okwamukaaga', 'Okwamushanju', 'Okwamunaana', 'Okwamwenda', 'Okwaikumi', 'Okwaikumi na kumwe', 'Okwaikumi na ibiri'], + 'months_short' => ['KBZ', 'KBR', 'KST', 'KKN', 'KTN', 'KMK', 'KMS', 'KMN', 'KMW', 'KKM', 'KNK', 'KNB'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/oc.php b/vendor/nesbot/carbon/src/Carbon/Lang/oc.php new file mode 100644 index 0000000..c9411d6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/oc.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Quentí + */ +// @codeCoverageIgnoreStart +use Symfony\Component\Translation\PluralizationRules; + +if (class_exists('Symfony\\Component\\Translation\\PluralizationRules')) { + PluralizationRules::set(static function ($number) { + return $number == 1 ? 0 : 1; + }, 'oc'); +} +// @codeCoverageIgnoreEnd + +return [ + 'year' => ':count an|:count ans', + 'a_year' => 'un an|:count ans', + 'y' => ':count an|:count ans', + 'month' => ':count mes|:count meses', + 'a_month' => 'un mes|:count meses', + 'm' => ':count mes|:count meses', + 'week' => ':count setmana|:count setmanas', + 'a_week' => 'una setmana|:count setmanas', + 'w' => ':count setmana|:count setmanas', + 'day' => ':count jorn|:count jorns', + 'a_day' => 'un jorn|:count jorns', + 'd' => ':count jorn|:count jorns', + 'hour' => ':count ora|:count oras', + 'a_hour' => 'una ora|:count oras', + 'h' => ':count ora|:count oras', + 'minute' => ':count minuta|:count minutas', + 'a_minute' => 'una minuta|:count minutas', + 'min' => ':count minuta|:count minutas', + 'second' => ':count segonda|:count segondas', + 'a_second' => 'una segonda|:count segondas', + 's' => ':count segonda|:count segondas', + 'ago' => 'fa :time', + 'from_now' => 'd\'aquí :time', + 'after' => ':time aprèp', + 'before' => ':time abans', + 'diff_now' => 'ara meteis', + 'diff_today' => 'Uèi', + 'diff_today_regexp' => 'Uèi(?:\\s+a)?', + 'diff_yesterday' => 'ièr', + 'diff_yesterday_regexp' => 'Ièr(?:\\s+a)?', + 'diff_tomorrow' => 'deman', + 'diff_tomorrow_regexp' => 'Deman(?:\\s+a)?', + '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', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM [de] YYYY', + 'LLL' => 'D MMMM [de] YYYY [a] H:mm', + 'LLLL' => 'dddd D MMMM [de] YYYY [a] H:mm', + ], + 'calendar' => [ + 'sameDay' => '[Uèi a] LT', + 'nextDay' => '[Deman a] LT', + 'nextWeek' => 'dddd [a] LT', + 'lastDay' => '[Ièr a] LT', + 'lastWeek' => 'dddd [passat a] LT', + 'sameElse' => 'L', + ], + 'months' => ['de genièr', 'de febrièr', 'de març', 'd\'abrial', 'de mai', 'de junh', 'de julhet', 'd\'agost', 'de setembre', 'd’octòbre', 'de novembre', 'de decembre'], + 'months_standalone' => ['genièr', 'febrièr', 'març', 'abrial', 'mai', 'junh', 'julh', 'agost', 'setembre', 'octòbre', 'novembre', 'decembre'], + 'months_short' => ['gen.', 'feb.', 'març', 'abr.', 'mai', 'junh', 'julh', 'ago.', 'sep.', 'oct.', 'nov.', 'dec.'], + 'weekdays' => ['dimenge', 'diluns', 'dimars', 'dimècres', 'dijòus', 'divendres', 'dissabte'], + 'weekdays_short' => ['dg', 'dl', 'dm', 'dc', 'dj', 'dv', 'ds'], + 'weekdays_min' => ['dg', 'dl', 'dm', 'dc', 'dj', 'dv', 'ds'], + 'ordinal' => function ($number, string $period = '') { + $ordinal = [1 => 'èr', 2 => 'nd'][(int) $number] ?? 'en'; + + // feminine for year, week, hour, minute, second + if (preg_match('/^[yYwWhHgGis]$/', $period)) { + $ordinal .= 'a'; + } + + return $number.$ordinal; + }, + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' e '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/oc_FR.php b/vendor/nesbot/carbon/src/Carbon/Lang/oc_FR.php new file mode 100644 index 0000000..01eb5c1 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/oc_FR.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__.'/oc.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/om.php b/vendor/nesbot/carbon/src/Carbon/Lang/om.php new file mode 100644 index 0000000..b8d5a0b --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/om.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation & Sagalee Oromoo Publishing Co. Inc. locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'dd-MMM-YYYY', + 'LLL' => 'dd MMMM YYYY HH:mm', + 'LLLL' => 'dddd, MMMM D, YYYY HH:mm', + ], + 'months' => ['Amajjii', 'Guraandhala', 'Bitooteessa', 'Elba', 'Caamsa', 'Waxabajjii', 'Adooleessa', 'Hagayya', 'Fuulbana', 'Onkololeessa', 'Sadaasa', 'Muddee'], + 'months_short' => ['Ama', 'Gur', 'Bit', 'Elb', 'Cam', 'Wax', 'Ado', 'Hag', 'Ful', 'Onk', 'Sad', 'Mud'], + 'weekdays' => ['Dilbata', 'Wiixata', 'Qibxata', 'Roobii', 'Kamiisa', 'Jimaata', 'Sanbata'], + 'weekdays_short' => ['Dil', 'Wix', 'Qib', 'Rob', 'Kam', 'Jim', 'San'], + 'weekdays_min' => ['Dil', 'Wix', 'Qib', 'Rob', 'Kam', 'Jim', 'San'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['WD', 'WB'], + + 'year' => 'wggoota :count', + 'y' => 'wggoota :count', + 'a_year' => 'wggoota :count', + + 'month' => 'ji’a :count', + 'm' => 'ji’a :count', + 'a_month' => 'ji’a :count', + + 'week' => 'torban :count', + 'w' => 'torban :count', + 'a_week' => 'torban :count', + + 'day' => 'guyyaa :count', + 'd' => 'guyyaa :count', + 'a_day' => 'guyyaa :count', + + 'hour' => 'saʼaatii :count', + 'h' => 'saʼaatii :count', + 'a_hour' => 'saʼaatii :count', + + 'minute' => 'daqiiqaa :count', + 'min' => 'daqiiqaa :count', + 'a_minute' => 'daqiiqaa :count', + + 'second' => 'sekoondii :count', + 's' => 'sekoondii :count', + 'a_second' => 'sekoondii :count', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/om_ET.php b/vendor/nesbot/carbon/src/Carbon/Lang/om_ET.php new file mode 100644 index 0000000..044760e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/om_ET.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__.'/om.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/om_KE.php b/vendor/nesbot/carbon/src/Carbon/Lang/om_KE.php new file mode 100644 index 0000000..f5a4d1c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/om_KE.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/om.php', [ + 'day_of_first_week_of_year' => 0, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/or.php b/vendor/nesbot/carbon/src/Carbon/Lang/or.php new file mode 100644 index 0000000..3aa7173 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/or.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/or_IN.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/or_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/or_IN.php new file mode 100644 index 0000000..57a89f5 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/or_IN.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM AP Linux Technology Center, Yamato Software Laboratory bug-glibc@gnu.org + */ +return [ + 'diff_now' => 'ବର୍ତ୍ତମାନ', + 'diff_yesterday' => 'ଗତକାଲି', + 'diff_tomorrow' => 'ଆସନ୍ତାକାଲି', + 'formats' => [ + 'LT' => 'Oh:Om A', + 'LTS' => 'Oh:Om:Os A', + 'L' => 'OD-OM-OY', + 'LL' => 'OD MMMM OY', + 'LLL' => 'OD MMMM OY Oh:Om A', + 'LLLL' => 'dddd OD MMMM OY Oh:Om A', + ], + 'months' => ['ଜାନୁଆରୀ', 'ଫେବୃଆରୀ', 'ମାର୍ଚ୍ଚ', 'ଅପ୍ରେଲ', 'ମଇ', 'ଜୁନ', 'ଜୁଲାଇ', 'ଅଗଷ୍ଟ', 'ସେପ୍ଟେମ୍ବର', 'ଅକ୍ଟୋବର', 'ନଭେମ୍ବର', 'ଡିସେମ୍ବର'], + 'months_short' => ['ଜାନୁଆରୀ', 'ଫେବୃଆରୀ', 'ମାର୍ଚ୍ଚ', 'ଅପ୍ରେଲ', 'ମଇ', 'ଜୁନ', 'ଜୁଲାଇ', 'ଅଗଷ୍ଟ', 'ସେପ୍ଟେମ୍ବର', 'ଅକ୍ଟୋବର', 'ନଭେମ୍ବର', 'ଡିସେମ୍ବର'], + 'weekdays' => ['ରବିବାର', 'ସୋମବାର', 'ମଙ୍ଗଳବାର', 'ବୁଧବାର', 'ଗୁରୁବାର', 'ଶୁକ୍ରବାର', 'ଶନିବାର'], + 'weekdays_short' => ['ରବି', 'ସୋମ', 'ମଙ୍ଗଳ', 'ବୁଧ', 'ଗୁରୁ', 'ଶୁକ୍ର', 'ଶନି'], + 'weekdays_min' => ['ରବି', 'ସୋମ', 'ମଙ୍ଗଳ', 'ବୁଧ', 'ଗୁରୁ', 'ଶୁକ୍ର', 'ଶନି'], + 'day_of_first_week_of_year' => 1, + 'alt_numbers' => ['୦', '୧', '୨', '୩', '୪', '୫', '୬', '୭', '୮', '୯', '୧୦', '୧୧', '୧୨', '୧୩', '୧୪', '୧୫', '୧୬', '୧୭', '୧୮', '୧୯', '୨୦', '୨୧', '୨୨', '୨୩', '୨୪', '୨୫', '୨୬', '୨୭', '୨୮', '୨୯', '୩୦', '୩୧', '୩୨', '୩୩', '୩୪', '୩୫', '୩୬', '୩୭', '୩୮', '୩୯', '୪୦', '୪୧', '୪୨', '୪୩', '୪୪', '୪୫', '୪୬', '୪୭', '୪୮', '୪୯', '୫୦', '୫୧', '୫୨', '୫୩', '୫୪', '୫୫', '୫୬', '୫୭', '୫୮', '୫୯', '୬୦', '୬୧', '୬୨', '୬୩', '୬୪', '୬୫', '୬୬', '୬୭', '୬୮', '୬୯', '୭୦', '୭୧', '୭୨', '୭୩', '୭୪', '୭୫', '୭୬', '୭୭', '୭୮', '୭୯', '୮୦', '୮୧', '୮୨', '୮୩', '୮୪', '୮୫', '୮୬', '୮୭', '୮୮', '୮୯', '୯୦', '୯୧', '୯୨', '୯୩', '୯୪', '୯୫', '୯୬', '୯୭', '୯୮', '୯୯'], + '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ରେ', +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/os.php b/vendor/nesbot/carbon/src/Carbon/Lang/os.php new file mode 100644 index 0000000..5f55e8a --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/os.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/os_RU.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/os_RU.php b/vendor/nesbot/carbon/src/Carbon/Lang/os_RU.php new file mode 100644 index 0000000..9592d15 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/os_RU.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['январы', 'февралы', 'мартъийы', 'апрелы', 'майы', 'июны', 'июлы', 'августы', 'сентябры', 'октябры', 'ноябры', 'декабры'], + 'months_short' => ['Янв', 'Фев', 'Мар', 'Апр', 'Май', 'Июн', 'Июл', 'Авг', 'Сен', 'Окт', 'Ноя', 'Дек'], + 'weekdays' => ['Хуыцаубон', 'Къуырисæр', 'Дыццæг', 'Æртыццæг', 'Цыппæрæм', 'Майрæмбон', 'Сабат'], + 'weekdays_short' => ['Хцб', 'Крс', 'Дцг', 'Æрт', 'Цпр', 'Мрб', 'Сбт'], + 'weekdays_min' => ['Хцб', 'Крс', 'Дцг', 'Æрт', 'Цпр', 'Мрб', 'Сбт'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + + 'minute' => ':count гыццыл', // less reliable + 'min' => ':count гыццыл', // less reliable + 'a_minute' => ':count гыццыл', // less reliable + + 'second' => ':count æндæр', // less reliable + 's' => ':count æндæр', // less reliable + 'a_second' => ':count æндæр', // less reliable + + 'year' => ':count аз', + 'y' => ':count аз', + 'a_year' => ':count аз', + + 'month' => ':count мӕй', + 'm' => ':count мӕй', + 'a_month' => ':count мӕй', + + 'week' => ':count къуыри', + 'w' => ':count къуыри', + 'a_week' => ':count къуыри', + + 'day' => ':count бон', + 'd' => ':count бон', + 'a_day' => ':count бон', + + 'hour' => ':count сахат', + 'h' => ':count сахат', + 'a_hour' => ':count сахат', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/pa.php b/vendor/nesbot/carbon/src/Carbon/Lang/pa.php new file mode 100644 index 0000000..48b2033 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/pa.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Philippe Vaucher + * - Tsutomu Kuroda + * - Punjab + */ +return [ + 'year' => 'ਇੱਕ ਸਾਲ|:count ਸਾਲ', + 'month' => 'ਇੱਕ ਮਹੀਨਾ|:count ਮਹੀਨੇ', + 'week' => 'ਹਫਤਾ|:count ਹਫ਼ਤੇ', + 'day' => 'ਇੱਕ ਦਿਨ|:count ਦਿਨ', + 'hour' => 'ਇੱਕ ਘੰਟਾ|:count ਘੰਟੇ', + 'minute' => 'ਇਕ ਮਿੰਟ|:count ਮਿੰਟ', + 'second' => 'ਕੁਝ ਸਕਿੰਟ|:count ਸਕਿੰਟ', + 'ago' => ':time ਪਹਿਲਾਂ', + 'from_now' => ':time ਵਿੱਚ', + 'before' => ':time ਤੋਂ ਪਹਿਲਾਂ', + 'after' => ':time ਤੋਂ ਬਾਅਦ', + 'diff_now' => 'ਹੁਣ', + 'diff_today' => 'ਅਜ', + 'diff_yesterday' => 'ਕਲ', + 'diff_tomorrow' => 'ਕਲ', + 'formats' => [ + 'LT' => 'A h:mm ਵਜੇ', + 'LTS' => 'A h:mm:ss ਵਜੇ', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY, A h:mm ਵਜੇ', + 'LLLL' => 'dddd, D MMMM YYYY, A h:mm ਵਜੇ', + ], + 'calendar' => [ + 'sameDay' => '[ਅਜ] LT', + 'nextDay' => '[ਕਲ] LT', + 'nextWeek' => '[ਅਗਲਾ] dddd, LT', + 'lastDay' => '[ਕਲ] LT', + 'lastWeek' => '[ਪਿਛਲੇ] dddd, LT', + 'sameElse' => 'L', + ], + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'ਰਾਤ'; + } + if ($hour < 10) { + return 'ਸਵੇਰ'; + } + if ($hour < 17) { + return 'ਦੁਪਹਿਰ'; + } + if ($hour < 20) { + return 'ਸ਼ਾਮ'; + } + + return 'ਰਾਤ'; + }, + 'months' => ['ਜਨਵਰੀ', 'ਫ਼ਰਵਰੀ', 'ਮਾਰਚ', 'ਅਪ੍ਰੈਲ', 'ਮਈ', 'ਜੂਨ', 'ਜੁਲਾਈ', 'ਅਗਸਤ', 'ਸਤੰਬਰ', 'ਅਕਤੂਬਰ', 'ਨਵੰਬਰ', 'ਦਸੰਬਰ'], + 'months_short' => ['ਜਨਵਰੀ', 'ਫ਼ਰਵਰੀ', 'ਮਾਰਚ', 'ਅਪ੍ਰੈਲ', 'ਮਈ', 'ਜੂਨ', 'ਜੁਲਾਈ', 'ਅਗਸਤ', 'ਸਤੰਬਰ', 'ਅਕਤੂਬਰ', 'ਨਵੰਬਰ', 'ਦਸੰਬਰ'], + 'weekdays' => ['ਐਤਵਾਰ', 'ਸੋਮਵਾਰ', 'ਮੰਗਲਵਾਰ', 'ਬੁਧਵਾਰ', 'ਵੀਰਵਾਰ', 'ਸ਼ੁੱਕਰਵਾਰ', 'ਸ਼ਨੀਚਰਵਾਰ'], + 'weekdays_short' => ['ਐਤ', 'ਸੋਮ', 'ਮੰਗਲ', 'ਬੁਧ', 'ਵੀਰ', 'ਸ਼ੁਕਰ', 'ਸ਼ਨੀ'], + 'weekdays_min' => ['ਐਤ', 'ਸੋਮ', 'ਮੰਗਲ', 'ਬੁਧ', 'ਵੀਰ', 'ਸ਼ੁਕਰ', 'ਸ਼ਨੀ'], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' ਅਤੇ '], + 'weekend' => [0, 0], + 'alt_numbers' => ['੦', '੧', '੨', '੩', '੪', '੫', '੬', '੭', '੮', '੯'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/pa_Arab.php b/vendor/nesbot/carbon/src/Carbon/Lang/pa_Arab.php new file mode 100644 index 0000000..39b0653 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/pa_Arab.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ur.php', [ + 'weekdays' => ['اتوار', 'پیر', 'منگل', 'بُدھ', 'جمعرات', 'جمعہ', 'ہفتہ'], + 'weekdays_short' => ['اتوار', 'پیر', 'منگل', 'بُدھ', 'جمعرات', 'جمعہ', 'ہفتہ'], + 'weekdays_min' => ['اتوار', 'پیر', 'منگل', 'بُدھ', 'جمعرات', 'جمعہ', 'ہفتہ'], + 'months' => ['جنوری', 'فروری', 'مارچ', 'اپریل', 'مئ', 'جون', 'جولائی', 'اگست', 'ستمبر', 'اکتوبر', 'نومبر', 'دسمبر'], + 'months_short' => ['جنوری', 'فروری', 'مارچ', 'اپریل', 'مئ', 'جون', 'جولائی', 'اگست', 'ستمبر', 'اکتوبر', 'نومبر', 'دسمبر'], + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'dddd, DD MMMM YYYY h:mm a', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/pa_Guru.php b/vendor/nesbot/carbon/src/Carbon/Lang/pa_Guru.php new file mode 100644 index 0000000..7adff5c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/pa_Guru.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/pa.php', [ + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'D/M/yy', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY, h:mm a', + 'LLLL' => 'dddd, D MMMM YYYY, h:mm a', + ], + 'months' => ['ਜਨਵਰੀ', 'ਫ਼ਰਵਰੀ', 'ਮਾਰਚ', 'ਅਪ੍ਰੈਲ', 'ਮਈ', 'ਜੂਨ', 'ਜੁਲਾਈ', 'ਅਗਸਤ', 'ਸਤੰਬਰ', 'ਅਕਤੂਬਰ', 'ਨਵੰਬਰ', 'ਦਸੰਬਰ'], + 'months_short' => ['ਜਨ', 'ਫ਼ਰ', 'ਮਾਰਚ', 'ਅਪ੍ਰੈ', 'ਮਈ', 'ਜੂਨ', 'ਜੁਲਾ', 'ਅਗ', 'ਸਤੰ', 'ਅਕਤੂ', 'ਨਵੰ', 'ਦਸੰ'], + 'weekdays' => ['ਐਤਵਾਰ', 'ਸੋਮਵਾਰ', 'ਮੰਗਲਵਾਰ', 'ਬੁੱਧਵਾਰ', 'ਵੀਰਵਾਰ', 'ਸ਼ੁੱਕਰਵਾਰ', 'ਸ਼ਨਿੱਚਰਵਾਰ'], + 'weekdays_short' => ['ਐਤ', 'ਸੋਮ', 'ਮੰਗਲ', 'ਬੁੱਧ', 'ਵੀਰ', 'ਸ਼ੁੱਕਰ', 'ਸ਼ਨਿੱਚਰ'], + 'weekdays_min' => ['ਐਤ', 'ਸੋਮ', 'ਮੰਗ', 'ਬੁੱਧ', 'ਵੀਰ', 'ਸ਼ੁੱਕ', 'ਸ਼ਨਿੱ'], + 'weekend' => [0, 0], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/pa_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/pa_IN.php new file mode 100644 index 0000000..ca67642 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/pa_IN.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Guo Xiang Tan + * - Josh Soref + * - Ash + * - harpreetkhalsagtbit + */ +return require __DIR__.'/pa.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/pa_PK.php b/vendor/nesbot/carbon/src/Carbon/Lang/pa_PK.php new file mode 100644 index 0000000..f9af11c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/pa_PK.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['جنوري', 'فروري', 'مارچ', 'اپريل', 'مٓی', 'جون', 'جولاي', 'اگست', 'ستمبر', 'اكتوبر', 'نومبر', 'دسمبر'], + 'months_short' => ['جنوري', 'فروري', 'مارچ', 'اپريل', 'مٓی', 'جون', 'جولاي', 'اگست', 'ستمبر', 'اكتوبر', 'نومبر', 'دسمبر'], + 'weekdays' => ['اتوار', 'پير', 'منگل', 'بدھ', 'جمعرات', 'جمعه', 'هفته'], + 'weekdays_short' => ['اتوار', 'پير', 'منگل', 'بدھ', 'جمعرات', 'جمعه', 'هفته'], + 'weekdays_min' => ['اتوار', 'پير', 'منگل', 'بدھ', 'جمعرات', 'جمعه', 'هفته'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['ص', 'ش'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/pap.php b/vendor/nesbot/carbon/src/Carbon/Lang/pap.php new file mode 100644 index 0000000..b4c1706 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/pap.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return [ + 'formats' => [ + 'LT' => 'HH.mm', + 'LTS' => 'HH.mm:ss', + 'L' => 'DD-MM-YY', + 'LL' => 'MMMM [di] DD, YYYY', + 'LLL' => 'DD MMM HH.mm', + 'LLLL' => 'MMMM DD, YYYY HH.mm', + ], + 'months' => ['yanüari', 'febrüari', 'mart', 'aprel', 'mei', 'yüni', 'yüli', 'ougùstùs', 'sèptèmber', 'oktober', 'novèmber', 'desèmber'], + 'months_short' => ['yan', 'feb', 'mar', 'apr', 'mei', 'yün', 'yül', 'oug', 'sèp', 'okt', 'nov', 'des'], + 'weekdays' => ['djadomingo', 'djaluna', 'djamars', 'djawebs', 'djarason', 'djabierne', 'djasabra'], + 'weekdays_short' => ['do', 'lu', 'ma', 'we', 'ra', 'bi', 'sa'], + 'weekdays_min' => ['do', 'lu', 'ma', 'we', 'ra', 'bi', 'sa'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'year' => ':count aña', + 'month' => ':count luna', + 'week' => ':count siman', + 'day' => ':count dia', + 'hour' => ':count ora', + 'minute' => ':count minüt', + 'second' => ':count sekònde', + 'list' => [', ', ' i '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/pap_AW.php b/vendor/nesbot/carbon/src/Carbon/Lang/pap_AW.php new file mode 100644 index 0000000..e9a48ff --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/pap_AW.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - information from native speaker Pablo Saratxaga pablo@mandrakesoft.com + */ +return require __DIR__.'/pap.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/pap_CW.php b/vendor/nesbot/carbon/src/Carbon/Lang/pap_CW.php new file mode 100644 index 0000000..e9a48ff --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/pap_CW.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - information from native speaker Pablo Saratxaga pablo@mandrakesoft.com + */ +return require __DIR__.'/pap.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/pl.php b/vendor/nesbot/carbon/src/Carbon/Lang/pl.php new file mode 100644 index 0000000..b720535 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/pl.php @@ -0,0 +1,126 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Wacław Jacek + * - François B + * - Tim Fish + * - Serhan Apaydın + * - Massimiliano Caniparoli + * - JD Isaacks + * - Jakub Szwacz + * - Jan + * - Paul + * - damlys + * - Marek (marast78) + * - Peter (UnrulyNatives) + * - Qrzysio + * - Jan (aso824) + * - diverpl + */ + +use Carbon\CarbonInterface; + +return [ + 'year' => ':count rok|:count lata|:count lat', + 'a_year' => 'rok|:count lata|:count lat', + 'y' => ':count r|:count l|:count l', + 'month' => ':count miesiąc|:count miesiące|:count miesięcy', + 'a_month' => 'miesiąc|:count miesiące|:count miesięcy', + 'm' => ':count mies.', + 'week' => ':count tydzień|:count tygodnie|:count tygodni', + 'a_week' => 'tydzień|:count tygodnie|:count tygodni', + 'w' => ':count tyg.', + 'day' => ':count dzień|:count dni|:count dni', + 'a_day' => 'dzień|:count dni|:count dni', + 'd' => ':count d', + 'hour' => ':count godzina|:count godziny|:count godzin', + 'a_hour' => 'godzina|:count godziny|:count godzin', + 'h' => ':count godz.', + 'minute' => ':count minuta|:count minuty|:count minut', + 'a_minute' => 'minuta|:count minuty|:count minut', + 'min' => ':count min', + 'second' => ':count sekunda|:count sekundy|:count sekund', + 'a_second' => '{1}kilka sekund|:count sekunda|:count sekundy|:count sekund', + 's' => ':count sek.', + 'ago' => ':time temu', + 'from_now' => static function ($time) { + return 'za '.strtr($time, [ + 'godzina' => 'godzinę', + 'minuta' => 'minutę', + 'sekunda' => 'sekundę', + ]); + }, + 'after' => ':time po', + 'before' => ':time przed', + 'diff_now' => 'teraz', + 'diff_today' => 'Dziś', + 'diff_today_regexp' => 'Dziś(?:\\s+o)?', + 'diff_yesterday' => 'wczoraj', + 'diff_yesterday_regexp' => 'Wczoraj(?:\\s+o)?', + 'diff_tomorrow' => 'jutro', + 'diff_tomorrow_regexp' => 'Jutro(?:\\s+o)?', + 'diff_before_yesterday' => 'przedwczoraj', + 'diff_after_tomorrow' => 'pojutrze', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Dziś o] LT', + 'nextDay' => '[Jutro o] LT', + 'nextWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + return '[W niedzielę o] LT'; + case 2: + return '[We wtorek o] LT'; + case 3: + return '[W środę o] LT'; + case 6: + return '[W sobotę o] LT'; + default: + return '[W] dddd [o] LT'; + } + }, + 'lastDay' => '[Wczoraj o] LT', + 'lastWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + return '[W zeszłą niedzielę o] LT'; + case 3: + return '[W zeszłą środę o] LT'; + case 6: + return '[W zeszłą sobotę o] LT'; + default: + return '[W zeszły] dddd [o] LT'; + } + }, + 'sameElse' => 'L', + ], + 'ordinal' => ':number.', + 'months' => ['stycznia', 'lutego', 'marca', 'kwietnia', 'maja', 'czerwca', 'lipca', 'sierpnia', 'września', 'października', 'listopada', 'grudnia'], + 'months_standalone' => ['styczeń', 'luty', 'marzec', 'kwiecień', 'maj', 'czerwiec', 'lipiec', 'sierpień', 'wrzesień', 'październik', 'listopad', 'grudzień'], + 'months_short' => ['sty', 'lut', 'mar', 'kwi', 'maj', 'cze', 'lip', 'sie', 'wrz', 'paź', 'lis', 'gru'], + 'months_regexp' => '/(DD?o?\.?(\[[^\[\]]*\]|\s)+MMMM?|L{2,4}|l{2,4})/', + 'weekdays' => ['niedziela', 'poniedziałek', 'wtorek', 'środa', 'czwartek', 'piątek', 'sobota'], + 'weekdays_short' => ['ndz', 'pon', 'wt', 'śr', 'czw', 'pt', 'sob'], + 'weekdays_min' => ['Nd', 'Pn', 'Wt', 'Śr', 'Cz', 'Pt', 'So'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' i '], + 'meridiem' => ['przed południem', 'po południu'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/pl_PL.php b/vendor/nesbot/carbon/src/Carbon/Lang/pl_PL.php new file mode 100644 index 0000000..222bcdb --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/pl_PL.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__.'/pl.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/prg.php b/vendor/nesbot/carbon/src/Carbon/Lang/prg.php new file mode 100644 index 0000000..6e63f4a --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/prg.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_replace_recursive(require __DIR__.'/en.php', [ + 'months' => ['M01', 'M02', 'M03', 'M04', 'M05', 'M06', 'M07', 'M08', 'M09', 'M10', 'M11', 'M12'], + 'months_short' => ['M01', 'M02', 'M03', 'M04', 'M05', 'M06', 'M07', 'M08', 'M09', 'M10', 'M11', 'M12'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-dd', + 'LL' => 'YYYY MMM D', + 'LLL' => 'YYYY MMMM D HH:mm', + 'LLLL' => 'YYYY MMMM D, dddd HH:mm', + ], + + 'year' => ':count meta', + 'y' => ':count meta', + 'a_year' => ':count meta', + + 'month' => ':count mēniks', // less reliable + 'm' => ':count mēniks', // less reliable + 'a_month' => ':count mēniks', // less reliable + + 'week' => ':count sawaītin', // less reliable + 'w' => ':count sawaītin', // less reliable + 'a_week' => ':count sawaītin', // less reliable + + 'day' => ':count di', + 'd' => ':count di', + 'a_day' => ':count di', + + 'hour' => ':count bruktēt', // less reliable + 'h' => ':count bruktēt', // less reliable + 'a_hour' => ':count bruktēt', // less reliable + + 'minute' => ':count līkuts', // less reliable + 'min' => ':count līkuts', // less reliable + 'a_minute' => ':count līkuts', // less reliable + + 'second' => ':count kitan', // less reliable + 's' => ':count kitan', // less reliable + 'a_second' => ':count kitan', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ps.php b/vendor/nesbot/carbon/src/Carbon/Lang/ps.php new file mode 100644 index 0000000..a928b28 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ps.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Muhammad Nasir Rahimi + * - Nassim Nasibullah (spinzar) + */ +return [ + '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 دمخه', + 'list' => ['، ', ' او '], + 'meridiem' => ['غ.م.', 'غ.و.'], + 'weekdays' => ['اتوار', 'ګل', 'نهه', 'شورو', 'زيارت', 'جمعه', 'خالي'], + 'weekdays_short' => ['ا', 'ګ', 'ن', 'ش', 'ز', 'ج', 'خ'], + 'weekdays_min' => ['ا', 'ګ', 'ن', 'ش', 'ز', 'ج', 'خ'], + 'months' => ['جنوري', 'فبروري', 'مارچ', 'اپریل', 'مۍ', 'جون', 'جولای', 'اگست', 'سېپتمبر', 'اکتوبر', 'نومبر', 'دسمبر'], + 'months_short' => ['جنوري', 'فبروري', 'مارچ', 'اپریل', 'مۍ', 'جون', 'جولای', 'اگست', 'سېپتمبر', 'اکتوبر', 'نومبر', 'دسمبر'], + 'months_standalone' => ['جنوري', 'فېبروري', 'مارچ', 'اپریل', 'مۍ', 'جون', 'جولای', 'اگست', 'سپتمبر', 'اکتوبر', 'نومبر', 'دسمبر'], + 'months_short_standalone' => ['جنوري', 'فبروري', 'مارچ', 'اپریل', 'مۍ', 'جون', 'جولای', 'اگست', 'سپتمبر', 'اکتوبر', 'نومبر', 'دسمبر'], + 'first_day_of_week' => 6, + 'weekend' => [4, 5], + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'YYYY/M/d', + 'LL' => 'YYYY MMM D', + 'LLL' => 'د YYYY د MMMM D H:mm', + 'LLLL' => 'dddd د YYYY د MMMM D H:mm', + ], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ps_AF.php b/vendor/nesbot/carbon/src/Carbon/Lang/ps_AF.php new file mode 100644 index 0000000..6ec5180 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ps_AF.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__.'/ps.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/pt.php b/vendor/nesbot/carbon/src/Carbon/Lang/pt.php new file mode 100644 index 0000000..bb6359b --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/pt.php @@ -0,0 +1,116 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Cassiano Montanari + * - Matt Pope + * - François B + * - Prodis + * - JD Isaacks + * - Raphael Amorim + * - João Magalhães + * - victortobias + * - Paulo Freitas + * - Sebastian Thierer + * - Claudson Martins (claudsonm) + */ + +use Carbon\CarbonInterface; + +return [ + 'year' => ':count ano|:count anos', + 'a_year' => 'um ano|:count anos', + 'y' => ':counta', + 'month' => ':count mês|:count meses', + 'a_month' => 'um mês|:count meses', + 'm' => ':countm', + 'week' => ':count semana|:count semanas', + 'a_week' => 'uma semana|:count semanas', + 'w' => ':countsem', + 'day' => ':count dia|:count dias', + 'a_day' => 'um dia|:count dias', + 'd' => ':countd', + 'hour' => ':count hora|:count horas', + 'a_hour' => 'uma hora|:count horas', + 'h' => ':counth', + 'minute' => ':count minuto|:count minutos', + 'a_minute' => 'um minuto|:count minutos', + 'min' => ':countmin', + 'second' => ':count segundo|:count segundos', + 'a_second' => 'alguns segundos|:count segundos', + 's' => ':counts', + 'millisecond' => ':count milissegundo|:count milissegundos', + 'a_millisecond' => 'um milissegundo|:count milissegundos', + 'ms' => ':countms', + 'microsecond' => ':count microssegundo|:count microssegundos', + 'a_microsecond' => 'um microssegundo|:count microssegundos', + 'µs' => ':countµs', + 'ago' => 'há :time', + 'from_now' => 'em :time', + 'after' => ':time depois', + 'before' => ':time antes', + 'diff_now' => 'agora', + 'diff_today' => 'Hoje', + 'diff_today_regexp' => 'Hoje(?:\\s+às)?', + 'diff_yesterday' => 'ontem', + 'diff_yesterday_regexp' => 'Ontem(?:\\s+às)?', + 'diff_tomorrow' => 'amanhã', + 'diff_tomorrow_regexp' => 'Amanhã(?:\\s+às)?', + 'diff_before_yesterday' => 'anteontem', + 'diff_after_tomorrow' => 'depois de amanhã', + 'period_recurrences' => 'uma vez|:count vezes', + 'period_interval' => 'cada :interval', + 'period_start_date' => 'de :date', + 'period_end_date' => 'até :date', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D [de] MMMM [de] YYYY', + 'LLL' => 'D [de] MMMM [de] YYYY HH:mm', + 'LLLL' => 'dddd, D [de] MMMM [de] YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Hoje às] LT', + 'nextDay' => '[Amanhã às] LT', + 'nextWeek' => 'dddd [às] LT', + 'lastDay' => '[Ontem às] LT', + 'lastWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + case 6: + return '[Último] dddd [às] LT'; + default: + return '[Última] dddd [às] LT'; + } + }, + 'sameElse' => 'L', + ], + 'ordinal' => ':numberº', + 'months' => ['janeiro', 'fevereiro', 'março', 'abril', 'maio', 'junho', 'julho', 'agosto', 'setembro', 'outubro', 'novembro', 'dezembro'], + 'months_short' => ['jan', 'fev', 'mar', 'abr', 'mai', 'jun', 'jul', 'ago', 'set', 'out', 'nov', 'dez'], + 'weekdays' => ['domingo', 'segunda-feira', 'terça-feira', 'quarta-feira', 'quinta-feira', 'sexta-feira', 'sábado'], + 'weekdays_short' => ['dom', 'seg', 'ter', 'qua', 'qui', 'sex', 'sáb'], + 'weekdays_min' => ['Do', '2ª', '3ª', '4ª', '5ª', '6ª', 'Sá'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' e '], + 'ordinal_words' => [ + 'of' => 'de', + 'first' => 'primeira', + 'second' => 'segunda', + 'third' => 'terceira', + 'fourth' => 'quarta', + 'fifth' => 'quinta', + 'last' => 'última', + ], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/pt_AO.php b/vendor/nesbot/carbon/src/Carbon/Lang/pt_AO.php new file mode 100644 index 0000000..22c01ec --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/pt_AO.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__.'/pt.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/pt_BR.php b/vendor/nesbot/carbon/src/Carbon/Lang/pt_BR.php new file mode 100644 index 0000000..e917c5c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/pt_BR.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Cassiano Montanari + * - Eduardo Dalla Vecchia + * - David Rodrigues + * - Matt Pope + * - François B + * - Prodis + * - Marlon Maxwel + * - JD Isaacks + * - Raphael Amorim + * - Rafael Raupp + * - felipeleite1 + * - swalker + * - Lucas Macedo + * - Paulo Freitas + * - Sebastian Thierer + */ +return array_replace_recursive(require __DIR__.'/pt.php', [ + 'period_recurrences' => 'uma|:count vez', + 'period_interval' => 'toda :interval', + 'formats' => [ + 'LLL' => 'D [de] MMMM [de] YYYY [às] HH:mm', + 'LLLL' => 'dddd, D [de] MMMM [de] YYYY [às] HH:mm', + ], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/pt_CH.php b/vendor/nesbot/carbon/src/Carbon/Lang/pt_CH.php new file mode 100644 index 0000000..22c01ec --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/pt_CH.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__.'/pt.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/pt_CV.php b/vendor/nesbot/carbon/src/Carbon/Lang/pt_CV.php new file mode 100644 index 0000000..22c01ec --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/pt_CV.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__.'/pt.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/pt_GQ.php b/vendor/nesbot/carbon/src/Carbon/Lang/pt_GQ.php new file mode 100644 index 0000000..22c01ec --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/pt_GQ.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__.'/pt.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/pt_GW.php b/vendor/nesbot/carbon/src/Carbon/Lang/pt_GW.php new file mode 100644 index 0000000..22c01ec --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/pt_GW.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__.'/pt.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/pt_LU.php b/vendor/nesbot/carbon/src/Carbon/Lang/pt_LU.php new file mode 100644 index 0000000..22c01ec --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/pt_LU.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__.'/pt.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/pt_MO.php b/vendor/nesbot/carbon/src/Carbon/Lang/pt_MO.php new file mode 100644 index 0000000..f2b5eab --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/pt_MO.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/pt.php', [ + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'LLL' => 'D [de] MMMM [de] YYYY, h:mm a', + 'LLLL' => 'dddd, D [de] MMMM [de] YYYY, h:mm a', + ], + 'first_day_of_week' => 0, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/pt_MZ.php b/vendor/nesbot/carbon/src/Carbon/Lang/pt_MZ.php new file mode 100644 index 0000000..fbc0c97 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/pt_MZ.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/pt.php', [ + 'first_day_of_week' => 0, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/pt_PT.php b/vendor/nesbot/carbon/src/Carbon/Lang/pt_PT.php new file mode 100644 index 0000000..2a76fc1 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/pt_PT.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/pt.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['janeiro', 'fevereiro', 'março', 'abril', 'maio', 'junho', 'julho', 'agosto', 'setembro', 'outubro', 'novembro', 'dezembro'], + 'months_short' => ['jan', 'fev', 'mar', 'abr', 'mai', 'jun', 'jul', 'ago', 'set', 'out', 'nov', 'dez'], + 'weekdays' => ['domingo', 'segunda', 'terça', 'quarta', 'quinta', 'sexta', 'sábado'], + 'weekdays_short' => ['dom', 'seg', 'ter', 'qua', 'qui', 'sex', 'sáb'], + 'weekdays_min' => ['dom', 'seg', 'ter', 'qua', 'qui', 'sex', 'sáb'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/pt_ST.php b/vendor/nesbot/carbon/src/Carbon/Lang/pt_ST.php new file mode 100644 index 0000000..22c01ec --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/pt_ST.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__.'/pt.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/pt_TL.php b/vendor/nesbot/carbon/src/Carbon/Lang/pt_TL.php new file mode 100644 index 0000000..22c01ec --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/pt_TL.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__.'/pt.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/qu.php b/vendor/nesbot/carbon/src/Carbon/Lang/qu.php new file mode 100644 index 0000000..65278cd --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/qu.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/es_UY.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM, YYYY HH:mm', + ], + 'first_day_of_week' => 0, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/qu_BO.php b/vendor/nesbot/carbon/src/Carbon/Lang/qu_BO.php new file mode 100644 index 0000000..d5db6bf --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/qu_BO.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/qu.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/qu_EC.php b/vendor/nesbot/carbon/src/Carbon/Lang/qu_EC.php new file mode 100644 index 0000000..d5db6bf --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/qu_EC.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/qu.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/quz.php b/vendor/nesbot/carbon/src/Carbon/Lang/quz.php new file mode 100644 index 0000000..1640c02 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/quz.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/quz_PE.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/quz_PE.php b/vendor/nesbot/carbon/src/Carbon/Lang/quz_PE.php new file mode 100644 index 0000000..d322918 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/quz_PE.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Sugar Labs // OLPC sugarlabs.org libc-alpha@sourceware.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['iniru', 'phiwriru', 'marsu', 'awril', 'mayu', 'huniyu', 'huliyu', 'agustu', 'siptiyimri', 'uktuwri', 'nuwiyimri', 'tisiyimri'], + 'months_short' => ['ini', 'phi', 'mar', 'awr', 'may', 'hun', 'hul', 'agu', 'sip', 'ukt', 'nuw', 'tis'], + 'weekdays' => ['tuminku', 'lunis', 'martis', 'miyirkulis', 'juywis', 'wiyirnis', 'sawatu'], + 'weekdays_short' => ['tum', 'lun', 'mar', 'miy', 'juy', 'wiy', 'saw'], + 'weekdays_min' => ['tum', 'lun', 'mar', 'miy', 'juy', 'wiy', 'saw'], + 'day_of_first_week_of_year' => 1, + + 'minute' => ':count uchuy', // less reliable + 'min' => ':count uchuy', // less reliable + 'a_minute' => ':count uchuy', // less reliable + + 'year' => ':count wata', + 'y' => ':count wata', + 'a_year' => ':count wata', + + 'month' => ':count killa', + 'm' => ':count killa', + 'a_month' => ':count killa', + + 'week' => ':count simana', + 'w' => ':count simana', + 'a_week' => ':count simana', + + 'day' => ':count pʼunchaw', + 'd' => ':count pʼunchaw', + 'a_day' => ':count pʼunchaw', + + 'hour' => ':count ura', + 'h' => ':count ura', + 'a_hour' => ':count ura', + + 'second' => ':count iskay ñiqin', + 's' => ':count iskay ñiqin', + 'a_second' => ':count iskay ñiqin', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/raj.php b/vendor/nesbot/carbon/src/Carbon/Lang/raj.php new file mode 100644 index 0000000..26138c9 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/raj.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/raj_IN.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/raj_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/raj_IN.php new file mode 100644 index 0000000..7b4589c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/raj_IN.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - meghrajsuthar03@gmail.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['जनवरी', 'फरवरी', 'मार्च', 'अप्रैल', 'मई', 'जून', 'जुलाई', 'अगस्त', 'सितंबर', 'अक्टूबर', 'नवंबर', 'दिसंबर'], + 'months_short' => ['जन', 'फर', 'मार्च', 'अप्रै', 'मई', 'जून', 'जुल', 'अग', 'सित', 'अक्टू', 'नव', 'दिस'], + 'weekdays' => ['रविवार', 'सोमवार', 'मंगल्लवार', 'बुधवार', 'बृहस्पतिवार', 'शुक्रवार', 'शनिवार'], + 'weekdays_short' => ['रवि', 'सोम', 'मंगल', 'बुध', 'बृहस्पति', 'शुक्र', 'शनि'], + 'weekdays_min' => ['रवि', 'सोम', 'मंगल', 'बुध', 'बृहस्पति', 'शुक्र', 'शनि'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['पूर्वाह्न', 'अपराह्न'], + + 'year' => ':count आंहू', // less reliable + 'y' => ':count आंहू', // less reliable + 'a_year' => ':count आंहू', // less reliable + + 'month' => ':count सूरज', // less reliable + 'm' => ':count सूरज', // less reliable + 'a_month' => ':count सूरज', // less reliable + + 'week' => ':count निवाज', // less reliable + 'w' => ':count निवाज', // less reliable + 'a_week' => ':count निवाज', // less reliable + + 'day' => ':count अेक', // less reliable + 'd' => ':count अेक', // less reliable + 'a_day' => ':count अेक', // less reliable + + 'hour' => ':count दुनियांण', // less reliable + 'h' => ':count दुनियांण', // less reliable + 'a_hour' => ':count दुनियांण', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/rm.php b/vendor/nesbot/carbon/src/Carbon/Lang/rm.php new file mode 100644 index 0000000..1843f45 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/rm.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Philippe Vaucher + * - tjku + * - Max Melentiev + * - Juanito Fatas + * - Tsutomu Kuroda + * - Akira Matsuda + * - Christopher Dell + * - Enrique Vidal + * - Simone Carletti + * - Aaron Patterson + * - Nicolás Hock Isaza + * - sebastian de castelberg + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'Do MMMM YYYY', + 'LLL' => 'Do MMMM, HH:mm [Uhr]', + 'LLLL' => 'dddd, Do MMMM YYYY, HH:mm [Uhr]', + ], + 'year' => ':count onn|:count onns', + 'month' => ':count mais', + 'week' => ':count emna|:count emnas', + 'day' => ':count di|:count dis', + 'hour' => ':count oura|:count ouras', + 'minute' => ':count minuta|:count minutas', + 'second' => ':count secunda|:count secundas', + 'weekdays' => ['dumengia', 'glindesdi', 'mardi', 'mesemna', 'gievgia', 'venderdi', 'sonda'], + 'weekdays_short' => ['du', 'gli', 'ma', 'me', 'gie', 've', 'so'], + 'weekdays_min' => ['du', 'gli', 'ma', 'me', 'gie', 've', 'so'], + 'months' => ['schaner', 'favrer', 'mars', 'avrigl', 'matg', 'zercladur', 'fanadur', 'avust', 'settember', 'october', 'november', 'december'], + 'months_short' => ['schan', 'favr', 'mars', 'avr', 'matg', 'zercl', 'fan', 'avust', 'sett', 'oct', 'nov', 'dec'], + 'meridiem' => ['avantmezdi', 'suentermezdi'], + 'list' => [', ', ' e '], + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/rn.php b/vendor/nesbot/carbon/src/Carbon/Lang/rn.php new file mode 100644 index 0000000..8ab958e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/rn.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['Z.MU.', 'Z.MW.'], + 'weekdays' => ['Ku w’indwi', 'Ku wa mbere', 'Ku wa kabiri', 'Ku wa gatatu', 'Ku wa kane', 'Ku wa gatanu', 'Ku wa gatandatu'], + 'weekdays_short' => ['cu.', 'mbe.', 'kab.', 'gtu.', 'kan.', 'gnu.', 'gnd.'], + 'weekdays_min' => ['cu.', 'mbe.', 'kab.', 'gtu.', 'kan.', 'gnu.', 'gnd.'], + 'months' => ['Nzero', 'Ruhuhuma', 'Ntwarante', 'Ndamukiza', 'Rusama', 'Ruheshi', 'Mukakaro', 'Nyandagaro', 'Nyakanga', 'Gitugutu', 'Munyonyo', 'Kigarama'], + 'months_short' => ['Mut.', 'Gas.', 'Wer.', 'Mat.', 'Gic.', 'Kam.', 'Nya.', 'Kan.', 'Nze.', 'Ukw.', 'Ugu.', 'Uku.'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + + 'year' => 'imyaka :count', + 'y' => 'imyaka :count', + 'a_year' => 'imyaka :count', + + 'month' => 'amezi :count', + 'm' => 'amezi :count', + 'a_month' => 'amezi :count', + + 'week' => 'indwi :count', + 'w' => 'indwi :count', + 'a_week' => 'indwi :count', + + 'day' => 'imisi :count', + 'd' => 'imisi :count', + 'a_day' => 'imisi :count', + + 'hour' => 'amasaha :count', + 'h' => 'amasaha :count', + 'a_hour' => 'amasaha :count', + + 'minute' => 'iminuta :count', + 'min' => 'iminuta :count', + 'a_minute' => 'iminuta :count', + + 'second' => 'inguvu :count', // less reliable + 's' => 'inguvu :count', // less reliable + 'a_second' => 'inguvu :count', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ro.php b/vendor/nesbot/carbon/src/Carbon/Lang/ro.php new file mode 100644 index 0000000..868a327 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ro.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - JD Isaacks + * - Cătălin Georgescu + * - Valentin Ivaşcu (oriceon) + */ +return [ + 'year' => ':count an|:count ani|:count ani', + 'a_year' => 'un an|:count ani|:count ani', + 'y' => ':count a.', + 'month' => ':count lună|:count luni|:count luni', + 'a_month' => 'o lună|:count luni|:count luni', + 'm' => ':count l.', + 'week' => ':count săptămână|:count săptămâni|:count săptămâni', + 'a_week' => 'o săptămână|:count săptămâni|:count săptămâni', + 'w' => ':count săp.', + 'day' => ':count zi|:count zile|:count zile', + 'a_day' => 'o zi|:count zile|:count zile', + 'd' => ':count z.', + 'hour' => ':count oră|:count ore|:count ore', + 'a_hour' => 'o oră|:count ore|:count ore', + 'h' => ':count o.', + 'minute' => ':count minut|:count minute|:count minute', + 'a_minute' => 'un minut|:count minute|:count minute', + 'min' => ':count m.', + 'second' => ':count secundă|:count secunde|:count secunde', + 'a_second' => 'câteva secunde|:count secunde|:count secunde', + 's' => ':count sec.', + 'ago' => ':time în urmă', + 'from_now' => 'peste :time', + 'after' => 'peste :time', + 'before' => 'acum :time', + 'diff_now' => 'acum', + 'diff_today' => 'azi', + 'diff_today_regexp' => 'azi(?:\\s+la)?', + 'diff_yesterday' => 'ieri', + 'diff_yesterday_regexp' => 'ieri(?:\\s+la)?', + 'diff_tomorrow' => 'mâine', + 'diff_tomorrow_regexp' => 'mâine(?:\\s+la)?', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY H:mm', + 'LLLL' => 'dddd, D MMMM YYYY H:mm', + ], + 'calendar' => [ + 'sameDay' => '[azi la] LT', + 'nextDay' => '[mâine la] LT', + 'nextWeek' => 'dddd [la] LT', + 'lastDay' => '[ieri la] LT', + 'lastWeek' => '[fosta] dddd [la] LT', + 'sameElse' => 'L', + ], + 'months' => ['ianuarie', 'februarie', 'martie', 'aprilie', 'mai', 'iunie', 'iulie', 'august', 'septembrie', 'octombrie', 'noiembrie', 'decembrie'], + 'months_short' => ['ian.', 'feb.', 'mar.', 'apr.', 'mai', 'iun.', 'iul.', 'aug.', 'sept.', 'oct.', 'nov.', 'dec.'], + 'weekdays' => ['duminică', 'luni', 'marți', 'miercuri', 'joi', 'vineri', 'sâmbătă'], + 'weekdays_short' => ['dum', 'lun', 'mar', 'mie', 'joi', 'vin', 'sâm'], + 'weekdays_min' => ['du', 'lu', 'ma', 'mi', 'jo', 'vi', 'sâ'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' și '], + 'meridiem' => ['a.m.', 'p.m.'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ro_MD.php b/vendor/nesbot/carbon/src/Carbon/Lang/ro_MD.php new file mode 100644 index 0000000..ad1d2fa --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ro_MD.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ro.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY, HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY, HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ro_RO.php b/vendor/nesbot/carbon/src/Carbon/Lang/ro_RO.php new file mode 100644 index 0000000..102afcd --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ro_RO.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__.'/ro.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/rof.php b/vendor/nesbot/carbon/src/Carbon/Lang/rof.php new file mode 100644 index 0000000..205fc26 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/rof.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['kang’ama', 'kingoto'], + 'weekdays' => ['Ijumapili', 'Ijumatatu', 'Ijumanne', 'Ijumatano', 'Alhamisi', 'Ijumaa', 'Ijumamosi'], + 'weekdays_short' => ['Ijp', 'Ijt', 'Ijn', 'Ijtn', 'Alh', 'Iju', 'Ijm'], + 'weekdays_min' => ['Ijp', 'Ijt', 'Ijn', 'Ijtn', 'Alh', 'Iju', 'Ijm'], + 'months' => ['Mweri wa kwanza', 'Mweri wa kaili', 'Mweri wa katatu', 'Mweri wa kaana', 'Mweri wa tanu', 'Mweri wa sita', 'Mweri wa saba', 'Mweri wa nane', 'Mweri wa tisa', 'Mweri wa ikumi', 'Mweri wa ikumi na moja', 'Mweri wa ikumi na mbili'], + 'months_short' => ['M1', 'M2', 'M3', 'M4', 'M5', 'M6', 'M7', 'M8', 'M9', 'M10', 'M11', 'M12'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ru.php b/vendor/nesbot/carbon/src/Carbon/Lang/ru.php new file mode 100644 index 0000000..673b043 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ru.php @@ -0,0 +1,191 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Bari Badamshin + * - Jørn Ølmheim + * - François B + * - Tim Fish + * - Коренберг Марк (imac) + * - Serhan Apaydın + * - RomeroMsk + * - vsn4ik + * - JD Isaacks + * - Bari Badamshin + * - Jørn Ølmheim + * - François B + * - Коренберг Марк (imac) + * - Serhan Apaydın + * - RomeroMsk + * - vsn4ik + * - JD Isaacks + * - Fellzo + * - andrey-helldar + * - Pavel Skripkin (psxx) + * - AlexWalkerson + * - Vladislav UnsealedOne + * - dima-bzz + */ + +use Carbon\CarbonInterface; + +$transformDiff = function ($input) { + return strtr($input, [ + 'неделя' => 'неделю', + 'секунда' => 'секунду', + 'минута' => 'минуту', + ]); +}; + +return [ + 'year' => ':count год|:count года|:count лет', + 'y' => ':count г.|:count г.|:count л.', + 'a_year' => '{1}год|:count год|:count года|:count лет', + 'month' => ':count месяц|:count месяца|:count месяцев', + 'm' => ':count мес.', + 'a_month' => '{1}месяц|:count месяц|:count месяца|:count месяцев', + 'week' => ':count неделя|:count недели|:count недель', + 'w' => ':count нед.', + 'a_week' => '{1}неделя|:count неделю|:count недели|:count недель', + 'day' => ':count день|:count дня|:count дней', + 'd' => ':count д.', + 'a_day' => '{1}день|:count день|:count дня|:count дней', + 'hour' => ':count час|:count часа|:count часов', + 'h' => ':count ч.', + 'a_hour' => '{1}час|:count час|:count часа|:count часов', + 'minute' => ':count минута|:count минуты|:count минут', + 'min' => ':count мин.', + 'a_minute' => '{1}минута|:count минута|:count минуты|:count минут', + 'second' => ':count секунда|:count секунды|:count секунд', + 's' => ':count сек.', + 'a_second' => '{1}несколько секунд|:count секунду|:count секунды|:count секунд', + 'ago' => function ($time) use ($transformDiff) { + return $transformDiff($time).' назад'; + }, + 'from_now' => function ($time) use ($transformDiff) { + return 'через '.$transformDiff($time); + }, + 'after' => function ($time) use ($transformDiff) { + return $transformDiff($time).' после'; + }, + 'before' => function ($time) use ($transformDiff) { + return $transformDiff($time).' до'; + }, + 'diff_now' => 'только что', + 'diff_today' => 'Сегодня,', + 'diff_today_regexp' => 'Сегодня,?(?:\\s+в)?', + 'diff_yesterday' => 'вчера', + 'diff_yesterday_regexp' => 'Вчера,?(?:\\s+в)?', + 'diff_tomorrow' => 'завтра', + 'diff_tomorrow_regexp' => 'Завтра,?(?:\\s+в)?', + 'diff_before_yesterday' => 'позавчера', + 'diff_after_tomorrow' => 'послезавтра', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMMM YYYY г.', + 'LLL' => 'D MMMM YYYY г., H:mm', + 'LLLL' => 'dddd, D MMMM YYYY г., H:mm', + ], + 'calendar' => [ + 'sameDay' => '[Сегодня, в] LT', + 'nextDay' => '[Завтра, в] LT', + 'nextWeek' => function (CarbonInterface $current, CarbonInterface $other) { + if ($current->week !== $other->week) { + switch ($current->dayOfWeek) { + case 0: + return '[В следующее] dddd, [в] LT'; + case 1: + case 2: + case 4: + return '[В следующий] dddd, [в] LT'; + case 3: + case 5: + case 6: + return '[В следующую] dddd, [в] LT'; + } + } + + if ($current->dayOfWeek === 2) { + return '[Во] dddd, [в] LT'; + } + + return '[В] dddd, [в] LT'; + }, + 'lastDay' => '[Вчера, в] LT', + 'lastWeek' => function (CarbonInterface $current, CarbonInterface $other) { + if ($current->week !== $other->week) { + switch ($current->dayOfWeek) { + case 0: + return '[В прошлое] dddd, [в] LT'; + case 1: + case 2: + case 4: + return '[В прошлый] dddd, [в] LT'; + case 3: + case 5: + case 6: + return '[В прошлую] dddd, [в] LT'; + } + } + + if ($current->dayOfWeek === 2) { + return '[Во] dddd, [в] LT'; + } + + return '[В] dddd, [в] LT'; + }, + 'sameElse' => 'L', + ], + 'ordinal' => function ($number, $period) { + switch ($period) { + case 'M': + case 'd': + case 'DDD': + return $number.'-й'; + case 'D': + return $number.'-го'; + case 'w': + case 'W': + return $number.'-я'; + default: + return $number; + } + }, + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'ночи'; + } + if ($hour < 12) { + return 'утра'; + } + if ($hour < 17) { + return 'дня'; + } + + return 'вечера'; + }, + 'months' => ['января', 'февраля', 'марта', 'апреля', 'мая', 'июня', 'июля', 'августа', 'сентября', 'октября', 'ноября', 'декабря'], + 'months_standalone' => ['январь', 'февраль', 'март', 'апрель', 'май', 'июнь', 'июль', 'август', 'сентябрь', 'октябрь', 'ноябрь', 'декабрь'], + 'months_short' => ['янв', 'фев', 'мар', 'апр', 'мая', 'июн', 'июл', 'авг', 'сен', 'окт', 'ноя', 'дек'], + 'months_short_standalone' => ['янв', 'фев', 'мар', 'апр', 'май', 'июн', 'июл', 'авг', 'сен', 'окт', 'ноя', 'дек'], + 'months_regexp' => '/(DD?o?\.?(\[[^\[\]]*\]|\s)+MMMM?|L{2,4}|l{2,4})/', + 'weekdays' => ['воскресенье', 'понедельник', 'вторник', 'среду', 'четверг', 'пятницу', 'субботу'], + 'weekdays_standalone' => ['воскресенье', 'понедельник', 'вторник', 'среда', 'четверг', 'пятница', 'суббота'], + 'weekdays_short' => ['вск', 'пнд', 'втр', 'срд', 'чтв', 'птн', 'сбт'], + 'weekdays_min' => ['вс', 'пн', 'вт', 'ср', 'чт', 'пт', 'сб'], + 'weekdays_regexp' => '/\[\s*(В|в)\s*((?:прошлую|следующую|эту)\s*)?\]\s*dddd/', + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' и '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ru_BY.php b/vendor/nesbot/carbon/src/Carbon/Lang/ru_BY.php new file mode 100644 index 0000000..8ca7df3 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ru_BY.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__.'/ru.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ru_KG.php b/vendor/nesbot/carbon/src/Carbon/Lang/ru_KG.php new file mode 100644 index 0000000..8ca7df3 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ru_KG.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__.'/ru.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ru_KZ.php b/vendor/nesbot/carbon/src/Carbon/Lang/ru_KZ.php new file mode 100644 index 0000000..8ca7df3 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ru_KZ.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__.'/ru.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ru_MD.php b/vendor/nesbot/carbon/src/Carbon/Lang/ru_MD.php new file mode 100644 index 0000000..8ca7df3 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ru_MD.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__.'/ru.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ru_RU.php b/vendor/nesbot/carbon/src/Carbon/Lang/ru_RU.php new file mode 100644 index 0000000..8ca7df3 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ru_RU.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__.'/ru.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ru_UA.php b/vendor/nesbot/carbon/src/Carbon/Lang/ru_UA.php new file mode 100644 index 0000000..db958d6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ru_UA.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RFC 2319 bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/ru.php', [ + 'weekdays' => ['воскресенье', 'понедельник', 'вторник', 'среда', 'четверг', 'пятница', 'суббота'], + 'weekdays_short' => ['вск', 'пнд', 'вто', 'срд', 'чтв', 'птн', 'суб'], + 'weekdays_min' => ['вс', 'пн', 'вт', 'ср', 'чт', 'пт', 'су'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/rw.php b/vendor/nesbot/carbon/src/Carbon/Lang/rw.php new file mode 100644 index 0000000..bc4a347 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/rw.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/rw_RW.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/rw_RW.php b/vendor/nesbot/carbon/src/Carbon/Lang/rw_RW.php new file mode 100644 index 0000000..9b3e068 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/rw_RW.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Rwanda Steve Murphy murf@e-tools.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['Mutarama', 'Gashyantare', 'Werurwe', 'Mata', 'Gicuransi', 'Kamena', 'Nyakanga', 'Kanama', 'Nzeli', 'Ukwakira', 'Ugushyingo', 'Ukuboza'], + 'months_short' => ['Mut', 'Gas', 'Wer', 'Mat', 'Gic', 'Kam', 'Nya', 'Kan', 'Nze', 'Ukw', 'Ugu', 'Uku'], + 'weekdays' => ['Ku cyumweru', 'Kuwa mbere', 'Kuwa kabiri', 'Kuwa gatatu', 'Kuwa kane', 'Kuwa gatanu', 'Kuwa gatandatu'], + 'weekdays_short' => ['Mwe', 'Mbe', 'Kab', 'Gtu', 'Kan', 'Gnu', 'Gnd'], + 'weekdays_min' => ['Mwe', 'Mbe', 'Kab', 'Gtu', 'Kan', 'Gnu', 'Gnd'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + + 'second' => ':count vuna', // less reliable + 's' => ':count vuna', // less reliable + 'a_second' => ':count vuna', // less reliable + + 'year' => 'aka :count', + 'y' => 'aka :count', + 'a_year' => 'aka :count', + + 'month' => 'ezi :count', + 'm' => 'ezi :count', + 'a_month' => 'ezi :count', + + 'week' => ':count icyumweru', + 'w' => ':count icyumweru', + 'a_week' => ':count icyumweru', + + 'day' => ':count nsi', + 'd' => ':count nsi', + 'a_day' => ':count nsi', + + 'hour' => 'saha :count', + 'h' => 'saha :count', + 'a_hour' => 'saha :count', + + 'minute' => ':count -nzinya', + 'min' => ':count -nzinya', + 'a_minute' => ':count -nzinya', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/rwk.php b/vendor/nesbot/carbon/src/Carbon/Lang/rwk.php new file mode 100644 index 0000000..ed92e8e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/rwk.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['utuko', 'kyiukonyi'], + 'weekdays' => ['Jumapilyi', 'Jumatatuu', 'Jumanne', 'Jumatanu', 'Alhamisi', 'Ijumaa', 'Jumamosi'], + 'weekdays_short' => ['Jpi', 'Jtt', 'Jnn', 'Jtn', 'Alh', 'Iju', 'Jmo'], + 'weekdays_min' => ['Jpi', 'Jtt', 'Jnn', 'Jtn', 'Alh', 'Iju', 'Jmo'], + 'months' => ['Januari', 'Februari', 'Machi', 'Aprilyi', 'Mei', 'Junyi', 'Julyai', 'Agusti', 'Septemba', 'Oktoba', 'Novemba', 'Desemba'], + 'months_short' => ['Jan', 'Feb', 'Mac', 'Apr', 'Mei', 'Jun', 'Jul', 'Ago', 'Sep', 'Okt', 'Nov', 'Des'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sa.php b/vendor/nesbot/carbon/src/Carbon/Lang/sa.php new file mode 100644 index 0000000..1357c03 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sa.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/sa_IN.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sa_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/sa_IN.php new file mode 100644 index 0000000..cfda9a6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sa_IN.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - The Debian project Christian Perrier bubulle@debian.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D-MM-YY', + ], + 'months' => ['जनवरी', 'फ़रवरी', 'मार्च', 'अप्रेल', 'मई', 'जून', 'जुलाई', 'अगस्त', 'सितम्बर', 'अक्टूबर', 'नवम्बर', 'दिसम्बर'], + 'months_short' => ['जनवरी', 'फ़रवरी', 'मार्च', 'अप्रेल', 'मई', 'जून', 'जुलाई', 'अगस्त', 'सितम्बर', 'अक्टूबर', 'नवम्बर', 'दिसम्बर'], + 'weekdays' => ['रविवासर:', 'सोमवासर:', 'मंगलवासर:', 'बुधवासर:', 'बृहस्पतिवासरः', 'शुक्रवासर', 'शनिवासर:'], + 'weekdays_short' => ['रविः', 'सोम:', 'मंगल:', 'बुध:', 'बृहस्पतिः', 'शुक्र', 'शनि:'], + 'weekdays_min' => ['रविः', 'सोम:', 'मंगल:', 'बुध:', 'बृहस्पतिः', 'शुक्र', 'शनि:'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['पूर्वाह्न', 'अपराह्न'], + + 'minute' => ':count होरा', // less reliable + 'min' => ':count होरा', // less reliable + 'a_minute' => ':count होरा', // less reliable + + 'year' => ':count वर्ष', + 'y' => ':count वर्ष', + 'a_year' => ':count वर्ष', + + 'month' => ':count मास', + 'm' => ':count मास', + 'a_month' => ':count मास', + + 'week' => ':count सप्ताहः saptahaĥ', + 'w' => ':count सप्ताहः saptahaĥ', + 'a_week' => ':count सप्ताहः saptahaĥ', + + 'day' => ':count दिन', + 'd' => ':count दिन', + 'a_day' => ':count दिन', + + 'hour' => ':count घण्टा', + 'h' => ':count घण्टा', + 'a_hour' => ':count घण्टा', + + 'second' => ':count द्वितीयः', + 's' => ':count द्वितीयः', + 'a_second' => ':count द्वितीयः', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sah.php b/vendor/nesbot/carbon/src/Carbon/Lang/sah.php new file mode 100644 index 0000000..b828824 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sah.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/sah_RU.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sah_RU.php b/vendor/nesbot/carbon/src/Carbon/Lang/sah_RU.php new file mode 100644 index 0000000..94cc0cb --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sah_RU.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Valery Timiriliyev Valery Timiriliyev timiriliyev@gmail.com + */ +return array_replace_recursive(require __DIR__.'/ru.php', [ + 'formats' => [ + 'L' => 'YYYY.MM.DD', + ], + 'months' => ['тохсунньу', 'олунньу', 'кулун тутар', 'муус устар', 'ыам ыйын', 'бэс ыйын', 'от ыйын', 'атырдьах ыйын', 'балаҕан ыйын', 'алтынньы', 'сэтинньи', 'ахсынньы'], + 'months_short' => ['тохс', 'олун', 'кул', 'муус', 'ыам', 'бэс', 'от', 'атыр', 'бал', 'алт', 'сэт', 'ахс'], + 'weekdays' => ['баскыһыанньа', 'бэнидиэнньик', 'оптуорунньук', 'сэрэдэ', 'чэппиэр', 'бээтинсэ', 'субуота'], + 'weekdays_short' => ['бс', 'бн', 'оп', 'ср', 'чп', 'бт', 'сб'], + 'weekdays_min' => ['бс', 'бн', 'оп', 'ср', 'чп', 'бт', 'сб'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/saq.php b/vendor/nesbot/carbon/src/Carbon/Lang/saq.php new file mode 100644 index 0000000..ff8bf60 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/saq.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['Tesiran', 'Teipa'], + 'weekdays' => ['Mderot ee are', 'Mderot ee kuni', 'Mderot ee ong’wan', 'Mderot ee inet', 'Mderot ee ile', 'Mderot ee sapa', 'Mderot ee kwe'], + 'weekdays_short' => ['Are', 'Kun', 'Ong', 'Ine', 'Ile', 'Sap', 'Kwe'], + 'weekdays_min' => ['Are', 'Kun', 'Ong', 'Ine', 'Ile', 'Sap', 'Kwe'], + 'months' => ['Lapa le obo', 'Lapa le waare', 'Lapa le okuni', 'Lapa le ong’wan', 'Lapa le imet', 'Lapa le ile', 'Lapa le sapa', 'Lapa le isiet', 'Lapa le saal', 'Lapa le tomon', 'Lapa le tomon obo', 'Lapa le tomon waare'], + 'months_short' => ['Obo', 'Waa', 'Oku', 'Ong', 'Ime', 'Ile', 'Sap', 'Isi', 'Saa', 'Tom', 'Tob', 'Tow'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sat.php b/vendor/nesbot/carbon/src/Carbon/Lang/sat.php new file mode 100644 index 0000000..c9914c6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sat.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/sat_IN.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sat_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/sat_IN.php new file mode 100644 index 0000000..632b1af --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sat_IN.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Red Hat Pune libc-alpha@sourceware.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['जनवरी', 'फरवरी', 'मार्च', 'अप्रेल', 'मई', 'जुन', 'जुलाई', 'अगस्त', 'सितम्बर', 'अखथबर', 'नवम्बर', 'दिसम्बर'], + 'months_short' => ['जनवरी', 'फरवरी', 'मार्च', 'अप्रेल', 'मई', 'जुन', 'जुलाई', 'अगस्त', 'सितम्बर', 'अखथबर', 'नवम्बर', 'दिसम्बर'], + 'weekdays' => ['सिंगेमाँहाँ', 'ओतेमाँहाँ', 'बालेमाँहाँ', 'सागुनमाँहाँ', 'सारदीमाँहाँ', 'जारुममाँहाँ', 'ञुहुममाँहाँ'], + 'weekdays_short' => ['सिंगे', 'ओते', 'बाले', 'सागुन', 'सारदी', 'जारुम', 'ञुहुम'], + 'weekdays_min' => ['सिंगे', 'ओते', 'बाले', 'सागुन', 'सारदी', 'जारुम', 'ञुहुम'], + 'day_of_first_week_of_year' => 1, + + 'month' => ':count ńindạ cando', // less reliable + 'm' => ':count ńindạ cando', // less reliable + 'a_month' => ':count ńindạ cando', // less reliable + + 'week' => ':count mãhã', // less reliable + 'w' => ':count mãhã', // less reliable + 'a_week' => ':count mãhã', // less reliable + + 'hour' => ':count ᱥᱳᱱᱚ', // less reliable + 'h' => ':count ᱥᱳᱱᱚ', // less reliable + 'a_hour' => ':count ᱥᱳᱱᱚ', // less reliable + + 'minute' => ':count ᱯᱤᱞᱪᱩ', // less reliable + 'min' => ':count ᱯᱤᱞᱪᱩ', // less reliable + 'a_minute' => ':count ᱯᱤᱞᱪᱩ', // less reliable + + 'second' => ':count ar', // less reliable + 's' => ':count ar', // less reliable + 'a_second' => ':count ar', // less reliable + + 'year' => ':count ne̲s', + 'y' => ':count ne̲s', + 'a_year' => ':count ne̲s', + + 'day' => ':count ᱫᱤᱱ', + 'd' => ':count ᱫᱤᱱ', + 'a_day' => ':count ᱫᱤᱱ', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sbp.php b/vendor/nesbot/carbon/src/Carbon/Lang/sbp.php new file mode 100644 index 0000000..e29ca37 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sbp.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['Lwamilawu', 'Pashamihe'], + 'weekdays' => ['Mulungu', 'Jumatatu', 'Jumanne', 'Jumatano', 'Alahamisi', 'Ijumaa', 'Jumamosi'], + 'weekdays_short' => ['Mul', 'Jtt', 'Jnn', 'Jtn', 'Alh', 'Iju', 'Jmo'], + 'weekdays_min' => ['Mul', 'Jtt', 'Jnn', 'Jtn', 'Alh', 'Iju', 'Jmo'], + 'months' => ['Mupalangulwa', 'Mwitope', 'Mushende', 'Munyi', 'Mushende Magali', 'Mujimbi', 'Mushipepo', 'Mupuguto', 'Munyense', 'Mokhu', 'Musongandembwe', 'Muhaano'], + 'months_short' => ['Mup', 'Mwi', 'Msh', 'Mun', 'Mag', 'Muj', 'Msp', 'Mpg', 'Mye', 'Mok', 'Mus', 'Muh'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sc.php b/vendor/nesbot/carbon/src/Carbon/Lang/sc.php new file mode 100644 index 0000000..7178cf4 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sc.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/sc_IT.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sc_IT.php b/vendor/nesbot/carbon/src/Carbon/Lang/sc_IT.php new file mode 100644 index 0000000..5d1e4ce --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sc_IT.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Sardinian Translators Team Massimeddu Cireddu massimeddu@gmail.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD. MM. YY', + ], + 'months' => ['Ghennàrgiu', 'Freàrgiu', 'Martzu', 'Abrile', 'Maju', 'Làmpadas', 'Argiolas//Trìulas', 'Austu', 'Cabudanni', 'Santugaine//Ladàmine', 'Onniasantu//Santandria', 'Nadale//Idas'], + 'months_short' => ['Ghe', 'Fre', 'Mar', 'Abr', 'Maj', 'Làm', 'Arg', 'Aus', 'Cab', 'Lad', 'Onn', 'Nad'], + 'weekdays' => ['Domìnigu', 'Lunis', 'Martis', 'Mèrcuris', 'Giòbia', 'Chenàbura', 'Sàbadu'], + 'weekdays_short' => ['Dom', 'Lun', 'Mar', 'Mèr', 'Giò', 'Che', 'Sàb'], + 'weekdays_min' => ['Dom', 'Lun', 'Mar', 'Mèr', 'Giò', 'Che', 'Sàb'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'minute' => ':count mementu', // less reliable + 'min' => ':count mementu', // less reliable + 'a_minute' => ':count mementu', // less reliable + + 'year' => ':count annu', + 'y' => ':count annu', + 'a_year' => ':count annu', + + 'month' => ':count mese', + 'm' => ':count mese', + 'a_month' => ':count mese', + + 'week' => ':count chida', + 'w' => ':count chida', + 'a_week' => ':count chida', + + 'day' => ':count dí', + 'd' => ':count dí', + 'a_day' => ':count dí', + + 'hour' => ':count ora', + 'h' => ':count ora', + 'a_hour' => ':count ora', + + 'second' => ':count secundu', + 's' => ':count secundu', + 'a_second' => ':count secundu', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sd.php b/vendor/nesbot/carbon/src/Carbon/Lang/sd.php new file mode 100644 index 0000000..0022c5a --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sd.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +$months = [ + 'جنوري', + 'فيبروري', + 'مارچ', + 'اپريل', + 'مئي', + 'جون', + 'جولاءِ', + 'آگسٽ', + 'سيپٽمبر', + 'آڪٽوبر', + 'نومبر', + 'ڊسمبر', +]; + +$weekdays = [ + 'آچر', + 'سومر', + 'اڱارو', + 'اربع', + 'خميس', + 'جمع', + 'ڇنڇر', +]; + +/* + * Authors: + * - Narain Sagar + * - Sawood Alam + * - Narain Sagar + */ +return [ + 'year' => '{1}'.'هڪ سال'.'|:count '.'سال', + 'month' => '{1}'.'هڪ مهينو'.'|:count '.'مهينا', + 'week' => '{1}'.'ھڪ ھفتو'.'|:count '.'هفتا', + 'day' => '{1}'.'هڪ ڏينهن'.'|:count '.'ڏينهن', + 'hour' => '{1}'.'هڪ ڪلاڪ'.'|:count '.'ڪلاڪ', + 'minute' => '{1}'.'هڪ منٽ'.'|:count '.'منٽ', + 'second' => '{1}'.'چند سيڪنڊ'.'|:count '.'سيڪنڊ', + 'ago' => ':time اڳ', + 'from_now' => ':time پوء', + 'diff_yesterday' => 'ڪالهه', + 'diff_today' => 'اڄ', + 'diff_tomorrow' => 'سڀاڻي', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd، D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[اڄ] LT', + 'nextDay' => '[سڀاڻي] LT', + 'nextWeek' => 'dddd [اڳين هفتي تي] LT', + 'lastDay' => '[ڪالهه] LT', + 'lastWeek' => '[گزريل هفتي] dddd [تي] LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['صبح', 'شام'], + 'months' => $months, + 'months_short' => $months, + 'weekdays' => $weekdays, + 'weekdays_short' => $weekdays, + 'weekdays_min' => $weekdays, + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => ['، ', ' ۽ '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sd_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/sd_IN.php new file mode 100644 index 0000000..de1dad0 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sd_IN.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Red Hat, Pune bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/sd.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['جنوري', 'فبروري', 'مارچ', 'اپريل', 'مي', 'جون', 'جولاءِ', 'آگسٽ', 'سيپٽيمبر', 'آڪٽوبر', 'نومبر', 'ڊسمبر'], + 'months_short' => ['جنوري', 'فبروري', 'مارچ', 'اپريل', 'مي', 'جون', 'جولاءِ', 'آگسٽ', 'سيپٽيمبر', 'آڪٽوبر', 'نومبر', 'ڊسمبر'], + 'weekdays' => ['آرتوارُ', 'سومرُ', 'منگلُ', 'ٻُڌرُ', 'وسپت', 'جُمو', 'ڇنڇر'], + 'weekdays_short' => ['آرتوارُ', 'سومرُ', 'منگلُ', 'ٻُڌرُ', 'وسپت', 'جُمو', 'ڇنڇر'], + 'weekdays_min' => ['آرتوارُ', 'سومرُ', 'منگلُ', 'ٻُڌرُ', 'وسپت', 'جُمو', 'ڇنڇر'], + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sd_IN@devanagari.php b/vendor/nesbot/carbon/src/Carbon/Lang/sd_IN@devanagari.php new file mode 100644 index 0000000..061fcc1 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sd_IN@devanagari.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Red Hat, Pune bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/sd.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['जनवरी', 'फबरवरी', 'मार्चि', 'अप्रेल', 'मे', 'जूनि', 'जूलाइ', 'आगस्टु', 'सेप्टेंबरू', 'आक्टूबरू', 'नवंबरू', 'ॾिसंबरू'], + 'months_short' => ['जनवरी', 'फबरवरी', 'मार्चि', 'अप्रेल', 'मे', 'जूनि', 'जूलाइ', 'आगस्टु', 'सेप्टेंबरू', 'आक्टूबरू', 'नवंबरू', 'ॾिसंबरू'], + 'weekdays' => ['आर्तवारू', 'सूमरू', 'मंगलू', 'ॿुधरू', 'विस्पति', 'जुमो', 'छंछस'], + 'weekdays_short' => ['आर्तवारू', 'सूमरू', 'मंगलू', 'ॿुधरू', 'विस्पति', 'जुमो', 'छंछस'], + 'weekdays_min' => ['आर्तवारू', 'सूमरू', 'मंगलू', 'ॿुधरू', 'विस्पति', 'जुमो', 'छंछस'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['म.पू.', 'म.नं.'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/se.php b/vendor/nesbot/carbon/src/Carbon/Lang/se.php new file mode 100644 index 0000000..7c4b92a --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/se.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - Karamell + */ +return [ + 'year' => '{1}:count jahki|:count jagit', + 'a_year' => '{1}okta jahki|:count jagit', + 'y' => ':count j.', + 'month' => '{1}:count mánnu|:count mánut', + 'a_month' => '{1}okta mánnu|:count mánut', + 'm' => ':count mán.', + 'week' => '{1}:count vahkku|:count vahkku', + 'a_week' => '{1}okta vahkku|:count vahkku', + 'w' => ':count v.', + 'day' => '{1}:count beaivi|:count beaivvit', + 'a_day' => '{1}okta beaivi|:count beaivvit', + 'd' => ':count b.', + 'hour' => '{1}:count diimmu|:count diimmut', + 'a_hour' => '{1}okta diimmu|:count diimmut', + 'h' => ':count d.', + 'minute' => '{1}:count minuhta|:count minuhtat', + 'a_minute' => '{1}okta minuhta|:count minuhtat', + 'min' => ':count min.', + 'second' => '{1}:count sekunddat|:count sekunddat', + 'a_second' => '{1}moadde sekunddat|:count sekunddat', + 's' => ':count s.', + 'ago' => 'maŋit :time', + 'from_now' => ':time geažes', + 'diff_yesterday' => 'ikte', + 'diff_yesterday_regexp' => 'ikte(?:\\s+ti)?', + 'diff_today' => 'otne', + 'diff_today_regexp' => 'otne(?:\\s+ti)?', + 'diff_tomorrow' => 'ihttin', + 'diff_tomorrow_regexp' => 'ihttin(?:\\s+ti)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'MMMM D. [b.] YYYY', + 'LLL' => 'MMMM D. [b.] YYYY [ti.] HH:mm', + 'LLLL' => 'dddd, MMMM D. [b.] YYYY [ti.] HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[otne ti] LT', + 'nextDay' => '[ihttin ti] LT', + 'nextWeek' => 'dddd [ti] LT', + 'lastDay' => '[ikte ti] LT', + 'lastWeek' => '[ovddit] dddd [ti] LT', + 'sameElse' => 'L', + ], + 'ordinal' => ':number.', + 'months' => ['ođđajagemánnu', 'guovvamánnu', 'njukčamánnu', 'cuoŋománnu', 'miessemánnu', 'geassemánnu', 'suoidnemánnu', 'borgemánnu', 'čakčamánnu', 'golggotmánnu', 'skábmamánnu', 'juovlamánnu'], + 'months_short' => ['ođđj', 'guov', 'njuk', 'cuo', 'mies', 'geas', 'suoi', 'borg', 'čakč', 'golg', 'skáb', 'juov'], + 'weekdays' => ['sotnabeaivi', 'vuossárga', 'maŋŋebárga', 'gaskavahkku', 'duorastat', 'bearjadat', 'lávvardat'], + 'weekdays_short' => ['sotn', 'vuos', 'maŋ', 'gask', 'duor', 'bear', 'láv'], + 'weekdays_min' => ['s', 'v', 'm', 'g', 'd', 'b', 'L'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' ja '], + 'meridiem' => ['i.b.', 'e.b.'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/se_FI.php b/vendor/nesbot/carbon/src/Carbon/Lang/se_FI.php new file mode 100644 index 0000000..cf01805 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/se_FI.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/se.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'months' => ['ođđajagemánnu', 'guovvamánnu', 'njukčamánnu', 'cuoŋománnu', 'miessemánnu', 'geassemánnu', 'suoidnemánnu', 'borgemánnu', 'čakčamánnu', 'golggotmánnu', 'skábmamánnu', 'juovlamánnu'], + 'months_short' => ['ođđj', 'guov', 'njuk', 'cuoŋ', 'mies', 'geas', 'suoi', 'borg', 'čakč', 'golg', 'skáb', 'juov'], + 'weekdays' => ['sotnabeaivi', 'mánnodat', 'disdat', 'gaskavahkku', 'duorastat', 'bearjadat', 'lávvordat'], + 'weekdays_short' => ['so', 'má', 'di', 'ga', 'du', 'be', 'lá'], + 'weekdays_min' => ['so', 'má', 'di', 'ga', 'du', 'be', 'lá'], + 'meridiem' => ['i', 'e'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/se_NO.php b/vendor/nesbot/carbon/src/Carbon/Lang/se_NO.php new file mode 100644 index 0000000..177c7e9 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/se_NO.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__.'/se.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/se_SE.php b/vendor/nesbot/carbon/src/Carbon/Lang/se_SE.php new file mode 100644 index 0000000..177c7e9 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/se_SE.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__.'/se.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/seh.php b/vendor/nesbot/carbon/src/Carbon/Lang/seh.php new file mode 100644 index 0000000..babf9af --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/seh.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'weekdays' => ['Dimingu', 'Chiposi', 'Chipiri', 'Chitatu', 'Chinai', 'Chishanu', 'Sabudu'], + 'weekdays_short' => ['Dim', 'Pos', 'Pir', 'Tat', 'Nai', 'Sha', 'Sab'], + 'weekdays_min' => ['Dim', 'Pos', 'Pir', 'Tat', 'Nai', 'Sha', 'Sab'], + 'months' => ['Janeiro', 'Fevreiro', 'Marco', 'Abril', 'Maio', 'Junho', 'Julho', 'Augusto', 'Setembro', 'Otubro', 'Novembro', 'Decembro'], + 'months_short' => ['Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Aug', 'Set', 'Otu', 'Nov', 'Dec'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'd [de] MMM [de] YYYY', + 'LLL' => 'd [de] MMMM [de] YYYY HH:mm', + 'LLLL' => 'dddd, d [de] MMMM [de] YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ses.php b/vendor/nesbot/carbon/src/Carbon/Lang/ses.php new file mode 100644 index 0000000..e1099e6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ses.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['Adduha', 'Aluula'], + 'weekdays' => ['Alhadi', 'Atinni', 'Atalaata', 'Alarba', 'Alhamiisa', 'Alzuma', 'Asibti'], + 'weekdays_short' => ['Alh', 'Ati', 'Ata', 'Ala', 'Alm', 'Alz', 'Asi'], + 'weekdays_min' => ['Alh', 'Ati', 'Ata', 'Ala', 'Alm', 'Alz', 'Asi'], + 'months' => ['Žanwiye', 'Feewiriye', 'Marsi', 'Awiril', 'Me', 'Žuweŋ', 'Žuyye', 'Ut', 'Sektanbur', 'Oktoobur', 'Noowanbur', 'Deesanbur'], + 'months_short' => ['Žan', 'Fee', 'Mar', 'Awi', 'Me', 'Žuw', 'Žuy', 'Ut', 'Sek', 'Okt', 'Noo', 'Dee'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + + 'month' => ':count alaada', // less reliable + 'm' => ':count alaada', // less reliable + 'a_month' => ':count alaada', // less reliable + + 'hour' => ':count ɲaajin', // less reliable + 'h' => ':count ɲaajin', // less reliable + 'a_hour' => ':count ɲaajin', // less reliable + + 'minute' => ':count zarbu', // less reliable + 'min' => ':count zarbu', // less reliable + 'a_minute' => ':count zarbu', // less reliable + + 'year' => ':count jiiri', + 'y' => ':count jiiri', + 'a_year' => ':count jiiri', + + 'week' => ':count jirbiiyye', + 'w' => ':count jirbiiyye', + 'a_week' => ':count jirbiiyye', + + 'day' => ':count zaari', + 'd' => ':count zaari', + 'a_day' => ':count zaari', + + 'second' => ':count ihinkante', + 's' => ':count ihinkante', + 'a_second' => ':count ihinkante', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sg.php b/vendor/nesbot/carbon/src/Carbon/Lang/sg.php new file mode 100644 index 0000000..9264e89 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sg.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_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['ND', 'LK'], + 'weekdays' => ['Bikua-ôko', 'Bïkua-ûse', 'Bïkua-ptâ', 'Bïkua-usïö', 'Bïkua-okü', 'Lâpôsö', 'Lâyenga'], + 'weekdays_short' => ['Bk1', 'Bk2', 'Bk3', 'Bk4', 'Bk5', 'Lâp', 'Lây'], + 'weekdays_min' => ['Bk1', 'Bk2', 'Bk3', 'Bk4', 'Bk5', 'Lâp', 'Lây'], + 'months' => ['Nyenye', 'Fulundïgi', 'Mbängü', 'Ngubùe', 'Bêläwü', 'Föndo', 'Lengua', 'Kükürü', 'Mvuka', 'Ngberere', 'Nabändüru', 'Kakauka'], + 'months_short' => ['Nye', 'Ful', 'Mbä', 'Ngu', 'Bêl', 'Fön', 'Len', 'Kük', 'Mvu', 'Ngb', 'Nab', 'Kak'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM, YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + + 'year' => ':count dā', // less reliable + 'y' => ':count dā', // less reliable + 'a_year' => ':count dā', // less reliable + + 'week' => ':count bïkua-okü', // less reliable + 'w' => ':count bïkua-okü', // less reliable + 'a_week' => ':count bïkua-okü', // less reliable + + 'day' => ':count ziggawâ', // less reliable + 'd' => ':count ziggawâ', // less reliable + 'a_day' => ':count ziggawâ', // less reliable + + 'hour' => ':count yângâködörö', // less reliable + 'h' => ':count yângâködörö', // less reliable + 'a_hour' => ':count yângâködörö', // less reliable + + 'second' => ':count bïkua-ôko', // less reliable + 's' => ':count bïkua-ôko', // less reliable + 'a_second' => ':count bïkua-ôko', // less reliable + + 'month' => ':count Nze tî ngu', + 'm' => ':count Nze tî ngu', + 'a_month' => ':count Nze tî ngu', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sgs.php b/vendor/nesbot/carbon/src/Carbon/Lang/sgs.php new file mode 100644 index 0000000..864b989 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sgs.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/sgs_LT.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sgs_LT.php b/vendor/nesbot/carbon/src/Carbon/Lang/sgs_LT.php new file mode 100644 index 0000000..aa9e942 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sgs_LT.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Arnas Udovičius bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'YYYY.MM.DD', + ], + 'months' => ['sausė', 'vasarė', 'kuova', 'balondė', 'gegožės', 'bėrželė', 'lëpas', 'rogpjūtė', 'siejės', 'spalė', 'lapkrėstė', 'grůdė'], + 'months_short' => ['Sau', 'Vas', 'Kuo', 'Bal', 'Geg', 'Bėr', 'Lëp', 'Rgp', 'Sie', 'Spa', 'Lap', 'Grd'], + 'weekdays' => ['nedielės dëna', 'panedielis', 'oterninks', 'sereda', 'četvergs', 'petnīčė', 'sobata'], + 'weekdays_short' => ['Nd', 'Pn', 'Ot', 'Sr', 'Čt', 'Pt', 'Sb'], + 'weekdays_min' => ['Nd', 'Pn', 'Ot', 'Sr', 'Čt', 'Pt', 'Sb'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'minute' => ':count mažos', // less reliable + 'min' => ':count mažos', // less reliable + 'a_minute' => ':count mažos', // less reliable + + 'year' => ':count metā', + 'y' => ':count metā', + 'a_year' => ':count metā', + + 'month' => ':count mienou', + 'm' => ':count mienou', + 'a_month' => ':count mienou', + + 'week' => ':count nedielė', + 'w' => ':count nedielė', + 'a_week' => ':count nedielė', + + 'day' => ':count dīna', + 'd' => ':count dīna', + 'a_day' => ':count dīna', + + 'hour' => ':count adīna', + 'h' => ':count adīna', + 'a_hour' => ':count adīna', + + 'second' => ':count Sekondė', + 's' => ':count Sekondė', + 'a_second' => ':count Sekondė', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sh.php b/vendor/nesbot/carbon/src/Carbon/Lang/sh.php new file mode 100644 index 0000000..e03b506 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sh.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +// @codeCoverageIgnoreStart +use Symfony\Component\Translation\PluralizationRules; + +if (class_exists('Symfony\\Component\\Translation\\PluralizationRules')) { + PluralizationRules::set(static function ($number) { + return (($number % 10 == 1) && ($number % 100 != 11)) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2); + }, 'sh'); +} +// @codeCoverageIgnoreEnd + +/* + * Authors: + * - Томица Кораћ + * - Enrique Vidal + * - Christopher Dell + * - dmilisic + * - danijel + * - Miroslav Matkovic (mikki021) + */ +return [ + 'diff_now' => 'sada', + 'diff_yesterday' => 'juče', + 'diff_tomorrow' => 'sutra', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'MMMM D, YYYY', + 'LLL' => 'DD MMM HH:mm', + 'LLLL' => 'MMMM DD, YYYY HH:mm', + ], + 'year' => ':count godina|:count godine|:count godina', + 'y' => ':count g.', + 'month' => ':count mesec|:count meseca|:count meseci', + 'm' => ':count m.', + 'week' => ':count nedelja|:count nedelje|:count nedelja', + 'w' => ':count n.', + 'day' => ':count dan|:count dana|:count dana', + 'd' => ':count d.', + 'hour' => ':count sat|:count sata|:count sati', + 'h' => ':count č.', + 'minute' => ':count minut|:count minuta|:count minuta', + 'min' => ':count min.', + 'second' => ':count sekund|:count sekunde|:count sekundi', + 's' => ':count s.', + 'ago' => 'pre :time', + 'from_now' => 'za :time', + 'after' => 'nakon :time', + 'before' => ':time raniјe', + 'weekdays' => ['Nedelja', 'Ponedeljak', 'Utorak', 'Sreda', 'Četvrtak', 'Petak', 'Subota'], + 'weekdays_short' => ['Ned', 'Pon', 'Uto', 'Sre', 'Čet', 'Pet', 'Sub'], + 'weekdays_min' => ['Ned', 'Pon', 'Uto', 'Sre', 'Čet', 'Pet', 'Sub'], + 'months' => ['Januar', 'Februar', 'Mart', 'April', 'Maj', 'Jun', 'Jul', 'Avgust', 'Septembar', 'Oktobar', 'Novembar', 'Decembar'], + 'months_short' => ['Jan', 'Feb', 'Mar', 'Apr', 'Maj', 'Jun', 'Jul', 'Avg', 'Sep', 'Okt', 'Nov', 'Dec'], + 'list' => [', ', ' i '], + 'meridiem' => ['pre podne', 'po podne'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/shi.php b/vendor/nesbot/carbon/src/Carbon/Lang/shi.php new file mode 100644 index 0000000..7815186 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/shi.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['ⵜⵉⴼⴰⵡⵜ', 'ⵜⴰⴷⴳⴳⵯⴰⵜ'], + 'weekdays' => ['ⴰⵙⴰⵎⴰⵙ', 'ⴰⵢⵏⴰⵙ', 'ⴰⵙⵉⵏⴰⵙ', 'ⴰⴽⵕⴰⵙ', 'ⴰⴽⵡⴰⵙ', 'ⵙⵉⵎⵡⴰⵙ', 'ⴰⵙⵉⴹⵢⴰⵙ'], + 'weekdays_short' => ['ⴰⵙⴰ', 'ⴰⵢⵏ', 'ⴰⵙⵉ', 'ⴰⴽⵕ', 'ⴰⴽⵡ', 'ⴰⵙⵉⵎ', 'ⴰⵙⵉⴹ'], + 'weekdays_min' => ['ⴰⵙⴰ', 'ⴰⵢⵏ', 'ⴰⵙⵉ', 'ⴰⴽⵕ', 'ⴰⴽⵡ', 'ⴰⵙⵉⵎ', 'ⴰⵙⵉⴹ'], + 'months' => ['ⵉⵏⵏⴰⵢⵔ', 'ⴱⵕⴰⵢⵕ', 'ⵎⴰⵕⵚ', 'ⵉⴱⵔⵉⵔ', 'ⵎⴰⵢⵢⵓ', 'ⵢⵓⵏⵢⵓ', 'ⵢⵓⵍⵢⵓⵣ', 'ⵖⵓⵛⵜ', 'ⵛⵓⵜⴰⵏⴱⵉⵔ', 'ⴽⵜⵓⴱⵔ', 'ⵏⵓⵡⴰⵏⴱⵉⵔ', 'ⴷⵓⵊⴰⵏⴱⵉⵔ'], + 'months_short' => ['ⵉⵏⵏ', 'ⴱⵕⴰ', 'ⵎⴰⵕ', 'ⵉⴱⵔ', 'ⵎⴰⵢ', 'ⵢⵓⵏ', 'ⵢⵓⵍ', 'ⵖⵓⵛ', 'ⵛⵓⵜ', 'ⴽⵜⵓ', 'ⵏⵓⵡ', 'ⴷⵓⵊ'], + 'first_day_of_week' => 6, + 'weekend' => [5, 6], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM, YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + + 'year' => ':count aseggwas', + 'y' => ':count aseggwas', + 'a_year' => ':count aseggwas', + + 'month' => ':count ayyur', + 'm' => ':count ayyur', + 'a_month' => ':count ayyur', + + 'week' => ':count imalass', + 'w' => ':count imalass', + 'a_week' => ':count imalass', + + 'day' => ':count ass', + 'd' => ':count ass', + 'a_day' => ':count ass', + + 'hour' => ':count urɣ', // less reliable + 'h' => ':count urɣ', // less reliable + 'a_hour' => ':count urɣ', // less reliable + + 'minute' => ':count ⴰⵎⵥⵉ', // less reliable + 'min' => ':count ⴰⵎⵥⵉ', // less reliable + 'a_minute' => ':count ⴰⵎⵥⵉ', // less reliable + + 'second' => ':count sin', // less reliable + 's' => ':count sin', // less reliable + 'a_second' => ':count sin', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/shi_Latn.php b/vendor/nesbot/carbon/src/Carbon/Lang/shi_Latn.php new file mode 100644 index 0000000..cddfb24 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/shi_Latn.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/shi.php', [ + 'meridiem' => ['tifawt', 'tadggʷat'], + 'weekdays' => ['asamas', 'aynas', 'asinas', 'akṛas', 'akwas', 'asimwas', 'asiḍyas'], + 'weekdays_short' => ['asa', 'ayn', 'asi', 'akṛ', 'akw', 'asim', 'asiḍ'], + 'weekdays_min' => ['asa', 'ayn', 'asi', 'akṛ', 'akw', 'asim', 'asiḍ'], + 'months' => ['innayr', 'bṛayṛ', 'maṛṣ', 'ibrir', 'mayyu', 'yunyu', 'yulyuz', 'ɣuct', 'cutanbir', 'ktubr', 'nuwanbir', 'dujanbir'], + 'months_short' => ['inn', 'bṛa', 'maṛ', 'ibr', 'may', 'yun', 'yul', 'ɣuc', 'cut', 'ktu', 'nuw', 'duj'], + 'first_day_of_week' => 6, + 'weekend' => [5, 6], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM, YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + + 'minute' => ':count agur', // less reliable + 'min' => ':count agur', // less reliable + 'a_minute' => ':count agur', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/shi_Tfng.php b/vendor/nesbot/carbon/src/Carbon/Lang/shi_Tfng.php new file mode 100644 index 0000000..f3df1f2 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/shi_Tfng.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__.'/shi.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/shn.php b/vendor/nesbot/carbon/src/Carbon/Lang/shn.php new file mode 100644 index 0000000..fe7b1ea --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/shn.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/shn_MM.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/shn_MM.php b/vendor/nesbot/carbon/src/Carbon/Lang/shn_MM.php new file mode 100644 index 0000000..f399acf --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/shn_MM.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - ubuntu Myanmar LoCo Team https://ubuntu-mm.net Bone Pyae Sone bone.burma@mail.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'OY MMM OD dddd', + ], + 'months' => ['လိူၼ်ၵမ်', 'လိူၼ်သၢမ်', 'လိူၼ်သီ', 'လိူၼ်ႁႃႈ', 'လိူၼ်ႁူၵ်း', 'လိူၼ်ၸဵတ်း', 'လိူၼ်ပႅတ်ႇ', 'လိူၼ်ၵဝ်ႈ', 'လိူၼ်သိပ်း', 'လိူၼ်သိပ်းဢိတ်း', 'လိူၼ်သိပ်းဢိတ်းသွင်', 'လိူၼ်ၸဵင်'], + 'months_short' => ['လိူၼ်ၵမ်', 'လိူၼ်သၢမ်', 'လိူၼ်သီ', 'လိူၼ်ႁႃႈ', 'လိူၼ်ႁူၵ်း', 'လိူၼ်ၸဵတ်း', 'လိူၼ်ပႅတ်ႇ', 'လိူၼ်ၵဝ်ႈ', 'လိူၼ်သိပ်း', 'လိူၼ်သိပ်းဢိတ်း', 'လိူၼ်သိပ်းဢိတ်းသွင်', 'လိူၼ်ၸဵင်'], + 'weekdays' => ['ဝၼ်းဢႃးတိတ်ႉ', 'ဝၼ်းၸၼ်', 'ဝၼ်း​ဢၢင်း​ၵၢၼ်း', 'ဝၼ်းပူတ်ႉ', 'ဝၼ်းၽတ်း', 'ဝၼ်းသုၵ်း', 'ဝၼ်းသဝ်'], + 'weekdays_short' => ['တိတ့်', 'ၸၼ်', 'ၵၢၼ်း', 'ပုတ့်', 'ၽတ်း', 'သုၵ်း', 'သဝ်'], + 'weekdays_min' => ['တိတ့်', 'ၸၼ်', 'ၵၢၼ်း', 'ပုတ့်', 'ၽတ်း', 'သုၵ်း', 'သဝ်'], + 'alt_numbers' => ['႐႐', '႐႑', '႐႒', '႐႓', '႐႔', '႐႕', '႐႖', '႐႗', '႐႘', '႐႙', '႑႐', '႑႑', '႑႒', '႑႓', '႑႔', '႑႕', '႑႖', '႑႗', '႑႘', '႑႙', '႒႐', '႒႑', '႒႒', '႒႓', '႒႔', '႒႕', '႒႖', '႒႗', '႒႘', '႒႙', '႓႐', '႓႑', '႓႒', '႓႓', '႓႔', '႓႕', '႓႖', '႓႗', '႓႘', '႓႙', '႔႐', '႔႑', '႔႒', '႔႓', '႔႔', '႔႕', '႔႖', '႔႗', '႔႘', '႔႙', '႕႐', '႕႑', '႕႒', '႕႓', '႕႔', '႕႕', '႕႖', '႕႗', '႕႘', '႕႙', '႖႐', '႖႑', '႖႒', '႖႓', '႖႔', '႖႕', '႖႖', '႖႗', '႖႘', '႖႙', '႗႐', '႗႑', '႗႒', '႗႓', '႗႔', '႗႕', '႗႖', '႗႗', '႗႘', '႗႙', '႘႐', '႘႑', '႘႒', '႘႓', '႘႔', '႘႕', '႘႖', '႘႗', '႘႘', '႘႙', '႙႐', '႙႑', '႙႒', '႙႓', '႙႔', '႙႕', '႙႖', '႙႗', '႙႘', '႙႙'], + 'meridiem' => ['ၵၢင်ၼႂ်', 'တၢမ်းၶမ်ႈ'], + + 'month' => ':count လိူၼ်', // less reliable + 'm' => ':count လိူၼ်', // less reliable + 'a_month' => ':count လိူၼ်', // less reliable + + 'week' => ':count ဝၼ်း', // less reliable + 'w' => ':count ဝၼ်း', // less reliable + 'a_week' => ':count ဝၼ်း', // less reliable + + 'hour' => ':count ຕີ', // less reliable + 'h' => ':count ຕີ', // less reliable + 'a_hour' => ':count ຕີ', // less reliable + + 'minute' => ':count ເດັກ', // less reliable + 'min' => ':count ເດັກ', // less reliable + 'a_minute' => ':count ເດັກ', // less reliable + + 'second' => ':count ဢိုၼ်ႇ', // less reliable + 's' => ':count ဢိုၼ်ႇ', // less reliable + 'a_second' => ':count ဢိုၼ်ႇ', // less reliable + + 'year' => ':count ပီ', + 'y' => ':count ပီ', + 'a_year' => ':count ပီ', + + 'day' => ':count ກາງວັນ', + 'd' => ':count ກາງວັນ', + 'a_day' => ':count ກາງວັນ', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/shs.php b/vendor/nesbot/carbon/src/Carbon/Lang/shs.php new file mode 100644 index 0000000..8d2e1d7 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/shs.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/shs_CA.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/shs_CA.php b/vendor/nesbot/carbon/src/Carbon/Lang/shs_CA.php new file mode 100644 index 0000000..08d385e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/shs_CA.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Neskie Manuel bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['Pellkwet̓min', 'Pelctsipwen̓ten', 'Pellsqépts', 'Peslléwten', 'Pell7ell7é7llqten', 'Pelltspéntsk', 'Pelltqwelq̓wél̓t', 'Pellct̓éxel̓cten', 'Pesqelqlélten', 'Pesllwélsten', 'Pellc7ell7é7llcwten̓', 'Pelltetétq̓em'], + 'months_short' => ['Kwe', 'Tsi', 'Sqe', 'Éwt', 'Ell', 'Tsp', 'Tqw', 'Ct̓é', 'Qel', 'Wél', 'U7l', 'Tet'], + 'weekdays' => ['Sxetspesq̓t', 'Spetkesq̓t', 'Selesq̓t', 'Skellesq̓t', 'Smesesq̓t', 'Stselkstesq̓t', 'Stqmekstesq̓t'], + 'weekdays_short' => ['Sxe', 'Spe', 'Sel', 'Ske', 'Sme', 'Sts', 'Stq'], + 'weekdays_min' => ['Sxe', 'Spe', 'Sel', 'Ske', 'Sme', 'Sts', 'Stq'], + 'day_of_first_week_of_year' => 1, + + 'year' => ':count sqlélten', // less reliable + 'y' => ':count sqlélten', // less reliable + 'a_year' => ':count sqlélten', // less reliable + + 'month' => ':count swewll', // less reliable + 'm' => ':count swewll', // less reliable + 'a_month' => ':count swewll', // less reliable + + 'hour' => ':count seqwlút', // less reliable + 'h' => ':count seqwlút', // less reliable + 'a_hour' => ':count seqwlút', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/si.php b/vendor/nesbot/carbon/src/Carbon/Lang/si.php new file mode 100644 index 0000000..636bf69 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/si.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - Serhan Apaydın + * - JD Isaacks + * - Malinda Weerasinghe (MalindaWMD) + */ +return [ + 'year' => '{1}වසර 1|වසර :count', + 'a_year' => '{1}වසරක්|වසර :count', + 'month' => '{1}මාස 1|මාස :count', + 'a_month' => '{1}මාසය|මාස :count', + 'week' => '{1}සති 1|සති :count', + 'a_week' => '{1}සතියක්|සති :count', + 'day' => '{1}දින 1|දින :count', + 'a_day' => '{1}දිනක්|දින :count', + 'hour' => '{1}පැය 1|පැය :count', + 'a_hour' => '{1}පැයක්|පැය :count', + 'minute' => '{1}මිනිත්තු 1|මිනිත්තු :count', + 'a_minute' => '{1}මිනිත්තුවක්|මිනිත්තු :count', + 'second' => '{1}තත්පර 1|තත්පර :count', + 'a_second' => '{1}තත්පර කිහිපයකට|තත්පර :count', + 'ago' => ':time කට පෙර', + 'from_now' => function ($time) { + if (preg_match('/දින \d/u', $time)) { + return $time.' න්'; + } + + return $time.' කින්'; + }, + 'before' => ':time කට පෙර', + 'after' => function ($time) { + if (preg_match('/දින \d/u', $time)) { + return $time.' න්'; + } + + return $time.' කින්'; + }, + 'diff_now' => 'දැන්', + 'diff_today' => 'අද', + 'diff_yesterday' => 'ඊයේ', + 'diff_tomorrow' => 'හෙට', + 'formats' => [ + 'LT' => 'a h:mm', + 'LTS' => 'a h:mm:ss', + 'L' => 'YYYY/MM/DD', + 'LL' => 'YYYY MMMM D', + 'LLL' => 'YYYY MMMM D, a h:mm', + 'LLLL' => 'YYYY MMMM D [වැනි] dddd, a h:mm:ss', + ], + 'calendar' => [ + 'sameDay' => '[අද] LT[ට]', + 'nextDay' => '[හෙට] LT[ට]', + 'nextWeek' => 'dddd LT[ට]', + 'lastDay' => '[ඊයේ] LT[ට]', + 'lastWeek' => '[පසුගිය] dddd LT[ට]', + 'sameElse' => 'L', + ], + 'ordinal' => ':number වැනි', + 'meridiem' => ['පෙර වරු', 'පස් වරු', 'පෙ.ව.', 'ප.ව.'], + 'months' => ['ජනවාරි', 'පෙබරවාරි', 'මාර්තු', 'අප්‍රේල්', 'මැයි', 'ජූනි', 'ජූලි', 'අගෝස්තු', 'සැප්තැම්බර්', 'ඔක්තෝබර්', 'නොවැම්බර්', 'දෙසැම්බර්'], + 'months_short' => ['ජන', 'පෙබ', 'මාර්', 'අප්', 'මැයි', 'ජූනි', 'ජූලි', 'අගෝ', 'සැප්', 'ඔක්', 'නොවැ', 'දෙසැ'], + 'weekdays' => ['ඉරිදා', 'සඳුදා', 'අඟහරුවාදා', 'බදාදා', 'බ්‍රහස්පතින්දා', 'සිකුරාදා', 'සෙනසුරාදා'], + 'weekdays_short' => ['ඉරි', 'සඳු', 'අඟ', 'බදා', 'බ්‍රහ', 'සිකු', 'සෙන'], + 'weekdays_min' => ['ඉ', 'ස', 'අ', 'බ', 'බ්‍ර', 'සි', 'සෙ'], + 'first_day_of_week' => 1, +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/si_LK.php b/vendor/nesbot/carbon/src/Carbon/Lang/si_LK.php new file mode 100644 index 0000000..81c44e0 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/si_LK.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__.'/si.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sid.php b/vendor/nesbot/carbon/src/Carbon/Lang/sid.php new file mode 100644 index 0000000..b1c6521 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sid.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/sid_ET.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sid_ET.php b/vendor/nesbot/carbon/src/Carbon/Lang/sid_ET.php new file mode 100644 index 0000000..1296f9b --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sid_ET.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], + 'months_short' => ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], + 'weekdays' => ['Sambata', 'Sanyo', 'Maakisanyo', 'Roowe', 'Hamuse', 'Arbe', 'Qidaame'], + 'weekdays_short' => ['Sam', 'San', 'Mak', 'Row', 'Ham', 'Arb', 'Qid'], + 'weekdays_min' => ['Sam', 'San', 'Mak', 'Row', 'Ham', 'Arb', 'Qid'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['soodo', 'hawwaro'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sk.php b/vendor/nesbot/carbon/src/Carbon/Lang/sk.php new file mode 100644 index 0000000..08af197 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sk.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Philippe Vaucher + * - Martin Suja + * - Tsutomu Kuroda + * - tjku + * - Max Melentiev + * - Juanito Fatas + * - Ivan Stana + * - Akira Matsuda + * - Christopher Dell + * - James McKinney + * - Enrique Vidal + * - Simone Carletti + * - Aaron Patterson + * - Jozef Fulop + * - Nicolás Hock Isaza + * - Tom Hughes + * - Simon Hürlimann (CyT) + * - jofi + * - Jakub ADAMEC + * - Marek Adamický + * - AlterwebStudio + */ +return [ + 'year' => 'rok|:count roky|:count rokov', + 'y' => ':count r', + 'month' => 'mesiac|:count mesiace|:count mesiacov', + 'm' => ':count m', + 'week' => 'týždeň|:count týždne|:count týždňov', + 'w' => ':count t', + 'day' => 'deň|:count dni|:count dní', + 'd' => ':count d', + 'hour' => 'hodinu|:count hodiny|:count hodín', + 'h' => ':count h', + 'minute' => 'minútu|:count minúty|:count minút', + 'min' => ':count min', + 'second' => 'sekundu|:count sekundy|:count sekúnd', + 'a_second' => 'pár sekúnd|:count sekundy|:count sekúnd', + 's' => ':count s', + 'ago' => 'pred :time', + 'from_now' => 'o :time', + 'after' => ':time po', + 'before' => ':time pred', + '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', + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' a '], + 'diff_now' => 'teraz', + 'diff_yesterday' => 'včera', + 'diff_tomorrow' => 'zajtra', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'DD. MMMM YYYY', + 'LLL' => 'D. M. HH:mm', + 'LLLL' => 'dddd D. MMMM YYYY HH:mm', + ], + 'weekdays' => ['nedeľa', 'pondelok', 'utorok', 'streda', 'štvrtok', 'piatok', 'sobota'], + 'weekdays_short' => ['ned', 'pod', 'uto', 'str', 'štv', 'pia', 'sob'], + 'weekdays_min' => ['ne', 'po', 'ut', 'st', 'št', 'pi', 'so'], + 'months' => ['január', 'február', 'marec', 'apríl', 'máj', 'jún', 'júl', 'august', 'september', 'október', 'november', 'december'], + 'months_short' => ['jan', 'feb', 'mar', 'apr', 'máj', 'jún', 'júl', 'aug', 'sep', 'okt', 'nov', 'dec'], + 'meridiem' => ['dopoludnia', 'popoludní'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sk_SK.php b/vendor/nesbot/carbon/src/Carbon/Lang/sk_SK.php new file mode 100644 index 0000000..0515601 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sk_SK.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__.'/sk.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sl.php b/vendor/nesbot/carbon/src/Carbon/Lang/sl.php new file mode 100644 index 0000000..b000e30 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sl.php @@ -0,0 +1,129 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Philippe Vaucher + * - Tsutomu Kuroda + * - tjku + * - Max Melentiev + * - Juanito Fatas + * - Akira Matsuda + * - Christopher Dell + * - Enrique Vidal + * - Simone Carletti + * - Aaron Patterson + * - Nicolás Hock Isaza + * - Miha Rebernik + * - Gal Jakič (morpheus7CS) + * - Glavić + * - Anže Časar + * - Lovro Tramšek (Lovro1107) + * - burut13 + */ + +use Carbon\CarbonInterface; + +return [ + '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 mes.', + 'week' => ':count teden|:count tedna|:count tedne|:count tednov', + 'w' => ':count ted.', + 'day' => ':count dan|:count dni|:count dni|:count dni', + 'd' => ':count dan|:count dni|:count dni|:count dni', + 'hour' => ':count ura|:count uri|:count ure|:count ur', + 'h' => ':count h', + 'minute' => ':count minuta|:count minuti|:count minute|:count minut', + 'min' => ':count min.', + 'second' => ':count sekunda|:count sekundi|:count sekunde|:count sekund', + 'a_second' => '{1}nekaj sekund|:count sekunda|:count sekundi|:count sekunde|:count sekund', + 's' => ':count s', + + 'year_ago' => ':count letom|:count letoma|:count leti|:count leti', + 'y_ago' => ':count letom|:count letoma|: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', + 'd_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', + + 'day_from_now' => ':count dan|:count dneva|:count dni|:count dni', + 'd_from_now' => ':count dan|:count dneva|:count dni|:count dni', + 'hour_from_now' => ':count uro|:count uri|:count ure|:count ur', + 'minute_from_now' => ':count minuto|:count minuti|:count minute|:count minut', + 'second_from_now' => ':count sekundo|:count sekundi|:count sekunde|:count sekund', + + 'ago' => 'pred :time', + 'from_now' => 'čez :time', + 'after' => ':time kasneje', + 'before' => ':time prej', + + 'diff_now' => 'ravnokar', + 'diff_today' => 'danes', + 'diff_today_regexp' => 'danes(?:\\s+ob)?', + 'diff_yesterday' => 'včeraj', + 'diff_yesterday_regexp' => 'včeraj(?:\\s+ob)?', + 'diff_tomorrow' => 'jutri', + 'diff_tomorrow_regexp' => 'jutri(?:\\s+ob)?', + 'diff_before_yesterday' => 'predvčerajšnjim', + 'diff_after_tomorrow' => 'pojutrišnjem', + + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + + 'period_start_date' => 'od :date', + 'period_end_date' => 'do :date', + + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D. MMMM YYYY', + 'LLL' => 'D. MMMM YYYY H:mm', + 'LLLL' => 'dddd, D. MMMM YYYY H:mm', + ], + 'calendar' => [ + 'sameDay' => '[danes ob] LT', + 'nextDay' => '[jutri ob] LT', + 'nextWeek' => 'dddd [ob] LT', + 'lastDay' => '[včeraj ob] LT', + 'lastWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + return '[preteklo] [nedeljo] [ob] LT'; + case 1: + return '[pretekli] [ponedeljek] [ob] LT'; + case 2: + return '[pretekli] [torek] [ob] LT'; + case 3: + return '[preteklo] [sredo] [ob] LT'; + case 4: + return '[pretekli] [četrtek] [ob] LT'; + case 5: + return '[pretekli] [petek] [ob] LT'; + case 6: + return '[preteklo] [soboto] [ob] LT'; + } + }, + 'sameElse' => 'L', + ], + 'months' => ['januar', 'februar', 'marec', 'april', 'maj', 'junij', 'julij', 'avgust', 'september', 'oktober', 'november', 'december'], + 'months_short' => ['jan', 'feb', 'mar', 'apr', 'maj', 'jun', 'jul', 'avg', 'sep', 'okt', 'nov', 'dec'], + 'weekdays' => ['nedelja', 'ponedeljek', 'torek', 'sreda', 'četrtek', 'petek', 'sobota'], + 'weekdays_short' => ['ned', 'pon', 'tor', 'sre', 'čet', 'pet', 'sob'], + 'weekdays_min' => ['ne', 'po', 'to', 'sr', 'če', 'pe', 'so'], + 'list' => [', ', ' in '], + 'meridiem' => ['dopoldan', 'popoldan'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sl_SI.php b/vendor/nesbot/carbon/src/Carbon/Lang/sl_SI.php new file mode 100644 index 0000000..5dad8c8 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sl_SI.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__.'/sl.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sm.php b/vendor/nesbot/carbon/src/Carbon/Lang/sm.php new file mode 100644 index 0000000..e8c118a --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sm.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/sm_WS.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sm_WS.php b/vendor/nesbot/carbon/src/Carbon/Lang/sm_WS.php new file mode 100644 index 0000000..f066068 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sm_WS.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Samsung Electronics Co., Ltd. akhilesh.k@samsung.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Ianuari', 'Fepuari', 'Mati', 'Aperila', 'Me', 'Iuni', 'Iulai', 'Auguso', 'Setema', 'Oketopa', 'Novema', 'Tesema'], + 'months_short' => ['Ian', 'Fep', 'Mat', 'Ape', 'Me', 'Iun', 'Iul', 'Aug', 'Set', 'Oke', 'Nov', 'Tes'], + 'weekdays' => ['Aso Sa', 'Aso Gafua', 'Aso Lua', 'Aso Lulu', 'Aso Tofi', 'Aso Farail', 'Aso To\'ana\'i'], + 'weekdays_short' => ['Aso Sa', 'Aso Gaf', 'Aso Lua', 'Aso Lul', 'Aso Tof', 'Aso Far', 'Aso To\''], + 'weekdays_min' => ['Aso Sa', 'Aso Gaf', 'Aso Lua', 'Aso Lul', 'Aso Tof', 'Aso Far', 'Aso To\''], + + 'hour' => ':count uati', // less reliable + 'h' => ':count uati', // less reliable + 'a_hour' => ':count uati', // less reliable + + 'minute' => ':count itiiti', // less reliable + 'min' => ':count itiiti', // less reliable + 'a_minute' => ':count itiiti', // less reliable + + 'second' => ':count lua', // less reliable + 's' => ':count lua', // less reliable + 'a_second' => ':count lua', // less reliable + + 'year' => ':count tausaga', + 'y' => ':count tausaga', + 'a_year' => ':count tausaga', + + 'month' => ':count māsina', + 'm' => ':count māsina', + 'a_month' => ':count māsina', + + 'week' => ':count vaiaso', + 'w' => ':count vaiaso', + 'a_week' => ':count vaiaso', + + 'day' => ':count aso', + 'd' => ':count aso', + 'a_day' => ':count aso', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/smn.php b/vendor/nesbot/carbon/src/Carbon/Lang/smn.php new file mode 100644 index 0000000..20add02 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/smn.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['ip.', 'ep.'], + 'weekdays' => ['pasepeeivi', 'vuossaargâ', 'majebaargâ', 'koskoho', 'tuorâstuv', 'vástuppeeivi', 'lávurduv'], + 'weekdays_short' => ['pas', 'vuo', 'maj', 'kos', 'tuo', 'vás', 'láv'], + 'weekdays_min' => ['pa', 'vu', 'ma', 'ko', 'tu', 'vá', 'lá'], + 'weekdays_standalone' => ['pasepeivi', 'vuossargâ', 'majebargâ', 'koskokko', 'tuorâstâh', 'vástuppeivi', 'lávurdâh'], + 'months' => ['uđđâivemáánu', 'kuovâmáánu', 'njuhčâmáánu', 'cuáŋuimáánu', 'vyesimáánu', 'kesimáánu', 'syeinimáánu', 'porgemáánu', 'čohčâmáánu', 'roovvâdmáánu', 'skammâmáánu', 'juovlâmáánu'], + 'months_short' => ['uđiv', 'kuovâ', 'njuhčâ', 'cuáŋui', 'vyesi', 'kesi', 'syeini', 'porge', 'čohčâ', 'roovvâd', 'skammâ', 'juovlâ'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'H.mm', + 'LTS' => 'H.mm.ss', + 'L' => 'D.M.YYYY', + 'LL' => 'MMM D. YYYY', + 'LLL' => 'MMMM D. YYYY H.mm', + 'LLLL' => 'dddd, MMMM D. YYYY H.mm', + ], + + 'hour' => ':count äigi', // less reliable + 'h' => ':count äigi', // less reliable + 'a_hour' => ':count äigi', // less reliable + + 'year' => ':count ihe', + 'y' => ':count ihe', + 'a_year' => ':count ihe', + + 'month' => ':count mánuppaje', + 'm' => ':count mánuppaje', + 'a_month' => ':count mánuppaje', + + 'week' => ':count okko', + 'w' => ':count okko', + 'a_week' => ':count okko', + + 'day' => ':count peivi', + 'd' => ':count peivi', + 'a_day' => ':count peivi', + + 'minute' => ':count miinut', + 'min' => ':count miinut', + 'a_minute' => ':count miinut', + + 'second' => ':count nubbe', + 's' => ':count nubbe', + 'a_second' => ':count nubbe', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sn.php b/vendor/nesbot/carbon/src/Carbon/Lang/sn.php new file mode 100644 index 0000000..4f25028 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sn.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['a', 'p'], + 'weekdays' => ['Svondo', 'Muvhuro', 'Chipiri', 'Chitatu', 'China', 'Chishanu', 'Mugovera'], + 'weekdays_short' => ['Svo', 'Muv', 'Chp', 'Cht', 'Chn', 'Chs', 'Mug'], + 'weekdays_min' => ['Sv', 'Mu', 'Cp', 'Ct', 'Cn', 'Cs', 'Mg'], + 'months' => ['Ndira', 'Kukadzi', 'Kurume', 'Kubvumbi', 'Chivabvu', 'Chikumi', 'Chikunguru', 'Nyamavhuvhu', 'Gunyana', 'Gumiguru', 'Mbudzi', 'Zvita'], + 'months_short' => ['Ndi', 'Kuk', 'Kur', 'Kub', 'Chv', 'Chk', 'Chg', 'Nya', 'Gun', 'Gum', 'Mbu', 'Zvi'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-dd', + 'LL' => 'YYYY MMM D', + 'LLL' => 'YYYY MMMM D HH:mm', + 'LLLL' => 'YYYY MMMM D, dddd HH:mm', + ], + + 'year' => 'makore :count', + 'y' => 'makore :count', + 'a_year' => 'makore :count', + + 'month' => 'mwedzi :count', + 'm' => 'mwedzi :count', + 'a_month' => 'mwedzi :count', + + 'week' => 'vhiki :count', + 'w' => 'vhiki :count', + 'a_week' => 'vhiki :count', + + 'day' => 'mazuva :count', + 'd' => 'mazuva :count', + 'a_day' => 'mazuva :count', + + 'hour' => 'maawa :count', + 'h' => 'maawa :count', + 'a_hour' => 'maawa :count', + + 'minute' => 'minitsi :count', + 'min' => 'minitsi :count', + 'a_minute' => 'minitsi :count', + + 'second' => 'sekonzi :count', + 's' => 'sekonzi :count', + 'a_second' => 'sekonzi :count', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/so.php b/vendor/nesbot/carbon/src/Carbon/Lang/so.php new file mode 100644 index 0000000..5785271 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/so.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Author: + * - Abdifatah Abdilahi(@abdifatahz) + */ +return [ + 'year' => ':count sanad|:count sanadood', + 'a_year' => 'sanad|:count sanadood', + 'y' => '{1}:countsn|{0}:countsns|]1,Inf[:countsn', + 'month' => ':count bil|:count bilood', + 'a_month' => 'bil|:count bilood', + 'm' => ':countbil', + 'week' => ':count isbuuc', + 'a_week' => 'isbuuc|:count isbuuc', + 'w' => ':countis', + 'day' => ':count maalin|:count maalmood', + 'a_day' => 'maalin|:count maalmood', + 'd' => ':countml', + 'hour' => ':count saac', + 'a_hour' => 'saacad|:count saac', + 'h' => ':countsc', + 'minute' => ':count daqiiqo', + 'a_minute' => 'daqiiqo|:count daqiiqo', + 'min' => ':countdq', + 'second' => ':count ilbidhiqsi', + 'a_second' => 'xooga ilbidhiqsiyo|:count ilbidhiqsi', + 's' => ':countil', + 'ago' => ':time kahor', + 'from_now' => ':time gudahood', + 'after' => ':time kedib', + 'before' => ':time kahor', + 'diff_now' => 'hada', + 'diff_today' => 'maanta', + 'diff_today_regexp' => 'maanta(?:\s+markay\s+(?:tahay|ahayd))?', + 'diff_yesterday' => 'shalayto', + 'diff_yesterday_regexp' => 'shalayto(?:\s+markay\s+ahayd)?', + 'diff_tomorrow' => 'beri', + 'diff_tomorrow_regexp' => 'beri(?:\s+markay\s+tahay)?', + 'diff_before_yesterday' => 'doraato', + 'diff_after_tomorrow' => 'saadanbe', + 'period_recurrences' => 'mar|:count jeer', + 'period_interval' => ':interval kasta', + 'period_start_date' => 'laga bilaabo :date', + 'period_end_date' => 'ilaa :date', + 'months' => ['Janaayo', 'Febraayo', 'Abriil', 'Maajo', 'Juun', 'Luuliyo', 'Agoosto', 'Sebteembar', 'Oktoobar', 'Nofeembar', 'Diseembar'], + 'months_short' => ['Jan', 'Feb', 'Mar', 'Abr', 'Mjo', 'Jun', 'Lyo', 'Agt', 'Seb', 'Okt', 'Nof', 'Dis'], + 'weekdays' => ['Axad', 'Isniin', 'Talaada', 'Arbaca', 'Khamiis', 'Jimce', 'Sabti'], + 'weekdays_short' => ['Axd', 'Isn', 'Tal', 'Arb', 'Kha', 'Jim', 'Sbt'], + 'weekdays_min' => ['Ax', 'Is', 'Ta', 'Ar', 'Kh', 'Ji', 'Sa'], + 'list' => [', ', ' and '], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'calendar' => [ + 'sameDay' => '[Maanta markay tahay] LT', + 'nextDay' => '[Beri markay tahay] LT', + 'nextWeek' => 'dddd [markay tahay] LT', + 'lastDay' => '[Shalay markay ahayd] LT', + 'lastWeek' => '[Hore] dddd [Markay ahayd] LT', + 'sameElse' => 'L', + ], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/so_DJ.php b/vendor/nesbot/carbon/src/Carbon/Lang/so_DJ.php new file mode 100644 index 0000000..273dda8 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/so_DJ.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/so.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/so_ET.php b/vendor/nesbot/carbon/src/Carbon/Lang/so_ET.php new file mode 100644 index 0000000..7b69971 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/so_ET.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return require __DIR__.'/so.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/so_KE.php b/vendor/nesbot/carbon/src/Carbon/Lang/so_KE.php new file mode 100644 index 0000000..7b69971 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/so_KE.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return require __DIR__.'/so.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/so_SO.php b/vendor/nesbot/carbon/src/Carbon/Lang/so_SO.php new file mode 100644 index 0000000..7b69971 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/so_SO.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return require __DIR__.'/so.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sq.php b/vendor/nesbot/carbon/src/Carbon/Lang/sq.php new file mode 100644 index 0000000..ffa592e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sq.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - JD Isaacks + * - Fadion Dashi + */ +return [ + 'year' => ':count vit|:count vjet', + 'a_year' => 'një vit|:count vite', + 'y' => ':count v.', + 'month' => ':count muaj', + 'a_month' => 'një muaj|:count muaj', + 'm' => ':count muaj', + 'week' => ':count javë', + 'a_week' => ':count javë|:count javë', + 'w' => ':count j.', + 'day' => ':count ditë', + 'a_day' => 'një ditë|:count ditë', + 'd' => ':count d.', + 'hour' => ':count orë', + 'a_hour' => 'një orë|:count orë', + 'h' => ':count o.', + 'minute' => ':count minutë|:count minuta', + 'a_minute' => 'një minutë|:count minuta', + 'min' => ':count min.', + 'second' => ':count sekondë|:count sekonda', + 'a_second' => 'disa sekonda|:count sekonda', + 's' => ':count s.', + 'ago' => ':time më parë', + 'from_now' => 'në :time', + 'after' => ':time pas', + 'before' => ':time para', + 'diff_now' => 'tani', + 'diff_today' => 'Sot', + 'diff_today_regexp' => 'Sot(?:\\s+në)?', + 'diff_yesterday' => 'dje', + 'diff_yesterday_regexp' => 'Dje(?:\\s+në)?', + 'diff_tomorrow' => 'nesër', + 'diff_tomorrow_regexp' => 'Nesër(?:\\s+në)?', + 'diff_before_yesterday' => 'pardje', + 'diff_after_tomorrow' => 'pasnesër', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Sot në] LT', + 'nextDay' => '[Nesër në] LT', + 'nextWeek' => 'dddd [në] LT', + 'lastDay' => '[Dje në] LT', + 'lastWeek' => 'dddd [e kaluar në] LT', + 'sameElse' => 'L', + ], + 'ordinal' => ':number.', + 'meridiem' => ['PD', 'MD'], + 'months' => ['janar', 'shkurt', 'mars', 'prill', 'maj', 'qershor', 'korrik', 'gusht', 'shtator', 'tetor', 'nëntor', 'dhjetor'], + 'months_short' => ['jan', 'shk', 'mar', 'pri', 'maj', 'qer', 'kor', 'gus', 'sht', 'tet', 'nën', 'dhj'], + 'weekdays' => ['e diel', 'e hënë', 'e martë', 'e mërkurë', 'e enjte', 'e premte', 'e shtunë'], + 'weekdays_short' => ['die', 'hën', 'mar', 'mër', 'enj', 'pre', 'sht'], + 'weekdays_min' => ['d', 'h', 'ma', 'më', 'e', 'p', 'sh'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' dhe '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sq_AL.php b/vendor/nesbot/carbon/src/Carbon/Lang/sq_AL.php new file mode 100644 index 0000000..ea5df3f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sq_AL.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__.'/sq.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sq_MK.php b/vendor/nesbot/carbon/src/Carbon/Lang/sq_MK.php new file mode 100644 index 0000000..62f752c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sq_MK.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/sq.php', [ + 'formats' => [ + 'L' => 'D.M.YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY, HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY, HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sq_XK.php b/vendor/nesbot/carbon/src/Carbon/Lang/sq_XK.php new file mode 100644 index 0000000..62f752c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sq_XK.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/sq.php', [ + 'formats' => [ + 'L' => 'D.M.YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY, HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY, HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sr.php b/vendor/nesbot/carbon/src/Carbon/Lang/sr.php new file mode 100644 index 0000000..68ba663 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sr.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - François B + * - shaishavgandhi05 + * - Serhan Apaydın + * - JD Isaacks + * - Glavić + * - Milos Sakovic + */ + +use Carbon\CarbonInterface; + +return [ + 'year' => ':count godina|:count godine|:count godina', + 'y' => ':count g.', + 'month' => ':count mesec|:count meseca|:count meseci', + 'm' => ':count mj.', + 'week' => ':count nedelja|:count nedelje|:count nedelja', + 'w' => ':count ned.', + 'day' => ':count dan|:count dana|:count dana', + 'd' => ':count d.', + 'hour' => ':count sat|:count sata|:count sati', + 'h' => ':count č.', + 'minute' => ':count minut|:count minuta|:count minuta', + 'min' => ':count min.', + 'second' => ':count sekundu|:count sekunde|:count sekundi', + 's' => ':count sek.', + 'ago' => 'pre :time', + 'from_now' => 'za :time', + 'after' => 'nakon :time', + 'before' => 'pre :time', + + 'year_from_now' => ':count godinu|:count godine|:count godina', + 'year_ago' => ':count godinu|:count godine|:count godina', + 'week_from_now' => ':count nedelju|:count nedelje|:count nedelja', + 'week_ago' => ':count nedelju|:count nedelje|:count nedelja', + + 'diff_now' => 'upravo sada', + 'diff_today' => 'danas', + 'diff_today_regexp' => 'danas(?:\\s+u)?', + 'diff_yesterday' => 'juče', + 'diff_yesterday_regexp' => 'juče(?:\\s+u)?', + 'diff_tomorrow' => 'sutra', + 'diff_tomorrow_regexp' => 'sutra(?:\\s+u)?', + 'diff_before_yesterday' => 'prekjuče', + 'diff_after_tomorrow' => 'preksutra', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D. MMMM YYYY', + 'LLL' => 'D. MMMM YYYY H:mm', + 'LLLL' => 'dddd, D. MMMM YYYY H:mm', + ], + 'calendar' => [ + 'sameDay' => '[danas u] LT', + 'nextDay' => '[sutra u] LT', + 'nextWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + return '[u nedelju u] LT'; + case 3: + return '[u sredu u] LT'; + case 6: + return '[u subotu u] LT'; + default: + return '[u] dddd [u] LT'; + } + }, + 'lastDay' => '[juče u] LT', + 'lastWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + return '[prošle nedelje u] LT'; + case 1: + return '[prošlog ponedeljka u] LT'; + case 2: + return '[prošlog utorka u] LT'; + case 3: + return '[prošle srede u] LT'; + case 4: + return '[prošlog četvrtka u] LT'; + case 5: + return '[prošlog petka u] LT'; + default: + return '[prošle subote u] LT'; + } + }, + 'sameElse' => 'L', + ], + 'ordinal' => ':number.', + 'months' => ['januar', 'februar', 'mart', 'april', 'maj', 'jun', 'jul', 'avgust', 'septembar', 'oktobar', 'novembar', 'decembar'], + 'months_short' => ['jan.', 'feb.', 'mar.', 'apr.', 'maj', 'jun', 'jul', 'avg.', 'sep.', 'okt.', 'nov.', 'dec.'], + 'weekdays' => ['nedelja', 'ponedeljak', 'utorak', 'sreda', 'četvrtak', 'petak', 'subota'], + 'weekdays_short' => ['ned.', 'pon.', 'uto.', 'sre.', 'čet.', 'pet.', 'sub.'], + 'weekdays_min' => ['ne', 'po', 'ut', 'sr', 'če', 'pe', 'su'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' i '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl.php b/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl.php new file mode 100644 index 0000000..8becbc5 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - François B + * - shaishavgandhi05 + * - Serhan Apaydın + * - JD Isaacks + * - Glavić + * - Nikola Zeravcic + * - Milos Sakovic + */ + +use Carbon\CarbonInterface; + +return [ + 'year' => ':count година|:count године|:count година', + 'y' => ':count г.', + 'month' => ':count месец|:count месеца|:count месеци', + 'm' => ':count м.', + 'week' => ':count недеља|:count недеље|:count недеља', + 'w' => ':count нед.', + 'day' => ':count дан|:count дана|:count дана', + 'd' => ':count д.', + 'hour' => ':count сат|:count сата|:count сати', + 'h' => ':count ч.', + 'minute' => ':count минут|:count минута|:count минута', + 'min' => ':count мин.', + 'second' => ':count секунд|:count секунде|:count секунди', + 's' => ':count сек.', + 'ago' => 'пре :time', + 'from_now' => 'за :time', + 'after' => ':time након', + 'before' => ':time пре', + 'year_from_now' => ':count годину|:count године|:count година', + 'year_ago' => ':count годину|:count године|:count година', + 'week_from_now' => ':count недељу|:count недеље|:count недеља', + 'week_ago' => ':count недељу|:count недеље|:count недеља', + 'diff_now' => 'управо сада', + 'diff_today' => 'данас', + 'diff_today_regexp' => 'данас(?:\\s+у)?', + 'diff_yesterday' => 'јуче', + 'diff_yesterday_regexp' => 'јуче(?:\\s+у)?', + 'diff_tomorrow' => 'сутра', + 'diff_tomorrow_regexp' => 'сутра(?:\\s+у)?', + 'diff_before_yesterday' => 'прекјуче', + 'diff_after_tomorrow' => 'прекосутра', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D. MMMM YYYY', + 'LLL' => 'D. MMMM YYYY H:mm', + 'LLLL' => 'dddd, D. MMMM YYYY H:mm', + ], + 'calendar' => [ + 'sameDay' => '[данас у] LT', + 'nextDay' => '[сутра у] LT', + 'nextWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + return '[у недељу у] LT'; + case 3: + return '[у среду у] LT'; + case 6: + return '[у суботу у] LT'; + default: + return '[у] dddd [у] LT'; + } + }, + 'lastDay' => '[јуче у] LT', + 'lastWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + return '[прошле недеље у] LT'; + case 1: + return '[прошлог понедељка у] LT'; + case 2: + return '[прошлог уторка у] LT'; + case 3: + return '[прошле среде у] LT'; + case 4: + return '[прошлог четвртка у] LT'; + case 5: + return '[прошлог петка у] LT'; + default: + return '[прошле суботе у] LT'; + } + }, + 'sameElse' => 'L', + ], + 'ordinal' => ':number.', + 'months' => ['јануар', 'фебруар', 'март', 'април', 'мај', 'јун', 'јул', 'август', 'септембар', 'октобар', 'новембар', 'децембар'], + 'months_short' => ['јан.', 'феб.', 'мар.', 'апр.', 'мај', 'јун', 'јул', 'авг.', 'сеп.', 'окт.', 'нов.', 'дец.'], + 'weekdays' => ['недеља', 'понедељак', 'уторак', 'среда', 'четвртак', 'петак', 'субота'], + 'weekdays_short' => ['нед.', 'пон.', 'уто.', 'сре.', 'чет.', 'пет.', 'суб.'], + 'weekdays_min' => ['не', 'по', 'ут', 'ср', 'че', 'пе', 'су'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' и '], + 'meridiem' => ['АМ', 'ПМ'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl_BA.php b/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl_BA.php new file mode 100644 index 0000000..4b29a45 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl_BA.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Component\Translation\PluralizationRules; + +// @codeCoverageIgnoreStart +if (class_exists(PluralizationRules::class)) { + PluralizationRules::set(static function ($number) { + return PluralizationRules::get($number, 'sr'); + }, 'sr_Cyrl_BA'); +} +// @codeCoverageIgnoreEnd + +return array_replace_recursive(require __DIR__.'/sr_Cyrl.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D.M.yy.', + 'LL' => 'DD.MM.YYYY.', + 'LLL' => 'DD. MMMM YYYY. HH:mm', + 'LLLL' => 'dddd, DD. MMMM YYYY. HH:mm', + ], + 'weekdays' => ['недјеља', 'понедељак', 'уторак', 'сриједа', 'четвртак', 'петак', 'субота'], + 'weekdays_short' => ['нед.', 'пон.', 'ут.', 'ср.', 'чет.', 'пет.', 'суб.'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl_ME.php b/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl_ME.php new file mode 100644 index 0000000..28d22fd --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl_ME.php @@ -0,0 +1,118 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Glavić + * - Milos Sakovic + */ + +use Carbon\CarbonInterface; +use Symfony\Component\Translation\PluralizationRules; + +// @codeCoverageIgnoreStart +if (class_exists(PluralizationRules::class)) { + PluralizationRules::set(static function ($number) { + return PluralizationRules::get($number, 'sr'); + }, 'sr_Cyrl_ME'); +} +// @codeCoverageIgnoreEnd + +return [ + 'year' => ':count година|:count године|:count година', + 'y' => ':count г.', + 'month' => ':count мјесец|:count мјесеца|:count мјесеци', + 'm' => ':count мј.', + 'week' => ':count недјеља|:count недјеље|:count недјеља', + 'w' => ':count нед.', + 'day' => ':count дан|:count дана|:count дана', + 'd' => ':count д.', + 'hour' => ':count сат|:count сата|:count сати', + 'h' => ':count ч.', + 'minute' => ':count минут|:count минута|:count минута', + 'min' => ':count мин.', + 'second' => ':count секунд|:count секунде|:count секунди', + 's' => ':count сек.', + 'ago' => 'прије :time', + 'from_now' => 'за :time', + 'after' => ':time након', + 'before' => ':time прије', + + 'year_from_now' => ':count годину|:count године|:count година', + 'year_ago' => ':count годину|:count године|:count година', + + 'week_from_now' => ':count недјељу|:count недјеље|:count недјеља', + 'week_ago' => ':count недјељу|:count недјеље|:count недјеља', + + 'diff_now' => 'управо сада', + 'diff_today' => 'данас', + 'diff_today_regexp' => 'данас(?:\\s+у)?', + 'diff_yesterday' => 'јуче', + 'diff_yesterday_regexp' => 'јуче(?:\\s+у)?', + 'diff_tomorrow' => 'сутра', + 'diff_tomorrow_regexp' => 'сутра(?:\\s+у)?', + 'diff_before_yesterday' => 'прекјуче', + 'diff_after_tomorrow' => 'прекосјутра', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D. MMMM YYYY', + 'LLL' => 'D. MMMM YYYY H:mm', + 'LLLL' => 'dddd, D. MMMM YYYY H:mm', + ], + 'calendar' => [ + 'sameDay' => '[данас у] LT', + 'nextDay' => '[сутра у] LT', + 'nextWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + return '[у недељу у] LT'; + case 3: + return '[у среду у] LT'; + case 6: + return '[у суботу у] LT'; + default: + return '[у] dddd [у] LT'; + } + }, + 'lastDay' => '[јуче у] LT', + 'lastWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + return '[прошле недеље у] LT'; + case 1: + return '[прошлог понедељка у] LT'; + case 2: + return '[прошлог уторка у] LT'; + case 3: + return '[прошле среде у] LT'; + case 4: + return '[прошлог четвртка у] LT'; + case 5: + return '[прошлог петка у] LT'; + default: + return '[прошле суботе у] LT'; + } + }, + 'sameElse' => 'L', + ], + 'ordinal' => ':number.', + 'months' => ['јануар', 'фебруар', 'март', 'април', 'мај', 'јун', 'јул', 'август', 'септембар', 'октобар', 'новембар', 'децембар'], + 'months_short' => ['јан.', 'феб.', 'мар.', 'апр.', 'мај', 'јун', 'јул', 'авг.', 'сеп.', 'окт.', 'нов.', 'дец.'], + 'weekdays' => ['недеља', 'понедељак', 'уторак', 'среда', 'четвртак', 'петак', 'субота'], + 'weekdays_short' => ['нед.', 'пон.', 'уто.', 'сре.', 'чет.', 'пет.', 'суб.'], + 'weekdays_min' => ['не', 'по', 'ут', 'ср', 'че', 'пе', 'су'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' и '], + 'meridiem' => ['АМ', 'ПМ'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl_XK.php b/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl_XK.php new file mode 100644 index 0000000..d6e29b8 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl_XK.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Component\Translation\PluralizationRules; + +// @codeCoverageIgnoreStart +if (class_exists(PluralizationRules::class)) { + PluralizationRules::set(static function ($number) { + return PluralizationRules::get($number, 'sr'); + }, 'sr_Cyrl_XK'); +} +// @codeCoverageIgnoreEnd + +return array_replace_recursive(require __DIR__.'/sr_Cyrl_BA.php', [ + 'weekdays' => ['недеља', 'понедељак', 'уторак', 'среда', 'четвртак', 'петак', 'субота'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sr_Latn.php b/vendor/nesbot/carbon/src/Carbon/Lang/sr_Latn.php new file mode 100644 index 0000000..9971674 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sr_Latn.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.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sr_Latn_BA.php b/vendor/nesbot/carbon/src/Carbon/Lang/sr_Latn_BA.php new file mode 100644 index 0000000..95b2770 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sr_Latn_BA.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Component\Translation\PluralizationRules; + +// @codeCoverageIgnoreStart +if (class_exists(PluralizationRules::class)) { + PluralizationRules::set(static function ($number) { + return PluralizationRules::get($number, 'sr'); + }, 'sr_Latn_BA'); +} +// @codeCoverageIgnoreEnd + +return array_replace_recursive(require __DIR__.'/sr_Latn.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D.M.yy.', + 'LL' => 'DD.MM.YYYY.', + 'LLL' => 'DD. MMMM YYYY. HH:mm', + 'LLLL' => 'dddd, DD. MMMM YYYY. HH:mm', + ], + 'weekdays' => ['nedjelja', 'ponedeljak', 'utorak', 'srijeda', 'četvrtak', 'petak', 'subota'], + 'weekdays_short' => ['ned.', 'pon.', 'ut.', 'sr.', 'čet.', 'pet.', 'sub.'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sr_Latn_ME.php b/vendor/nesbot/carbon/src/Carbon/Lang/sr_Latn_ME.php new file mode 100644 index 0000000..5b8f2d0 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sr_Latn_ME.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Glavić + * - Milos Sakovic + */ + +use Carbon\CarbonInterface; +use Symfony\Component\Translation\PluralizationRules; + +// @codeCoverageIgnoreStart +if (class_exists(PluralizationRules::class)) { + PluralizationRules::set(static function ($number) { + return PluralizationRules::get($number, 'sr'); + }, 'sr_Latn_ME'); +} +// @codeCoverageIgnoreEnd + +return array_replace_recursive(require __DIR__.'/sr.php', [ + 'month' => ':count mjesec|:count mjeseca|:count mjeseci', + 'week' => ':count nedjelja|:count nedjelje|:count nedjelja', + 'second' => ':count sekund|:count sekunde|:count sekundi', + 'ago' => 'prije :time', + 'from_now' => 'za :time', + 'after' => ':time nakon', + 'before' => ':time prije', + 'week_from_now' => ':count nedjelju|:count nedjelje|:count nedjelja', + 'week_ago' => ':count nedjelju|:count nedjelje|:count nedjelja', + 'second_ago' => ':count sekund|:count sekunde|:count sekundi', + 'diff_tomorrow' => 'sjutra', + 'calendar' => [ + 'nextDay' => '[sjutra u] LT', + 'nextWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + return '[u nedjelju u] LT'; + case 3: + return '[u srijedu u] LT'; + case 6: + return '[u subotu u] LT'; + default: + return '[u] dddd [u] LT'; + } + }, + 'lastWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + return '[prošle nedjelje u] LT'; + case 1: + return '[prošle nedjelje u] LT'; + case 2: + return '[prošlog utorka u] LT'; + case 3: + return '[prošle srijede u] LT'; + case 4: + return '[prošlog četvrtka u] LT'; + case 5: + return '[prošlog petka u] LT'; + default: + return '[prošle subote u] LT'; + } + }, + ], + 'weekdays' => ['nedjelja', 'ponedjeljak', 'utorak', 'srijeda', 'četvrtak', 'petak', 'subota'], + 'weekdays_short' => ['ned.', 'pon.', 'uto.', 'sri.', 'čet.', 'pet.', 'sub.'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sr_Latn_XK.php b/vendor/nesbot/carbon/src/Carbon/Lang/sr_Latn_XK.php new file mode 100644 index 0000000..5278e2e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sr_Latn_XK.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Component\Translation\PluralizationRules; + +// @codeCoverageIgnoreStart +if (class_exists(PluralizationRules::class)) { + PluralizationRules::set(static function ($number) { + return PluralizationRules::get($number, 'sr'); + }, 'sr_Latn_XK'); +} +// @codeCoverageIgnoreEnd + +return array_replace_recursive(require __DIR__.'/sr_Latn_BA.php', [ + 'weekdays' => ['nedelja', 'ponedeljak', 'utorak', 'sreda', 'četvrtak', 'petak', 'subota'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sr_ME.php b/vendor/nesbot/carbon/src/Carbon/Lang/sr_ME.php new file mode 100644 index 0000000..d7c65b9 --- /dev/null +++ b/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/vendor/nesbot/carbon/src/Carbon/Lang/sr_RS.php b/vendor/nesbot/carbon/src/Carbon/Lang/sr_RS.php new file mode 100644 index 0000000..bc5e04b --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sr_RS.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - sr_YU, sr_CS locale Danilo Segan bug-glibc-locales@gnu.org + */ +return require __DIR__.'/sr_Cyrl.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sr_RS@latin.php b/vendor/nesbot/carbon/src/Carbon/Lang/sr_RS@latin.php new file mode 100644 index 0000000..9971674 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sr_RS@latin.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.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ss.php b/vendor/nesbot/carbon/src/Carbon/Lang/ss.php new file mode 100644 index 0000000..1c52c9b --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ss.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - Nicolai Davies + */ +return [ + 'year' => '{1}umnyaka|:count iminyaka', + 'month' => '{1}inyanga|:count tinyanga', + 'week' => '{1}:count liviki|:count emaviki', + 'day' => '{1}lilanga|:count emalanga', + 'hour' => '{1}lihora|:count emahora', + 'minute' => '{1}umzuzu|:count emizuzu', + 'second' => '{1}emizuzwana lomcane|:count mzuzwana', + 'ago' => 'wenteka nga :time', + 'from_now' => 'nga :time', + 'diff_yesterday' => 'Itolo', + 'diff_yesterday_regexp' => 'Itolo(?:\\s+nga)?', + 'diff_today' => 'Namuhla', + 'diff_today_regexp' => 'Namuhla(?:\\s+nga)?', + 'diff_tomorrow' => 'Kusasa', + 'diff_tomorrow_regexp' => 'Kusasa(?:\\s+nga)?', + 'formats' => [ + 'LT' => 'h:mm A', + 'LTS' => 'h:mm:ss A', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm A', + 'LLLL' => 'dddd, D MMMM YYYY h:mm A', + ], + 'calendar' => [ + 'sameDay' => '[Namuhla nga] LT', + 'nextDay' => '[Kusasa nga] LT', + 'nextWeek' => 'dddd [nga] LT', + 'lastDay' => '[Itolo nga] LT', + 'lastWeek' => 'dddd [leliphelile] [nga] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + $lastDigit = $number % 10; + + return $number.( + ((int) ($number % 100 / 10) === 1) ? 'e' : ( + ($lastDigit === 1 || $lastDigit === 2) ? 'a' : 'e' + ) + ); + }, + 'meridiem' => function ($hour) { + if ($hour < 11) { + return 'ekuseni'; + } + if ($hour < 15) { + return 'emini'; + } + if ($hour < 19) { + return 'entsambama'; + } + + return 'ebusuku'; + }, + 'months' => ['Bhimbidvwane', 'Indlovana', 'Indlov\'lenkhulu', 'Mabasa', 'Inkhwekhweti', 'Inhlaba', 'Kholwane', 'Ingci', 'Inyoni', 'Imphala', 'Lweti', 'Ingongoni'], + 'months_short' => ['Bhi', 'Ina', 'Inu', 'Mab', 'Ink', 'Inh', 'Kho', 'Igc', 'Iny', 'Imp', 'Lwe', 'Igo'], + 'weekdays' => ['Lisontfo', 'Umsombuluko', 'Lesibili', 'Lesitsatfu', 'Lesine', 'Lesihlanu', 'Umgcibelo'], + 'weekdays_short' => ['Lis', 'Umb', 'Lsb', 'Les', 'Lsi', 'Lsh', 'Umg'], + 'weekdays_min' => ['Li', 'Us', 'Lb', 'Lt', 'Ls', 'Lh', 'Ug'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ss_ZA.php b/vendor/nesbot/carbon/src/Carbon/Lang/ss_ZA.php new file mode 100644 index 0000000..ba89527 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ss_ZA.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__.'/ss.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/st.php b/vendor/nesbot/carbon/src/Carbon/Lang/st.php new file mode 100644 index 0000000..b065445 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/st.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/st_ZA.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/st_ZA.php b/vendor/nesbot/carbon/src/Carbon/Lang/st_ZA.php new file mode 100644 index 0000000..5bce7f2 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/st_ZA.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Zuza Software Foundation (Translate.org.za) Dwayne Bailey dwayne@translate.org.za + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Pherekgong', 'Hlakola', 'Tlhakubele', 'Mmese', 'Motsheanong', 'Phupjane', 'Phupu', 'Phato', 'Leotse', 'Mphalane', 'Pudungwana', 'Tshitwe'], + 'months_short' => ['Phe', 'Hla', 'TlH', 'Mme', 'Mot', 'Jan', 'Upu', 'Pha', 'Leo', 'Mph', 'Pud', 'Tsh'], + 'weekdays' => ['Sontaha', 'Mantaha', 'Labobedi', 'Laboraro', 'Labone', 'Labohlano', 'Moqebelo'], + 'weekdays_short' => ['Son', 'Mma', 'Bed', 'Rar', 'Ne', 'Hla', 'Moq'], + 'weekdays_min' => ['Son', 'Mma', 'Bed', 'Rar', 'Ne', 'Hla', 'Moq'], + 'day_of_first_week_of_year' => 1, + + 'week' => ':count Sontaha', // less reliable + 'w' => ':count Sontaha', // less reliable + 'a_week' => ':count Sontaha', // less reliable + + 'day' => ':count letsatsi', // less reliable + 'd' => ':count letsatsi', // less reliable + 'a_day' => ':count letsatsi', // less reliable + + 'hour' => ':count sešupanako', // less reliable + 'h' => ':count sešupanako', // less reliable + 'a_hour' => ':count sešupanako', // less reliable + + 'minute' => ':count menyane', // less reliable + 'min' => ':count menyane', // less reliable + 'a_minute' => ':count menyane', // less reliable + + 'second' => ':count thusa', // less reliable + 's' => ':count thusa', // less reliable + 'a_second' => ':count thusa', // less reliable + + 'year' => ':count selemo', + 'y' => ':count selemo', + 'a_year' => ':count selemo', + + 'month' => ':count kgwedi', + 'm' => ':count kgwedi', + 'a_month' => ':count kgwedi', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sv.php b/vendor/nesbot/carbon/src/Carbon/Lang/sv.php new file mode 100644 index 0000000..1706c71 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sv.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - Kristoffer Snabb + * - JD Isaacks + * - Jens Herlevsen + * - Nightpine + * - Anders Nygren (litemerafrukt) + */ +return [ + 'year' => ':count år', + 'a_year' => 'ett år|:count år', + 'y' => ':count år', + 'month' => ':count månad|:count månader', + 'a_month' => 'en månad|:count månader', + 'm' => ':count mån', + 'week' => ':count vecka|:count veckor', + 'a_week' => 'en vecka|:count veckor', + 'w' => ':count v', + 'day' => ':count dag|:count dagar', + 'a_day' => 'en dag|:count dagar', + 'd' => ':count dgr', + 'hour' => ':count timme|:count timmar', + 'a_hour' => 'en timme|:count timmar', + 'h' => ':count tim', + 'minute' => ':count minut|:count minuter', + 'a_minute' => 'en minut|:count minuter', + 'min' => ':count min', + 'second' => ':count sekund|:count sekunder', + 'a_second' => 'några sekunder|:count sekunder', + 's' => ':count s', + 'ago' => 'för :time sedan', + 'from_now' => 'om :time', + 'after' => ':time efter', + 'before' => ':time före', + 'diff_now' => 'nu', + 'diff_today' => 'I dag', + 'diff_yesterday' => 'i går', + 'diff_yesterday_regexp' => 'I går', + 'diff_tomorrow' => 'i morgon', + 'diff_tomorrow_regexp' => 'I morgon', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-DD', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY [kl.] HH:mm', + 'LLLL' => 'dddd D MMMM YYYY [kl.] HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[I dag] LT', + 'nextDay' => '[I morgon] LT', + 'nextWeek' => '[På] dddd LT', + 'lastDay' => '[I går] LT', + 'lastWeek' => '[I] dddd[s] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + $lastDigit = $number % 10; + + return $number.( + ((int) ($number % 100 / 10) === 1) ? 'e' : ( + ($lastDigit === 1 || $lastDigit === 2) ? 'a' : 'e' + ) + ); + }, + 'months' => ['januari', 'februari', 'mars', 'april', 'maj', 'juni', 'juli', 'augusti', 'september', 'oktober', 'november', 'december'], + 'months_short' => ['jan', 'feb', 'mar', 'apr', 'maj', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'], + 'weekdays' => ['söndag', 'måndag', 'tisdag', 'onsdag', 'torsdag', 'fredag', 'lördag'], + 'weekdays_short' => ['sön', 'mån', 'tis', 'ons', 'tors', 'fre', 'lör'], + 'weekdays_min' => ['sö', 'må', 'ti', 'on', 'to', 'fr', 'lö'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' och '], + 'meridiem' => ['fm', 'em'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sv_AX.php b/vendor/nesbot/carbon/src/Carbon/Lang/sv_AX.php new file mode 100644 index 0000000..70cc558 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sv_AX.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/sv.php', [ + 'formats' => [ + 'L' => 'YYYY-MM-dd', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sv_FI.php b/vendor/nesbot/carbon/src/Carbon/Lang/sv_FI.php new file mode 100644 index 0000000..d7182c8 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sv_FI.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__.'/sv.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sv_SE.php b/vendor/nesbot/carbon/src/Carbon/Lang/sv_SE.php new file mode 100644 index 0000000..d7182c8 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sv_SE.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__.'/sv.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sw.php b/vendor/nesbot/carbon/src/Carbon/Lang/sw.php new file mode 100644 index 0000000..f8630d5 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sw.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - leyluj + * - Josh Soref + * - ryanhart2 + */ +return [ + 'year' => 'mwaka :count|miaka :count', + 'a_year' => 'mwaka mmoja|miaka :count', + 'y' => 'mwaka :count|miaka :count', + 'month' => 'mwezi :count|miezi :count', + 'a_month' => 'mwezi mmoja|miezi :count', + 'm' => 'mwezi :count|miezi :count', + 'week' => 'wiki :count', + 'a_week' => 'wiki mmoja|wiki :count', + 'w' => 'w. :count', + 'day' => 'siku :count', + 'a_day' => 'siku moja|masiku :count', + 'd' => 'si. :count', + 'hour' => 'saa :count|masaa :count', + 'a_hour' => 'saa limoja|masaa :count', + 'h' => 'saa :count|masaa :count', + 'minute' => 'dakika :count', + 'a_minute' => 'dakika moja|dakika :count', + 'min' => 'd. :count', + 'second' => 'sekunde :count', + 'a_second' => 'hivi punde|sekunde :count', + 's' => 'se. :count', + 'ago' => 'tokea :time', + 'from_now' => ':time baadaye', + 'after' => ':time baada', + 'before' => ':time kabla', + 'diff_now' => 'sasa hivi', + 'diff_today' => 'leo', + 'diff_today_regexp' => 'leo(?:\\s+saa)?', + 'diff_yesterday' => 'jana', + 'diff_tomorrow' => 'kesho', + 'diff_tomorrow_regexp' => 'kesho(?:\\s+saa)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[leo saa] LT', + 'nextDay' => '[kesho saa] LT', + 'nextWeek' => '[wiki ijayo] dddd [saat] LT', + 'lastDay' => '[jana] LT', + 'lastWeek' => '[wiki iliyopita] dddd [saat] LT', + 'sameElse' => 'L', + ], + 'months' => ['Januari', 'Februari', 'Machi', 'Aprili', 'Mei', 'Juni', 'Julai', 'Agosti', 'Septemba', 'Oktoba', 'Novemba', 'Desemba'], + 'months_short' => ['Jan', 'Feb', 'Mac', 'Apr', 'Mei', 'Jun', 'Jul', 'Ago', 'Sep', 'Okt', 'Nov', 'Des'], + 'weekdays' => ['Jumapili', 'Jumatatu', 'Jumanne', 'Jumatano', 'Alhamisi', 'Ijumaa', 'Jumamosi'], + 'weekdays_short' => ['Jpl', 'Jtat', 'Jnne', 'Jtan', 'Alh', 'Ijm', 'Jmos'], + 'weekdays_min' => ['J2', 'J3', 'J4', 'J5', 'Al', 'Ij', 'J1'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' na '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sw_CD.php b/vendor/nesbot/carbon/src/Carbon/Lang/sw_CD.php new file mode 100644 index 0000000..ec9117b --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sw_CD.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/sw.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sw_KE.php b/vendor/nesbot/carbon/src/Carbon/Lang/sw_KE.php new file mode 100644 index 0000000..2ace0db --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sw_KE.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Kamusi Project Martin Benjamin locales@kamusi.org + */ +return array_replace_recursive(require __DIR__.'/sw.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Januari', 'Februari', 'Machi', 'Aprili', 'Mei', 'Juni', 'Julai', 'Agosti', 'Septemba', 'Oktoba', 'Novemba', 'Desemba'], + 'months_short' => ['Jan', 'Feb', 'Mac', 'Apr', 'Mei', 'Jun', 'Jul', 'Ago', 'Sep', 'Okt', 'Nov', 'Des'], + 'weekdays' => ['Jumapili', 'Jumatatu', 'Jumanne', 'Jumatano', 'Alhamisi', 'Ijumaa', 'Jumamosi'], + 'weekdays_short' => ['J2', 'J3', 'J4', 'J5', 'Alh', 'Ij', 'J1'], + 'weekdays_min' => ['J2', 'J3', 'J4', 'J5', 'Alh', 'Ij', 'J1'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['asubuhi', 'alasiri'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sw_TZ.php b/vendor/nesbot/carbon/src/Carbon/Lang/sw_TZ.php new file mode 100644 index 0000000..fab3cd6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sw_TZ.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Kamusi Project Martin Benjamin locales@kamusi.org + */ +return array_replace_recursive(require __DIR__.'/sw.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Januari', 'Februari', 'Machi', 'Aprili', 'Mei', 'Juni', 'Julai', 'Agosti', 'Septemba', 'Oktoba', 'Novemba', 'Desemba'], + 'months_short' => ['Jan', 'Feb', 'Mac', 'Apr', 'Mei', 'Jun', 'Jul', 'Ago', 'Sep', 'Okt', 'Nov', 'Des'], + 'weekdays' => ['Jumapili', 'Jumatatu', 'Jumanne', 'Jumatano', 'Alhamisi', 'Ijumaa', 'Jumamosi'], + 'weekdays_short' => ['J2', 'J3', 'J4', 'J5', 'Alh', 'Ij', 'J1'], + 'weekdays_min' => ['J2', 'J3', 'J4', 'J5', 'Alh', 'Ij', 'J1'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['asubuhi', 'alasiri'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sw_UG.php b/vendor/nesbot/carbon/src/Carbon/Lang/sw_UG.php new file mode 100644 index 0000000..ec9117b --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sw_UG.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/sw.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/szl.php b/vendor/nesbot/carbon/src/Carbon/Lang/szl.php new file mode 100644 index 0000000..4429c4f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/szl.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/szl_PL.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/szl_PL.php b/vendor/nesbot/carbon/src/Carbon/Lang/szl_PL.php new file mode 100644 index 0000000..9adddcf --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/szl_PL.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - szl_PL locale Przemyslaw Buczkowski libc-alpha@sourceware.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['styczyń', 'luty', 'merc', 'kwjeciyń', 'moj', 'czyrwjyń', 'lipjyń', 'siyrpjyń', 'wrzesiyń', 'październik', 'listopad', 'grudziyń'], + 'months_short' => ['sty', 'lut', 'mer', 'kwj', 'moj', 'czy', 'lip', 'siy', 'wrz', 'paź', 'lis', 'gru'], + 'weekdays' => ['niydziela', 'pyńdziŏek', 'wtŏrek', 'strzŏda', 'sztwortek', 'pjōntek', 'sobŏta'], + 'weekdays_short' => ['niy', 'pyń', 'wtŏ', 'str', 'szt', 'pjō', 'sob'], + 'weekdays_min' => ['niy', 'pyń', 'wtŏ', 'str', 'szt', 'pjō', 'sob'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'year' => ':count rok', + 'y' => ':count rok', + 'a_year' => ':count rok', + + 'month' => ':count mjeśůnc', + 'm' => ':count mjeśůnc', + 'a_month' => ':count mjeśůnc', + + 'week' => ':count tydźyń', + 'w' => ':count tydźyń', + 'a_week' => ':count tydźyń', + + 'day' => ':count dźyń', + 'd' => ':count dźyń', + 'a_day' => ':count dźyń', + + 'hour' => ':count godzina', + 'h' => ':count godzina', + 'a_hour' => ':count godzina', + + 'minute' => ':count minuta', + 'min' => ':count minuta', + 'a_minute' => ':count minuta', + + 'second' => ':count sekůnda', + 's' => ':count sekůnda', + 'a_second' => ':count sekůnda', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ta.php b/vendor/nesbot/carbon/src/Carbon/Lang/ta.php new file mode 100644 index 0000000..c1d89cb --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ta.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - François B + * - JD Isaacks + * - Satheez + */ +return [ + 'year' => ':count வருடம்|:count ஆண்டுகள்', + 'a_year' => 'ஒரு வருடம்|:count ஆண்டுகள்', + 'y' => ':count வருட.|:count ஆண்.', + 'month' => ':count மாதம்|:count மாதங்கள்', + 'a_month' => 'ஒரு மாதம்|:count மாதங்கள்', + 'm' => ':count மாத.', + 'week' => ':count வாரம்|:count வாரங்கள்', + 'a_week' => 'ஒரு வாரம்|:count வாரங்கள்', + 'w' => ':count வார.', + 'day' => ':count நாள்|:count நாட்கள்', + 'a_day' => 'ஒரு நாள்|:count நாட்கள்', + 'd' => ':count நாள்|:count நாட்.', + 'hour' => ':count மணி நேரம்|:count மணி நேரம்', + 'a_hour' => 'ஒரு மணி நேரம்|:count மணி நேரம்', + 'h' => ':count மணி.', + 'minute' => ':count நிமிடம்|:count நிமிடங்கள்', + 'a_minute' => 'ஒரு நிமிடம்|:count நிமிடங்கள்', + 'min' => ':count நிமி.', + 'second' => ':count சில விநாடிகள்|:count விநாடிகள்', + 'a_second' => 'ஒரு சில விநாடிகள்|:count விநாடிகள்', + 's' => ':count விநா.', + 'ago' => ':time முன்', + 'from_now' => ':time இல்', + 'before' => ':time முன்', + 'after' => ':time பின்', + 'diff_now' => 'இப்போது', + 'diff_today' => 'இன்று', + 'diff_yesterday' => 'நேற்று', + 'diff_tomorrow' => 'நாளை', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY, HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY, HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[இன்று] LT', + 'nextDay' => '[நாளை] LT', + 'nextWeek' => 'dddd, LT', + 'lastDay' => '[நேற்று] LT', + 'lastWeek' => '[கடந்த வாரம்] dddd, LT', + 'sameElse' => 'L', + ], + 'ordinal' => ':numberவது', + 'meridiem' => function ($hour) { + if ($hour < 2) { + return ' யாமம்'; + } + if ($hour < 6) { + return ' வைகறை'; + } + if ($hour < 10) { + return ' காலை'; + } + if ($hour < 14) { + return ' நண்பகல்'; + } + if ($hour < 18) { + return ' எற்பாடு'; + } + if ($hour < 22) { + return ' மாலை'; + } + + return ' யாமம்'; + }, + 'months' => ['ஜனவரி', 'பிப்ரவரி', 'மார்ச்', 'ஏப்ரல்', 'மே', 'ஜூன்', 'ஜூலை', 'ஆகஸ்ட்', 'செப்டெம்பர்', 'அக்டோபர்', 'நவம்பர்', 'டிசம்பர்'], + 'months_short' => ['ஜனவரி', 'பிப்ரவரி', 'மார்ச்', 'ஏப்ரல்', 'மே', 'ஜூன்', 'ஜூலை', 'ஆகஸ்ட்', 'செப்டெம்பர்', 'அக்டோபர்', 'நவம்பர்', 'டிசம்பர்'], + 'weekdays' => ['ஞாயிற்றுக்கிழமை', 'திங்கட்கிழமை', 'செவ்வாய்கிழமை', 'புதன்கிழமை', 'வியாழக்கிழமை', 'வெள்ளிக்கிழமை', 'சனிக்கிழமை'], + 'weekdays_short' => ['ஞாயிறு', 'திங்கள்', 'செவ்வாய்', 'புதன்', 'வியாழன்', 'வெள்ளி', 'சனி'], + 'weekdays_min' => ['ஞா', 'தி', 'செ', 'பு', 'வி', 'வெ', 'ச'], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' மற்றும் '], + 'weekend' => [0, 0], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ta_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/ta_IN.php new file mode 100644 index 0000000..492d4c5 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ta_IN.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/ta.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['ஜனவரி', 'பிப்ரவரி', 'மார்ச்', 'ஏப்ரல்', 'மே', 'ஜூன்', 'ஜூலை', 'ஆகஸ்ட்', 'செப்டம்பர்', 'அக்டோபர்', 'நவம்பர்', 'டிசம்பர்'], + 'months_short' => ['ஜன.', 'பிப்.', 'மார்.', 'ஏப்.', 'மே', 'ஜூன்', 'ஜூலை', 'ஆக.', 'செப்.', 'அக்.', 'நவ.', 'டிச.'], + 'weekdays' => ['ஞாயிறு', 'திங்கள்', 'செவ்வாய்', 'புதன்', 'வியாழன்', 'வெள்ளி', 'சனி'], + 'weekdays_short' => ['ஞா', 'தி', 'செ', 'பு', 'வி', 'வெ', 'ச'], + 'weekdays_min' => ['ஞா', 'தி', 'செ', 'பு', 'வி', 'வெ', 'ச'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['காலை', 'மாலை'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ta_LK.php b/vendor/nesbot/carbon/src/Carbon/Lang/ta_LK.php new file mode 100644 index 0000000..8e2afbf --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ta_LK.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - J.Yogaraj 94-777-315206 yogaraj.ubuntu@gmail.com + */ +return array_replace_recursive(require __DIR__.'/ta.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['ஜனவரி', 'பிப்ரவரி', 'மார்ச்', 'ஏப்ரல்', 'மே', 'ஜூன்', 'ஜூலை', 'ஆகஸ்ட்', 'செப்டம்பர்', 'அக்டோபர்', 'நவம்பர்', 'டிசம்பர்'], + 'months_short' => ['ஜன', 'பிப்', 'மார்', 'ஏப்', 'மே', 'ஜூன்', 'ஜூலை', 'ஆக', 'செப்', 'அக்', 'நவ', 'டிச'], + 'weekdays' => ['ஞாயிறு', 'திங்கள்', 'செவ்வாய்', 'புதன்', 'வியாழன்', 'வெள்ளி', 'சனி'], + 'weekdays_short' => ['ஞா', 'தி', 'செ', 'பு', 'வி', 'வெ', 'ச'], + 'weekdays_min' => ['ஞா', 'தி', 'செ', 'பு', 'வி', 'வெ', 'ச'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['காலை', 'மாலை'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ta_MY.php b/vendor/nesbot/carbon/src/Carbon/Lang/ta_MY.php new file mode 100644 index 0000000..a6cd8b5 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ta_MY.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ta.php', [ + 'formats' => [ + 'LT' => 'a h:mm', + 'LTS' => 'a h:mm:ss', + 'L' => 'D/M/yy', + 'LL' => 'D MMM, YYYY', + 'LLL' => 'D MMMM, YYYY, a h:mm', + 'LLLL' => 'dddd, D MMMM, YYYY, a h:mm', + ], + 'months' => ['ஜனவரி', 'பிப்ரவரி', 'மார்ச்', 'ஏப்ரல்', 'மே', 'ஜூன்', 'ஜூலை', 'ஆகஸ்ட்', 'செப்டம்பர்', 'அக்டோபர்', 'நவம்பர்', 'டிசம்பர்'], + 'months_short' => ['ஜன.', 'பிப்.', 'மார்.', 'ஏப்.', 'மே', 'ஜூன்', 'ஜூலை', 'ஆக.', 'செப்.', 'அக்.', 'நவ.', 'டிச.'], + 'weekdays' => ['ஞாயிறு', 'திங்கள்', 'செவ்வாய்', 'புதன்', 'வியாழன்', 'வெள்ளி', 'சனி'], + 'weekdays_short' => ['ஞாயி.', 'திங்.', 'செவ்.', 'புத.', 'வியா.', 'வெள்.', 'சனி'], + 'weekdays_min' => ['ஞா', 'தி', 'செ', 'பு', 'வி', 'வெ', 'ச'], + 'first_day_of_week' => 1, + 'meridiem' => ['மு.ப', 'பி.ப'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ta_SG.php b/vendor/nesbot/carbon/src/Carbon/Lang/ta_SG.php new file mode 100644 index 0000000..7dbedee --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ta_SG.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ta.php', [ + 'formats' => [ + 'LT' => 'a h:mm', + 'LTS' => 'a h:mm:ss', + 'L' => 'D/M/yy', + 'LL' => 'D MMM, YYYY', + 'LLL' => 'D MMMM, YYYY, a h:mm', + 'LLLL' => 'dddd, D MMMM, YYYY, a h:mm', + ], + 'months' => ['ஜனவரி', 'பிப்ரவரி', 'மார்ச்', 'ஏப்ரல்', 'மே', 'ஜூன்', 'ஜூலை', 'ஆகஸ்ட்', 'செப்டம்பர்', 'அக்டோபர்', 'நவம்பர்', 'டிசம்பர்'], + 'months_short' => ['ஜன.', 'பிப்.', 'மார்.', 'ஏப்.', 'மே', 'ஜூன்', 'ஜூலை', 'ஆக.', 'செப்.', 'அக்.', 'நவ.', 'டிச.'], + 'weekdays' => ['ஞாயிறு', 'திங்கள்', 'செவ்வாய்', 'புதன்', 'வியாழன்', 'வெள்ளி', 'சனி'], + 'weekdays_short' => ['ஞாயி.', 'திங்.', 'செவ்.', 'புத.', 'வியா.', 'வெள்.', 'சனி'], + 'weekdays_min' => ['ஞா', 'தி', 'செ', 'பு', 'வி', 'வெ', 'ச'], + 'meridiem' => ['மு.ப', 'பி.ப'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/tcy.php b/vendor/nesbot/carbon/src/Carbon/Lang/tcy.php new file mode 100644 index 0000000..2eb9905 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/tcy.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/tcy_IN.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/tcy_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/tcy_IN.php new file mode 100644 index 0000000..2ff20e0 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/tcy_IN.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IndLinux.org, Samsung Electronics Co., Ltd. alexey.merzlyakov@samsung.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['ಜನವರಿ', 'ಫೆಬ್ರುವರಿ', 'ಮಾರ್ಚ್', 'ಏಪ್ರಿಲ್‌‌', 'ಮೇ', 'ಜೂನ್', 'ಜುಲೈ', 'ಆಗಸ್ಟ್', 'ಸೆಪ್ಟೆಂಬರ್‌', 'ಅಕ್ಟೋಬರ್', 'ನವೆಂಬರ್', 'ಡಿಸೆಂಬರ್'], + 'months_short' => ['ಜ', 'ಫೆ', 'ಮಾ', 'ಏ', 'ಮೇ', 'ಜೂ', 'ಜು', 'ಆ', 'ಸೆ', 'ಅ', 'ನ', 'ಡಿ'], + 'weekdays' => ['ಐಥಾರ', 'ಸೋಮಾರ', 'ಅಂಗರೆ', 'ಬುಧಾರ', 'ಗುರುವಾರ', 'ಶುಕ್ರರ', 'ಶನಿವಾರ'], + 'weekdays_short' => ['ಐ', 'ಸೋ', 'ಅಂ', 'ಬು', 'ಗು', 'ಶು', 'ಶ'], + 'weekdays_min' => ['ಐ', 'ಸೋ', 'ಅಂ', 'ಬು', 'ಗು', 'ಶು', 'ಶ'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['ಕಾಂಡೆ', 'ಬಯ್ಯ'], + + 'year' => ':count ನೀರ್', // less reliable + 'y' => ':count ನೀರ್', // less reliable + 'a_year' => ':count ನೀರ್', // less reliable + + 'month' => ':count ಮೀನ್', // less reliable + 'm' => ':count ಮೀನ್', // less reliable + 'a_month' => ':count ಮೀನ್', // less reliable + + 'day' => ':count ಸುಗ್ಗಿ', // less reliable + 'd' => ':count ಸುಗ್ಗಿ', // less reliable + 'a_day' => ':count ಸುಗ್ಗಿ', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/te.php b/vendor/nesbot/carbon/src/Carbon/Lang/te.php new file mode 100644 index 0000000..ac38218 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/te.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Kunal Marwaha + * - Josh Soref + * - François B + * - kc + */ +return [ + 'year' => ':count సంవత్సరం|:count సంవత్సరాలు', + 'a_year' => 'ఒక సంవత్సరం|:count సంవత్సరాలు', + 'y' => ':count సం.', + 'month' => ':count నెల|:count నెలలు', + 'a_month' => 'ఒక నెల|:count నెలలు', + 'm' => ':count నెల|:count నెల.', + 'week' => ':count వారం|:count వారాలు', + 'a_week' => 'ఒక వారం|:count వారాలు', + 'w' => ':count వార.|:count వారా.', + 'day' => ':count రోజు|:count రోజులు', + 'a_day' => 'ఒక రోజు|:count రోజులు', + 'd' => ':count రోజు|:count రోజు.', + 'hour' => ':count గంట|:count గంటలు', + 'a_hour' => 'ఒక గంట|:count గంటలు', + 'h' => ':count గం.', + 'minute' => ':count నిమిషం|:count నిమిషాలు', + 'a_minute' => 'ఒక నిమిషం|:count నిమిషాలు', + 'min' => ':count నిమి.', + 'second' => ':count సెకను|:count సెకన్లు', + 'a_second' => 'కొన్ని క్షణాలు|:count సెకన్లు', + 's' => ':count సెక.', + 'ago' => ':time క్రితం', + 'from_now' => ':time లో', + 'diff_now' => 'ప్రస్తుతం', + 'diff_today' => 'నేడు', + 'diff_yesterday' => 'నిన్న', + 'diff_tomorrow' => 'రేపు', + 'formats' => [ + 'LT' => 'A h:mm', + 'LTS' => 'A h:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY, A h:mm', + 'LLLL' => 'dddd, D MMMM YYYY, A h:mm', + ], + 'calendar' => [ + 'sameDay' => '[నేడు] LT', + 'nextDay' => '[రేపు] LT', + 'nextWeek' => 'dddd, LT', + 'lastDay' => '[నిన్న] LT', + 'lastWeek' => '[గత] dddd, LT', + 'sameElse' => 'L', + ], + 'ordinal' => ':numberవ', + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'రాత్రి'; + } + if ($hour < 10) { + return 'ఉదయం'; + } + if ($hour < 17) { + return 'మధ్యాహ్నం'; + } + if ($hour < 20) { + return 'సాయంత్రం'; + } + + return ' రాత్రి'; + }, + 'months' => ['జనవరి', 'ఫిబ్రవరి', 'మార్చి', 'ఏప్రిల్', 'మే', 'జూన్', 'జూలై', 'ఆగస్టు', 'సెప్టెంబర్', 'అక్టోబర్', 'నవంబర్', 'డిసెంబర్'], + 'months_short' => ['జన.', 'ఫిబ్ర.', 'మార్చి', 'ఏప్రి.', 'మే', 'జూన్', 'జూలై', 'ఆగ.', 'సెప్.', 'అక్టో.', 'నవ.', 'డిసె.'], + 'weekdays' => ['ఆదివారం', 'సోమవారం', 'మంగళవారం', 'బుధవారం', 'గురువారం', 'శుక్రవారం', 'శనివారం'], + 'weekdays_short' => ['ఆది', 'సోమ', 'మంగళ', 'బుధ', 'గురు', 'శుక్ర', 'శని'], + 'weekdays_min' => ['ఆ', 'సో', 'మం', 'బు', 'గు', 'శు', 'శ'], + 'list' => ', ', + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, + 'weekend' => [0, 0], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/te_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/te_IN.php new file mode 100644 index 0000000..3963f8d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/te_IN.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__.'/te.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/teo.php b/vendor/nesbot/carbon/src/Carbon/Lang/teo.php new file mode 100644 index 0000000..ca30c37 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/teo.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ta.php', [ + 'meridiem' => ['Taparachu', 'Ebongi'], + 'weekdays' => ['Nakaejuma', 'Nakaebarasa', 'Nakaare', 'Nakauni', 'Nakaung’on', 'Nakakany', 'Nakasabiti'], + 'weekdays_short' => ['Jum', 'Bar', 'Aar', 'Uni', 'Ung', 'Kan', 'Sab'], + 'weekdays_min' => ['Jum', 'Bar', 'Aar', 'Uni', 'Ung', 'Kan', 'Sab'], + 'months' => ['Orara', 'Omuk', 'Okwamg’', 'Odung’el', 'Omaruk', 'Omodok’king’ol', 'Ojola', 'Opedel', 'Osokosokoma', 'Otibar', 'Olabor', 'Opoo'], + 'months_short' => ['Rar', 'Muk', 'Kwa', 'Dun', 'Mar', 'Mod', 'Jol', 'Ped', 'Sok', 'Tib', 'Lab', 'Poo'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/teo_KE.php b/vendor/nesbot/carbon/src/Carbon/Lang/teo_KE.php new file mode 100644 index 0000000..010a04f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/teo_KE.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/teo.php', [ + 'first_day_of_week' => 0, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/tet.php b/vendor/nesbot/carbon/src/Carbon/Lang/tet.php new file mode 100644 index 0000000..d0544d4 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/tet.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Joshua Brooks + * - François B + */ +return [ + 'year' => 'tinan :count', + 'a_year' => '{1}tinan ida|tinan :count', + 'month' => 'fulan :count', + 'a_month' => '{1}fulan ida|fulan :count', + 'week' => 'semana :count', + 'a_week' => '{1}semana ida|semana :count', + 'day' => 'loron :count', + 'a_day' => '{1}loron ida|loron :count', + 'hour' => 'oras :count', + 'a_hour' => '{1}oras ida|oras :count', + 'minute' => 'minutu :count', + 'a_minute' => '{1}minutu ida|minutu :count', + 'second' => 'segundu :count', + 'a_second' => '{1}segundu balun|segundu :count', + 'ago' => ':time liuba', + 'from_now' => 'iha :time', + 'diff_yesterday' => 'Horiseik', + 'diff_yesterday_regexp' => 'Horiseik(?:\\s+iha)?', + 'diff_today' => 'Ohin', + 'diff_today_regexp' => 'Ohin(?:\\s+iha)?', + 'diff_tomorrow' => 'Aban', + 'diff_tomorrow_regexp' => 'Aban(?:\\s+iha)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Ohin iha] LT', + 'nextDay' => '[Aban iha] LT', + 'nextWeek' => 'dddd [iha] LT', + 'lastDay' => '[Horiseik iha] LT', + 'lastWeek' => 'dddd [semana kotuk] [iha] LT', + 'sameElse' => 'L', + ], + 'ordinal' => ':numberº', + 'months' => ['Janeiru', 'Fevereiru', 'Marsu', 'Abril', 'Maiu', 'Juñu', 'Jullu', 'Agustu', 'Setembru', 'Outubru', 'Novembru', 'Dezembru'], + 'months_short' => ['Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez'], + 'weekdays' => ['Domingu', 'Segunda', 'Tersa', 'Kuarta', 'Kinta', 'Sesta', 'Sabadu'], + 'weekdays_short' => ['Dom', 'Seg', 'Ters', 'Kua', 'Kint', 'Sest', 'Sab'], + 'weekdays_min' => ['Do', 'Seg', 'Te', 'Ku', 'Ki', 'Ses', 'Sa'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/tg.php b/vendor/nesbot/carbon/src/Carbon/Lang/tg.php new file mode 100644 index 0000000..b7df893 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/tg.php @@ -0,0 +1,104 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Orif N. Jr + */ +return [ + 'year' => '{1}як сол|:count сол', + 'month' => '{1}як моҳ|:count моҳ', + 'week' => '{1}як ҳафта|:count ҳафта', + 'day' => '{1}як рӯз|:count рӯз', + 'hour' => '{1}як соат|:count соат', + 'minute' => '{1}як дақиқа|:count дақиқа', + 'second' => '{1}якчанд сония|:count сония', + 'ago' => ':time пеш', + 'from_now' => 'баъди :time', + 'diff_today' => 'Имрӯз', + 'diff_yesterday' => 'Дирӯз', + 'diff_yesterday_regexp' => 'Дирӯз(?:\\s+соати)?', + 'diff_tomorrow' => 'Пагоҳ', + 'diff_tomorrow_regexp' => 'Пагоҳ(?:\\s+соати)?', + 'diff_today_regexp' => 'Имрӯз(?:\\s+соати)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Имрӯз соати] LT', + 'nextDay' => '[Пагоҳ соати] LT', + 'nextWeek' => 'dddd[и] [ҳафтаи оянда соати] LT', + 'lastDay' => '[Дирӯз соати] LT', + 'lastWeek' => 'dddd[и] [ҳафтаи гузашта соати] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + if ($number === 0) { // special case for zero + return "$number-ıncı"; + } + + static $suffixes = [ + 0 => '-ум', + 1 => '-ум', + 2 => '-юм', + 3 => '-юм', + 4 => '-ум', + 5 => '-ум', + 6 => '-ум', + 7 => '-ум', + 8 => '-ум', + 9 => '-ум', + 10 => '-ум', + 12 => '-ум', + 13 => '-ум', + 20 => '-ум', + 30 => '-юм', + 40 => '-ум', + 50 => '-ум', + 60 => '-ум', + 70 => '-ум', + 80 => '-ум', + 90 => '-ум', + 100 => '-ум', + ]; + + return $number.($suffixes[$number] ?? $suffixes[$number % 10] ?? $suffixes[$number >= 100 ? 100 : -1] ?? ''); + }, + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'шаб'; + } + if ($hour < 11) { + return 'субҳ'; + } + if ($hour < 16) { + return 'рӯз'; + } + if ($hour < 19) { + return 'бегоҳ'; + } + + return 'шаб'; + }, + 'months' => ['январ', 'феврал', 'март', 'апрел', 'май', 'июн', 'июл', 'август', 'сентябр', 'октябр', 'ноябр', 'декабр'], + 'months_short' => ['янв', 'фев', 'мар', 'апр', 'май', 'июн', 'июл', 'авг', 'сен', 'окт', 'ноя', 'дек'], + 'weekdays' => ['якшанбе', 'душанбе', 'сешанбе', 'чоршанбе', 'панҷшанбе', 'ҷумъа', 'шанбе'], + 'weekdays_short' => ['яшб', 'дшб', 'сшб', 'чшб', 'пшб', 'ҷум', 'шнб'], + 'weekdays_min' => ['яш', 'дш', 'сш', 'чш', 'пш', 'ҷм', 'шб'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' ва '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/tg_TJ.php b/vendor/nesbot/carbon/src/Carbon/Lang/tg_TJ.php new file mode 100644 index 0000000..badc7d1 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/tg_TJ.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__.'/tg.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/th.php b/vendor/nesbot/carbon/src/Carbon/Lang/th.php new file mode 100644 index 0000000..6397f6e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/th.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Nate Whittaker + * - John MacAslan + * - Chanintorn Asavavichairoj + * - JD Isaacks + * - ROKAISAKKON + * - RO'KAISAKKON + * - Andreas Möller + * - nithisa + */ +return [ + '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 วินาที', + 'a_second' => '{1}ไม่กี่วินาที|]1,Inf[:count วินาที', + 's' => ':count วินาที', + 'ago' => ':timeที่แล้ว', + 'from_now' => 'อีก :time', + 'after' => ':timeหลังจากนี้', + 'before' => ':timeก่อน', + 'diff_now' => 'ขณะนี้', + 'diff_today' => 'วันนี้', + 'diff_today_regexp' => 'วันนี้(?:\\s+เวลา)?', + 'diff_yesterday' => 'เมื่อวาน', + 'diff_yesterday_regexp' => 'เมื่อวานนี้(?:\\s+เวลา)?', + 'diff_tomorrow' => 'พรุ่งนี้', + 'diff_tomorrow_regexp' => 'พรุ่งนี้(?:\\s+เวลา)?', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY เวลา H:mm', + 'LLLL' => 'วันddddที่ D MMMM YYYY เวลา H:mm', + ], + 'calendar' => [ + 'sameDay' => '[วันนี้ เวลา] LT', + 'nextDay' => '[พรุ่งนี้ เวลา] LT', + 'nextWeek' => 'dddd[หน้า เวลา] LT', + 'lastDay' => '[เมื่อวานนี้ เวลา] LT', + 'lastWeek' => '[วัน]dddd[ที่แล้ว เวลา] LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['ก่อนเที่ยง', 'หลังเที่ยง'], + 'months' => ['มกราคม', 'กุมภาพันธ์', 'มีนาคม', 'เมษายน', 'พฤษภาคม', 'มิถุนายน', 'กรกฎาคม', 'สิงหาคม', 'กันยายน', 'ตุลาคม', 'พฤศจิกายน', 'ธันวาคม'], + 'months_short' => ['ม.ค.', 'ก.พ.', 'มี.ค.', 'เม.ย.', 'พ.ค.', 'มิ.ย.', 'ก.ค.', 'ส.ค.', 'ก.ย.', 'ต.ค.', 'พ.ย.', 'ธ.ค.'], + 'weekdays' => ['อาทิตย์', 'จันทร์', 'อังคาร', 'พุธ', 'พฤหัสบดี', 'ศุกร์', 'เสาร์'], + 'weekdays_short' => ['อาทิตย์', 'จันทร์', 'อังคาร', 'พุธ', 'พฤหัส', 'ศุกร์', 'เสาร์'], + 'weekdays_min' => ['อา.', 'จ.', 'อ.', 'พ.', 'พฤ.', 'ศ.', 'ส.'], + 'list' => [', ', ' และ '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/th_TH.php b/vendor/nesbot/carbon/src/Carbon/Lang/th_TH.php new file mode 100644 index 0000000..b9f94b2 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/th_TH.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__.'/th.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/the.php b/vendor/nesbot/carbon/src/Carbon/Lang/the.php new file mode 100644 index 0000000..85f8333 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/the.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/the_NP.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/the_NP.php b/vendor/nesbot/carbon/src/Carbon/Lang/the_NP.php new file mode 100644 index 0000000..34da162 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/the_NP.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Chitwanix OS Development info@chitwanix.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'dddd DD MMM YYYY', + ], + 'months' => ['जनवरी', 'फ़रवरी', 'मार्च', 'अप्रेल', 'मई', 'जून', 'जुलाई', 'अगस्त', 'सितम्बर', 'अक्टूबर', 'नवम्बर', 'दिसम्बर'], + 'months_short' => ['जनवरी', 'फ़रवरी', 'मार्च', 'अप्रेल', 'मई', 'जून', 'जुलाई', 'अगस्त', 'सितम्बर', 'अक्टूबर', 'नवम्बर', 'दिसम्बर'], + 'weekdays' => ['आइतबार', 'सोमबार', 'मंगलबार', 'बुधबार', 'बिहिबार', 'शुक्रबार', 'शनिबार'], + 'weekdays_short' => ['आइत', 'सोम', 'मंगल', 'बुध', 'बिहि', 'शुक्र', 'शनि'], + 'weekdays_min' => ['आइत', 'सोम', 'मंगल', 'बुध', 'बिहि', 'शुक्र', 'शनि'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['पूर्वाह्न', 'अपराह्न'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ti.php b/vendor/nesbot/carbon/src/Carbon/Lang/ti.php new file mode 100644 index 0000000..ffd3236 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ti.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/ti_ER.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ti_ER.php b/vendor/nesbot/carbon/src/Carbon/Lang/ti_ER.php new file mode 100644 index 0000000..310c51c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ti_ER.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['ጥሪ', 'ለካቲት', 'መጋቢት', 'ሚያዝያ', 'ግንቦት', 'ሰነ', 'ሓምለ', 'ነሓሰ', 'መስከረም', 'ጥቅምቲ', 'ሕዳር', 'ታሕሳስ'], + 'months_short' => ['ጥሪ ', 'ለካቲ', 'መጋቢ', 'ሚያዝ', 'ግንቦ', 'ሰነ ', 'ሓምለ', 'ነሓሰ', 'መስከ', 'ጥቅም', 'ሕዳር', 'ታሕሳ'], + 'weekdays' => ['ሰንበት', 'ሰኑይ', 'ሰሉስ', 'ረቡዕ', 'ሓሙስ', 'ዓርቢ', 'ቀዳም'], + 'weekdays_short' => ['ሰንበ', 'ሰኑይ', 'ሰሉስ', 'ረቡዕ', 'ሓሙስ', 'ዓርቢ', 'ቀዳም'], + 'weekdays_min' => ['ሰንበ', 'ሰኑይ', 'ሰሉስ', 'ረቡዕ', 'ሓሙስ', 'ዓርቢ', 'ቀዳም'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['ንጉሆ ሰዓተ', 'ድሕር ሰዓት'], + + 'year' => ':count ዓመት', + 'y' => ':count ዓመት', + 'a_year' => ':count ዓመት', + + 'month' => 'ወርሒ :count', + 'm' => 'ወርሒ :count', + 'a_month' => 'ወርሒ :count', + + 'week' => ':count ሰሙን', + 'w' => ':count ሰሙን', + 'a_week' => ':count ሰሙን', + + 'day' => ':count መዓልቲ', + 'd' => ':count መዓልቲ', + 'a_day' => ':count መዓልቲ', + + 'hour' => ':count ሰዓት', + 'h' => ':count ሰዓት', + 'a_hour' => ':count ሰዓት', + + 'minute' => ':count ደቒቕ', + 'min' => ':count ደቒቕ', + 'a_minute' => ':count ደቒቕ', + + 'second' => ':count ሰከንድ', + 's' => ':count ሰከንድ', + 'a_second' => ':count ሰከንድ', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ti_ET.php b/vendor/nesbot/carbon/src/Carbon/Lang/ti_ET.php new file mode 100644 index 0000000..024217f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ti_ET.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['ጃንዩወሪ', 'ፌብሩወሪ', 'ማርች', 'ኤፕረል', 'ሜይ', 'ጁን', 'ጁላይ', 'ኦገስት', 'ሴፕቴምበር', 'ኦክተውበር', 'ኖቬምበር', 'ዲሴምበር'], + 'months_short' => ['ጃንዩ', 'ፌብሩ', 'ማርች', 'ኤፕረ', 'ሜይ ', 'ጁን ', 'ጁላይ', 'ኦገስ', 'ሴፕቴ', 'ኦክተ', 'ኖቬም', 'ዲሴም'], + 'weekdays' => ['ሰንበት', 'ሰኑይ', 'ሰሉስ', 'ረቡዕ', 'ሓሙስ', 'ዓርቢ', 'ቀዳም'], + 'weekdays_short' => ['ሰንበ', 'ሰኑይ', 'ሰሉስ', 'ረቡዕ', 'ሓሙስ', 'ዓርቢ', 'ቀዳም'], + 'weekdays_min' => ['ሰንበ', 'ሰኑይ', 'ሰሉስ', 'ረቡዕ', 'ሓሙስ', 'ዓርቢ', 'ቀዳም'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['ንጉሆ ሰዓተ', 'ድሕር ሰዓት'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/tig.php b/vendor/nesbot/carbon/src/Carbon/Lang/tig.php new file mode 100644 index 0000000..186fe71 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/tig.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/tig_ER.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/tig_ER.php b/vendor/nesbot/carbon/src/Carbon/Lang/tig_ER.php new file mode 100644 index 0000000..46887b0 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/tig_ER.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['ጥሪ', 'ለካቲት', 'መጋቢት', 'ሚያዝያ', 'ግንቦት', 'ሰነ', 'ሓምለ', 'ነሓሰ', 'መስከረም', 'ጥቅምቲ', 'ሕዳር', 'ታሕሳስ'], + 'months_short' => ['ጥሪ ', 'ለካቲ', 'መጋቢ', 'ሚያዝ', 'ግንቦ', 'ሰነ ', 'ሓምለ', 'ነሓሰ', 'መስከ', 'ጥቅም', 'ሕዳር', 'ታሕሳ'], + 'weekdays' => ['ሰንበት ዓባይ', 'ሰኖ', 'ታላሸኖ', 'ኣረርባዓ', 'ከሚሽ', 'ጅምዓት', 'ሰንበት ንኢሽ'], + 'weekdays_short' => ['ሰ//ዓ', 'ሰኖ ', 'ታላሸ', 'ኣረር', 'ከሚሽ', 'ጅምዓ', 'ሰ//ን'], + 'weekdays_min' => ['ሰ//ዓ', 'ሰኖ ', 'ታላሸ', 'ኣረር', 'ከሚሽ', 'ጅምዓ', 'ሰ//ን'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['ቀደም ሰር ምዕል', 'ሓቆ ሰር ምዕል'], + + 'year' => ':count ማይ', // less reliable + 'y' => ':count ማይ', // less reliable + 'a_year' => ':count ማይ', // less reliable + + 'month' => ':count ሸምሽ', // less reliable + 'm' => ':count ሸምሽ', // less reliable + 'a_month' => ':count ሸምሽ', // less reliable + + 'week' => ':count ሰቡዕ', // less reliable + 'w' => ':count ሰቡዕ', // less reliable + 'a_week' => ':count ሰቡዕ', // less reliable + + 'day' => ':count ዎሮ', // less reliable + 'd' => ':count ዎሮ', // less reliable + 'a_day' => ':count ዎሮ', // less reliable + + 'hour' => ':count ሰዓት', // less reliable + 'h' => ':count ሰዓት', // less reliable + 'a_hour' => ':count ሰዓት', // less reliable + + 'minute' => ':count ካልኣይት', // less reliable + 'min' => ':count ካልኣይት', // less reliable + 'a_minute' => ':count ካልኣይት', // less reliable + + 'second' => ':count ካልኣይ', + 's' => ':count ካልኣይ', + 'a_second' => ':count ካልኣይ', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/tk.php b/vendor/nesbot/carbon/src/Carbon/Lang/tk.php new file mode 100644 index 0000000..d8f7d19 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/tk.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/tk_TM.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/tk_TM.php b/vendor/nesbot/carbon/src/Carbon/Lang/tk_TM.php new file mode 100644 index 0000000..f949a43 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/tk_TM.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Authors: + * - Ghorban M. Tavakoly Pablo Saratxaga & Ghorban M. Tavakoly pablo@walon.org & gmt314@yahoo.com + * - SuperManPHP + * - Maksat Meredow (isadma) + */ +$transformDiff = function ($input) { + return strtr($input, [ + 'sekunt' => 'sekunt', + 'hepde' => 'hepde', + ]); +}; + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['Ýanwar', 'Fewral', 'Mart', 'Aprel', 'Maý', 'Iýun', 'Iýul', 'Awgust', 'Sentýabr', 'Oktýabr', 'Noýabr', 'Dekabr'], + 'months_short' => ['Ýan', 'Few', 'Mar', 'Apr', 'Maý', 'Iýn', 'Iýl', 'Awg', 'Sen', 'Okt', 'Noý', 'Dek'], + 'weekdays' => ['Duşenbe', 'Sişenbe', 'Çarşenbe', 'Penşenbe', 'Anna', 'Şenbe', 'Ýekşenbe'], + 'weekdays_short' => ['Duş', 'Siş', 'Çar', 'Pen', 'Ann', 'Şen', 'Ýek'], + 'weekdays_min' => ['Du', 'Si', 'Ça', 'Pe', 'An', 'Şe', 'Ýe'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + + 'year' => ':count ýyl', + 'y' => ':count ýyl', + 'a_year' => ':count ýyl', + + 'month' => ':count aý', + 'm' => ':count aý', + 'a_month' => ':count aý', + + 'week' => ':count hepde', + 'w' => ':count hepde', + 'a_week' => ':count hepde', + + 'day' => ':count gün', + 'd' => ':count gün', + 'a_day' => ':count gün', + + 'hour' => ':count sagat', + 'h' => ':count sagat', + 'a_hour' => ':count sagat', + + 'minute' => ':count minut', + 'min' => ':count minut', + 'a_minute' => ':count minut', + + 'second' => ':count sekunt', + 's' => ':count sekunt', + 'a_second' => ':count sekunt', + + 'ago' => function ($time) use ($transformDiff) { + return $transformDiff($time).' ozal'; + }, + 'from_now' => function ($time) use ($transformDiff) { + return $transformDiff($time).' soňra'; + }, + 'after' => function ($time) use ($transformDiff) { + return $transformDiff($time).' soň'; + }, + 'before' => function ($time) use ($transformDiff) { + return $transformDiff($time).' öň'; + }, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/tl.php b/vendor/nesbot/carbon/src/Carbon/Lang/tl.php new file mode 100644 index 0000000..410a266 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/tl.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'year' => ':count taon', + 'a_year' => '{1}isang taon|:count taon', + 'month' => ':count buwan', + 'a_month' => '{1}isang buwan|:count buwan', + 'week' => ':count linggo', + 'a_week' => '{1}isang linggo|:count linggo', + 'day' => ':count araw', + 'a_day' => '{1}isang araw|:count araw', + 'hour' => ':count oras', + 'a_hour' => '{1}isang oras|:count oras', + 'minute' => ':count minuto', + 'a_minute' => '{1}isang minuto|:count minuto', + 'min' => ':count min.', + 'second' => ':count segundo', + 'a_second' => '{1}ilang segundo|:count segundo', + 's' => ':count seg.', + 'ago' => ':time ang nakalipas', + 'from_now' => 'sa loob ng :time', + 'diff_now' => 'ngayon', + 'diff_today' => 'ngayong', + 'diff_today_regexp' => 'ngayong(?:\\s+araw)?', + 'diff_yesterday' => 'kahapon', + 'diff_tomorrow' => 'bukas', + 'diff_tomorrow_regexp' => 'Bukas(?:\\s+ng)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'MM/D/YYYY', + 'LL' => 'MMMM D, YYYY', + 'LLL' => 'MMMM D, YYYY HH:mm', + 'LLLL' => 'dddd, MMMM DD, YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => 'LT [ngayong araw]', + 'nextDay' => '[Bukas ng] LT', + 'nextWeek' => 'LT [sa susunod na] dddd', + 'lastDay' => 'LT [kahapon]', + 'lastWeek' => 'LT [noong nakaraang] dddd', + 'sameElse' => 'L', + ], + 'months' => ['Enero', 'Pebrero', 'Marso', 'Abril', 'Mayo', 'Hunyo', 'Hulyo', 'Agosto', 'Setyembre', 'Oktubre', 'Nobyembre', 'Disyembre'], + 'months_short' => ['Ene', 'Peb', 'Mar', 'Abr', 'May', 'Hun', 'Hul', 'Ago', 'Set', 'Okt', 'Nob', 'Dis'], + 'weekdays' => ['Linggo', 'Lunes', 'Martes', 'Miyerkules', 'Huwebes', 'Biyernes', 'Sabado'], + 'weekdays_short' => ['Lin', 'Lun', 'Mar', 'Miy', 'Huw', 'Biy', 'Sab'], + 'weekdays_min' => ['Li', 'Lu', 'Ma', 'Mi', 'Hu', 'Bi', 'Sab'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' at '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/tl_PH.php b/vendor/nesbot/carbon/src/Carbon/Lang/tl_PH.php new file mode 100644 index 0000000..95f508c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/tl_PH.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - Ian De La Cruz + * - JD Isaacks + */ +return require __DIR__.'/tl.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/tlh.php b/vendor/nesbot/carbon/src/Carbon/Lang/tlh.php new file mode 100644 index 0000000..fbf9e6f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/tlh.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - Serhan Apaydın + * - Dominika + */ +return [ + 'year' => '{1}wa’ DIS|:count DIS', + 'month' => '{1}wa’ jar|:count jar', + 'week' => '{1}wa’ hogh|:count hogh', + 'day' => '{1}wa’ jaj|:count jaj', + 'hour' => '{1}wa’ rep|:count rep', + 'minute' => '{1}wa’ tup|:count tup', + 'second' => '{1}puS lup|:count lup', + 'ago' => function ($time) { + $output = strtr($time, [ + 'jaj' => 'Hu’', + 'jar' => 'wen', + 'DIS' => 'ben', + ]); + + return $output === $time ? "$time ret" : $output; + }, + 'from_now' => function ($time) { + $output = strtr($time, [ + 'jaj' => 'leS', + 'jar' => 'waQ', + 'DIS' => 'nem', + ]); + + return $output === $time ? "$time pIq" : $output; + }, + 'diff_yesterday' => 'wa’Hu’', + 'diff_today' => 'DaHjaj', + 'diff_tomorrow' => 'wa’leS', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[DaHjaj] LT', + 'nextDay' => '[wa’leS] LT', + 'nextWeek' => 'LLL', + 'lastDay' => '[wa’Hu’] LT', + 'lastWeek' => 'LLL', + 'sameElse' => 'L', + ], + 'ordinal' => ':number.', + 'months' => ['tera’ jar wa’', 'tera’ jar cha’', 'tera’ jar wej', 'tera’ jar loS', 'tera’ jar vagh', 'tera’ jar jav', 'tera’ jar Soch', 'tera’ jar chorgh', 'tera’ jar Hut', 'tera’ jar wa’maH', 'tera’ jar wa’maH wa’', 'tera’ jar wa’maH cha’'], + 'months_short' => ['jar wa’', 'jar cha’', 'jar wej', 'jar loS', 'jar vagh', 'jar jav', 'jar Soch', 'jar chorgh', 'jar Hut', 'jar wa’maH', 'jar wa’maH wa’', 'jar wa’maH cha’'], + 'weekdays' => ['lojmItjaj', 'DaSjaj', 'povjaj', 'ghItlhjaj', 'loghjaj', 'buqjaj', 'ghInjaj'], + 'weekdays_short' => ['lojmItjaj', 'DaSjaj', 'povjaj', 'ghItlhjaj', 'loghjaj', 'buqjaj', 'ghInjaj'], + 'weekdays_min' => ['lojmItjaj', 'DaSjaj', 'povjaj', 'ghItlhjaj', 'loghjaj', 'buqjaj', 'ghInjaj'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' ’ej '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/tn.php b/vendor/nesbot/carbon/src/Carbon/Lang/tn.php new file mode 100644 index 0000000..f29bdf6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/tn.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/tn_ZA.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/tn_ZA.php b/vendor/nesbot/carbon/src/Carbon/Lang/tn_ZA.php new file mode 100644 index 0000000..aada7db --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/tn_ZA.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Zuza Software Foundation (Translate.org.za) Dwayne Bailey dwayne@translate.org.za + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Ferikgong', 'Tlhakole', 'Mopitlwe', 'Moranang', 'Motsheganong', 'Seetebosigo', 'Phukwi', 'Phatwe', 'Lwetse', 'Diphalane', 'Ngwanatsele', 'Sedimonthole'], + 'months_short' => ['Fer', 'Tlh', 'Mop', 'Mor', 'Mot', 'See', 'Phu', 'Pha', 'Lwe', 'Dip', 'Ngw', 'Sed'], + 'weekdays' => ['laTshipi', 'Mosupologo', 'Labobedi', 'Laboraro', 'Labone', 'Labotlhano', 'Lamatlhatso'], + 'weekdays_short' => ['Tsh', 'Mos', 'Bed', 'Rar', 'Ne', 'Tlh', 'Mat'], + 'weekdays_min' => ['Tsh', 'Mos', 'Bed', 'Rar', 'Ne', 'Tlh', 'Mat'], + 'day_of_first_week_of_year' => 1, + + 'year' => 'dingwaga di le :count', + 'y' => 'dingwaga di le :count', + 'a_year' => 'dingwaga di le :count', + + 'month' => 'dikgwedi di le :count', + 'm' => 'dikgwedi di le :count', + 'a_month' => 'dikgwedi di le :count', + + 'week' => 'dibeke di le :count', + 'w' => 'dibeke di le :count', + 'a_week' => 'dibeke di le :count', + + 'day' => 'malatsi :count', + 'd' => 'malatsi :count', + 'a_day' => 'malatsi :count', + + 'hour' => 'diura di le :count', + 'h' => 'diura di le :count', + 'a_hour' => 'diura di le :count', + + 'minute' => 'metsotso e le :count', + 'min' => 'metsotso e le :count', + 'a_minute' => 'metsotso e le :count', + + 'second' => 'metsotswana e le :count', + 's' => 'metsotswana e le :count', + 'a_second' => 'metsotswana e le :count', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/to.php b/vendor/nesbot/carbon/src/Carbon/Lang/to.php new file mode 100644 index 0000000..20581bb --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/to.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/to_TO.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/to_TO.php b/vendor/nesbot/carbon/src/Carbon/Lang/to_TO.php new file mode 100644 index 0000000..335c69a --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/to_TO.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - International Components for Unicode akhilesh.k@samsung.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'dddd DD MMM YYYY', + ], + 'months' => ['Sānuali', 'Fēpueli', 'Maʻasi', 'ʻEpeleli', 'Mē', 'Sune', 'Siulai', 'ʻAokosi', 'Sepitema', 'ʻOkatopa', 'Nōvema', 'Tīsema'], + 'months_short' => ['Sān', 'Fēp', 'Maʻa', 'ʻEpe', 'Mē', 'Sun', 'Siu', 'ʻAok', 'Sep', 'ʻOka', 'Nōv', 'Tīs'], + 'weekdays' => ['Sāpate', 'Mōnite', 'Tūsite', 'Pulelulu', 'Tuʻapulelulu', 'Falaite', 'Tokonaki'], + 'weekdays_short' => ['Sāp', 'Mōn', 'Tūs', 'Pul', 'Tuʻa', 'Fal', 'Tok'], + 'weekdays_min' => ['Sāp', 'Mōn', 'Tūs', 'Pul', 'Tuʻa', 'Fal', 'Tok'], + 'meridiem' => ['hengihengi', 'efiafi'], + + 'year' => ':count fitu', // less reliable + 'y' => ':count fitu', // less reliable + 'a_year' => ':count fitu', // less reliable + + 'month' => ':count mahina', // less reliable + 'm' => ':count mahina', // less reliable + 'a_month' => ':count mahina', // less reliable + + 'week' => ':count Sapate', // less reliable + 'w' => ':count Sapate', // less reliable + 'a_week' => ':count Sapate', // less reliable + + 'day' => ':count ʻaho', // less reliable + 'd' => ':count ʻaho', // less reliable + 'a_day' => ':count ʻaho', // less reliable + + 'hour' => ':count houa', + 'h' => ':count houa', + 'a_hour' => ':count houa', + + 'minute' => ':count miniti', + 'min' => ':count miniti', + 'a_minute' => ':count miniti', + + 'second' => ':count sekoni', + 's' => ':count sekoni', + 'a_second' => ':count sekoni', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/tpi.php b/vendor/nesbot/carbon/src/Carbon/Lang/tpi.php new file mode 100644 index 0000000..7d38dae --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/tpi.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/tpi_PG.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/tpi_PG.php b/vendor/nesbot/carbon/src/Carbon/Lang/tpi_PG.php new file mode 100644 index 0000000..5f58c44 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/tpi_PG.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Samsung Electronics Co., Ltd. akhilesh.k@samsung.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Janueri', 'Februeri', 'Mas', 'Epril', 'Me', 'Jun', 'Julai', 'Ogas', 'Septemba', 'Oktoba', 'Novemba', 'Desemba'], + 'months_short' => ['Jan', 'Feb', 'Mas', 'Epr', 'Me', 'Jun', 'Jul', 'Oga', 'Sep', 'Okt', 'Nov', 'Des'], + 'weekdays' => ['Sande', 'Mande', 'Tunde', 'Trinde', 'Fonde', 'Fraide', 'Sarere'], + 'weekdays_short' => ['San', 'Man', 'Tun', 'Tri', 'Fon', 'Fra', 'Sar'], + 'weekdays_min' => ['San', 'Man', 'Tun', 'Tri', 'Fon', 'Fra', 'Sar'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['biknait', 'apinun'], + + 'year' => 'yia :count', + 'y' => 'yia :count', + 'a_year' => 'yia :count', + + 'month' => ':count mun', + 'm' => ':count mun', + 'a_month' => ':count mun', + + 'week' => ':count wik', + 'w' => ':count wik', + 'a_week' => ':count wik', + + 'day' => ':count de', + 'd' => ':count de', + 'a_day' => ':count de', + + 'hour' => ':count aua', + 'h' => ':count aua', + 'a_hour' => ':count aua', + + 'minute' => ':count minit', + 'min' => ':count minit', + 'a_minute' => ':count minit', + + 'second' => ':count namba tu', + 's' => ':count namba tu', + 'a_second' => ':count namba tu', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/tr.php b/vendor/nesbot/carbon/src/Carbon/Lang/tr.php new file mode 100644 index 0000000..f5d9f4c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/tr.php @@ -0,0 +1,121 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - Alan Agius + * - Erhan Gundogan + * - François B + * - JD Isaacks + * - Murat Yüksel + * - Baran Şengül + * - Selami (selamialtin) + * - TeomanBey + */ +return [ + 'year' => ':count yıl', + 'a_year' => '{1}bir yıl|]1,Inf[:count yıl', + 'y' => ':county', + 'month' => ':count ay', + 'a_month' => '{1}bir ay|]1,Inf[:count ay', + 'm' => ':countay', + 'week' => ':count hafta', + 'a_week' => '{1}bir hafta|]1,Inf[:count hafta', + 'w' => ':counth', + 'day' => ':count gün', + 'a_day' => '{1}bir gün|]1,Inf[:count gün', + 'd' => ':countg', + 'hour' => ':count saat', + 'a_hour' => '{1}bir saat|]1,Inf[:count saat', + 'h' => ':countsa', + 'minute' => ':count dakika', + 'a_minute' => '{1}bir dakika|]1,Inf[:count dakika', + 'min' => ':countdk', + 'second' => ':count saniye', + 'a_second' => '{1}birkaç saniye|]1,Inf[:count saniye', + 's' => ':countsn', + 'ago' => ':time önce', + 'from_now' => ':time sonra', + 'after' => ':time sonra', + 'before' => ':time önce', + 'diff_now' => 'şimdi', + 'diff_today' => 'bugün', + 'diff_today_regexp' => 'bugün(?:\\s+saat)?', + 'diff_yesterday' => 'dün', + 'diff_tomorrow' => 'yarın', + 'diff_tomorrow_regexp' => 'yarın(?:\\s+saat)?', + 'diff_before_yesterday' => 'evvelsi gün', + 'diff_after_tomorrow' => 'öbür gün', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[bugün saat] LT', + 'nextDay' => '[yarın saat] LT', + 'nextWeek' => '[gelecek] dddd [saat] LT', + 'lastDay' => '[dün] LT', + 'lastWeek' => '[geçen] dddd [saat] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number, $period) { + switch ($period) { + case 'd': + case 'D': + case 'Do': + case 'DD': + return $number; + default: + if ($number === 0) { // special case for zero + return "$number'ıncı"; + } + + static $suffixes = [ + 1 => '\'inci', + 5 => '\'inci', + 8 => '\'inci', + 70 => '\'inci', + 80 => '\'inci', + 2 => '\'nci', + 7 => '\'nci', + 20 => '\'nci', + 50 => '\'nci', + 3 => '\'üncü', + 4 => '\'üncü', + 100 => '\'üncü', + 6 => '\'ncı', + 9 => '\'uncu', + 10 => '\'uncu', + 30 => '\'uncu', + 60 => '\'ıncı', + 90 => '\'ıncı', + ]; + + $lastDigit = $number % 10; + + return $number.($suffixes[$lastDigit] ?? $suffixes[$number % 100 - $lastDigit] ?? $suffixes[$number >= 100 ? 100 : -1] ?? ''); + } + }, + 'meridiem' => ['ÖÖ', 'ÖS', 'öö', 'ös'], + 'months' => ['Ocak', 'Şubat', 'Mart', 'Nisan', 'Mayıs', 'Haziran', 'Temmuz', 'Ağustos', 'Eylül', 'Ekim', 'Kasım', 'Aralık'], + 'months_short' => ['Oca', 'Şub', 'Mar', 'Nis', 'May', 'Haz', 'Tem', 'Ağu', 'Eyl', 'Eki', 'Kas', 'Ara'], + 'weekdays' => ['Pazar', 'Pazartesi', 'Salı', 'Çarşamba', 'Perşembe', 'Cuma', 'Cumartesi'], + 'weekdays_short' => ['Paz', 'Pts', 'Sal', 'Çar', 'Per', 'Cum', 'Cts'], + 'weekdays_min' => ['Pz', 'Pt', 'Sa', 'Ça', 'Pe', 'Cu', 'Ct'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' ve '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/tr_CY.php b/vendor/nesbot/carbon/src/Carbon/Lang/tr_CY.php new file mode 100644 index 0000000..23f1144 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/tr_CY.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/tr.php', [ + 'weekdays_short' => ['Paz', 'Pzt', 'Sal', 'Çar', 'Per', 'Cum', 'Cmt'], + 'weekdays_min' => ['Pa', 'Pt', 'Sa', 'Ça', 'Pe', 'Cu', 'Ct'], + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'D.MM.YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'D MMMM YYYY dddd h:mm a', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/tr_TR.php b/vendor/nesbot/carbon/src/Carbon/Lang/tr_TR.php new file mode 100644 index 0000000..9e99482 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/tr_TR.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__.'/tr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ts.php b/vendor/nesbot/carbon/src/Carbon/Lang/ts.php new file mode 100644 index 0000000..525736b --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ts.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/ts_ZA.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ts_ZA.php b/vendor/nesbot/carbon/src/Carbon/Lang/ts_ZA.php new file mode 100644 index 0000000..37a24ec --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ts_ZA.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Zuza Software Foundation (Translate.org.za) Dwayne Bailey dwayne@translate.org.za + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Sunguti', 'Nyenyenyani', 'Nyenyankulu', 'Dzivamisoko', 'Mudyaxihi', 'Khotavuxika', 'Mawuwani', 'Mhawuri', 'Ndzhati', 'Nhlangula', 'Hukuri', 'N\'wendzamhala'], + 'months_short' => ['Sun', 'Yan', 'Kul', 'Dzi', 'Mud', 'Kho', 'Maw', 'Mha', 'Ndz', 'Nhl', 'Huk', 'N\'w'], + 'weekdays' => ['Sonto', 'Musumbhunuku', 'Ravumbirhi', 'Ravunharhu', 'Ravumune', 'Ravuntlhanu', 'Mugqivela'], + 'weekdays_short' => ['Son', 'Mus', 'Bir', 'Har', 'Ne', 'Tlh', 'Mug'], + 'weekdays_min' => ['Son', 'Mus', 'Bir', 'Har', 'Ne', 'Tlh', 'Mug'], + 'day_of_first_week_of_year' => 1, + + 'year' => 'malembe ya :count', + 'y' => 'malembe ya :count', + 'a_year' => 'malembe ya :count', + + 'month' => 'tin’hweti ta :count', + 'm' => 'tin’hweti ta :count', + 'a_month' => 'tin’hweti ta :count', + + 'week' => 'mavhiki ya :count', + 'w' => 'mavhiki ya :count', + 'a_week' => 'mavhiki ya :count', + + 'day' => 'masiku :count', + 'd' => 'masiku :count', + 'a_day' => 'masiku :count', + + 'hour' => 'tiawara ta :count', + 'h' => 'tiawara ta :count', + 'a_hour' => 'tiawara ta :count', + + 'minute' => 'timinete ta :count', + 'min' => 'timinete ta :count', + 'a_minute' => 'timinete ta :count', + + 'second' => 'tisekoni ta :count', + 's' => 'tisekoni ta :count', + 'a_second' => 'tisekoni ta :count', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/tt.php b/vendor/nesbot/carbon/src/Carbon/Lang/tt.php new file mode 100644 index 0000000..d67d896 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/tt.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/tt_RU.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/tt_RU.php b/vendor/nesbot/carbon/src/Carbon/Lang/tt_RU.php new file mode 100644 index 0000000..38e42d0 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/tt_RU.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Rinat Norkin Pablo Saratxaga, Rinat Norkin pablo@mandrakesoft.com, rinat@taif.ru + */ +return [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'DD MMM, HH:mm', + 'LLLL' => 'DD MMMM YYYY, HH:mm', + ], + 'months' => ['января', 'февраля', 'марта', 'апреля', 'мая', 'июня', 'июля', 'августа', 'сентября', 'октября', 'ноября', 'декабря'], + 'months_short' => ['янв', 'фев', 'мар', 'апр', 'май', 'июн', 'июл', 'авг', 'сен', 'окт', 'ноя', 'дек'], + 'weekdays' => ['якшәмбе', 'дышәмбе', 'сишәмбе', 'чәршәәмбе', 'пәнҗешмбе', 'җомга', 'шимбә'], + 'weekdays_short' => ['якш', 'дыш', 'сиш', 'чәрш', 'пәнҗ', 'җом', 'шим'], + 'weekdays_min' => ['якш', 'дыш', 'сиш', 'чәрш', 'пәнҗ', 'җом', 'шим'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'year' => ':count ел', + 'month' => ':count ай', + 'week' => ':count атна', + 'day' => ':count көн', + 'hour' => ':count сәгать', + 'minute' => ':count минут', + 'second' => ':count секунд', +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/tt_RU@iqtelif.php b/vendor/nesbot/carbon/src/Carbon/Lang/tt_RU@iqtelif.php new file mode 100644 index 0000000..16b8efb --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/tt_RU@iqtelif.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Reshat Sabiq tatar.iqtelif.i18n@gmail.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['Ğınwar', 'Fiwral\'', 'Mart', 'April', 'May', 'Yün', 'Yül', 'Awgust', 'Sintebír', 'Üktebír', 'Noyebír', 'Dikebír'], + 'months_short' => ['Ğın', 'Fiw', 'Mar', 'Apr', 'May', 'Yün', 'Yül', 'Awg', 'Sin', 'Ükt', 'Noy', 'Dik'], + 'weekdays' => ['Yekşembí', 'Düşembí', 'Sişembí', 'Çerşembí', 'Pencíşembí', 'Comğa', 'Şimbe'], + 'weekdays_short' => ['Yek', 'Düş', 'Siş', 'Çer', 'Pen', 'Com', 'Şim'], + 'weekdays_min' => ['Yek', 'Düş', 'Siş', 'Çer', 'Pen', 'Com', 'Şim'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['ÖA', 'ÖS'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/twq.php b/vendor/nesbot/carbon/src/Carbon/Lang/twq.php new file mode 100644 index 0000000..5cbb46e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/twq.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ses.php', [ + 'meridiem' => ['Subbaahi', 'Zaarikay b'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/tzl.php b/vendor/nesbot/carbon/src/Carbon/Lang/tzl.php new file mode 100644 index 0000000..50bf26d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/tzl.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'year' => '[0,1]:count ar|:count ars', + 'y' => '[0,1]:count ar|:count ars', + 'month' => '[0,1]:count mes|:count mesen', + 'm' => '[0,1]:count mes|:count mesen', + 'week' => '[0,1]:count seifetziua|:count seifetziuas', + 'w' => '[0,1]:count seifetziua|:count seifetziuas', + 'day' => '[0,1]:count ziua|:count ziuas', + 'd' => '[0,1]:count ziua|:count ziuas', + 'hour' => '[0,1]:count þora|:count þoras', + 'h' => '[0,1]:count þora|:count þoras', + 'minute' => '[0,1]:count míut|:count míuts', + 'min' => '[0,1]:count míut|:count míuts', + 'second' => ':count secunds', + 's' => ':count secunds', + + 'ago' => 'ja :time', + 'from_now' => 'osprei :time', + + 'diff_yesterday' => 'ieiri', + 'diff_yesterday_regexp' => 'ieiri(?:\\s+à)?', + 'diff_today' => 'oxhi', + 'diff_today_regexp' => 'oxhi(?:\\s+à)?', + 'diff_tomorrow' => 'demà', + 'diff_tomorrow_regexp' => 'demà(?:\\s+à)?', + + 'formats' => [ + 'LT' => 'HH.mm', + 'LTS' => 'HH.mm.ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D. MMMM [dallas] YYYY', + 'LLL' => 'D. MMMM [dallas] YYYY HH.mm', + 'LLLL' => 'dddd, [li] D. MMMM [dallas] YYYY HH.mm', + ], + + 'calendar' => [ + 'sameDay' => '[oxhi à] LT', + 'nextDay' => '[demà à] LT', + 'nextWeek' => 'dddd [à] LT', + 'lastDay' => '[ieiri à] LT', + 'lastWeek' => '[sür el] dddd [lasteu à] LT', + 'sameElse' => 'L', + ], + + 'meridiem' => ["D'A", "D'O"], + 'months' => ['Januar', 'Fevraglh', 'Març', 'Avrïu', 'Mai', 'Gün', 'Julia', 'Guscht', 'Setemvar', 'Listopäts', 'Noemvar', 'Zecemvar'], + 'months_short' => ['Jan', 'Fev', 'Mar', 'Avr', 'Mai', 'Gün', 'Jul', 'Gus', 'Set', 'Lis', 'Noe', 'Zec'], + 'weekdays' => ['Súladi', 'Lúneçi', 'Maitzi', 'Márcuri', 'Xhúadi', 'Viénerçi', 'Sáturi'], + 'weekdays_short' => ['Súl', 'Lún', 'Mai', 'Már', 'Xhú', 'Vié', 'Sát'], + 'weekdays_min' => ['Sú', 'Lú', 'Ma', 'Má', 'Xh', 'Vi', 'Sá'], + 'ordinal' => ':number.', + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/tzm.php b/vendor/nesbot/carbon/src/Carbon/Lang/tzm.php new file mode 100644 index 0000000..2a1a0f2 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/tzm.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - JD Isaacks + */ +return [ + 'year' => '{1}ⴰⵙⴳⴰⵙ|:count ⵉⵙⴳⴰⵙⵏ', + 'month' => '{1}ⴰⵢoⵓⵔ|:count ⵉⵢⵢⵉⵔⵏ', + 'week' => ':count ⵉⵎⴰⵍⴰⵙⵙ', + 'day' => '{1}ⴰⵙⵙ|:count oⵙⵙⴰⵏ', + 'hour' => '{1}ⵙⴰⵄⴰ|:count ⵜⴰⵙⵙⴰⵄⵉⵏ', + 'minute' => '{1}ⵎⵉⵏⵓⴺ|:count ⵎⵉⵏⵓⴺ', + 'second' => '{1}ⵉⵎⵉⴽ|:count ⵉⵎⵉⴽ', + 'ago' => 'ⵢⴰⵏ :time', + 'from_now' => 'ⴷⴰⴷⵅ ⵙ ⵢⴰⵏ :time', + 'diff_today' => 'ⴰⵙⴷⵅ', + 'diff_yesterday' => 'ⴰⵚⴰⵏⵜ', + 'diff_yesterday_regexp' => 'ⴰⵚⴰⵏⵜ(?:\\s+ⴴ)?', + 'diff_tomorrow' => 'ⴰⵙⴽⴰ', + 'diff_tomorrow_regexp' => 'ⴰⵙⴽⴰ(?:\\s+ⴴ)?', + 'diff_today_regexp' => 'ⴰⵙⴷⵅ(?:\\s+ⴴ)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[ⴰⵙⴷⵅ ⴴ] LT', + 'nextDay' => '[ⴰⵙⴽⴰ ⴴ] LT', + 'nextWeek' => 'dddd [ⴴ] LT', + 'lastDay' => '[ⴰⵚⴰⵏⵜ ⴴ] LT', + 'lastWeek' => 'dddd [ⴴ] LT', + 'sameElse' => 'L', + ], + 'months' => ['ⵉⵏⵏⴰⵢⵔ', 'ⴱⵕⴰⵢⵕ', 'ⵎⴰⵕⵚ', 'ⵉⴱⵔⵉⵔ', 'ⵎⴰⵢⵢⵓ', 'ⵢⵓⵏⵢⵓ', 'ⵢⵓⵍⵢⵓⵣ', 'ⵖⵓⵛⵜ', 'ⵛⵓⵜⴰⵏⴱⵉⵔ', 'ⴽⵟⵓⴱⵕ', 'ⵏⵓⵡⴰⵏⴱⵉⵔ', 'ⴷⵓⵊⵏⴱⵉⵔ'], + 'months_short' => ['ⵉⵏⵏⴰⵢⵔ', 'ⴱⵕⴰⵢⵕ', 'ⵎⴰⵕⵚ', 'ⵉⴱⵔⵉⵔ', 'ⵎⴰⵢⵢⵓ', 'ⵢⵓⵏⵢⵓ', 'ⵢⵓⵍⵢⵓⵣ', 'ⵖⵓⵛⵜ', 'ⵛⵓⵜⴰⵏⴱⵉⵔ', 'ⴽⵟⵓⴱⵕ', 'ⵏⵓⵡⴰⵏⴱⵉⵔ', 'ⴷⵓⵊⵏⴱⵉⵔ'], + 'weekdays' => ['ⴰⵙⴰⵎⴰⵙ', 'ⴰⵢⵏⴰⵙ', 'ⴰⵙⵉⵏⴰⵙ', 'ⴰⴽⵔⴰⵙ', 'ⴰⴽⵡⴰⵙ', 'ⴰⵙⵉⵎⵡⴰⵙ', 'ⴰⵙⵉⴹⵢⴰⵙ'], + 'weekdays_short' => ['ⴰⵙⴰⵎⴰⵙ', 'ⴰⵢⵏⴰⵙ', 'ⴰⵙⵉⵏⴰⵙ', 'ⴰⴽⵔⴰⵙ', 'ⴰⴽⵡⴰⵙ', 'ⴰⵙⵉⵎⵡⴰⵙ', 'ⴰⵙⵉⴹⵢⴰⵙ'], + 'weekdays_min' => ['ⴰⵙⴰⵎⴰⵙ', 'ⴰⵢⵏⴰⵙ', 'ⴰⵙⵉⵏⴰⵙ', 'ⴰⴽⵔⴰⵙ', 'ⴰⴽⵡⴰⵙ', 'ⴰⵙⵉⵎⵡⴰⵙ', 'ⴰⵙⵉⴹⵢⴰⵙ'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'weekend' => [5, 6], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/tzm_Latn.php b/vendor/nesbot/carbon/src/Carbon/Lang/tzm_Latn.php new file mode 100644 index 0000000..5840d20 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/tzm_Latn.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - JD Isaacks + */ +return [ + 'year' => '{1}:count asgas|:count isgasn', + 'a_year' => 'asgas|:count isgasn', + 'month' => '{1}:count ayowr|:count iyyirn', + 'a_month' => 'ayowr|:count iyyirn', + 'week' => ':count imalass', + 'a_week' => ':imalass', + 'day' => '{1}:count ass|:count ossan', + 'a_day' => 'ass|:count ossan', + 'hour' => '{1}:count saɛa|:count tassaɛin', + 'a_hour' => '{1}saɛa|:count tassaɛin', + 'minute' => ':count minuḍ', + 'a_minute' => '{1}minuḍ|:count minuḍ', + 'second' => ':count imik', + 'a_second' => '{1}imik|:count imik', + 'ago' => 'yan :time', + 'from_now' => 'dadkh s yan :time', + 'diff_yesterday' => 'assant', + 'diff_yesterday_regexp' => 'assant(?:\\s+g)?', + 'diff_today' => 'asdkh', + 'diff_today_regexp' => 'asdkh(?:\\s+g)?', + 'diff_tomorrow' => 'aska', + 'diff_tomorrow_regexp' => 'aska(?:\\s+g)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[asdkh g] LT', + 'nextDay' => '[aska g] LT', + 'nextWeek' => 'dddd [g] LT', + 'lastDay' => '[assant g] LT', + 'lastWeek' => 'dddd [g] LT', + 'sameElse' => 'L', + ], + 'months' => ['innayr', 'brˤayrˤ', 'marˤsˤ', 'ibrir', 'mayyw', 'ywnyw', 'ywlywz', 'ɣwšt', 'šwtanbir', 'ktˤwbrˤ', 'nwwanbir', 'dwjnbir'], + 'months_short' => ['innayr', 'brˤayrˤ', 'marˤsˤ', 'ibrir', 'mayyw', 'ywnyw', 'ywlywz', 'ɣwšt', 'šwtanbir', 'ktˤwbrˤ', 'nwwanbir', 'dwjnbir'], + 'weekdays' => ['asamas', 'aynas', 'asinas', 'akras', 'akwas', 'asimwas', 'asiḍyas'], + 'weekdays_short' => ['asamas', 'aynas', 'asinas', 'akras', 'akwas', 'asimwas', 'asiḍyas'], + 'weekdays_min' => ['asamas', 'aynas', 'asinas', 'akras', 'akwas', 'asimwas', 'asiḍyas'], + 'meridiem' => ['Zdat azal', 'Ḍeffir aza'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ug.php b/vendor/nesbot/carbon/src/Carbon/Lang/ug.php new file mode 100644 index 0000000..259b99a --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ug.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Philippe Vaucher + * - Tsutomu Kuroda + * - yasinn + */ +return [ + 'year' => '{1}'.'بىر يىل'.'|:count '.'يىل', + 'month' => '{1}'.'بىر ئاي'.'|:count '.'ئاي', + 'week' => '{1}'.'بىر ھەپتە'.'|:count '.'ھەپتە', + 'day' => '{1}'.'بىر كۈن'.'|:count '.'كۈن', + 'hour' => '{1}'.'بىر سائەت'.'|:count '.'سائەت', + 'minute' => '{1}'.'بىر مىنۇت'.'|:count '.'مىنۇت', + 'second' => '{1}'.'نەچچە سېكونت'.'|:count '.'سېكونت', + 'ago' => ':time بۇرۇن', + 'from_now' => ':time كېيىن', + 'diff_today' => 'بۈگۈن', + 'diff_yesterday' => 'تۆنۈگۈن', + 'diff_tomorrow' => 'ئەتە', + 'diff_tomorrow_regexp' => 'ئەتە(?:\\s+سائەت)?', + 'diff_today_regexp' => 'بۈگۈن(?:\\s+سائەت)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-DD', + 'LL' => 'YYYY-يىلىM-ئاينىڭD-كۈنى', + 'LLL' => 'YYYY-يىلىM-ئاينىڭD-كۈنى، HH:mm', + 'LLLL' => 'dddd، YYYY-يىلىM-ئاينىڭD-كۈنى، HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[بۈگۈن سائەت] LT', + 'nextDay' => '[ئەتە سائەت] LT', + 'nextWeek' => '[كېلەركى] dddd [سائەت] LT', + 'lastDay' => '[تۆنۈگۈن] LT', + 'lastWeek' => '[ئالدىنقى] dddd [سائەت] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number, $period) { + switch ($period) { + case 'd': + case 'D': + case 'DDD': + return $number.'-كۈنى'; + case 'w': + case 'W': + return $number.'-ھەپتە'; + default: + return $number; + } + }, + 'meridiem' => function ($hour, $minute) { + $time = $hour * 100 + $minute; + if ($time < 600) { + return 'يېرىم كېچە'; + } + if ($time < 900) { + return 'سەھەر'; + } + if ($time < 1130) { + return 'چۈشتىن بۇرۇن'; + } + if ($time < 1230) { + return 'چۈش'; + } + if ($time < 1800) { + return 'چۈشتىن كېيىن'; + } + + return 'كەچ'; + }, + 'months' => ['يانۋار', 'فېۋرال', 'مارت', 'ئاپرېل', 'ماي', 'ئىيۇن', 'ئىيۇل', 'ئاۋغۇست', 'سېنتەبىر', 'ئۆكتەبىر', 'نويابىر', 'دېكابىر'], + 'months_short' => ['يانۋار', 'فېۋرال', 'مارت', 'ئاپرېل', 'ماي', 'ئىيۇن', 'ئىيۇل', 'ئاۋغۇست', 'سېنتەبىر', 'ئۆكتەبىر', 'نويابىر', 'دېكابىر'], + 'weekdays' => ['يەكشەنبە', 'دۈشەنبە', 'سەيشەنبە', 'چارشەنبە', 'پەيشەنبە', 'جۈمە', 'شەنبە'], + 'weekdays_short' => ['يە', 'دۈ', 'سە', 'چا', 'پە', 'جۈ', 'شە'], + 'weekdays_min' => ['يە', 'دۈ', 'سە', 'چا', 'پە', 'جۈ', 'شە'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' ۋە '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ug_CN.php b/vendor/nesbot/carbon/src/Carbon/Lang/ug_CN.php new file mode 100644 index 0000000..deb828c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ug_CN.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Kunal Marwaha + * - Alim Boyaq + */ +return require __DIR__.'/ug.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/uk.php b/vendor/nesbot/carbon/src/Carbon/Lang/uk.php new file mode 100644 index 0000000..1d5ba70 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/uk.php @@ -0,0 +1,212 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Carbon\CarbonInterface; + +$processHoursFunction = function (CarbonInterface $date, string $format) { + return $format.'о'.($date->hour === 11 ? 'б' : '').'] LT'; +}; + +/* + * Authors: + * - Kunal Marwaha + * - Josh Soref + * - François B + * - Tim Fish + * - Serhan Apaydın + * - Max Mykhailenko + * - JD Isaacks + * - Max Kovpak + * - AucT + * - Philippe Vaucher + * - Ilya Shaplyko + * - Vadym Ievsieiev + * - Denys Kurets + * - Igor Kasyanchuk + * - Tsutomu Kuroda + * - tjku + * - Max Melentiev + * - Oleh + * - epaminond + * - Juanito Fatas + * - Vitalii Khustochka + * - Akira Matsuda + * - Christopher Dell + * - Enrique Vidal + * - Simone Carletti + * - Aaron Patterson + * - Andriy Tyurnikov + * - Nicolás Hock Isaza + * - Iwakura Taro + * - Andrii Ponomarov + * - alecrabbit + * - vystepanenko + * - AlexWalkerson + * - Andre Havryliuk (Andrend) + * - Max Datsenko (datsenko-md) + */ +return [ + 'year' => ':count рік|:count роки|:count років', + 'y' => ':countр', + 'a_year' => '{1}рік|:count рік|:count роки|:count років', + 'month' => ':count місяць|:count місяці|:count місяців', + 'm' => ':countм', + 'a_month' => '{1}місяць|:count місяць|:count місяці|:count місяців', + 'week' => ':count тиждень|:count тижні|:count тижнів', + 'w' => ':countт', + 'a_week' => '{1}тиждень|:count тиждень|:count тижні|:count тижнів', + 'day' => ':count день|:count дні|:count днів', + 'd' => ':countд', + 'a_day' => '{1}день|:count день|:count дні|:count днів', + 'hour' => ':count година|:count години|:count годин', + 'h' => ':countг', + 'a_hour' => '{1}година|:count година|:count години|:count годин', + 'minute' => ':count хвилина|:count хвилини|:count хвилин', + 'min' => ':countхв', + 'a_minute' => '{1}хвилина|:count хвилина|:count хвилини|:count хвилин', + 'second' => ':count секунда|:count секунди|:count секунд', + 's' => ':countсек', + 'a_second' => '{1}декілька секунд|:count секунда|:count секунди|:count секунд', + + 'hour_ago' => ':count годину|:count години|:count годин', + 'a_hour_ago' => '{1}годину|:count годину|:count години|:count годин', + 'minute_ago' => ':count хвилину|:count хвилини|:count хвилин', + 'a_minute_ago' => '{1}хвилину|:count хвилину|:count хвилини|:count хвилин', + 'second_ago' => ':count секунду|:count секунди|:count секунд', + 'a_second_ago' => '{1}декілька секунд|:count секунду|:count секунди|:count секунд', + + 'hour_from_now' => ':count годину|:count години|:count годин', + 'a_hour_from_now' => '{1}годину|:count годину|:count години|:count годин', + 'minute_from_now' => ':count хвилину|:count хвилини|:count хвилин', + 'a_minute_from_now' => '{1}хвилину|:count хвилину|:count хвилини|:count хвилин', + 'second_from_now' => ':count секунду|:count секунди|:count секунд', + 'a_second_from_now' => '{1}декілька секунд|:count секунду|:count секунди|:count секунд', + + 'hour_after' => ':count годину|:count години|:count годин', + 'a_hour_after' => '{1}годину|:count годину|:count години|:count годин', + 'minute_after' => ':count хвилину|:count хвилини|:count хвилин', + 'a_minute_after' => '{1}хвилину|:count хвилину|:count хвилини|:count хвилин', + 'second_after' => ':count секунду|:count секунди|:count секунд', + 'a_second_after' => '{1}декілька секунд|:count секунду|:count секунди|:count секунд', + + 'hour_before' => ':count годину|:count години|:count годин', + 'a_hour_before' => '{1}годину|:count годину|:count години|:count годин', + 'minute_before' => ':count хвилину|:count хвилини|:count хвилин', + 'a_minute_before' => '{1}хвилину|:count хвилину|:count хвилини|:count хвилин', + 'second_before' => ':count секунду|:count секунди|:count секунд', + 'a_second_before' => '{1}декілька секунд|:count секунду|:count секунди|:count секунд', + + 'ago' => ':time тому', + 'from_now' => 'за :time', + 'after' => ':time після', + 'before' => ':time до', + 'diff_now' => 'щойно', + 'diff_today' => 'Сьогодні', + 'diff_today_regexp' => 'Сьогодні(?:\\s+о)?', + 'diff_yesterday' => 'вчора', + 'diff_yesterday_regexp' => 'Вчора(?:\\s+о)?', + 'diff_tomorrow' => 'завтра', + 'diff_tomorrow_regexp' => 'Завтра(?:\\s+о)?', + 'diff_before_yesterday' => 'позавчора', + 'diff_after_tomorrow' => 'післязавтра', + 'period_recurrences' => 'один раз|:count рази|:count разів', + 'period_interval' => 'кожні :interval', + 'period_start_date' => 'з :date', + 'period_end_date' => 'до :date', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY, HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY, HH:mm', + ], + 'calendar' => [ + 'sameDay' => function (CarbonInterface $date) use ($processHoursFunction) { + return $processHoursFunction($date, '[Сьогодні '); + }, + 'nextDay' => function (CarbonInterface $date) use ($processHoursFunction) { + return $processHoursFunction($date, '[Завтра '); + }, + 'nextWeek' => function (CarbonInterface $date) use ($processHoursFunction) { + return $processHoursFunction($date, '[У] dddd ['); + }, + 'lastDay' => function (CarbonInterface $date) use ($processHoursFunction) { + return $processHoursFunction($date, '[Вчора '); + }, + 'lastWeek' => function (CarbonInterface $date) use ($processHoursFunction) { + switch ($date->dayOfWeek) { + case 0: + case 3: + case 5: + case 6: + return $processHoursFunction($date, '[Минулої] dddd ['); + default: + return $processHoursFunction($date, '[Минулого] dddd ['); + } + }, + 'sameElse' => 'L', + ], + 'ordinal' => function ($number, $period) { + switch ($period) { + case 'M': + case 'd': + case 'DDD': + case 'w': + case 'W': + return $number.'-й'; + case 'D': + return $number.'-го'; + default: + return $number; + } + }, + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'ночі'; + } + if ($hour < 12) { + return 'ранку'; + } + if ($hour < 17) { + return 'дня'; + } + + return 'вечора'; + }, + 'months' => ['січня', 'лютого', 'березня', 'квітня', 'травня', 'червня', 'липня', 'серпня', 'вересня', 'жовтня', 'листопада', 'грудня'], + 'months_standalone' => ['січень', 'лютий', 'березень', 'квітень', 'травень', 'червень', 'липень', 'серпень', 'вересень', 'жовтень', 'листопад', 'грудень'], + 'months_short' => ['січ', 'лют', 'бер', 'кві', 'тра', 'чер', 'лип', 'сер', 'вер', 'жов', 'лис', 'гру'], + 'months_regexp' => '/(D[oD]?(\[[^\[\]]*\]|\s)+MMMM?|L{2,4}|l{2,4})/', + 'weekdays' => function (CarbonInterface $date, $format, $index) { + static $words = [ + 'nominative' => ['неділя', 'понеділок', 'вівторок', 'середа', 'четвер', 'п’ятниця', 'субота'], + 'accusative' => ['неділю', 'понеділок', 'вівторок', 'середу', 'четвер', 'п’ятницю', 'суботу'], + 'genitive' => ['неділі', 'понеділка', 'вівторка', 'середи', 'четверга', 'п’ятниці', 'суботи'], + ]; + + $format = $format ?? ''; + $nounCase = preg_match('/(\[(В|в|У|у)\])\s+dddd/u', $format) + ? 'accusative' + : ( + preg_match('/\[?(?:минулої|наступної)?\s*\]\s+dddd/u', $format) + ? 'genitive' + : 'nominative' + ); + + return $words[$nounCase][$index] ?? null; + }, + 'weekdays_short' => ['нд', 'пн', 'вт', 'ср', 'чт', 'пт', 'сб'], + 'weekdays_min' => ['нд', 'пн', 'вт', 'ср', 'чт', 'пт', 'сб'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' i '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/uk_UA.php b/vendor/nesbot/carbon/src/Carbon/Lang/uk_UA.php new file mode 100644 index 0000000..bd11d86 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/uk_UA.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__.'/uk.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/unm.php b/vendor/nesbot/carbon/src/Carbon/Lang/unm.php new file mode 100644 index 0000000..d3f19f0 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/unm.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/unm_US.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/unm_US.php b/vendor/nesbot/carbon/src/Carbon/Lang/unm_US.php new file mode 100644 index 0000000..fa5c374 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/unm_US.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['enikwsi', 'chkwali', 'xamokhwite', 'kwetayoxe', 'tainipen', 'kichinipen', 'lainipen', 'winaminke', 'kichitahkok', 'puksit', 'wini', 'muxkotae'], + 'months_short' => ['eni', 'chk', 'xam', 'kwe', 'tai', 'nip', 'lai', 'win', 'tah', 'puk', 'kun', 'mux'], + 'weekdays' => ['kentuwei', 'manteke', 'tusteke', 'lelai', 'tasteke', 'pelaiteke', 'sateteke'], + 'weekdays_short' => ['ken', 'man', 'tus', 'lel', 'tas', 'pel', 'sat'], + 'weekdays_min' => ['ken', 'man', 'tus', 'lel', 'tas', 'pel', 'sat'], + 'day_of_first_week_of_year' => 1, + + // Too unreliable + /* + 'year' => ':count kaxtëne', + 'y' => ':count kaxtëne', + 'a_year' => ':count kaxtëne', + + 'month' => ':count piskewëni kishux', // less reliable + 'm' => ':count piskewëni kishux', // less reliable + 'a_month' => ':count piskewëni kishux', // less reliable + + 'week' => ':count kishku', // less reliable + 'w' => ':count kishku', // less reliable + 'a_week' => ':count kishku', // less reliable + + 'day' => ':count kishku', + 'd' => ':count kishku', + 'a_day' => ':count kishku', + + 'hour' => ':count xkuk', // less reliable + 'h' => ':count xkuk', // less reliable + 'a_hour' => ':count xkuk', // less reliable + + 'minute' => ':count txituwàk', // less reliable + 'min' => ':count txituwàk', // less reliable + 'a_minute' => ':count txituwàk', // less reliable + + 'second' => ':count nisha', // less reliable + 's' => ':count nisha', // less reliable + 'a_second' => ':count nisha', // less reliable + */ +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ur.php b/vendor/nesbot/carbon/src/Carbon/Lang/ur.php new file mode 100644 index 0000000..dc16c2c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ur.php @@ -0,0 +1,94 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +$months = [ + 'جنوری', + 'فروری', + 'مارچ', + 'اپریل', + 'مئی', + 'جون', + 'جولائی', + 'اگست', + 'ستمبر', + 'اکتوبر', + 'نومبر', + 'دسمبر', +]; + +$weekdays = [ + 'اتوار', + 'پیر', + 'منگل', + 'بدھ', + 'جمعرات', + 'جمعہ', + 'ہفتہ', +]; + +/* + * Authors: + * - Sawood Alam + * - Mehshan + * - Philippe Vaucher + * - Tsutomu Kuroda + * - tjku + * - Zaid Akram + * - Max Melentiev + * - hafezdivandari + * - Hossein Jabbari + * - nimamo + */ +return [ + 'year' => 'ایک سال|:count سال', + 'month' => 'ایک ماہ|:count ماہ', + 'week' => ':count ہفتے', + 'day' => 'ایک دن|:count دن', + 'hour' => 'ایک گھنٹہ|:count گھنٹے', + 'minute' => 'ایک منٹ|:count منٹ', + 'second' => 'چند سیکنڈ|:count سیکنڈ', + 'ago' => ':time قبل', + 'from_now' => ':time بعد', + 'after' => ':time بعد', + 'before' => ':time پہلے', + 'diff_now' => 'اب', + 'diff_today' => 'آج', + 'diff_today_regexp' => 'آج(?:\\s+بوقت)?', + 'diff_yesterday' => 'گزشتہ کل', + 'diff_yesterday_regexp' => 'گذشتہ(?:\\s+روز)?(?:\\s+بوقت)?', + 'diff_tomorrow' => 'آئندہ کل', + 'diff_tomorrow_regexp' => 'کل(?:\\s+بوقت)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd، D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[آج بوقت] LT', + 'nextDay' => '[کل بوقت] LT', + 'nextWeek' => 'dddd [بوقت] LT', + 'lastDay' => '[گذشتہ روز بوقت] LT', + 'lastWeek' => '[گذشتہ] dddd [بوقت] LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['صبح', 'شام'], + 'months' => $months, + 'months_short' => $months, + 'weekdays' => $weekdays, + 'weekdays_short' => $weekdays, + 'weekdays_min' => $weekdays, + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => ['، ', ' اور '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ur_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/ur_IN.php new file mode 100644 index 0000000..f81c84d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ur_IN.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Red Hat, Pune bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/ur.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['جنوری', 'فروری', 'مارچ', 'اپریل', 'مئی', 'جون', 'جولائی', 'اگست', 'ستمبر', 'اکتوبر', 'نومبر', 'دسمبر'], + 'months_short' => ['جنوری', 'فروری', 'مارچ', 'اپریل', 'مئی', 'جون', 'جولائی', 'اگست', 'ستمبر', 'اکتوبر', 'نومبر', 'دسمبر'], + 'weekdays' => ['اتوار', 'پیر', 'منگل', 'بدھ', 'جمعرات', 'جمعہ', 'سنیچر'], + 'weekdays_short' => ['اتوار', 'پیر', 'منگل', 'بدھ', 'جمعرات', 'جمعہ', 'سنیچر'], + 'weekdays_min' => ['اتوار', 'پیر', 'منگل', 'بدھ', 'جمعرات', 'جمعہ', 'سنیچر'], + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ur_PK.php b/vendor/nesbot/carbon/src/Carbon/Lang/ur_PK.php new file mode 100644 index 0000000..8cd593d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ur_PK.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/ur.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['جنوری', 'فروری', 'مارچ', 'اپریل', 'مئی', 'جون', 'جولائی', 'اگست', 'ستمبر', 'اکتوبر', 'نومبر', 'دسمبر'], + 'months_short' => ['جنوری', 'فروری', 'مارچ', 'اپریل', 'مئی', 'جون', 'جولائی', 'اگست', 'ستمبر', 'اکتوبر', 'نومبر', 'دسمبر'], + 'weekdays' => ['اتوار', 'پير', 'منگل', 'بدھ', 'جمعرات', 'جمعه', 'هفته'], + 'weekdays_short' => ['اتوار', 'پير', 'منگل', 'بدھ', 'جمعرات', 'جمعه', 'هفته'], + 'weekdays_min' => ['اتوار', 'پير', 'منگل', 'بدھ', 'جمعرات', 'جمعه', 'هفته'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['ص', 'ش'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/uz.php b/vendor/nesbot/carbon/src/Carbon/Lang/uz.php new file mode 100644 index 0000000..61f3b64 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/uz.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Dmitriy Shabanov + * - JD Isaacks + * - Inoyatulloh + * - Jamshid + * - aarkhipov + * - Philippe Vaucher + * - felixthemagnificent + * - Tsutomu Kuroda + * - tjku + * - Max Melentiev + * - Juanito Fatas + * - Alisher Ulugbekov + * - Ergashev Adizbek + */ +return [ + 'year' => ':count йил', + 'a_year' => '{1}бир йил|:count йил', + 'y' => ':count й', + 'month' => ':count ой', + 'a_month' => '{1}бир ой|:count ой', + 'm' => ':count о', + 'week' => ':count ҳафта', + 'a_week' => '{1}бир ҳафта|:count ҳафта', + 'w' => ':count ҳ', + 'day' => ':count кун', + 'a_day' => '{1}бир кун|:count кун', + 'd' => ':count к', + 'hour' => ':count соат', + 'a_hour' => '{1}бир соат|:count соат', + 'h' => ':count с', + 'minute' => ':count дақиқа', + 'a_minute' => '{1}бир дақиқа|:count дақиқа', + 'min' => ':count д', + 'second' => ':count сония', + 'a_second' => '{1}сония|:count сония', + 's' => ':count с', + 'ago' => ':time аввал', + 'from_now' => 'Якин :time ичида', + 'after' => ':timeдан кейин', + 'before' => ':time олдин', + 'diff_now' => 'ҳозир', + 'diff_today' => 'Бугун', + 'diff_today_regexp' => 'Бугун(?:\\s+соат)?', + 'diff_yesterday' => 'Кеча', + 'diff_yesterday_regexp' => 'Кеча(?:\\s+соат)?', + 'diff_tomorrow' => 'Эртага', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'D MMMM YYYY, dddd HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Бугун соат] LT [да]', + 'nextDay' => '[Эртага] LT [да]', + 'nextWeek' => 'dddd [куни соат] LT [да]', + 'lastDay' => '[Кеча соат] LT [да]', + 'lastWeek' => '[Утган] dddd [куни соат] LT [да]', + 'sameElse' => 'L', + ], + 'months' => ['январ', 'феврал', 'март', 'апрел', 'май', 'июн', 'июл', 'август', 'сентябр', 'октябр', 'ноябр', 'декабр'], + 'months_short' => ['янв', 'фев', 'мар', 'апр', 'май', 'июн', 'июл', 'авг', 'сен', 'окт', 'ноя', 'дек'], + 'weekdays' => ['якшанба', 'душанба', 'сешанба', 'чоршанба', 'пайшанба', 'жума', 'шанба'], + 'weekdays_short' => ['якш', 'душ', 'сеш', 'чор', 'пай', 'жум', 'шан'], + 'weekdays_min' => ['як', 'ду', 'се', 'чо', 'па', 'жу', 'ша'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['эрталаб', 'кечаси'], + 'list' => [', ', ' ва '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/uz_Arab.php b/vendor/nesbot/carbon/src/Carbon/Lang/uz_Arab.php new file mode 100644 index 0000000..ffb5131 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/uz_Arab.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/fa.php', [ + 'weekdays' => ['یکشنبه', 'دوشنبه', 'سه‌شنبه', 'چهارشنبه', 'پنجشنبه', 'جمعه', 'شنبه'], + 'weekdays_short' => ['ی.', 'د.', 'س.', 'چ.', 'پ.', 'ج.', 'ش.'], + 'weekdays_min' => ['ی.', 'د.', 'س.', 'چ.', 'پ.', 'ج.', 'ش.'], + 'months' => ['جنوری', 'فبروری', 'مارچ', 'اپریل', 'می', 'جون', 'جولای', 'اگست', 'سپتمبر', 'اکتوبر', 'نومبر', 'دسمبر'], + 'months_short' => ['جنو', 'فبر', 'مار', 'اپر', 'می', 'جون', 'جول', 'اگس', 'سپت', 'اکت', 'نوم', 'دسم'], + 'first_day_of_week' => 6, + 'weekend' => [4, 5], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-dd', + 'LL' => 'YYYY MMM D', + 'LLL' => 'YYYY MMMM D HH:mm', + 'LLLL' => 'YYYY MMMM D, dddd HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/uz_Cyrl.php b/vendor/nesbot/carbon/src/Carbon/Lang/uz_Cyrl.php new file mode 100644 index 0000000..89e9971 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/uz_Cyrl.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/uz.php', [ + 'formats' => [ + 'L' => 'DD/MM/yy', + 'LL' => 'D MMM, YYYY', + 'LLL' => 'D MMMM, YYYY HH:mm', + 'LLLL' => 'dddd, DD MMMM, YYYY HH:mm', + ], + 'meridiem' => ['ТО', 'ТК'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/uz_Latn.php b/vendor/nesbot/carbon/src/Carbon/Lang/uz_Latn.php new file mode 100644 index 0000000..ecceeaa --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/uz_Latn.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - Rasulbek + * - Ilyosjon Kamoldinov (ilyosjon09) + */ +return [ + 'year' => ':count yil', + 'a_year' => '{1}bir yil|:count yil', + 'y' => ':count y', + 'month' => ':count oy', + 'a_month' => '{1}bir oy|:count oy', + 'm' => ':count o', + 'week' => ':count hafta', + 'a_week' => '{1}bir hafta|:count hafta', + 'w' => ':count h', + 'day' => ':count kun', + 'a_day' => '{1}bir kun|:count kun', + 'd' => ':count k', + 'hour' => ':count soat', + 'a_hour' => '{1}bir soat|:count soat', + 'h' => ':count soat', + 'minute' => ':count daqiqa', + 'a_minute' => '{1}bir daqiqa|:count daqiqa', + 'min' => ':count d', + 'second' => ':count soniya', + 'a_second' => '{1}soniya|:count soniya', + 's' => ':count son.', + 'ago' => ':time avval', + 'from_now' => 'Yaqin :time ichida', + 'after' => ':timedan keyin', + 'before' => ':time oldin', + 'diff_yesterday' => 'Kecha', + 'diff_yesterday_regexp' => 'Kecha(?:\\s+soat)?', + 'diff_today' => 'Bugun', + 'diff_today_regexp' => 'Bugun(?:\\s+soat)?', + 'diff_tomorrow' => 'Ertaga', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'D MMMM YYYY, dddd HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Bugun soat] LT [da]', + 'nextDay' => '[Ertaga] LT [da]', + 'nextWeek' => 'dddd [kuni soat] LT [da]', + 'lastDay' => '[Kecha soat] LT [da]', + 'lastWeek' => '[O\'tgan] dddd [kuni soat] LT [da]', + 'sameElse' => 'L', + ], + 'months' => ['Yanvar', 'Fevral', 'Mart', 'Aprel', 'May', 'Iyun', 'Iyul', 'Avgust', 'Sentabr', 'Oktabr', 'Noyabr', 'Dekabr'], + 'months_short' => ['Yan', 'Fev', 'Mar', 'Apr', 'May', 'Iyun', 'Iyul', 'Avg', 'Sen', 'Okt', 'Noy', 'Dek'], + 'weekdays' => ['Yakshanba', 'Dushanba', 'Seshanba', 'Chorshanba', 'Payshanba', 'Juma', 'Shanba'], + 'weekdays_short' => ['Yak', 'Dush', 'Sesh', 'Chor', 'Pay', 'Jum', 'Shan'], + 'weekdays_min' => ['Ya', 'Du', 'Se', 'Cho', 'Pa', 'Ju', 'Sha'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' va '], + 'meridiem' => ['TO', 'TK'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/uz_UZ.php b/vendor/nesbot/carbon/src/Carbon/Lang/uz_UZ.php new file mode 100644 index 0000000..d41bfee --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/uz_UZ.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Bobir Ismailov Bobir Ismailov, Pablo Saratxaga, Mashrab Kuvatov bobir_is@yahoo.com, pablo@mandrakesoft.com, kmashrab@uni-bremen.de + */ +return array_replace_recursive(require __DIR__.'/uz_Latn.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['Yanvar', 'Fevral', 'Mart', 'Aprel', 'May', 'Iyun', 'Iyul', 'Avgust', 'Sentabr', 'Oktabr', 'Noyabr', 'Dekabr'], + 'months_short' => ['Yan', 'Fev', 'Mar', 'Apr', 'May', 'Iyn', 'Iyl', 'Avg', 'Sen', 'Okt', 'Noy', 'Dek'], + 'weekdays' => ['Yakshanba', 'Dushanba', 'Seshanba', 'Chorshanba', 'Payshanba', 'Juma', 'Shanba'], + 'weekdays_short' => ['Yak', 'Du', 'Se', 'Cho', 'Pay', 'Ju', 'Sha'], + 'weekdays_min' => ['Yak', 'Du', 'Se', 'Cho', 'Pay', 'Ju', 'Sha'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/uz_UZ@cyrillic.php b/vendor/nesbot/carbon/src/Carbon/Lang/uz_UZ@cyrillic.php new file mode 100644 index 0000000..2fa967c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/uz_UZ@cyrillic.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Mashrab Kuvatov Mashrab Kuvatov, Pablo Saratxaga kmashrab@uni-bremen.de, pablo@mandrakesoft.com + */ +return array_replace_recursive(require __DIR__.'/uz.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['Январ', 'Феврал', 'Март', 'Апрел', 'Май', 'Июн', 'Июл', 'Август', 'Сентябр', 'Октябр', 'Ноябр', 'Декабр'], + 'months_short' => ['Янв', 'Фев', 'Мар', 'Апр', 'Май', 'Июн', 'Июл', 'Авг', 'Сен', 'Окт', 'Ноя', 'Дек'], + 'weekdays' => ['Якшанба', 'Душанба', 'Сешанба', 'Чоршанба', 'Пайшанба', 'Жума', 'Шанба'], + 'weekdays_short' => ['Якш', 'Душ', 'Сеш', 'Чор', 'Пай', 'Жум', 'Шан'], + 'weekdays_min' => ['Якш', 'Душ', 'Сеш', 'Чор', 'Пай', 'Жум', 'Шан'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/vai.php b/vendor/nesbot/carbon/src/Carbon/Lang/vai.php new file mode 100644 index 0000000..3c378df --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/vai.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'weekdays' => ['ꕞꕌꔵ', 'ꗳꗡꘉ', 'ꕚꕞꕚ', 'ꕉꕞꕒ', 'ꕉꔤꕆꕢ', 'ꕉꔤꕀꕮ', 'ꔻꔬꔳ'], + 'weekdays_short' => ['ꕞꕌꔵ', 'ꗳꗡꘉ', 'ꕚꕞꕚ', 'ꕉꕞꕒ', 'ꕉꔤꕆꕢ', 'ꕉꔤꕀꕮ', 'ꔻꔬꔳ'], + 'weekdays_min' => ['ꕞꕌꔵ', 'ꗳꗡꘉ', 'ꕚꕞꕚ', 'ꕉꕞꕒ', 'ꕉꔤꕆꕢ', 'ꕉꔤꕀꕮ', 'ꔻꔬꔳ'], + 'months' => ['ꖨꖕ ꕪꕴ ꔞꔀꕮꕊ', 'ꕒꕡꖝꖕ', 'ꕾꖺ', 'ꖢꖕ', 'ꖑꕱ', 'ꖱꘋ', 'ꖱꕞꔤ', 'ꗛꔕ', 'ꕢꕌ', 'ꕭꖃ', 'ꔞꘋꕔꕿ ꕸꖃꗏ', 'ꖨꖕ ꕪꕴ ꗏꖺꕮꕊ'], + 'months_short' => ['ꖨꖕꔞ', 'ꕒꕡ', 'ꕾꖺ', 'ꖢꖕ', 'ꖑꕱ', 'ꖱꘋ', 'ꖱꕞ', 'ꗛꔕ', 'ꕢꕌ', 'ꕭꖃ', 'ꔞꘋ', 'ꖨꖕꗏ'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'dddd, D MMMM YYYY h:mm a', + ], + + 'year' => ':count ꕀ', // less reliable + 'y' => ':count ꕀ', // less reliable + 'a_year' => ':count ꕀ', // less reliable + + 'second' => ':count ꗱꕞꕯꕊ', // less reliable + 's' => ':count ꗱꕞꕯꕊ', // less reliable + 'a_second' => ':count ꗱꕞꕯꕊ', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/vai_Latn.php b/vendor/nesbot/carbon/src/Carbon/Lang/vai_Latn.php new file mode 100644 index 0000000..51e83cc --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/vai_Latn.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'weekdays' => ['lahadi', 'tɛɛnɛɛ', 'talata', 'alaba', 'aimisa', 'aijima', 'siɓiti'], + 'weekdays_short' => ['lahadi', 'tɛɛnɛɛ', 'talata', 'alaba', 'aimisa', 'aijima', 'siɓiti'], + 'weekdays_min' => ['lahadi', 'tɛɛnɛɛ', 'talata', 'alaba', 'aimisa', 'aijima', 'siɓiti'], + 'months' => ['luukao kemã', 'ɓandaɓu', 'vɔɔ', 'fulu', 'goo', '6', '7', 'kɔnde', 'saah', 'galo', 'kenpkato ɓololɔ', 'luukao lɔma'], + 'months_short' => ['luukao kemã', 'ɓandaɓu', 'vɔɔ', 'fulu', 'goo', '6', '7', 'kɔnde', 'saah', 'galo', 'kenpkato ɓololɔ', 'luukao lɔma'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'dddd, D MMMM YYYY h:mm a', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/vai_Vaii.php b/vendor/nesbot/carbon/src/Carbon/Lang/vai_Vaii.php new file mode 100644 index 0000000..b4bb533 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/vai_Vaii.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__.'/vai.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ve.php b/vendor/nesbot/carbon/src/Carbon/Lang/ve.php new file mode 100644 index 0000000..7f10aeb --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ve.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/ve_ZA.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ve_ZA.php b/vendor/nesbot/carbon/src/Carbon/Lang/ve_ZA.php new file mode 100644 index 0000000..5eb2b91 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ve_ZA.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Zuza Software Foundation (Translate.org.za) Dwayne Bailey dwayne@translate.org.za + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Phando', 'Luhuhi', 'Ṱhafamuhwe', 'Lambamai', 'Shundunthule', 'Fulwi', 'Fulwana', 'Ṱhangule', 'Khubvumedzi', 'Tshimedzi', 'Ḽara', 'Nyendavhusiku'], + 'months_short' => ['Pha', 'Luh', 'Fam', 'Lam', 'Shu', 'Lwi', 'Lwa', 'Ngu', 'Khu', 'Tsh', 'Ḽar', 'Nye'], + 'weekdays' => ['Swondaha', 'Musumbuluwo', 'Ḽavhuvhili', 'Ḽavhuraru', 'Ḽavhuṋa', 'Ḽavhuṱanu', 'Mugivhela'], + 'weekdays_short' => ['Swo', 'Mus', 'Vhi', 'Rar', 'ṋa', 'Ṱan', 'Mug'], + 'weekdays_min' => ['Swo', 'Mus', 'Vhi', 'Rar', 'ṋa', 'Ṱan', 'Mug'], + 'day_of_first_week_of_year' => 1, + + // Too unreliable + /* + 'day' => ':count vhege', // less reliable + 'd' => ':count vhege', // less reliable + 'a_day' => ':count vhege', // less reliable + + 'hour' => ':count watshi', // less reliable + 'h' => ':count watshi', // less reliable + 'a_hour' => ':count watshi', // less reliable + + 'minute' => ':count watshi', // less reliable + 'min' => ':count watshi', // less reliable + 'a_minute' => ':count watshi', // less reliable + + 'second' => ':count Mu', // less reliable + 's' => ':count Mu', // less reliable + 'a_second' => ':count Mu', // less reliable + + 'week' => ':count vhege', + 'w' => ':count vhege', + 'a_week' => ':count vhege', + */ +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/vi.php b/vendor/nesbot/carbon/src/Carbon/Lang/vi.php new file mode 100644 index 0000000..73e2852 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/vi.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - Andre Polykanine A.K.A. Menelion Elensúlë + * - JD Isaacks + */ +return [ + 'year' => ':count năm', + 'a_year' => '{1}một năm|]1, Inf[:count năm', + 'y' => ':count năm', + 'month' => ':count tháng', + 'a_month' => '{1}một tháng|]1, Inf[:count tháng', + 'm' => ':count tháng', + 'week' => ':count tuần', + 'a_week' => '{1}một tuần|]1, Inf[:count tuần', + 'w' => ':count tuần', + 'day' => ':count ngày', + 'a_day' => '{1}một ngày|]1, Inf[:count ngày', + 'd' => ':count ngày', + 'hour' => ':count giờ', + 'a_hour' => '{1}một giờ|]1, Inf[:count giờ', + 'h' => ':count giờ', + 'minute' => ':count phút', + 'a_minute' => '{1}một phút|]1, Inf[:count phút', + 'min' => ':count phút', + 'second' => ':count giây', + 'a_second' => '{1}vài giây|]1, Inf[:count giây', + 's' => ':count giây', + 'ago' => ':time trước', + 'from_now' => ':time tới', + 'after' => ':time sau', + 'before' => ':time trước', + 'diff_now' => 'bây giờ', + 'diff_today' => 'Hôm', + 'diff_today_regexp' => 'Hôm(?:\\s+nay)?(?:\\s+lúc)?', + 'diff_yesterday' => 'Hôm qua', + 'diff_yesterday_regexp' => 'Hôm(?:\\s+qua)?(?:\\s+lúc)?', + 'diff_tomorrow' => 'Ngày mai', + 'diff_tomorrow_regexp' => 'Ngày(?:\\s+mai)?(?:\\s+lúc)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM [năm] YYYY', + 'LLL' => 'D MMMM [năm] YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM [năm] YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Hôm nay lúc] LT', + 'nextDay' => '[Ngày mai lúc] LT', + 'nextWeek' => 'dddd [tuần tới lúc] LT', + 'lastDay' => '[Hôm qua lúc] LT', + 'lastWeek' => 'dddd [tuần trước lúc] LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['SA', 'CH'], + 'months' => ['tháng 1', 'tháng 2', 'tháng 3', 'tháng 4', 'tháng 5', 'tháng 6', 'tháng 7', 'tháng 8', 'tháng 9', 'tháng 10', 'tháng 11', 'tháng 12'], + 'months_short' => ['Th01', 'Th02', 'Th03', 'Th04', 'Th05', 'Th06', 'Th07', 'Th08', 'Th09', 'Th10', 'Th11', 'Th12'], + 'weekdays' => ['chủ nhật', 'thứ hai', 'thứ ba', 'thứ tư', 'thứ năm', 'thứ sáu', 'thứ bảy'], + 'weekdays_short' => ['CN', 'T2', 'T3', 'T4', 'T5', 'T6', 'T7'], + 'weekdays_min' => ['CN', 'T2', 'T3', 'T4', 'T5', 'T6', 'T7'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' và '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/vi_VN.php b/vendor/nesbot/carbon/src/Carbon/Lang/vi_VN.php new file mode 100644 index 0000000..18d8987 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/vi_VN.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__.'/vi.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/vo.php b/vendor/nesbot/carbon/src/Carbon/Lang/vo.php new file mode 100644 index 0000000..e273033 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/vo.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_replace_recursive(require __DIR__.'/en.php', [ + 'months' => ['M01', 'M02', 'M03', 'M04', 'M05', 'M06', 'M07', 'M08', 'M09', 'M10', 'M11', 'M12'], + 'months_short' => ['M01', 'M02', 'M03', 'M04', 'M05', 'M06', 'M07', 'M08', 'M09', 'M10', 'M11', 'M12'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-dd', + 'LL' => 'YYYY MMM D', + 'LLL' => 'YYYY MMMM D HH:mm', + 'LLLL' => 'YYYY MMMM D, dddd HH:mm', + ], + + 'year' => ':count yel', + 'y' => ':count yel', + 'a_year' => ':count yel', + + 'month' => ':count mul', + 'm' => ':count mul', + 'a_month' => ':count mul', + + 'week' => ':count vig', + 'w' => ':count vig', + 'a_week' => ':count vig', + + 'day' => ':count del', + 'd' => ':count del', + 'a_day' => ':count del', + + 'hour' => ':count düp', + 'h' => ':count düp', + 'a_hour' => ':count düp', + + 'minute' => ':count minut', + 'min' => ':count minut', + 'a_minute' => ':count minut', + + 'second' => ':count sekun', + 's' => ':count sekun', + 'a_second' => ':count sekun', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/vun.php b/vendor/nesbot/carbon/src/Carbon/Lang/vun.php new file mode 100644 index 0000000..ed92e8e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/vun.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['utuko', 'kyiukonyi'], + 'weekdays' => ['Jumapilyi', 'Jumatatuu', 'Jumanne', 'Jumatanu', 'Alhamisi', 'Ijumaa', 'Jumamosi'], + 'weekdays_short' => ['Jpi', 'Jtt', 'Jnn', 'Jtn', 'Alh', 'Iju', 'Jmo'], + 'weekdays_min' => ['Jpi', 'Jtt', 'Jnn', 'Jtn', 'Alh', 'Iju', 'Jmo'], + 'months' => ['Januari', 'Februari', 'Machi', 'Aprilyi', 'Mei', 'Junyi', 'Julyai', 'Agusti', 'Septemba', 'Oktoba', 'Novemba', 'Desemba'], + 'months_short' => ['Jan', 'Feb', 'Mac', 'Apr', 'Mei', 'Jun', 'Jul', 'Ago', 'Sep', 'Okt', 'Nov', 'Des'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/wa.php b/vendor/nesbot/carbon/src/Carbon/Lang/wa.php new file mode 100644 index 0000000..f6dc4cc --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/wa.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/wa_BE.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/wa_BE.php b/vendor/nesbot/carbon/src/Carbon/Lang/wa_BE.php new file mode 100644 index 0000000..a76d80d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/wa_BE.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Djan SACRE Pablo Saratxaga pablo@mandrakesoft.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['di djanvî', 'di fevrî', 'di måss', 'd’ avri', 'di may', 'di djun', 'di djulete', 'd’ awousse', 'di setimbe', 'd’ octôbe', 'di nôvimbe', 'di decimbe'], + 'months_short' => ['dja', 'fev', 'mås', 'avr', 'may', 'djn', 'djl', 'awo', 'set', 'oct', 'nôv', 'dec'], + 'weekdays' => ['dimegne', 'londi', 'mårdi', 'mierkidi', 'djudi', 'vénrdi', 'semdi'], + 'weekdays_short' => ['dim', 'lon', 'mår', 'mie', 'dju', 'vén', 'sem'], + 'weekdays_min' => ['dim', 'lon', 'mår', 'mie', 'dju', 'vén', 'sem'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'year' => ':count anêye', + 'y' => ':count anêye', + 'a_year' => ':count anêye', + + 'month' => ':count meûs', + 'm' => ':count meûs', + 'a_month' => ':count meûs', + + 'week' => ':count samwinne', + 'w' => ':count samwinne', + 'a_week' => ':count samwinne', + + 'day' => ':count djoû', + 'd' => ':count djoû', + 'a_day' => ':count djoû', + + 'hour' => ':count eure', + 'h' => ':count eure', + 'a_hour' => ':count eure', + + 'minute' => ':count munute', + 'min' => ':count munute', + 'a_minute' => ':count munute', + + 'second' => ':count Sigonde', + 's' => ':count Sigonde', + 'a_second' => ':count Sigonde', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/wae.php b/vendor/nesbot/carbon/src/Carbon/Lang/wae.php new file mode 100644 index 0000000..bf57f23 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/wae.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/wae_CH.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/wae_CH.php b/vendor/nesbot/carbon/src/Carbon/Lang/wae_CH.php new file mode 100644 index 0000000..2af50b4 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/wae_CH.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Walser Translation Team ml@translate-wae.ch + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'YYYY-MM-DD', + ], + 'months' => ['Jenner', 'Hornig', 'Märze', 'Abrille', 'Meije', 'Bráčet', 'Heiwet', 'Öigšte', 'Herbštmánet', 'Wímánet', 'Wintermánet', 'Chrištmánet'], + 'months_short' => ['Jen', 'Hor', 'Mär', 'Abr', 'Mei', 'Brá', 'Hei', 'Öig', 'Her', 'Wím', 'Win', 'Chr'], + 'weekdays' => ['Suntag', 'Mäntag', 'Zischtag', 'Mittwuch', 'Frontag', 'Fritag', 'Samschtag'], + 'weekdays_short' => ['Sun', 'Män', 'Zis', 'Mit', 'Fro', 'Fri', 'Sam'], + 'weekdays_min' => ['Sun', 'Män', 'Zis', 'Mit', 'Fro', 'Fri', 'Sam'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'month' => ':count Maano', // less reliable + 'm' => ':count Maano', // less reliable + 'a_month' => ':count Maano', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/wal.php b/vendor/nesbot/carbon/src/Carbon/Lang/wal.php new file mode 100644 index 0000000..e8ec40f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/wal.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/wal_ET.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/wal_ET.php b/vendor/nesbot/carbon/src/Carbon/Lang/wal_ET.php new file mode 100644 index 0000000..a4e619a --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/wal_ET.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['ጃንዩወሪ', 'ፌብሩወሪ', 'ማርች', 'ኤፕረል', 'ሜይ', 'ጁን', 'ጁላይ', 'ኦገስት', 'ሴፕቴምበር', 'ኦክተውበር', 'ኖቬምበር', 'ዲሴምበር'], + 'months_short' => ['ጃንዩ', 'ፌብሩ', 'ማርች', 'ኤፕረ', 'ሜይ ', 'ጁን ', 'ጁላይ', 'ኦገስ', 'ሴፕቴ', 'ኦክተ', 'ኖቬም', 'ዲሴም'], + 'weekdays' => ['ወጋ', 'ሳይኖ', 'ማቆሳኛ', 'አሩዋ', 'ሃሙሳ', 'አርባ', 'ቄራ'], + 'weekdays_short' => ['ወጋ ', 'ሳይኖ', 'ማቆሳ', 'አሩዋ', 'ሃሙሳ', 'አርባ', 'ቄራ '], + 'weekdays_min' => ['ወጋ ', 'ሳይኖ', 'ማቆሳ', 'አሩዋ', 'ሃሙሳ', 'አርባ', 'ቄራ '], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['ማለዶ', 'ቃማ'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/wo.php b/vendor/nesbot/carbon/src/Carbon/Lang/wo.php new file mode 100644 index 0000000..74b95df --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/wo.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/wo_SN.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/wo_SN.php b/vendor/nesbot/carbon/src/Carbon/Lang/wo_SN.php new file mode 100644 index 0000000..f8a85b3 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/wo_SN.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - The Debian Project Christian Perrier bubulle@debian.org + */ +return [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'MMMM DD, YYYY', + 'LLL' => 'DD MMM HH:mm', + 'LLLL' => 'MMMM DD, YYYY HH:mm', + ], + 'months' => ['sanwiy\'e', 'feebriy\'e', 'mars', 'awril', 'me', 'suwen', 'sulet', 'uut', 'septaambar', 'oktoobar', 'nowaambar', 'desaambar'], + 'months_short' => ['san', 'fee', 'mar', 'awr', 'me ', 'suw', 'sul', 'uut', 'sep', 'okt', 'now', 'des'], + 'weekdays' => ['dib\'eer', 'altine', 'talaata', 'allarba', 'alxames', 'ajjuma', 'gaawu'], + 'weekdays_short' => ['dib', 'alt', 'tal', 'all', 'alx', 'ajj', 'gaa'], + 'weekdays_min' => ['dib', 'alt', 'tal', 'all', 'alx', 'ajj', 'gaa'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'year' => ':count at', + 'month' => ':count wèr', + 'week' => ':count ayubés', + 'day' => ':count bés', + 'hour' => ':count waxtu', + 'minute' => ':count simili', + 'second' => ':count saa', +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/xh.php b/vendor/nesbot/carbon/src/Carbon/Lang/xh.php new file mode 100644 index 0000000..e88c78d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/xh.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/xh_ZA.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/xh_ZA.php b/vendor/nesbot/carbon/src/Carbon/Lang/xh_ZA.php new file mode 100644 index 0000000..910f831 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/xh_ZA.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Zuza Software Foundation (Translate.org.za) Dwayne Bailey dwayne@translate.org.za + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['eyoMqungu', 'eyoMdumba', 'eyoKwindla', 'uTshazimpuzi', 'uCanzibe', 'eyeSilimela', 'eyeKhala', 'eyeThupa', 'eyoMsintsi', 'eyeDwarha', 'eyeNkanga', 'eyoMnga'], + 'months_short' => ['Mqu', 'Mdu', 'Kwi', 'Tsh', 'Can', 'Sil', 'Kha', 'Thu', 'Msi', 'Dwa', 'Nka', 'Mng'], + 'weekdays' => ['iCawa', 'uMvulo', 'lwesiBini', 'lwesiThathu', 'ulweSine', 'lwesiHlanu', 'uMgqibelo'], + 'weekdays_short' => ['Caw', 'Mvu', 'Bin', 'Tha', 'Sin', 'Hla', 'Mgq'], + 'weekdays_min' => ['Caw', 'Mvu', 'Bin', 'Tha', 'Sin', 'Hla', 'Mgq'], + 'day_of_first_week_of_year' => 1, + + 'year' => ':count ihlobo', // less reliable + 'y' => ':count ihlobo', // less reliable + 'a_year' => ':count ihlobo', // less reliable + + 'hour' => ':count iwotshi', // less reliable + 'h' => ':count iwotshi', // less reliable + 'a_hour' => ':count iwotshi', // less reliable + + 'minute' => ':count ingqalelo', // less reliable + 'min' => ':count ingqalelo', // less reliable + 'a_minute' => ':count ingqalelo', // less reliable + + 'second' => ':count nceda', // less reliable + 's' => ':count nceda', // less reliable + 'a_second' => ':count nceda', // less reliable + + 'month' => ':count inyanga', + 'm' => ':count inyanga', + 'a_month' => ':count inyanga', + + 'week' => ':count veki', + 'w' => ':count veki', + 'a_week' => ':count veki', + + 'day' => ':count imini', + 'd' => ':count imini', + 'a_day' => ':count imini', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/xog.php b/vendor/nesbot/carbon/src/Carbon/Lang/xog.php new file mode 100644 index 0000000..eb55b4a --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/xog.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['Munkyo', 'Eigulo'], + 'weekdays' => ['Sabiiti', 'Balaza', 'Owokubili', 'Owokusatu', 'Olokuna', 'Olokutaanu', 'Olomukaaga'], + 'weekdays_short' => ['Sabi', 'Bala', 'Kubi', 'Kusa', 'Kuna', 'Kuta', 'Muka'], + 'weekdays_min' => ['Sabi', 'Bala', 'Kubi', 'Kusa', 'Kuna', 'Kuta', 'Muka'], + 'months' => ['Janwaliyo', 'Febwaliyo', 'Marisi', 'Apuli', 'Maayi', 'Juuni', 'Julaayi', 'Agusito', 'Sebuttemba', 'Okitobba', 'Novemba', 'Desemba'], + 'months_short' => ['Jan', 'Feb', 'Mar', 'Apu', 'Maa', 'Juu', 'Jul', 'Agu', 'Seb', 'Oki', 'Nov', 'Des'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/yav.php b/vendor/nesbot/carbon/src/Carbon/Lang/yav.php new file mode 100644 index 0000000..225a20d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/yav.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['kiɛmɛ́ɛm', 'kisɛ́ndɛ'], + 'weekdays' => ['sɔ́ndiɛ', 'móndie', 'muányáŋmóndie', 'metúkpíápɛ', 'kúpélimetúkpiapɛ', 'feléte', 'séselé'], + 'weekdays_short' => ['sd', 'md', 'mw', 'et', 'kl', 'fl', 'ss'], + 'weekdays_min' => ['sd', 'md', 'mw', 'et', 'kl', 'fl', 'ss'], + 'months' => ['pikítíkítie, oólí ú kutúan', 'siɛyɛ́, oóli ú kándíɛ', 'ɔnsúmbɔl, oóli ú kátátúɛ', 'mesiŋ, oóli ú kénie', 'ensil, oóli ú kátánuɛ', 'ɔsɔn', 'efute', 'pisuyú', 'imɛŋ i puɔs', 'imɛŋ i putúk,oóli ú kátíɛ', 'makandikɛ', 'pilɔndɔ́'], + 'months_short' => ['o.1', 'o.2', 'o.3', 'o.4', 'o.5', 'o.6', 'o.7', 'o.8', 'o.9', 'o.10', 'o.11', 'o.12'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/yi.php b/vendor/nesbot/carbon/src/Carbon/Lang/yi.php new file mode 100644 index 0000000..8f32022 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/yi.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/yi_US.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/yi_US.php b/vendor/nesbot/carbon/src/Carbon/Lang/yi_US.php new file mode 100644 index 0000000..f764d36 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/yi_US.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - http://www.uyip.org/ Pablo Saratxaga pablo@mandrakesoft.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['יאַנואַר', 'פֿעברואַר', 'מערץ', 'אַפּריל', 'מיי', 'יוני', 'יולי', 'אויגוסט', 'סעפּטעמבער', 'אקטאבער', 'נאוועמבער', 'דעצעמבער'], + 'months_short' => ['יאַנ', 'פֿעב', 'מאַר', 'אַפּר', 'מײַ ', 'יונ', 'יול', 'אױג', 'סעפּ', 'אָקט', 'נאָװ', 'דעצ'], + 'weekdays' => ['זונטיק', 'מאָנטיק', 'דינסטיק', 'מיטװאָך', 'דאָנערשטיק', 'פֿרײַטיק', 'שבת'], + 'weekdays_short' => ['זונ\'', 'מאָנ\'', 'דינ\'', 'מיט\'', 'דאָנ\'', 'פֿרײַ\'', 'שבת'], + 'weekdays_min' => ['זונ\'', 'מאָנ\'', 'דינ\'', 'מיט\'', 'דאָנ\'', 'פֿרײַ\'', 'שבת'], + 'day_of_first_week_of_year' => 1, + + 'year' => ':count יאר', + 'y' => ':count יאר', + 'a_year' => ':count יאר', + + 'month' => ':count חודש', + 'm' => ':count חודש', + 'a_month' => ':count חודש', + + 'week' => ':count וואָך', + 'w' => ':count וואָך', + 'a_week' => ':count וואָך', + + 'day' => ':count טאָג', + 'd' => ':count טאָג', + 'a_day' => ':count טאָג', + + 'hour' => ':count שעה', + 'h' => ':count שעה', + 'a_hour' => ':count שעה', + + 'minute' => ':count מינוט', + 'min' => ':count מינוט', + 'a_minute' => ':count מינוט', + + 'second' => ':count סעקונדע', + 's' => ':count סעקונדע', + 'a_second' => ':count סעקונדע', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/yo.php b/vendor/nesbot/carbon/src/Carbon/Lang/yo.php new file mode 100644 index 0000000..0a82981 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/yo.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - Atolagbe Abisoye + */ +return [ + 'year' => 'ọdún :count', + 'a_year' => '{1}ọdún kan|ọdún :count', + 'month' => 'osù :count', + 'a_month' => '{1}osù kan|osù :count', + 'week' => 'ọsẹ :count', + 'a_week' => '{1}ọsẹ kan|ọsẹ :count', + 'day' => 'ọjọ́ :count', + 'a_day' => '{1}ọjọ́ kan|ọjọ́ :count', + 'hour' => 'wákati :count', + 'a_hour' => '{1}wákati kan|wákati :count', + 'minute' => 'ìsẹjú :count', + 'a_minute' => '{1}ìsẹjú kan|ìsẹjú :count', + 'second' => 'iaayá :count', + 'a_second' => '{1}ìsẹjú aayá die|aayá :count', + 'ago' => ':time kọjá', + 'from_now' => 'ní :time', + 'diff_yesterday' => 'Àna', + 'diff_yesterday_regexp' => 'Àna(?:\\s+ni)?', + 'diff_today' => 'Ònì', + 'diff_today_regexp' => 'Ònì(?:\\s+ni)?', + 'diff_tomorrow' => 'Ọ̀la', + 'diff_tomorrow_regexp' => 'Ọ̀la(?:\\s+ni)?', + 'formats' => [ + 'LT' => 'h:mm A', + 'LTS' => 'h:mm:ss A', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm A', + 'LLLL' => 'dddd, D MMMM YYYY h:mm A', + ], + 'calendar' => [ + 'sameDay' => '[Ònì ni] LT', + 'nextDay' => '[Ọ̀la ni] LT', + 'nextWeek' => 'dddd [Ọsẹ̀ tón\'bọ] [ni] LT', + 'lastDay' => '[Àna ni] LT', + 'lastWeek' => 'dddd [Ọsẹ̀ tólọ́] [ni] LT', + 'sameElse' => 'L', + ], + 'ordinal' => 'ọjọ́ :number', + 'months' => ['Sẹ́rẹ́', 'Èrèlè', 'Ẹrẹ̀nà', 'Ìgbé', 'Èbibi', 'Òkùdu', 'Agẹmo', 'Ògún', 'Owewe', 'Ọ̀wàrà', 'Bélú', 'Ọ̀pẹ̀̀'], + 'months_short' => ['Sẹ́r', 'Èrl', 'Ẹrn', 'Ìgb', 'Èbi', 'Òkù', 'Agẹ', 'Ògú', 'Owe', 'Ọ̀wà', 'Bél', 'Ọ̀pẹ̀̀'], + 'weekdays' => ['Àìkú', 'Ajé', 'Ìsẹ́gun', 'Ọjọ́rú', 'Ọjọ́bọ', 'Ẹtì', 'Àbámẹ́ta'], + 'weekdays_short' => ['Àìk', 'Ajé', 'Ìsẹ́', 'Ọjr', 'Ọjb', 'Ẹtì', 'Àbá'], + 'weekdays_min' => ['Àì', 'Aj', 'Ìs', 'Ọr', 'Ọb', 'Ẹt', 'Àb'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'meridiem' => ['Àárọ̀', 'Ọ̀sán'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/yo_BJ.php b/vendor/nesbot/carbon/src/Carbon/Lang/yo_BJ.php new file mode 100644 index 0000000..12b9e81 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/yo_BJ.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/yo.php', [ + 'meridiem' => ['Àárɔ̀', 'Ɔ̀sán'], + 'weekdays' => ['Ɔjɔ́ Àìkú', 'Ɔjɔ́ Ajé', 'Ɔjɔ́ Ìsɛ́gun', 'Ɔjɔ́rú', 'Ɔjɔ́bɔ', 'Ɔjɔ́ Ɛtì', 'Ɔjɔ́ Àbámɛ́ta'], + 'weekdays_short' => ['Àìkú', 'Ajé', 'Ìsɛ́gun', 'Ɔjɔ́rú', 'Ɔjɔ́bɔ', 'Ɛtì', 'Àbámɛ́ta'], + 'weekdays_min' => ['Àìkú', 'Ajé', 'Ìsɛ́gun', 'Ɔjɔ́rú', 'Ɔjɔ́bɔ', 'Ɛtì', 'Àbámɛ́ta'], + 'months' => ['Oshù Shɛ́rɛ́', 'Oshù Èrèlè', 'Oshù Ɛrɛ̀nà', 'Oshù Ìgbé', 'Oshù Ɛ̀bibi', 'Oshù Òkúdu', 'Oshù Agɛmɔ', 'Oshù Ògún', 'Oshù Owewe', 'Oshù Ɔ̀wàrà', 'Oshù Bélú', 'Oshù Ɔ̀pɛ̀'], + 'months_short' => ['Shɛ́rɛ́', 'Èrèlè', 'Ɛrɛ̀nà', 'Ìgbé', 'Ɛ̀bibi', 'Òkúdu', 'Agɛmɔ', 'Ògún', 'Owewe', 'Ɔ̀wàrà', 'Bélú', 'Ɔ̀pɛ̀'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/yo_NG.php b/vendor/nesbot/carbon/src/Carbon/Lang/yo_NG.php new file mode 100644 index 0000000..6860bc1 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/yo_NG.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__.'/yo.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/yue.php b/vendor/nesbot/carbon/src/Carbon/Lang/yue.php new file mode 100644 index 0000000..ce233a4 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/yue.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/yue_HK.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/yue_HK.php b/vendor/nesbot/carbon/src/Carbon/Lang/yue_HK.php new file mode 100644 index 0000000..4e7d5c3 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/yue_HK.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/zh_HK.php', [ + 'formats' => [ + 'L' => 'YYYY年MM月DD日 dddd', + ], + 'months' => ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'], + 'months_short' => ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'], + 'weekdays' => ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'], + 'weekdays_short' => ['日', '一', '二', '三', '四', '五', '六'], + 'weekdays_min' => ['日', '一', '二', '三', '四', '五', '六'], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['上午', '下午'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/yue_Hans.php b/vendor/nesbot/carbon/src/Carbon/Lang/yue_Hans.php new file mode 100644 index 0000000..db913ca --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/yue_Hans.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__.'/zh_Hans.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/yue_Hant.php b/vendor/nesbot/carbon/src/Carbon/Lang/yue_Hant.php new file mode 100644 index 0000000..e2526f1 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/yue_Hant.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__.'/zh_Hant.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/yuw.php b/vendor/nesbot/carbon/src/Carbon/Lang/yuw.php new file mode 100644 index 0000000..8efdc93 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/yuw.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/yuw_PG.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/yuw_PG.php b/vendor/nesbot/carbon/src/Carbon/Lang/yuw_PG.php new file mode 100644 index 0000000..b99ad2e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/yuw_PG.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Information from native speakers Hannah Sarvasy nungon.localization@gmail.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['jenuari', 'febuari', 'mas', 'epril', 'mei', 'jun', 'julai', 'ögus', 'septemba', 'öktoba', 'nöwemba', 'diksemba'], + 'months_short' => ['jen', 'feb', 'mas', 'epr', 'mei', 'jun', 'jul', 'ögu', 'sep', 'ökt', 'nöw', 'dis'], + 'weekdays' => ['sönda', 'mönda', 'sinda', 'mitiwö', 'sogipbono', 'nenggo', 'söndanggie'], + 'weekdays_short' => ['sön', 'mön', 'sin', 'mit', 'soi', 'nen', 'sab'], + 'weekdays_min' => ['sön', 'mön', 'sin', 'mit', 'soi', 'nen', 'sab'], + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/zgh.php b/vendor/nesbot/carbon/src/Carbon/Lang/zgh.php new file mode 100644 index 0000000..4d2c3b3 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/zgh.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - BAKTETE Miloud + */ +return [ + 'year' => ':count ⵓⵙⴳⴳⵯⴰⵙ|:count ⵉⵙⴳⴳⵓⵙⴰ', + 'a_year' => 'ⵓⵙⴳⴳⵯⴰⵙ|:count ⵉⵙⴳⴳⵓⵙⴰ', + 'y' => ':count ⵓⵙⴳⴳⵯⴰⵙ|:count ⵉⵙⴳⴳⵓⵙⴰ', + 'month' => ':count ⵡⴰⵢⵢⵓⵔ|:count ⴰⵢⵢⵓⵔⵏ', + 'a_month' => 'ⵉⴷⵊ ⵡⴰⵢⵢⵓⵔ|:count ⴰⵢⵢⵓⵔⵏ', + 'm' => ':count ⴰⵢⵢⵓⵔⵏ', + 'week' => ':count ⵉⵎⴰⵍⴰⵙⵙ|:count ⵉⵎⴰⵍⴰⵙⵙⵏ', + 'a_week' => 'ⵉⵛⵜ ⵉⵎⴰⵍⴰⵙⵙ|:count ⵉⵎⴰⵍⴰⵙⵙⵏ', + 'w' => ':count ⵉⵎⴰⵍⴰⵙⵙ.', + 'day' => ':count ⵡⴰⵙⵙ|:count ⵓⵙⵙⴰⵏ', + 'a_day' => 'ⵉⴷⵊ ⵡⴰⵙⵙ|:count ⵓⵙⵙⴰⵏ', + 'd' => ':count ⵓ', + 'hour' => ':count ⵜⵙⵔⴰⴳⵜ|:count ⵜⵉⵙⵔⴰⴳⵉⵏ', + 'a_hour' => 'ⵉⵛⵜ ⵜⵙⵔⴰⴳⵜ|:count ⵜⵉⵙⵔⴰⴳⵉⵏ', + 'h' => ':count ⵜ', + 'minute' => ':count ⵜⵓⵙⴷⵉⴷⵜ|:count ⵜⵓⵙⴷⵉⴷⵉⵏ', + 'a_minute' => 'ⵉⵛⵜ ⵜⵓⵙⴷⵉⴷⵜ|:count ⵜⵓⵙⴷⵉⴷⵉⵏ', + 'min' => ':count ⵜⵓⵙ', + 'second' => ':count ⵜⵙⵉⵏⵜ|:count ⵜⵉⵙⵉⵏⴰ', + 'a_second' => 'ⴽⵔⴰ ⵜⵉⵙⵉⵏⴰ|:count ⵜⵉⵙⵉⵏⴰ', + 's' => ':count ⵜ', + 'ago' => 'ⵣⴳ :time', + 'from_now' => 'ⴷⴳ :time', + 'after' => ':time ⴰⵡⴰⵔ', + 'before' => ':time ⴷⴰⵜ', + 'diff_now' => 'ⴰⴷⵡⴰⵍⵉ', + 'diff_today' => 'ⴰⵙⵙ', + 'diff_today_regexp' => 'ⴰⵙⵙ(?:\\s+ⴰ/ⴰⴷ)?(?:\\s+ⴳ)?', + 'diff_yesterday' => 'ⴰⵙⵙⵏⵏⴰⵟ', + 'diff_yesterday_regexp' => 'ⴰⵙⵙⵏⵏⴰⵟ(?:\\s+ⴳ)?', + 'diff_tomorrow' => 'ⴰⵙⴽⴽⴰ', + 'diff_tomorrow_regexp' => 'ⴰⵙⴽⴽⴰ(?:\\s+ⴳ)?', + 'diff_before_yesterday' => 'ⴼⵔ ⵉⴹⵏⵏⴰⵟ', + 'diff_after_tomorrow' => 'ⵏⴰⴼ ⵓⵙⴽⴽⴰ', + 'period_recurrences' => ':count ⵜⵉⴽⴽⴰⵍ', + 'period_interval' => 'ⴽⵓ :interval', + 'period_start_date' => 'ⴳ :date', + 'period_end_date' => 'ⵉ :date', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[ⴰⵙⵙ ⴰ/ⴰⴷ ⴳ] LT', + 'nextDay' => '[ⴰⵙⴽⴽⴰ ⴳ] LT', + 'nextWeek' => 'dddd [ⴳ] LT', + 'lastDay' => '[ⴰⵙⵙⵏⵏⴰⵟ ⴳ] LT', + 'lastWeek' => 'dddd [ⴰⵎⴳⴳⴰⵔⵓ ⴳ] LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['ⵜⵉⴼⴰⵡⵜ', 'ⵜⴰⴷⴳⴳⵯⴰⵜ'], + 'months' => ['ⵉⵏⵏⴰⵢⵔ', 'ⴱⵕⴰⵢⵕ', 'ⵎⴰⵕⵚ', 'ⵉⴱⵔⵉⵔ', 'ⵎⴰⵢⵢⵓ', 'ⵢⵓⵏⵢⵓ', 'ⵢⵓⵍⵢⵓⵣ', 'ⵖⵓⵛⵜ', 'ⵛⵓⵜⴰⵏⴱⵉⵔ', 'ⴽⵟⵓⴱⵕ', 'ⵏⵓⵡⴰⵏⴱⵉⵔ', 'ⴷⵓⵊⴰⵏⴱⵉⵔ'], + 'months_short' => ['ⵉⵏⵏ', 'ⴱⵕⴰ', 'ⵎⴰⵕ', 'ⵉⴱⵔ', 'ⵎⴰⵢ', 'ⵢⵓⵏ', 'ⵢⵓⵍ', 'ⵖⵓⵛ', 'ⵛⵓⵜ', 'ⴽⵟⵓ', 'ⵏⵓⵡ', 'ⴷⵓⵊ'], + 'weekdays' => ['ⵓⵙⴰⵎⴰⵙ', 'ⵡⴰⵢⵏⴰⵙ', 'ⵓⵙⵉⵏⴰⵙ', 'ⵡⴰⴽⵕⴰⵙ', 'ⵓⴽⵡⴰⵙ', 'ⵓⵙⵉⵎⵡⴰⵙ', 'ⵓⵙⵉⴹⵢⴰⵙ'], + 'weekdays_short' => ['ⵓⵙⴰ', 'ⵡⴰⵢ', 'ⵓⵙⵉ', 'ⵡⴰⴽ', 'ⵓⴽⵡ', 'ⵓⵙⵉⵎ', 'ⵓⵙⵉⴹ'], + 'weekdays_min' => ['ⵓⵙⴰ', 'ⵡⴰⵢ', 'ⵓⵙⵉ', 'ⵡⴰⴽ', 'ⵓⴽⵡ', 'ⵓⵙⵉⵎ', 'ⵓⵙⵉⴹ'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' ⴷ '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/zh.php b/vendor/nesbot/carbon/src/Carbon/Lang/zh.php new file mode 100644 index 0000000..1187c3d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/zh.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - xuri + * - sycuato + * - bokideckonja + * - Luo Ning + * - William Yang (williamyang233) + */ +return array_merge(require __DIR__.'/zh_Hans.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY/MM/DD', + 'LL' => 'YYYY年M月D日', + 'LLL' => 'YYYY年M月D日 A h点mm分', + 'LLLL' => 'YYYY年M月D日dddd A h点mm分', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/zh_CN.php b/vendor/nesbot/carbon/src/Carbon/Lang/zh_CN.php new file mode 100644 index 0000000..9c05d5a --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/zh_CN.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - monkeycon + * - François B + * - Jason Katz-Brown + * - Serhan Apaydın + * - Matt Johnson + * - JD Isaacks + * - Zeno Zeng + * - Chris Hemp + * - shankesgk2 + */ +return array_merge(require __DIR__.'/zh.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY/MM/DD', + 'LL' => 'YYYY年M月D日', + 'LLL' => 'YYYY年M月D日Ah点mm分', + 'LLLL' => 'YYYY年M月D日ddddAh点mm分', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/zh_HK.php b/vendor/nesbot/carbon/src/Carbon/Lang/zh_HK.php new file mode 100644 index 0000000..c3ee9fc --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/zh_HK.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__.'/zh_Hant_HK.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hans.php b/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hans.php new file mode 100644 index 0000000..9b91785 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hans.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - monkeycon + * - François B + * - Jason Katz-Brown + * - Konstantin Konev + * - Chris Lam + * - Serhan Apaydın + * - Gary Lo + * - JD Isaacks + * - Chris Hemp + * - shankesgk2 + * - Daniel Cheung (danvim) + */ +return [ + 'year' => ':count:optional-space年', + 'y' => ':count:optional-space年', + 'month' => ':count:optional-space个月', + 'm' => ':count:optional-space个月', + 'week' => ':count:optional-space周', + 'w' => ':count:optional-space周', + 'day' => ':count:optional-space天', + 'd' => ':count:optional-space天', + 'hour' => ':count:optional-space小时', + 'h' => ':count:optional-space小时', + 'minute' => ':count:optional-space分钟', + 'min' => ':count:optional-space分钟', + 'second' => ':count:optional-space秒', + 'a_second' => '{1}几秒|]1,Inf[:count:optional-space秒', + 's' => ':count:optional-space秒', + 'ago' => ':time前', + 'from_now' => ':time后', + 'after' => ':time后', + 'before' => ':time前', + 'diff_now' => '现在', + 'diff_today' => '今天', + 'diff_yesterday' => '昨天', + 'diff_tomorrow' => '明天', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY/MM/DD', + 'LL' => 'YYYY年M月D日', + 'LLL' => 'YYYY年M月D日 HH:mm', + 'LLLL' => 'YYYY年M月D日dddd HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[今天]LT', + 'nextDay' => '[明天]LT', + 'nextWeek' => '[下]ddddLT', + 'lastDay' => '[昨天]LT', + 'lastWeek' => '[上]ddddLT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number, $period) { + switch ($period) { + case 'd': + case 'D': + case 'DDD': + return $number.'日'; + case 'M': + return $number.'月'; + case 'w': + case 'W': + return $number.'周'; + default: + return $number; + } + }, + 'meridiem' => function ($hour, $minute) { + $time = $hour * 100 + $minute; + if ($time < 600) { + return '凌晨'; + } + if ($time < 900) { + return '早上'; + } + if ($time < 1130) { + return '上午'; + } + if ($time < 1230) { + return '中午'; + } + if ($time < 1800) { + return '下午'; + } + + return '晚上'; + }, + 'months' => ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'], + 'months_short' => ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'], + 'weekdays' => ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'], + 'weekdays_short' => ['周日', '周一', '周二', '周三', '周四', '周五', '周六'], + 'weekdays_min' => ['日', '一', '二', '三', '四', '五', '六'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => '', +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hans_HK.php b/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hans_HK.php new file mode 100644 index 0000000..db913ca --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hans_HK.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__.'/zh_Hans.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hans_MO.php b/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hans_MO.php new file mode 100644 index 0000000..db913ca --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hans_MO.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__.'/zh_Hans.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hans_SG.php b/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hans_SG.php new file mode 100644 index 0000000..db913ca --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hans_SG.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__.'/zh_Hans.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hant.php b/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hant.php new file mode 100644 index 0000000..a27b610 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hant.php @@ -0,0 +1,111 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Adam + * - monkeycon + * - François B + * - Jason Katz-Brown + * - Chris Lam + * - Serhan Apaydın + * - Gary Lo + * - JD Isaacks + * - Chris Hemp + * - Eddie + * - KID + * - shankesgk2 + * - Daniel Cheung (danvim) + */ +return [ + 'year' => ':count:optional-space年', + 'y' => ':count:optional-space年', + 'month' => ':count:optional-space個月', + 'm' => ':count:optional-space月', + 'week' => ':count:optional-space週', + 'w' => ':count:optional-space週', + 'day' => ':count:optional-space天', + 'd' => ':count:optional-space天', + 'hour' => ':count:optional-space小時', + 'h' => ':count:optional-space小時', + 'minute' => ':count:optional-space分鐘', + 'min' => ':count:optional-space分鐘', + 'second' => ':count:optional-space秒', + 'a_second' => '{1}幾秒|]1,Inf[:count:optional-space秒', + 's' => ':count:optional-space秒', + 'ago' => ':time前', + 'from_now' => ':time後', + 'after' => ':time後', + 'before' => ':time前', + 'diff_now' => '現在', + 'diff_today' => '今天', + 'diff_yesterday' => '昨天', + 'diff_tomorrow' => '明天', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY/MM/DD', + 'LL' => 'YYYY年M月D日', + 'LLL' => 'YYYY年M月D日 HH:mm', + 'LLLL' => 'YYYY年M月D日dddd HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[今天] LT', + 'nextDay' => '[明天] LT', + 'nextWeek' => '[下]dddd LT', + 'lastDay' => '[昨天] LT', + 'lastWeek' => '[上]dddd LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number, $period) { + switch ($period) { + case 'd': + case 'D': + case 'DDD': + return $number.'日'; + case 'M': + return $number.'月'; + case 'w': + case 'W': + return $number.'周'; + default: + return $number; + } + }, + 'meridiem' => function ($hour, $minute) { + $time = $hour * 100 + $minute; + if ($time < 600) { + return '凌晨'; + } + if ($time < 900) { + return '早上'; + } + if ($time < 1130) { + return '上午'; + } + if ($time < 1230) { + return '中午'; + } + if ($time < 1800) { + return '下午'; + } + + return '晚上'; + }, + 'months' => ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'], + 'months_short' => ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'], + 'weekdays' => ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'], + 'weekdays_short' => ['週日', '週一', '週二', '週三', '週四', '週五', '週六'], + 'weekdays_min' => ['日', '一', '二', '三', '四', '五', '六'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => '', +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hant_HK.php b/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hant_HK.php new file mode 100644 index 0000000..e2526f1 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hant_HK.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__.'/zh_Hant.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hant_MO.php b/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hant_MO.php new file mode 100644 index 0000000..e2526f1 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hant_MO.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__.'/zh_Hant.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hant_TW.php b/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hant_TW.php new file mode 100644 index 0000000..e2526f1 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hant_TW.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__.'/zh_Hant.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/zh_MO.php b/vendor/nesbot/carbon/src/Carbon/Lang/zh_MO.php new file mode 100644 index 0000000..1c86d47 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/zh_MO.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - tarunvelli + * - Eddie + * - KID + * - shankesgk2 + */ +return array_replace_recursive(require __DIR__.'/zh_Hant.php', [ + 'after' => ':time后', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/zh_SG.php b/vendor/nesbot/carbon/src/Carbon/Lang/zh_SG.php new file mode 100644 index 0000000..c451a56 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/zh_SG.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/zh.php', [ + 'formats' => [ + 'L' => 'YYYY年MM月DD日', + ], + 'months' => ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'], + 'months_short' => ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'], + 'weekdays' => ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'], + 'weekdays_short' => ['日', '一', '二', '三', '四', '五', '六'], + 'weekdays_min' => ['日', '一', '二', '三', '四', '五', '六'], + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/zh_TW.php b/vendor/nesbot/carbon/src/Carbon/Lang/zh_TW.php new file mode 100644 index 0000000..c6789ed --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/zh_TW.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__.'/zh_Hant_TW.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/zh_YUE.php b/vendor/nesbot/carbon/src/Carbon/Lang/zh_YUE.php new file mode 100644 index 0000000..b0d9ba8 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/zh_YUE.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/zh.php', [ + 'formats' => [ + 'L' => 'YYYY-MM-DD', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/zu.php b/vendor/nesbot/carbon/src/Carbon/Lang/zu.php new file mode 100644 index 0000000..9a6cce0 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/zu.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/zu_ZA.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/zu_ZA.php b/vendor/nesbot/carbon/src/Carbon/Lang/zu_ZA.php new file mode 100644 index 0000000..6bfb72f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/zu_ZA.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Zuza Software Foundation (Translate.org.za) Dwayne Bailey dwayne@translate.org.za + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Januwari', 'Februwari', 'Mashi', 'Ephreli', 'Meyi', 'Juni', 'Julayi', 'Agasti', 'Septhemba', 'Okthoba', 'Novemba', 'Disemba'], + 'months_short' => ['Jan', 'Feb', 'Mas', 'Eph', 'Mey', 'Jun', 'Jul', 'Aga', 'Sep', 'Okt', 'Nov', 'Dis'], + 'weekdays' => ['iSonto', 'uMsombuluko', 'uLwesibili', 'uLwesithathu', 'uLwesine', 'uLwesihlanu', 'uMgqibelo'], + 'weekdays_short' => ['Son', 'Mso', 'Bil', 'Tha', 'Sin', 'Hla', 'Mgq'], + 'weekdays_min' => ['Son', 'Mso', 'Bil', 'Tha', 'Sin', 'Hla', 'Mgq'], + 'day_of_first_week_of_year' => 1, + + 'year' => 'kweminyaka engu-:count', + 'y' => 'kweminyaka engu-:count', + 'a_year' => 'kweminyaka engu-:count', + + 'month' => 'izinyanga ezingu-:count', + 'm' => 'izinyanga ezingu-:count', + 'a_month' => 'izinyanga ezingu-:count', + + 'week' => 'lwamasonto angu-:count', + 'w' => 'lwamasonto angu-:count', + 'a_week' => 'lwamasonto angu-:count', + + 'day' => 'ezingaba ngu-:count', + 'd' => 'ezingaba ngu-:count', + 'a_day' => 'ezingaba ngu-:count', + + 'hour' => 'amahora angu-:count', + 'h' => 'amahora angu-:count', + 'a_hour' => 'amahora angu-:count', + + 'minute' => 'ngemizuzu engu-:count', + 'min' => 'ngemizuzu engu-:count', + 'a_minute' => 'ngemizuzu engu-:count', + + 'second' => 'imizuzwana engu-:count', + 's' => 'imizuzwana engu-:count', + 'a_second' => 'imizuzwana engu-:count', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Language.php b/vendor/nesbot/carbon/src/Carbon/Language.php new file mode 100644 index 0000000..1fb5baf --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Language.php @@ -0,0 +1,342 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +use JsonSerializable; +use ReturnTypeWillChange; + +class Language implements JsonSerializable +{ + /** + * @var array + */ + protected static $languagesNames; + + /** + * @var array + */ + protected static $regionsNames; + + /** + * @var string + */ + protected $id; + + /** + * @var string + */ + protected $code; + + /** + * @var string|null + */ + protected $variant; + + /** + * @var string|null + */ + protected $region; + + /** + * @var array + */ + protected $names; + + /** + * @var string + */ + protected $isoName; + + /** + * @var string + */ + protected $nativeName; + + public function __construct(string $id) + { + $this->id = str_replace('-', '_', $id); + $parts = explode('_', $this->id); + $this->code = $parts[0]; + + if (isset($parts[1])) { + if (!preg_match('/^[A-Z]+$/', $parts[1])) { + $this->variant = $parts[1]; + $parts[1] = $parts[2] ?? null; + } + if ($parts[1]) { + $this->region = $parts[1]; + } + } + } + + /** + * Get the list of the known languages. + * + * @return array + */ + public static function all() + { + if (!static::$languagesNames) { + static::$languagesNames = require __DIR__.'/List/languages.php'; + } + + return static::$languagesNames; + } + + /** + * Get the list of the known regions. + * + * @return array + */ + public static function regions() + { + if (!static::$regionsNames) { + static::$regionsNames = require __DIR__.'/List/regions.php'; + } + + return static::$regionsNames; + } + + /** + * Get both isoName and nativeName as an array. + * + * @return array + */ + public function getNames(): array + { + if (!$this->names) { + $this->names = static::all()[$this->code] ?? [ + 'isoName' => $this->code, + 'nativeName' => $this->code, + ]; + } + + return $this->names; + } + + /** + * Returns the original locale ID. + * + * @return string + */ + public function getId(): string + { + return $this->id; + } + + /** + * Returns the code of the locale "en"/"fr". + * + * @return string + */ + public function getCode(): string + { + return $this->code; + } + + /** + * Returns the variant code such as cyrl/latn. + * + * @return string|null + */ + public function getVariant(): ?string + { + return $this->variant; + } + + /** + * Returns the variant such as Cyrillic/Latin. + * + * @return string|null + */ + public function getVariantName(): ?string + { + if ($this->variant === 'Latn') { + return 'Latin'; + } + + if ($this->variant === 'Cyrl') { + return 'Cyrillic'; + } + + return $this->variant; + } + + /** + * Returns the region part of the locale. + * + * @return string|null + */ + public function getRegion(): ?string + { + return $this->region; + } + + /** + * Returns the region name for the current language. + * + * @return string|null + */ + public function getRegionName(): ?string + { + return $this->region ? (static::regions()[$this->region] ?? $this->region) : null; + } + + /** + * Returns the long ISO language name. + * + * @return string + */ + public function getFullIsoName(): string + { + if (!$this->isoName) { + $this->isoName = $this->getNames()['isoName']; + } + + return $this->isoName; + } + + /** + * Set the ISO language name. + * + * @param string $isoName + */ + public function setIsoName(string $isoName): self + { + $this->isoName = $isoName; + + return $this; + } + + /** + * Return the full name of the language in this language. + * + * @return string + */ + public function getFullNativeName(): string + { + if (!$this->nativeName) { + $this->nativeName = $this->getNames()['nativeName']; + } + + return $this->nativeName; + } + + /** + * Set the name of the language in this language. + * + * @param string $nativeName + */ + public function setNativeName(string $nativeName): self + { + $this->nativeName = $nativeName; + + return $this; + } + + /** + * Returns the short ISO language name. + * + * @return string + */ + public function getIsoName(): string + { + $name = $this->getFullIsoName(); + + return trim(strstr($name, ',', true) ?: $name); + } + + /** + * Get the short name of the language in this language. + * + * @return string + */ + public function getNativeName(): string + { + $name = $this->getFullNativeName(); + + return trim(strstr($name, ',', true) ?: $name); + } + + /** + * Get a string with short ISO name, region in parentheses if applicable, variant in parentheses if applicable. + * + * @return string + */ + public function getIsoDescription() + { + $region = $this->getRegionName(); + $variant = $this->getVariantName(); + + return $this->getIsoName().($region ? ' ('.$region.')' : '').($variant ? ' ('.$variant.')' : ''); + } + + /** + * Get a string with short native name, region in parentheses if applicable, variant in parentheses if applicable. + * + * @return string + */ + public function getNativeDescription() + { + $region = $this->getRegionName(); + $variant = $this->getVariantName(); + + return $this->getNativeName().($region ? ' ('.$region.')' : '').($variant ? ' ('.$variant.')' : ''); + } + + /** + * Get a string with long ISO name, region in parentheses if applicable, variant in parentheses if applicable. + * + * @return string + */ + public function getFullIsoDescription() + { + $region = $this->getRegionName(); + $variant = $this->getVariantName(); + + return $this->getFullIsoName().($region ? ' ('.$region.')' : '').($variant ? ' ('.$variant.')' : ''); + } + + /** + * Get a string with long native name, region in parentheses if applicable, variant in parentheses if applicable. + * + * @return string + */ + public function getFullNativeDescription() + { + $region = $this->getRegionName(); + $variant = $this->getVariantName(); + + return $this->getFullNativeName().($region ? ' ('.$region.')' : '').($variant ? ' ('.$variant.')' : ''); + } + + /** + * Returns the original locale ID. + * + * @return string + */ + public function __toString() + { + return $this->getId(); + } + + /** + * Get a string with short ISO name, region in parentheses if applicable, variant in parentheses if applicable. + * + * @return string + */ + #[ReturnTypeWillChange] + public function jsonSerialize() + { + return $this->getIsoDescription(); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Laravel/ServiceProvider.php b/vendor/nesbot/carbon/src/Carbon/Laravel/ServiceProvider.php new file mode 100644 index 0000000..84e241e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Laravel/ServiceProvider.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 Carbon\Laravel; + +use Carbon\Carbon; +use Carbon\CarbonImmutable; +use Carbon\CarbonInterval; +use Carbon\CarbonPeriod; +use Illuminate\Contracts\Events\Dispatcher as DispatcherContract; +use Illuminate\Events\Dispatcher; +use Illuminate\Events\EventDispatcher; +use Illuminate\Support\Carbon as IlluminateCarbon; +use Illuminate\Support\Facades\Date; +use Throwable; + +class ServiceProvider extends \Illuminate\Support\ServiceProvider +{ + /** @var callable|null */ + protected $appGetter = null; + + /** @var callable|null */ + protected $localeGetter = null; + + public function setAppGetter(?callable $appGetter): void + { + $this->appGetter = $appGetter; + } + + public function setLocaleGetter(?callable $localeGetter): void + { + $this->localeGetter = $localeGetter; + } + + public function boot() + { + $this->updateLocale(); + + if (!$this->app->bound('events')) { + return; + } + + $service = $this; + $events = $this->app['events']; + + if ($this->isEventDispatcher($events)) { + $events->listen(class_exists('Illuminate\Foundation\Events\LocaleUpdated') ? 'Illuminate\Foundation\Events\LocaleUpdated' : 'locale.changed', function () use ($service) { + $service->updateLocale(); + }); + } + } + + public function updateLocale() + { + $locale = $this->getLocale(); + + if ($locale === null) { + return; + } + + Carbon::setLocale($locale); + CarbonImmutable::setLocale($locale); + CarbonPeriod::setLocale($locale); + CarbonInterval::setLocale($locale); + + if (class_exists(IlluminateCarbon::class)) { + IlluminateCarbon::setLocale($locale); + } + + if (class_exists(Date::class)) { + try { + $root = Date::getFacadeRoot(); + $root->setLocale($locale); + } catch (Throwable $e) { + // Non Carbon class in use in Date facade + } + } + } + + public function register() + { + // Needed for Laravel < 5.3 compatibility + } + + protected function getLocale() + { + if ($this->localeGetter) { + return ($this->localeGetter)(); + } + + $app = $this->getApp(); + $app = $app && method_exists($app, 'getLocale') + ? $app + : $this->getGlobalApp('translator'); + + return $app ? $app->getLocale() : null; + } + + protected function getApp() + { + if ($this->appGetter) { + return ($this->appGetter)(); + } + + return $this->app ?? $this->getGlobalApp(); + } + + protected function getGlobalApp(...$args) + { + return \function_exists('app') ? \app(...$args) : null; + } + + protected function isEventDispatcher($instance) + { + return $instance instanceof EventDispatcher + || $instance instanceof Dispatcher + || $instance instanceof DispatcherContract; + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/List/languages.php b/vendor/nesbot/carbon/src/Carbon/List/languages.php new file mode 100644 index 0000000..5b5d9a1 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/List/languages.php @@ -0,0 +1,1239 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + /* + * ISO 639-2 + */ + 'ab' => [ + 'isoName' => 'Abkhazian', + 'nativeName' => 'аҧсуа бызшәа, аҧсшәа', + ], + 'aa' => [ + 'isoName' => 'Afar', + 'nativeName' => 'Afaraf', + ], + 'af' => [ + 'isoName' => 'Afrikaans', + 'nativeName' => 'Afrikaans', + ], + 'ak' => [ + 'isoName' => 'Akan', + 'nativeName' => 'Akan', + ], + 'sq' => [ + 'isoName' => 'Albanian', + 'nativeName' => 'Shqip', + ], + 'am' => [ + 'isoName' => 'Amharic', + 'nativeName' => 'አማርኛ', + ], + 'ar' => [ + 'isoName' => 'Arabic', + 'nativeName' => 'العربية', + ], + 'an' => [ + 'isoName' => 'Aragonese', + 'nativeName' => 'aragonés', + ], + 'hy' => [ + 'isoName' => 'Armenian', + 'nativeName' => 'Հայերեն', + ], + 'as' => [ + 'isoName' => 'Assamese', + 'nativeName' => 'অসমীয়া', + ], + 'av' => [ + 'isoName' => 'Avaric', + 'nativeName' => 'авар мацӀ, магӀарул мацӀ', + ], + 'ae' => [ + 'isoName' => 'Avestan', + 'nativeName' => 'avesta', + ], + 'ay' => [ + 'isoName' => 'Aymara', + 'nativeName' => 'aymar aru', + ], + 'az' => [ + 'isoName' => 'Azerbaijani', + 'nativeName' => 'azərbaycan dili', + ], + 'bm' => [ + 'isoName' => 'Bambara', + 'nativeName' => 'bamanankan', + ], + 'ba' => [ + 'isoName' => 'Bashkir', + 'nativeName' => 'башҡорт теле', + ], + 'eu' => [ + 'isoName' => 'Basque', + 'nativeName' => 'euskara, euskera', + ], + 'be' => [ + 'isoName' => 'Belarusian', + 'nativeName' => 'беларуская мова', + ], + 'bn' => [ + 'isoName' => 'Bengali', + 'nativeName' => 'বাংলা', + ], + 'bh' => [ + 'isoName' => 'Bihari languages', + 'nativeName' => 'भोजपुरी', + ], + 'bi' => [ + 'isoName' => 'Bislama', + 'nativeName' => 'Bislama', + ], + 'bs' => [ + 'isoName' => 'Bosnian', + 'nativeName' => 'bosanski jezik', + ], + 'br' => [ + 'isoName' => 'Breton', + 'nativeName' => 'brezhoneg', + ], + 'bg' => [ + 'isoName' => 'Bulgarian', + 'nativeName' => 'български език', + ], + 'my' => [ + 'isoName' => 'Burmese', + 'nativeName' => 'ဗမာစာ', + ], + 'ca' => [ + 'isoName' => 'Catalan, Valencian', + 'nativeName' => 'català, valencià', + ], + 'ch' => [ + 'isoName' => 'Chamorro', + 'nativeName' => 'Chamoru', + ], + 'ce' => [ + 'isoName' => 'Chechen', + 'nativeName' => 'нохчийн мотт', + ], + 'ny' => [ + 'isoName' => 'Chichewa, Chewa, Nyanja', + 'nativeName' => 'chiCheŵa, chinyanja', + ], + 'zh' => [ + 'isoName' => 'Chinese', + 'nativeName' => '中文 (Zhōngwén), 汉语, 漢語', + ], + 'cv' => [ + 'isoName' => 'Chuvash', + 'nativeName' => 'чӑваш чӗлхи', + ], + 'kw' => [ + 'isoName' => 'Cornish', + 'nativeName' => 'Kernewek', + ], + 'co' => [ + 'isoName' => 'Corsican', + 'nativeName' => 'corsu, lingua corsa', + ], + 'cr' => [ + 'isoName' => 'Cree', + 'nativeName' => 'ᓀᐦᐃᔭᐍᐏᐣ', + ], + 'hr' => [ + 'isoName' => 'Croatian', + 'nativeName' => 'hrvatski jezik', + ], + 'cs' => [ + 'isoName' => 'Czech', + 'nativeName' => 'čeština, český jazyk', + ], + 'da' => [ + 'isoName' => 'Danish', + 'nativeName' => 'dansk', + ], + 'dv' => [ + 'isoName' => 'Divehi, Dhivehi, Maldivian', + 'nativeName' => 'ދިވެހި', + ], + 'nl' => [ + 'isoName' => 'Dutch, Flemish', + 'nativeName' => 'Nederlands, Vlaams', + ], + 'dz' => [ + 'isoName' => 'Dzongkha', + 'nativeName' => 'རྫོང་ཁ', + ], + 'en' => [ + 'isoName' => 'English', + 'nativeName' => 'English', + ], + 'eo' => [ + 'isoName' => 'Esperanto', + 'nativeName' => 'Esperanto', + ], + 'et' => [ + 'isoName' => 'Estonian', + 'nativeName' => 'eesti, eesti keel', + ], + 'ee' => [ + 'isoName' => 'Ewe', + 'nativeName' => 'Eʋegbe', + ], + 'fo' => [ + 'isoName' => 'Faroese', + 'nativeName' => 'føroyskt', + ], + 'fj' => [ + 'isoName' => 'Fijian', + 'nativeName' => 'vosa Vakaviti', + ], + 'fi' => [ + 'isoName' => 'Finnish', + 'nativeName' => 'suomi, suomen kieli', + ], + 'fr' => [ + 'isoName' => 'French', + 'nativeName' => 'français', + ], + 'ff' => [ + 'isoName' => 'Fulah', + 'nativeName' => 'Fulfulde, Pulaar, Pular', + ], + 'gl' => [ + 'isoName' => 'Galician', + 'nativeName' => 'Galego', + ], + 'ka' => [ + 'isoName' => 'Georgian', + 'nativeName' => 'ქართული', + ], + 'de' => [ + 'isoName' => 'German', + 'nativeName' => 'Deutsch', + ], + 'el' => [ + 'isoName' => 'Greek (modern)', + 'nativeName' => 'ελληνικά', + ], + 'gn' => [ + 'isoName' => 'Guaraní', + 'nativeName' => 'Avañe\'ẽ', + ], + 'gu' => [ + 'isoName' => 'Gujarati', + 'nativeName' => 'ગુજરાતી', + ], + 'ht' => [ + 'isoName' => 'Haitian, Haitian Creole', + 'nativeName' => 'Kreyòl ayisyen', + ], + 'ha' => [ + 'isoName' => 'Hausa', + 'nativeName' => '(Hausa) هَوُسَ', + ], + 'he' => [ + 'isoName' => 'Hebrew (modern)', + 'nativeName' => 'עברית', + ], + 'hz' => [ + 'isoName' => 'Herero', + 'nativeName' => 'Otjiherero', + ], + 'hi' => [ + 'isoName' => 'Hindi', + 'nativeName' => 'हिन्दी, हिंदी', + ], + 'ho' => [ + 'isoName' => 'Hiri Motu', + 'nativeName' => 'Hiri Motu', + ], + 'hu' => [ + 'isoName' => 'Hungarian', + 'nativeName' => 'magyar', + ], + 'ia' => [ + 'isoName' => 'Interlingua', + 'nativeName' => 'Interlingua', + ], + 'id' => [ + 'isoName' => 'Indonesian', + 'nativeName' => 'Bahasa Indonesia', + ], + 'ie' => [ + 'isoName' => 'Interlingue', + 'nativeName' => 'Originally called Occidental; then Interlingue after WWII', + ], + 'ga' => [ + 'isoName' => 'Irish', + 'nativeName' => 'Gaeilge', + ], + 'ig' => [ + 'isoName' => 'Igbo', + 'nativeName' => 'Asụsụ Igbo', + ], + 'ik' => [ + 'isoName' => 'Inupiaq', + 'nativeName' => 'Iñupiaq, Iñupiatun', + ], + 'io' => [ + 'isoName' => 'Ido', + 'nativeName' => 'Ido', + ], + 'is' => [ + 'isoName' => 'Icelandic', + 'nativeName' => 'Íslenska', + ], + 'it' => [ + 'isoName' => 'Italian', + 'nativeName' => 'Italiano', + ], + 'iu' => [ + 'isoName' => 'Inuktitut', + 'nativeName' => 'ᐃᓄᒃᑎᑐᑦ', + ], + 'ja' => [ + 'isoName' => 'Japanese', + 'nativeName' => '日本語 (にほんご)', + ], + 'jv' => [ + 'isoName' => 'Javanese', + 'nativeName' => 'ꦧꦱꦗꦮ, Basa Jawa', + ], + 'kl' => [ + 'isoName' => 'Kalaallisut, Greenlandic', + 'nativeName' => 'kalaallisut, kalaallit oqaasii', + ], + 'kn' => [ + 'isoName' => 'Kannada', + 'nativeName' => 'ಕನ್ನಡ', + ], + 'kr' => [ + 'isoName' => 'Kanuri', + 'nativeName' => 'Kanuri', + ], + 'ks' => [ + 'isoName' => 'Kashmiri', + 'nativeName' => 'कश्मीरी, كشميري‎', + ], + 'kk' => [ + 'isoName' => 'Kazakh', + 'nativeName' => 'қазақ тілі', + ], + 'km' => [ + 'isoName' => 'Central Khmer', + 'nativeName' => 'ខ្មែរ, ខេមរភាសា, ភាសាខ្មែរ', + ], + 'ki' => [ + 'isoName' => 'Kikuyu, Gikuyu', + 'nativeName' => 'Gĩkũyũ', + ], + 'rw' => [ + 'isoName' => 'Kinyarwanda', + 'nativeName' => 'Ikinyarwanda', + ], + 'ky' => [ + 'isoName' => 'Kirghiz, Kyrgyz', + 'nativeName' => 'Кыргызча, Кыргыз тили', + ], + 'kv' => [ + 'isoName' => 'Komi', + 'nativeName' => 'коми кыв', + ], + 'kg' => [ + 'isoName' => 'Kongo', + 'nativeName' => 'Kikongo', + ], + 'ko' => [ + 'isoName' => 'Korean', + 'nativeName' => '한국어', + ], + 'ku' => [ + 'isoName' => 'Kurdish', + 'nativeName' => 'Kurdî, کوردی‎', + ], + 'kj' => [ + 'isoName' => 'Kuanyama, Kwanyama', + 'nativeName' => 'Kuanyama', + ], + 'la' => [ + 'isoName' => 'Latin', + 'nativeName' => 'latine, lingua latina', + ], + 'lb' => [ + 'isoName' => 'Luxembourgish, Letzeburgesch', + 'nativeName' => 'Lëtzebuergesch', + ], + 'lg' => [ + 'isoName' => 'Ganda', + 'nativeName' => 'Luganda', + ], + 'li' => [ + 'isoName' => 'Limburgan, Limburger, Limburgish', + 'nativeName' => 'Limburgs', + ], + 'ln' => [ + 'isoName' => 'Lingala', + 'nativeName' => 'Lingála', + ], + 'lo' => [ + 'isoName' => 'Lao', + 'nativeName' => 'ພາສາລາວ', + ], + 'lt' => [ + 'isoName' => 'Lithuanian', + 'nativeName' => 'lietuvių kalba', + ], + 'lu' => [ + 'isoName' => 'Luba-Katanga', + 'nativeName' => 'Kiluba', + ], + 'lv' => [ + 'isoName' => 'Latvian', + 'nativeName' => 'latviešu valoda', + ], + 'gv' => [ + 'isoName' => 'Manx', + 'nativeName' => 'Gaelg, Gailck', + ], + 'mk' => [ + 'isoName' => 'Macedonian', + 'nativeName' => 'македонски јазик', + ], + 'mg' => [ + 'isoName' => 'Malagasy', + 'nativeName' => 'fiteny malagasy', + ], + 'ms' => [ + 'isoName' => 'Malay', + 'nativeName' => 'Bahasa Melayu, بهاس ملايو‎', + ], + 'ml' => [ + 'isoName' => 'Malayalam', + 'nativeName' => 'മലയാളം', + ], + 'mt' => [ + 'isoName' => 'Maltese', + 'nativeName' => 'Malti', + ], + 'mi' => [ + 'isoName' => 'Maori', + 'nativeName' => 'te reo Māori', + ], + 'mr' => [ + 'isoName' => 'Marathi', + 'nativeName' => 'मराठी', + ], + 'mh' => [ + 'isoName' => 'Marshallese', + 'nativeName' => 'Kajin M̧ajeļ', + ], + 'mn' => [ + 'isoName' => 'Mongolian', + 'nativeName' => 'Монгол хэл', + ], + 'na' => [ + 'isoName' => 'Nauru', + 'nativeName' => 'Dorerin Naoero', + ], + 'nv' => [ + 'isoName' => 'Navajo, Navaho', + 'nativeName' => 'Diné bizaad', + ], + 'nd' => [ + 'isoName' => 'North Ndebele', + 'nativeName' => 'isiNdebele', + ], + 'ne' => [ + 'isoName' => 'Nepali', + 'nativeName' => 'नेपाली', + ], + 'ng' => [ + 'isoName' => 'Ndonga', + 'nativeName' => 'Owambo', + ], + 'nb' => [ + 'isoName' => 'Norwegian Bokmål', + 'nativeName' => 'Norsk Bokmål', + ], + 'nn' => [ + 'isoName' => 'Norwegian Nynorsk', + 'nativeName' => 'Norsk Nynorsk', + ], + 'no' => [ + 'isoName' => 'Norwegian', + 'nativeName' => 'Norsk', + ], + 'ii' => [ + 'isoName' => 'Sichuan Yi, Nuosu', + 'nativeName' => 'ꆈꌠ꒿ Nuosuhxop', + ], + 'nr' => [ + 'isoName' => 'South Ndebele', + 'nativeName' => 'isiNdebele', + ], + 'oc' => [ + 'isoName' => 'Occitan', + 'nativeName' => 'occitan, lenga d\'òc', + ], + 'oj' => [ + 'isoName' => 'Ojibwa', + 'nativeName' => 'ᐊᓂᔑᓈᐯᒧᐎᓐ', + ], + 'cu' => [ + 'isoName' => 'Church Slavic, Church Slavonic, Old Church Slavonic, Old Slavonic, Old Bulgarian', + 'nativeName' => 'ѩзыкъ словѣньскъ', + ], + 'om' => [ + 'isoName' => 'Oromo', + 'nativeName' => 'Afaan Oromoo', + ], + 'or' => [ + 'isoName' => 'Oriya', + 'nativeName' => 'ଓଡ଼ିଆ', + ], + 'os' => [ + 'isoName' => 'Ossetian, Ossetic', + 'nativeName' => 'ирон æвзаг', + ], + 'pa' => [ + 'isoName' => 'Panjabi, Punjabi', + 'nativeName' => 'ਪੰਜਾਬੀ', + ], + 'pi' => [ + 'isoName' => 'Pali', + 'nativeName' => 'पाऴि', + ], + 'fa' => [ + 'isoName' => 'Persian', + 'nativeName' => 'فارسی', + ], + 'pl' => [ + 'isoName' => 'Polish', + 'nativeName' => 'język polski, polszczyzna', + ], + 'ps' => [ + 'isoName' => 'Pashto, Pushto', + 'nativeName' => 'پښتو', + ], + 'pt' => [ + 'isoName' => 'Portuguese', + 'nativeName' => 'Português', + ], + 'qu' => [ + 'isoName' => 'Quechua', + 'nativeName' => 'Runa Simi, Kichwa', + ], + 'rm' => [ + 'isoName' => 'Romansh', + 'nativeName' => 'Rumantsch Grischun', + ], + 'rn' => [ + 'isoName' => 'Rundi', + 'nativeName' => 'Ikirundi', + ], + 'ro' => [ + 'isoName' => 'Romanian, Moldavian, Moldovan', + 'nativeName' => 'Română', + ], + 'ru' => [ + 'isoName' => 'Russian', + 'nativeName' => 'русский', + ], + 'sa' => [ + 'isoName' => 'Sanskrit', + 'nativeName' => 'संस्कृतम्', + ], + 'sc' => [ + 'isoName' => 'Sardinian', + 'nativeName' => 'sardu', + ], + 'sd' => [ + 'isoName' => 'Sindhi', + 'nativeName' => 'सिन्धी, سنڌي، سندھی‎', + ], + 'se' => [ + 'isoName' => 'Northern Sami', + 'nativeName' => 'Davvisámegiella', + ], + 'sm' => [ + 'isoName' => 'Samoan', + 'nativeName' => 'gagana fa\'a Samoa', + ], + 'sg' => [ + 'isoName' => 'Sango', + 'nativeName' => 'yângâ tî sängö', + ], + 'sr' => [ + 'isoName' => 'Serbian', + 'nativeName' => 'српски језик', + ], + 'gd' => [ + 'isoName' => 'Gaelic, Scottish Gaelic', + 'nativeName' => 'Gàidhlig', + ], + 'sn' => [ + 'isoName' => 'Shona', + 'nativeName' => 'chiShona', + ], + 'si' => [ + 'isoName' => 'Sinhala, Sinhalese', + 'nativeName' => 'සිංහල', + ], + 'sk' => [ + 'isoName' => 'Slovak', + 'nativeName' => 'Slovenčina, Slovenský Jazyk', + ], + 'sl' => [ + 'isoName' => 'Slovene', + 'nativeName' => 'Slovenski Jezik, Slovenščina', + ], + 'so' => [ + 'isoName' => 'Somali', + 'nativeName' => 'Soomaaliga, af Soomaali', + ], + 'st' => [ + 'isoName' => 'Southern Sotho', + 'nativeName' => 'Sesotho', + ], + 'es' => [ + 'isoName' => 'Spanish, Castilian', + 'nativeName' => 'Español', + ], + 'su' => [ + 'isoName' => 'Sundanese', + 'nativeName' => 'Basa Sunda', + ], + 'sw' => [ + 'isoName' => 'Swahili', + 'nativeName' => 'Kiswahili', + ], + 'ss' => [ + 'isoName' => 'Swati', + 'nativeName' => 'SiSwati', + ], + 'sv' => [ + 'isoName' => 'Swedish', + 'nativeName' => 'Svenska', + ], + 'ta' => [ + 'isoName' => 'Tamil', + 'nativeName' => 'தமிழ்', + ], + 'te' => [ + 'isoName' => 'Telugu', + 'nativeName' => 'తెలుగు', + ], + 'tg' => [ + 'isoName' => 'Tajik', + 'nativeName' => 'тоҷикӣ, toçikī, تاجیکی‎', + ], + 'th' => [ + 'isoName' => 'Thai', + 'nativeName' => 'ไทย', + ], + 'ti' => [ + 'isoName' => 'Tigrinya', + 'nativeName' => 'ትግርኛ', + ], + 'bo' => [ + 'isoName' => 'Tibetan', + 'nativeName' => 'བོད་ཡིག', + ], + 'tk' => [ + 'isoName' => 'Turkmen', + 'nativeName' => 'Türkmen, Түркмен', + ], + 'tl' => [ + 'isoName' => 'Tagalog', + 'nativeName' => 'Wikang Tagalog', + ], + 'tn' => [ + 'isoName' => 'Tswana', + 'nativeName' => 'Setswana', + ], + 'to' => [ + 'isoName' => 'Tongan (Tonga Islands)', + 'nativeName' => 'Faka Tonga', + ], + 'tr' => [ + 'isoName' => 'Turkish', + 'nativeName' => 'Türkçe', + ], + 'ts' => [ + 'isoName' => 'Tsonga', + 'nativeName' => 'Xitsonga', + ], + 'tt' => [ + 'isoName' => 'Tatar', + 'nativeName' => 'татар теле, tatar tele', + ], + 'tw' => [ + 'isoName' => 'Twi', + 'nativeName' => 'Twi', + ], + 'ty' => [ + 'isoName' => 'Tahitian', + 'nativeName' => 'Reo Tahiti', + ], + 'ug' => [ + 'isoName' => 'Uighur, Uyghur', + 'nativeName' => 'Uyƣurqə, ‫ئۇيغۇرچ', + ], + 'uk' => [ + 'isoName' => 'Ukrainian', + 'nativeName' => 'Українська', + ], + 'ur' => [ + 'isoName' => 'Urdu', + 'nativeName' => 'اردو', + ], + 'uz' => [ + 'isoName' => 'Uzbek', + 'nativeName' => 'Oʻzbek, Ўзбек, أۇزبېك‎', + ], + 've' => [ + 'isoName' => 'Venda', + 'nativeName' => 'Tshivenḓa', + ], + 'vi' => [ + 'isoName' => 'Vietnamese', + 'nativeName' => 'Tiếng Việt', + ], + 'vo' => [ + 'isoName' => 'Volapük', + 'nativeName' => 'Volapük', + ], + 'wa' => [ + 'isoName' => 'Walloon', + 'nativeName' => 'Walon', + ], + 'cy' => [ + 'isoName' => 'Welsh', + 'nativeName' => 'Cymraeg', + ], + 'wo' => [ + 'isoName' => 'Wolof', + 'nativeName' => 'Wollof', + ], + 'fy' => [ + 'isoName' => 'Western Frisian', + 'nativeName' => 'Frysk', + ], + 'xh' => [ + 'isoName' => 'Xhosa', + 'nativeName' => 'isiXhosa', + ], + 'yi' => [ + 'isoName' => 'Yiddish', + 'nativeName' => 'ייִדיש', + ], + 'yo' => [ + 'isoName' => 'Yoruba', + 'nativeName' => 'Yorùbá', + ], + 'za' => [ + 'isoName' => 'Zhuang, Chuang', + 'nativeName' => 'Saɯ cueŋƅ, Saw cuengh', + ], + 'zu' => [ + 'isoName' => 'Zulu', + 'nativeName' => 'isiZulu', + ], + /* + * Add ISO 639-3 languages available in Carbon + */ + 'agq' => [ + 'isoName' => 'Aghem', + 'nativeName' => 'Aghem', + ], + 'agr' => [ + 'isoName' => 'Aguaruna', + 'nativeName' => 'Aguaruna', + ], + 'anp' => [ + 'isoName' => 'Angika', + 'nativeName' => 'Angika', + ], + 'asa' => [ + 'isoName' => 'Asu', + 'nativeName' => 'Asu', + ], + 'ast' => [ + 'isoName' => 'Asturian', + 'nativeName' => 'Asturian', + ], + 'ayc' => [ + 'isoName' => 'Southern Aymara', + 'nativeName' => 'Southern Aymara', + ], + 'bas' => [ + 'isoName' => 'Basaa', + 'nativeName' => 'Basaa', + ], + 'bem' => [ + 'isoName' => 'Bemba', + 'nativeName' => 'Bemba', + ], + 'bez' => [ + 'isoName' => 'Bena', + 'nativeName' => 'Bena', + ], + 'bhb' => [ + 'isoName' => 'Bhili', + 'nativeName' => 'Bhili', + ], + 'bho' => [ + 'isoName' => 'Bhojpuri', + 'nativeName' => 'Bhojpuri', + ], + 'brx' => [ + 'isoName' => 'Bodo', + 'nativeName' => 'Bodo', + ], + 'byn' => [ + 'isoName' => 'Bilin', + 'nativeName' => 'Bilin', + ], + 'ccp' => [ + 'isoName' => 'Chakma', + 'nativeName' => 'Chakma', + ], + 'cgg' => [ + 'isoName' => 'Chiga', + 'nativeName' => 'Chiga', + ], + 'chr' => [ + 'isoName' => 'Cherokee', + 'nativeName' => 'Cherokee', + ], + 'cmn' => [ + 'isoName' => 'Chinese', + 'nativeName' => 'Chinese', + ], + 'crh' => [ + 'isoName' => 'Crimean Turkish', + 'nativeName' => 'Crimean Turkish', + ], + 'csb' => [ + 'isoName' => 'Kashubian', + 'nativeName' => 'Kashubian', + ], + 'dav' => [ + 'isoName' => 'Taita', + 'nativeName' => 'Taita', + ], + 'dje' => [ + 'isoName' => 'Zarma', + 'nativeName' => 'Zarma', + ], + 'doi' => [ + 'isoName' => 'Dogri (macrolanguage)', + 'nativeName' => 'Dogri (macrolanguage)', + ], + 'dsb' => [ + 'isoName' => 'Lower Sorbian', + 'nativeName' => 'Lower Sorbian', + ], + 'dua' => [ + 'isoName' => 'Duala', + 'nativeName' => 'Duala', + ], + 'dyo' => [ + 'isoName' => 'Jola-Fonyi', + 'nativeName' => 'Jola-Fonyi', + ], + 'ebu' => [ + 'isoName' => 'Embu', + 'nativeName' => 'Embu', + ], + 'ewo' => [ + 'isoName' => 'Ewondo', + 'nativeName' => 'Ewondo', + ], + 'fil' => [ + 'isoName' => 'Filipino', + 'nativeName' => 'Filipino', + ], + 'fur' => [ + 'isoName' => 'Friulian', + 'nativeName' => 'Friulian', + ], + 'gez' => [ + 'isoName' => 'Geez', + 'nativeName' => 'Geez', + ], + 'gom' => [ + 'isoName' => 'Konkani, Goan', + 'nativeName' => 'ಕೊಂಕಣಿ', + ], + 'gsw' => [ + 'isoName' => 'Swiss German', + 'nativeName' => 'Swiss German', + ], + 'guz' => [ + 'isoName' => 'Gusii', + 'nativeName' => 'Gusii', + ], + 'hak' => [ + 'isoName' => 'Hakka Chinese', + 'nativeName' => 'Hakka Chinese', + ], + 'haw' => [ + 'isoName' => 'Hawaiian', + 'nativeName' => 'Hawaiian', + ], + 'hif' => [ + 'isoName' => 'Fiji Hindi', + 'nativeName' => 'Fiji Hindi', + ], + 'hne' => [ + 'isoName' => 'Chhattisgarhi', + 'nativeName' => 'Chhattisgarhi', + ], + 'hsb' => [ + 'isoName' => 'Upper Sorbian', + 'nativeName' => 'Upper Sorbian', + ], + 'jgo' => [ + 'isoName' => 'Ngomba', + 'nativeName' => 'Ngomba', + ], + 'jmc' => [ + 'isoName' => 'Machame', + 'nativeName' => 'Machame', + ], + 'kab' => [ + 'isoName' => 'Kabyle', + 'nativeName' => 'Kabyle', + ], + 'kam' => [ + 'isoName' => 'Kamba', + 'nativeName' => 'Kamba', + ], + 'kde' => [ + 'isoName' => 'Makonde', + 'nativeName' => 'Makonde', + ], + 'kea' => [ + 'isoName' => 'Kabuverdianu', + 'nativeName' => 'Kabuverdianu', + ], + 'khq' => [ + 'isoName' => 'Koyra Chiini', + 'nativeName' => 'Koyra Chiini', + ], + 'kkj' => [ + 'isoName' => 'Kako', + 'nativeName' => 'Kako', + ], + 'kln' => [ + 'isoName' => 'Kalenjin', + 'nativeName' => 'Kalenjin', + ], + 'kok' => [ + 'isoName' => 'Konkani', + 'nativeName' => 'Konkani', + ], + 'ksb' => [ + 'isoName' => 'Shambala', + 'nativeName' => 'Shambala', + ], + 'ksf' => [ + 'isoName' => 'Bafia', + 'nativeName' => 'Bafia', + ], + 'ksh' => [ + 'isoName' => 'Colognian', + 'nativeName' => 'Colognian', + ], + 'lag' => [ + 'isoName' => 'Langi', + 'nativeName' => 'Langi', + ], + 'lij' => [ + 'isoName' => 'Ligurian', + 'nativeName' => 'Ligurian', + ], + 'lkt' => [ + 'isoName' => 'Lakota', + 'nativeName' => 'Lakota', + ], + 'lrc' => [ + 'isoName' => 'Northern Luri', + 'nativeName' => 'Northern Luri', + ], + 'luo' => [ + 'isoName' => 'Luo', + 'nativeName' => 'Luo', + ], + 'luy' => [ + 'isoName' => 'Luyia', + 'nativeName' => 'Luyia', + ], + 'lzh' => [ + 'isoName' => 'Literary Chinese', + 'nativeName' => 'Literary Chinese', + ], + 'mag' => [ + 'isoName' => 'Magahi', + 'nativeName' => 'Magahi', + ], + 'mai' => [ + 'isoName' => 'Maithili', + 'nativeName' => 'Maithili', + ], + 'mas' => [ + 'isoName' => 'Masai', + 'nativeName' => 'Masai', + ], + 'mer' => [ + 'isoName' => 'Meru', + 'nativeName' => 'Meru', + ], + 'mfe' => [ + 'isoName' => 'Morisyen', + 'nativeName' => 'Morisyen', + ], + 'mgh' => [ + 'isoName' => 'Makhuwa-Meetto', + 'nativeName' => 'Makhuwa-Meetto', + ], + 'mgo' => [ + 'isoName' => 'Metaʼ', + 'nativeName' => 'Metaʼ', + ], + 'mhr' => [ + 'isoName' => 'Eastern Mari', + 'nativeName' => 'Eastern Mari', + ], + 'miq' => [ + 'isoName' => 'Mískito', + 'nativeName' => 'Mískito', + ], + 'mjw' => [ + 'isoName' => 'Karbi', + 'nativeName' => 'Karbi', + ], + 'mni' => [ + 'isoName' => 'Manipuri', + 'nativeName' => 'Manipuri', + ], + 'mua' => [ + 'isoName' => 'Mundang', + 'nativeName' => 'Mundang', + ], + 'mzn' => [ + 'isoName' => 'Mazanderani', + 'nativeName' => 'Mazanderani', + ], + 'nan' => [ + 'isoName' => 'Min Nan Chinese', + 'nativeName' => 'Min Nan Chinese', + ], + 'naq' => [ + 'isoName' => 'Nama', + 'nativeName' => 'Nama', + ], + 'nds' => [ + 'isoName' => 'Low German', + 'nativeName' => 'Low German', + ], + 'nhn' => [ + 'isoName' => 'Central Nahuatl', + 'nativeName' => 'Central Nahuatl', + ], + 'niu' => [ + 'isoName' => 'Niuean', + 'nativeName' => 'Niuean', + ], + 'nmg' => [ + 'isoName' => 'Kwasio', + 'nativeName' => 'Kwasio', + ], + 'nnh' => [ + 'isoName' => 'Ngiemboon', + 'nativeName' => 'Ngiemboon', + ], + 'nso' => [ + 'isoName' => 'Northern Sotho', + 'nativeName' => 'Northern Sotho', + ], + 'nus' => [ + 'isoName' => 'Nuer', + 'nativeName' => 'Nuer', + ], + 'nyn' => [ + 'isoName' => 'Nyankole', + 'nativeName' => 'Nyankole', + ], + 'pap' => [ + 'isoName' => 'Papiamento', + 'nativeName' => 'Papiamento', + ], + 'prg' => [ + 'isoName' => 'Prussian', + 'nativeName' => 'Prussian', + ], + 'quz' => [ + 'isoName' => 'Cusco Quechua', + 'nativeName' => 'Cusco Quechua', + ], + 'raj' => [ + 'isoName' => 'Rajasthani', + 'nativeName' => 'Rajasthani', + ], + 'rof' => [ + 'isoName' => 'Rombo', + 'nativeName' => 'Rombo', + ], + 'rwk' => [ + 'isoName' => 'Rwa', + 'nativeName' => 'Rwa', + ], + 'sah' => [ + 'isoName' => 'Sakha', + 'nativeName' => 'Sakha', + ], + 'saq' => [ + 'isoName' => 'Samburu', + 'nativeName' => 'Samburu', + ], + 'sat' => [ + 'isoName' => 'Santali', + 'nativeName' => 'Santali', + ], + 'sbp' => [ + 'isoName' => 'Sangu', + 'nativeName' => 'Sangu', + ], + 'scr' => [ + 'isoName' => 'Serbo Croatian', + 'nativeName' => 'Serbo Croatian', + ], + 'seh' => [ + 'isoName' => 'Sena', + 'nativeName' => 'Sena', + ], + 'ses' => [ + 'isoName' => 'Koyraboro Senni', + 'nativeName' => 'Koyraboro Senni', + ], + 'sgs' => [ + 'isoName' => 'Samogitian', + 'nativeName' => 'Samogitian', + ], + 'shi' => [ + 'isoName' => 'Tachelhit', + 'nativeName' => 'Tachelhit', + ], + 'shn' => [ + 'isoName' => 'Shan', + 'nativeName' => 'Shan', + ], + 'shs' => [ + 'isoName' => 'Shuswap', + 'nativeName' => 'Shuswap', + ], + 'sid' => [ + 'isoName' => 'Sidamo', + 'nativeName' => 'Sidamo', + ], + 'smn' => [ + 'isoName' => 'Inari Sami', + 'nativeName' => 'Inari Sami', + ], + 'szl' => [ + 'isoName' => 'Silesian', + 'nativeName' => 'Silesian', + ], + 'tcy' => [ + 'isoName' => 'Tulu', + 'nativeName' => 'Tulu', + ], + 'teo' => [ + 'isoName' => 'Teso', + 'nativeName' => 'Teso', + ], + 'tet' => [ + 'isoName' => 'Tetum', + 'nativeName' => 'Tetum', + ], + 'the' => [ + 'isoName' => 'Chitwania Tharu', + 'nativeName' => 'Chitwania Tharu', + ], + 'tig' => [ + 'isoName' => 'Tigre', + 'nativeName' => 'Tigre', + ], + 'tlh' => [ + 'isoName' => 'Klingon', + 'nativeName' => 'tlhIngan Hol', + ], + 'tpi' => [ + 'isoName' => 'Tok Pisin', + 'nativeName' => 'Tok Pisin', + ], + 'twq' => [ + 'isoName' => 'Tasawaq', + 'nativeName' => 'Tasawaq', + ], + 'tzl' => [ + 'isoName' => 'Talossan', + 'nativeName' => 'Talossan', + ], + 'tzm' => [ + 'isoName' => 'Tamazight, Central Atlas', + 'nativeName' => 'ⵜⵎⴰⵣⵉⵖⵜ', + ], + 'unm' => [ + 'isoName' => 'Unami', + 'nativeName' => 'Unami', + ], + 'vai' => [ + 'isoName' => 'Vai', + 'nativeName' => 'Vai', + ], + 'vun' => [ + 'isoName' => 'Vunjo', + 'nativeName' => 'Vunjo', + ], + 'wae' => [ + 'isoName' => 'Walser', + 'nativeName' => 'Walser', + ], + 'wal' => [ + 'isoName' => 'Wolaytta', + 'nativeName' => 'Wolaytta', + ], + 'xog' => [ + 'isoName' => 'Soga', + 'nativeName' => 'Soga', + ], + 'yav' => [ + 'isoName' => 'Yangben', + 'nativeName' => 'Yangben', + ], + 'yue' => [ + 'isoName' => 'Cantonese', + 'nativeName' => 'Cantonese', + ], + 'yuw' => [ + 'isoName' => 'Yau (Morobe Province)', + 'nativeName' => 'Yau (Morobe Province)', + ], + 'zgh' => [ + 'isoName' => 'Standard Moroccan Tamazight', + 'nativeName' => 'Standard Moroccan Tamazight', + ], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/List/regions.php b/vendor/nesbot/carbon/src/Carbon/List/regions.php new file mode 100644 index 0000000..8ab8a9e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/List/regions.php @@ -0,0 +1,265 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * ISO 3166-2 + */ +return [ + 'AD' => 'Andorra', + 'AE' => 'United Arab Emirates', + 'AF' => 'Afghanistan', + 'AG' => 'Antigua and Barbuda', + 'AI' => 'Anguilla', + 'AL' => 'Albania', + 'AM' => 'Armenia', + 'AO' => 'Angola', + 'AQ' => 'Antarctica', + 'AR' => 'Argentina', + 'AS' => 'American Samoa', + 'AT' => 'Austria', + 'AU' => 'Australia', + 'AW' => 'Aruba', + 'AX' => 'Åland Islands', + 'AZ' => 'Azerbaijan', + 'BA' => 'Bosnia and Herzegovina', + 'BB' => 'Barbados', + 'BD' => 'Bangladesh', + 'BE' => 'Belgium', + 'BF' => 'Burkina Faso', + 'BG' => 'Bulgaria', + 'BH' => 'Bahrain', + 'BI' => 'Burundi', + 'BJ' => 'Benin', + 'BL' => 'Saint Barthélemy', + 'BM' => 'Bermuda', + 'BN' => 'Brunei Darussalam', + 'BO' => 'Bolivia (Plurinational State of)', + 'BQ' => 'Bonaire, Sint Eustatius and Saba', + 'BR' => 'Brazil', + 'BS' => 'Bahamas', + 'BT' => 'Bhutan', + 'BV' => 'Bouvet Island', + 'BW' => 'Botswana', + 'BY' => 'Belarus', + 'BZ' => 'Belize', + 'CA' => 'Canada', + 'CC' => 'Cocos (Keeling) Islands', + 'CD' => 'Congo, Democratic Republic of the', + 'CF' => 'Central African Republic', + 'CG' => 'Congo', + 'CH' => 'Switzerland', + 'CI' => 'Côte d\'Ivoire', + 'CK' => 'Cook Islands', + 'CL' => 'Chile', + 'CM' => 'Cameroon', + 'CN' => 'China', + 'CO' => 'Colombia', + 'CR' => 'Costa Rica', + 'CU' => 'Cuba', + 'CV' => 'Cabo Verde', + 'CW' => 'Curaçao', + 'CX' => 'Christmas Island', + 'CY' => 'Cyprus', + 'CZ' => 'Czechia', + 'DE' => 'Germany', + 'DJ' => 'Djibouti', + 'DK' => 'Denmark', + 'DM' => 'Dominica', + 'DO' => 'Dominican Republic', + 'DZ' => 'Algeria', + 'EC' => 'Ecuador', + 'EE' => 'Estonia', + 'EG' => 'Egypt', + 'EH' => 'Western Sahara', + 'ER' => 'Eritrea', + 'ES' => 'Spain', + 'ET' => 'Ethiopia', + 'FI' => 'Finland', + 'FJ' => 'Fiji', + 'FK' => 'Falkland Islands (Malvinas)', + 'FM' => 'Micronesia (Federated States of)', + 'FO' => 'Faroe Islands', + 'FR' => 'France', + 'GA' => 'Gabon', + 'GB' => 'United Kingdom of Great Britain and Northern Ireland', + 'GD' => 'Grenada', + 'GE' => 'Georgia', + 'GF' => 'French Guiana', + 'GG' => 'Guernsey', + 'GH' => 'Ghana', + 'GI' => 'Gibraltar', + 'GL' => 'Greenland', + 'GM' => 'Gambia', + 'GN' => 'Guinea', + 'GP' => 'Guadeloupe', + 'GQ' => 'Equatorial Guinea', + 'GR' => 'Greece', + 'GS' => 'South Georgia and the South Sandwich Islands', + 'GT' => 'Guatemala', + 'GU' => 'Guam', + 'GW' => 'Guinea-Bissau', + 'GY' => 'Guyana', + 'HK' => 'Hong Kong', + 'HM' => 'Heard Island and McDonald Islands', + 'HN' => 'Honduras', + 'HR' => 'Croatia', + 'HT' => 'Haiti', + 'HU' => 'Hungary', + 'ID' => 'Indonesia', + 'IE' => 'Ireland', + 'IL' => 'Israel', + 'IM' => 'Isle of Man', + 'IN' => 'India', + 'IO' => 'British Indian Ocean Territory', + 'IQ' => 'Iraq', + 'IR' => 'Iran (Islamic Republic of)', + 'IS' => 'Iceland', + 'IT' => 'Italy', + 'JE' => 'Jersey', + 'JM' => 'Jamaica', + 'JO' => 'Jordan', + 'JP' => 'Japan', + 'KE' => 'Kenya', + 'KG' => 'Kyrgyzstan', + 'KH' => 'Cambodia', + 'KI' => 'Kiribati', + 'KM' => 'Comoros', + 'KN' => 'Saint Kitts and Nevis', + 'KP' => 'Korea (Democratic People\'s Republic of)', + 'KR' => 'Korea, Republic of', + 'KW' => 'Kuwait', + 'KY' => 'Cayman Islands', + 'KZ' => 'Kazakhstan', + 'LA' => 'Lao People\'s Democratic Republic', + 'LB' => 'Lebanon', + 'LC' => 'Saint Lucia', + 'LI' => 'Liechtenstein', + 'LK' => 'Sri Lanka', + 'LR' => 'Liberia', + 'LS' => 'Lesotho', + 'LT' => 'Lithuania', + 'LU' => 'Luxembourg', + 'LV' => 'Latvia', + 'LY' => 'Libya', + 'MA' => 'Morocco', + 'MC' => 'Monaco', + 'MD' => 'Moldova, Republic of', + 'ME' => 'Montenegro', + 'MF' => 'Saint Martin (French part)', + 'MG' => 'Madagascar', + 'MH' => 'Marshall Islands', + 'MK' => 'Macedonia, the former Yugoslav Republic of', + 'ML' => 'Mali', + 'MM' => 'Myanmar', + 'MN' => 'Mongolia', + 'MO' => 'Macao', + 'MP' => 'Northern Mariana Islands', + 'MQ' => 'Martinique', + 'MR' => 'Mauritania', + 'MS' => 'Montserrat', + 'MT' => 'Malta', + 'MU' => 'Mauritius', + 'MV' => 'Maldives', + 'MW' => 'Malawi', + 'MX' => 'Mexico', + 'MY' => 'Malaysia', + 'MZ' => 'Mozambique', + 'NA' => 'Namibia', + 'NC' => 'New Caledonia', + 'NE' => 'Niger', + 'NF' => 'Norfolk Island', + 'NG' => 'Nigeria', + 'NI' => 'Nicaragua', + 'NL' => 'Netherlands', + 'NO' => 'Norway', + 'NP' => 'Nepal', + 'NR' => 'Nauru', + 'NU' => 'Niue', + 'NZ' => 'New Zealand', + 'OM' => 'Oman', + 'PA' => 'Panama', + 'PE' => 'Peru', + 'PF' => 'French Polynesia', + 'PG' => 'Papua New Guinea', + 'PH' => 'Philippines', + 'PK' => 'Pakistan', + 'PL' => 'Poland', + 'PM' => 'Saint Pierre and Miquelon', + 'PN' => 'Pitcairn', + 'PR' => 'Puerto Rico', + 'PS' => 'Palestine, State of', + 'PT' => 'Portugal', + 'PW' => 'Palau', + 'PY' => 'Paraguay', + 'QA' => 'Qatar', + 'RE' => 'Réunion', + 'RO' => 'Romania', + 'RS' => 'Serbia', + 'RU' => 'Russian Federation', + 'RW' => 'Rwanda', + 'SA' => 'Saudi Arabia', + 'SB' => 'Solomon Islands', + 'SC' => 'Seychelles', + 'SD' => 'Sudan', + 'SE' => 'Sweden', + 'SG' => 'Singapore', + 'SH' => 'Saint Helena, Ascension and Tristan da Cunha', + 'SI' => 'Slovenia', + 'SJ' => 'Svalbard and Jan Mayen', + 'SK' => 'Slovakia', + 'SL' => 'Sierra Leone', + 'SM' => 'San Marino', + 'SN' => 'Senegal', + 'SO' => 'Somalia', + 'SR' => 'Suriname', + 'SS' => 'South Sudan', + 'ST' => 'Sao Tome and Principe', + 'SV' => 'El Salvador', + 'SX' => 'Sint Maarten (Dutch part)', + 'SY' => 'Syrian Arab Republic', + 'SZ' => 'Eswatini', + 'TC' => 'Turks and Caicos Islands', + 'TD' => 'Chad', + 'TF' => 'French Southern Territories', + 'TG' => 'Togo', + 'TH' => 'Thailand', + 'TJ' => 'Tajikistan', + 'TK' => 'Tokelau', + 'TL' => 'Timor-Leste', + 'TM' => 'Turkmenistan', + 'TN' => 'Tunisia', + 'TO' => 'Tonga', + 'TR' => 'Turkey', + 'TT' => 'Trinidad and Tobago', + 'TV' => 'Tuvalu', + 'TW' => 'Taiwan, Province of China', + 'TZ' => 'Tanzania, United Republic of', + 'UA' => 'Ukraine', + 'UG' => 'Uganda', + 'UM' => 'United States Minor Outlying Islands', + 'US' => 'United States of America', + 'UY' => 'Uruguay', + 'UZ' => 'Uzbekistan', + 'VA' => 'Holy See', + 'VC' => 'Saint Vincent and the Grenadines', + 'VE' => 'Venezuela (Bolivarian Republic of)', + 'VG' => 'Virgin Islands (British)', + 'VI' => 'Virgin Islands (U.S.)', + 'VN' => 'Viet Nam', + 'VU' => 'Vanuatu', + 'WF' => 'Wallis and Futuna', + 'WS' => 'Samoa', + 'YE' => 'Yemen', + 'YT' => 'Mayotte', + 'ZA' => 'South Africa', + 'ZM' => 'Zambia', + 'ZW' => 'Zimbabwe', +]; diff --git a/vendor/nesbot/carbon/src/Carbon/MessageFormatter/MessageFormatterMapper.php b/vendor/nesbot/carbon/src/Carbon/MessageFormatter/MessageFormatterMapper.php new file mode 100644 index 0000000..a230b06 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/MessageFormatter/MessageFormatterMapper.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 Carbon\MessageFormatter; + +use ReflectionMethod; +use Symfony\Component\Translation\Formatter\MessageFormatter; +use Symfony\Component\Translation\Formatter\MessageFormatterInterface; + +$transMethod = new ReflectionMethod(MessageFormatterInterface::class, 'format'); + +require $transMethod->getParameters()[0]->hasType() + ? __DIR__.'/../../../lazy/Carbon/MessageFormatter/MessageFormatterMapperStrongType.php' + : __DIR__.'/../../../lazy/Carbon/MessageFormatter/MessageFormatterMapperWeakType.php'; + +final class MessageFormatterMapper extends LazyMessageFormatter +{ + /** + * Wrapped formatter. + * + * @var MessageFormatterInterface + */ + protected $formatter; + + public function __construct(?MessageFormatterInterface $formatter = null) + { + $this->formatter = $formatter ?? new MessageFormatter(); + } + + protected function transformLocale(?string $locale): ?string + { + return $locale ? preg_replace('/[_@][A-Za-z][a-z]{2,}/', '', $locale) : $locale; + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/PHPStan/AbstractMacro.php b/vendor/nesbot/carbon/src/Carbon/PHPStan/AbstractMacro.php new file mode 100644 index 0000000..5123d1e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/PHPStan/AbstractMacro.php @@ -0,0 +1,288 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\PHPStan; + +use Closure; +use InvalidArgumentException; +use PHPStan\BetterReflection\Reflection\Adapter\ReflectionParameter as AdapterReflectionParameter; +use PHPStan\BetterReflection\Reflection\Adapter\ReflectionType as AdapterReflectionType; +use PHPStan\BetterReflection\Reflection\ReflectionClass as BetterReflectionClass; +use PHPStan\BetterReflection\Reflection\ReflectionFunction as BetterReflectionFunction; +use PHPStan\BetterReflection\Reflection\ReflectionParameter as BetterReflectionParameter; +use PHPStan\Reflection\Php\BuiltinMethodReflection; +use PHPStan\TrinaryLogic; +use ReflectionClass; +use ReflectionFunction; +use ReflectionMethod; +use ReflectionParameter; +use ReflectionType; +use stdClass; +use Throwable; + +abstract class AbstractMacro implements BuiltinMethodReflection +{ + /** + * The reflection function/method. + * + * @var ReflectionFunction|ReflectionMethod + */ + protected $reflectionFunction; + + /** + * The class name. + * + * @var class-string + */ + private $className; + + /** + * The method name. + * + * @var string + */ + private $methodName; + + /** + * The parameters. + * + * @var ReflectionParameter[] + */ + private $parameters; + + /** + * The is static. + * + * @var bool + */ + private $static = false; + + /** + * Macro constructor. + * + * @param string $className + * @phpstan-param class-string $className + * + * @param string $methodName + * @param callable $macro + */ + public function __construct(string $className, string $methodName, $macro) + { + $this->className = $className; + $this->methodName = $methodName; + $rawReflectionFunction = \is_array($macro) + ? new ReflectionMethod($macro[0], $macro[1]) + : new ReflectionFunction($macro); + $this->reflectionFunction = self::hasModernParser() + ? $this->getReflectionFunction($macro) + : $rawReflectionFunction; // @codeCoverageIgnore + $this->parameters = array_map( + function ($parameter) { + if ($parameter instanceof BetterReflectionParameter) { + return new AdapterReflectionParameter($parameter); + } + + return $parameter; // @codeCoverageIgnore + }, + $this->reflectionFunction->getParameters() + ); + + if ($rawReflectionFunction->isClosure()) { + try { + $closure = $rawReflectionFunction->getClosure(); + $boundClosure = Closure::bind($closure, new stdClass()); + $this->static = (!$boundClosure || (new ReflectionFunction($boundClosure))->getClosureThis() === null); + } catch (Throwable $e) { + $this->static = true; + } + } + } + + private function getReflectionFunction($spec) + { + if (\is_array($spec) && \count($spec) === 2 && \is_string($spec[1])) { + \assert($spec[1] !== ''); + + if (\is_object($spec[0])) { + return BetterReflectionClass::createFromInstance($spec[0]) + ->getMethod($spec[1]); + } + + return BetterReflectionClass::createFromName($spec[0]) + ->getMethod($spec[1]); + } + + if (\is_string($spec)) { + return BetterReflectionFunction::createFromName($spec); + } + + if ($spec instanceof Closure) { + return BetterReflectionFunction::createFromClosure($spec); + } + + throw new InvalidArgumentException('Could not create reflection from the spec given'); // @codeCoverageIgnore + } + + /** + * {@inheritdoc} + */ + public function getDeclaringClass(): ReflectionClass + { + return new ReflectionClass($this->className); + } + + /** + * {@inheritdoc} + */ + public function isPrivate(): bool + { + return false; + } + + /** + * {@inheritdoc} + */ + public function isPublic(): bool + { + return true; + } + + /** + * {@inheritdoc} + */ + public function isFinal(): bool + { + return false; + } + + /** + * {@inheritdoc} + */ + public function isInternal(): bool + { + return false; + } + + /** + * {@inheritdoc} + */ + public function isAbstract(): bool + { + return false; + } + + /** + * {@inheritdoc} + */ + public function isStatic(): bool + { + return $this->static; + } + + /** + * {@inheritdoc} + */ + public function getDocComment(): ?string + { + return $this->reflectionFunction->getDocComment() ?: null; + } + + /** + * {@inheritdoc} + */ + public function getName(): string + { + return $this->methodName; + } + + /** + * {@inheritdoc} + */ + public function getParameters(): array + { + return $this->parameters; + } + + /** + * {@inheritdoc} + */ + public function getReturnType(): ?ReflectionType + { + $type = $this->reflectionFunction->getReturnType(); + + if ($type instanceof ReflectionType) { + return $type; // @codeCoverageIgnore + } + + return self::adaptType($type); + } + + /** + * {@inheritdoc} + */ + public function isDeprecated(): TrinaryLogic + { + return TrinaryLogic::createFromBoolean( + $this->reflectionFunction->isDeprecated() || + preg_match('/@deprecated/i', $this->getDocComment() ?: '') + ); + } + + /** + * {@inheritdoc} + */ + public function isVariadic(): bool + { + return $this->reflectionFunction->isVariadic(); + } + + /** + * {@inheritdoc} + */ + public function getPrototype(): BuiltinMethodReflection + { + return $this; + } + + public function getTentativeReturnType(): ?ReflectionType + { + return null; + } + + public function returnsByReference(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + private static function adaptType($type) + { + $method = method_exists(AdapterReflectionType::class, 'fromTypeOrNull') + ? 'fromTypeOrNull' + : 'fromReturnTypeOrNull'; // @codeCoverageIgnore + + return AdapterReflectionType::$method($type); + } + + private static function hasModernParser(): bool + { + static $modernParser = null; + + if ($modernParser !== null) { + return $modernParser; + } + + $modernParser = method_exists(AdapterReflectionType::class, 'fromTypeOrNull'); + + return $modernParser; + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/PHPStan/Macro.php b/vendor/nesbot/carbon/src/Carbon/PHPStan/Macro.php new file mode 100644 index 0000000..de3e51f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/PHPStan/Macro.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\PHPStan; + +use PHPStan\BetterReflection\Reflection\Adapter; +use PHPStan\Reflection\Php\BuiltinMethodReflection; +use ReflectionMethod; + +$method = new ReflectionMethod(BuiltinMethodReflection::class, 'getReflection'); + +require $method->hasReturnType() && $method->getReturnType()->getName() === Adapter\ReflectionMethod::class + ? __DIR__.'/../../../lazy/Carbon/PHPStan/AbstractMacroStatic.php' + : __DIR__.'/../../../lazy/Carbon/PHPStan/AbstractMacroBuiltin.php'; + +$method = new ReflectionMethod(BuiltinMethodReflection::class, 'getFileName'); + +require $method->hasReturnType() + ? __DIR__.'/../../../lazy/Carbon/PHPStan/MacroStrongType.php' + : __DIR__.'/../../../lazy/Carbon/PHPStan/MacroWeakType.php'; + +final class Macro extends LazyMacro +{ +} diff --git a/vendor/nesbot/carbon/src/Carbon/PHPStan/MacroExtension.php b/vendor/nesbot/carbon/src/Carbon/PHPStan/MacroExtension.php new file mode 100644 index 0000000..2cd6fce --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/PHPStan/MacroExtension.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 Carbon\PHPStan; + +use PHPStan\Reflection\Assertions; +use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\MethodReflection; +use PHPStan\Reflection\MethodsClassReflectionExtension; +use PHPStan\Reflection\Php\PhpMethodReflectionFactory; +use PHPStan\Reflection\ReflectionProvider; +use PHPStan\Type\TypehintHelper; + +/** + * Class MacroExtension. + * + * @codeCoverageIgnore Pure PHPStan wrapper. + */ +final class MacroExtension implements MethodsClassReflectionExtension +{ + /** + * @var PhpMethodReflectionFactory + */ + protected $methodReflectionFactory; + + /** + * @var MacroScanner + */ + protected $scanner; + + /** + * Extension constructor. + * + * @param PhpMethodReflectionFactory $methodReflectionFactory + * @param ReflectionProvider $reflectionProvider + */ + public function __construct( + PhpMethodReflectionFactory $methodReflectionFactory, + ReflectionProvider $reflectionProvider + ) { + $this->scanner = new MacroScanner($reflectionProvider); + $this->methodReflectionFactory = $methodReflectionFactory; + } + + /** + * {@inheritdoc} + */ + public function hasMethod(ClassReflection $classReflection, string $methodName): bool + { + return $this->scanner->hasMethod($classReflection->getName(), $methodName); + } + + /** + * {@inheritdoc} + */ + public function getMethod(ClassReflection $classReflection, string $methodName): MethodReflection + { + $builtinMacro = $this->scanner->getMethod($classReflection->getName(), $methodName); + $supportAssertions = class_exists(Assertions::class); + + return $this->methodReflectionFactory->create( + $classReflection, + null, + $builtinMacro, + $classReflection->getActiveTemplateTypeMap(), + [], + TypehintHelper::decideTypeFromReflection($builtinMacro->getReturnType()), + null, + null, + $builtinMacro->isDeprecated()->yes(), + $builtinMacro->isInternal(), + $builtinMacro->isFinal(), + $supportAssertions ? null : $builtinMacro->getDocComment(), + $supportAssertions ? Assertions::createEmpty() : null, + null, + $builtinMacro->getDocComment(), + [] + ); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/PHPStan/MacroScanner.php b/vendor/nesbot/carbon/src/Carbon/PHPStan/MacroScanner.php new file mode 100644 index 0000000..c88e49e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/PHPStan/MacroScanner.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\PHPStan; + +use Carbon\CarbonInterface; +use PHPStan\Reflection\ReflectionProvider; +use ReflectionClass; +use ReflectionException; + +final class MacroScanner +{ + /** + * @var \PHPStan\Reflection\ReflectionProvider + */ + private $reflectionProvider; + + /** + * MacroScanner constructor. + * + * @param \PHPStan\Reflection\ReflectionProvider $reflectionProvider + */ + public function __construct(ReflectionProvider $reflectionProvider) + { + $this->reflectionProvider = $reflectionProvider; + } + + /** + * Return true if the given pair class-method is a Carbon macro. + * + * @param string $className + * @phpstan-param class-string $className + * + * @param string $methodName + * + * @return bool + */ + public function hasMethod(string $className, string $methodName): bool + { + $classReflection = $this->reflectionProvider->getClass($className); + + if ( + $classReflection->getName() !== CarbonInterface::class && + !$classReflection->isSubclassOf(CarbonInterface::class) + ) { + return false; + } + + return \is_callable([$className, 'hasMacro']) && + $className::hasMacro($methodName); + } + + /** + * Return the Macro for a given pair class-method. + * + * @param string $className + * @phpstan-param class-string $className + * + * @param string $methodName + * + * @throws ReflectionException + * + * @return Macro + */ + public function getMethod(string $className, string $methodName): Macro + { + $reflectionClass = new ReflectionClass($className); + $property = $reflectionClass->getProperty('globalMacros'); + + $property->setAccessible(true); + $macro = $property->getValue()[$methodName]; + + return new Macro( + $className, + $methodName, + $macro + ); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Traits/Boundaries.php b/vendor/nesbot/carbon/src/Carbon/Traits/Boundaries.php new file mode 100644 index 0000000..71bbb72 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Traits/Boundaries.php @@ -0,0 +1,443 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use Carbon\Exceptions\UnknownUnitException; + +/** + * Trait Boundaries. + * + * startOf, endOf and derived method for each unit. + * + * Depends on the following properties: + * + * @property int $year + * @property int $month + * @property int $daysInMonth + * @property int $quarter + * + * Depends on the following methods: + * + * @method $this setTime(int $hour, int $minute, int $second = 0, int $microseconds = 0) + * @method $this setDate(int $year, int $month, int $day) + * @method $this addMonths(int $value = 1) + */ +trait Boundaries +{ + /** + * Resets the time to 00:00:00 start of day + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfDay(); + * ``` + * + * @return static + */ + public function startOfDay() + { + return $this->setTime(0, 0, 0, 0); + } + + /** + * Resets the time to 23:59:59.999999 end of day + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfDay(); + * ``` + * + * @return static + */ + public function endOfDay() + { + return $this->setTime(static::HOURS_PER_DAY - 1, static::MINUTES_PER_HOUR - 1, static::SECONDS_PER_MINUTE - 1, static::MICROSECONDS_PER_SECOND - 1); + } + + /** + * Resets the date to the first day of the month and the time to 00:00:00 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfMonth(); + * ``` + * + * @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.999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfMonth(); + * ``` + * + * @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 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfQuarter(); + * ``` + * + * @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.999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfQuarter(); + * ``` + * + * @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 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfYear(); + * ``` + * + * @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.999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfYear(); + * ``` + * + * @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 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfDecade(); + * ``` + * + * @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.999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfDecade(); + * ``` + * + * @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 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfCentury(); + * ``` + * + * @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.999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfCentury(); + * ``` + * + * @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 millennium and the time to 00:00:00 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfMillennium(); + * ``` + * + * @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 millennium and time to 23:59:59.999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfMillennium(); + * ``` + * + * @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 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfWeek() . "\n"; + * echo Carbon::parse('2018-07-25 12:45:16')->locale('ar')->startOfWeek() . "\n"; + * echo Carbon::parse('2018-07-25 12:45:16')->startOfWeek(Carbon::SUNDAY) . "\n"; + * ``` + * + * @param int $weekStartsAt optional start allow you to specify the day of week to use to start the week + * + * @return static + */ + public function startOfWeek($weekStartsAt = null) + { + return $this->subDays((7 + $this->dayOfWeek - ($weekStartsAt ?? $this->firstWeekDay)) % 7)->startOfDay(); + } + + /** + * Resets the date to end of week (defined in $weekEndsAt) and time to 23:59:59.999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfWeek() . "\n"; + * echo Carbon::parse('2018-07-25 12:45:16')->locale('ar')->endOfWeek() . "\n"; + * echo Carbon::parse('2018-07-25 12:45:16')->endOfWeek(Carbon::SATURDAY) . "\n"; + * ``` + * + * @param int $weekEndsAt optional start allow you to specify the day of week to use to end the week + * + * @return static + */ + public function endOfWeek($weekEndsAt = null) + { + return $this->addDays((7 - $this->dayOfWeek + ($weekEndsAt ?? $this->lastWeekDay)) % 7)->endOfDay(); + } + + /** + * Modify to start of current hour, minutes and seconds become 0 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfHour(); + * ``` + * + * @return static + */ + public function startOfHour() + { + return $this->setTime($this->hour, 0, 0, 0); + } + + /** + * Modify to end of current hour, minutes and seconds become 59 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfHour(); + * ``` + * + * @return static + */ + public function endOfHour() + { + return $this->setTime($this->hour, static::MINUTES_PER_HOUR - 1, static::SECONDS_PER_MINUTE - 1, static::MICROSECONDS_PER_SECOND - 1); + } + + /** + * Modify to start of current minute, seconds become 0 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfMinute(); + * ``` + * + * @return static + */ + public function startOfMinute() + { + return $this->setTime($this->hour, $this->minute, 0, 0); + } + + /** + * Modify to end of current minute, seconds become 59 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfMinute(); + * ``` + * + * @return static + */ + public function endOfMinute() + { + return $this->setTime($this->hour, $this->minute, static::SECONDS_PER_MINUTE - 1, static::MICROSECONDS_PER_SECOND - 1); + } + + /** + * Modify to start of current second, microseconds become 0 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16.334455') + * ->startOfSecond() + * ->format('H:i:s.u'); + * ``` + * + * @return static + */ + public function startOfSecond() + { + return $this->setTime($this->hour, $this->minute, $this->second, 0); + } + + /** + * Modify to end of current second, microseconds become 999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16.334455') + * ->endOfSecond() + * ->format('H:i:s.u'); + * ``` + * + * @return static + */ + public function endOfSecond() + { + return $this->setTime($this->hour, $this->minute, $this->second, static::MICROSECONDS_PER_SECOND - 1); + } + + /** + * Modify to start of current given unit. + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16.334455') + * ->startOf('month') + * ->endOf('week', Carbon::FRIDAY); + * ``` + * + * @param string $unit + * @param array $params + * + * @return static + */ + public function startOf($unit, ...$params) + { + $ucfUnit = ucfirst(static::singularUnit($unit)); + $method = "startOf$ucfUnit"; + if (!method_exists($this, $method)) { + throw new UnknownUnitException($unit); + } + + return $this->$method(...$params); + } + + /** + * Modify to end of current given unit. + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16.334455') + * ->startOf('month') + * ->endOf('week', Carbon::FRIDAY); + * ``` + * + * @param string $unit + * @param array $params + * + * @return static + */ + public function endOf($unit, ...$params) + { + $ucfUnit = ucfirst(static::singularUnit($unit)); + $method = "endOf$ucfUnit"; + if (!method_exists($this, $method)) { + throw new UnknownUnitException($unit); + } + + return $this->$method(...$params); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Traits/Cast.php b/vendor/nesbot/carbon/src/Carbon/Traits/Cast.php new file mode 100644 index 0000000..5f7c7c0 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Traits/Cast.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 Carbon\Traits; + +use Carbon\Exceptions\InvalidCastException; +use DateTimeInterface; + +/** + * Trait Cast. + * + * Utils to cast into an other class. + */ +trait Cast +{ + /** + * Cast the current instance into the given class. + * + * @param string $className The $className::instance() method will be called to cast the current object. + * + * @return DateTimeInterface + */ + public function cast(string $className) + { + if (!method_exists($className, 'instance')) { + if (is_a($className, DateTimeInterface::class, true)) { + return new $className($this->rawFormat('Y-m-d H:i:s.u'), $this->getTimezone()); + } + + throw new InvalidCastException("$className has not the instance() method needed to cast the date."); + } + + return $className::instance($this); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Traits/Comparison.php b/vendor/nesbot/carbon/src/Carbon/Traits/Comparison.php new file mode 100644 index 0000000..f6261d8 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Traits/Comparison.php @@ -0,0 +1,1125 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use BadMethodCallException; +use Carbon\CarbonInterface; +use Carbon\Exceptions\BadComparisonUnitException; +use InvalidArgumentException; + +/** + * Trait Comparison. + * + * Comparison utils and testers. All the following methods return booleans. + * nowWithSameTz + * + * Depends on the following methods: + * + * @method static resolveCarbon($date) + * @method static copy() + * @method static nowWithSameTz() + * @method static static yesterday($timezone = null) + * @method static static tomorrow($timezone = null) + */ +trait Comparison +{ + /** @var bool */ + protected $endOfTime = false; + + /** @var bool */ + protected $startOfTime = false; + + /** + * Determines if the instance is equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->eq('2018-07-25 12:45:16'); // true + * Carbon::parse('2018-07-25 12:45:16')->eq(Carbon::parse('2018-07-25 12:45:16')); // true + * Carbon::parse('2018-07-25 12:45:16')->eq('2018-07-25 12:45:17'); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see equalTo() + * + * @return bool + */ + public function eq($date): bool + { + return $this->equalTo($date); + } + + /** + * Determines if the instance is equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->equalTo('2018-07-25 12:45:16'); // true + * Carbon::parse('2018-07-25 12:45:16')->equalTo(Carbon::parse('2018-07-25 12:45:16')); // true + * Carbon::parse('2018-07-25 12:45:16')->equalTo('2018-07-25 12:45:17'); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function equalTo($date): bool + { + $this->discourageNull($date); + $this->discourageBoolean($date); + + return $this == $this->resolveCarbon($date); + } + + /** + * Determines if the instance is not equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->ne('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->ne(Carbon::parse('2018-07-25 12:45:16')); // false + * Carbon::parse('2018-07-25 12:45:16')->ne('2018-07-25 12:45:17'); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see notEqualTo() + * + * @return bool + */ + public function ne($date): bool + { + return $this->notEqualTo($date); + } + + /** + * Determines if the instance is not equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->notEqualTo('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->notEqualTo(Carbon::parse('2018-07-25 12:45:16')); // false + * Carbon::parse('2018-07-25 12:45:16')->notEqualTo('2018-07-25 12:45:17'); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function notEqualTo($date): bool + { + return !$this->equalTo($date); + } + + /** + * Determines if the instance is greater (after) than another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->gt('2018-07-25 12:45:15'); // true + * Carbon::parse('2018-07-25 12:45:16')->gt('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->gt('2018-07-25 12:45:17'); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see greaterThan() + * + * @return bool + */ + public function gt($date): bool + { + return $this->greaterThan($date); + } + + /** + * Determines if the instance is greater (after) than another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->greaterThan('2018-07-25 12:45:15'); // true + * Carbon::parse('2018-07-25 12:45:16')->greaterThan('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->greaterThan('2018-07-25 12:45:17'); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function greaterThan($date): bool + { + $this->discourageNull($date); + $this->discourageBoolean($date); + + return $this > $this->resolveCarbon($date); + } + + /** + * Determines if the instance is greater (after) than another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->isAfter('2018-07-25 12:45:15'); // true + * Carbon::parse('2018-07-25 12:45:16')->isAfter('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->isAfter('2018-07-25 12:45:17'); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see greaterThan() + * + * @return bool + */ + public function isAfter($date): bool + { + return $this->greaterThan($date); + } + + /** + * Determines if the instance is greater (after) than or equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->gte('2018-07-25 12:45:15'); // true + * Carbon::parse('2018-07-25 12:45:16')->gte('2018-07-25 12:45:16'); // true + * Carbon::parse('2018-07-25 12:45:16')->gte('2018-07-25 12:45:17'); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see greaterThanOrEqualTo() + * + * @return bool + */ + public function gte($date): bool + { + return $this->greaterThanOrEqualTo($date); + } + + /** + * Determines if the instance is greater (after) than or equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->greaterThanOrEqualTo('2018-07-25 12:45:15'); // true + * Carbon::parse('2018-07-25 12:45:16')->greaterThanOrEqualTo('2018-07-25 12:45:16'); // true + * Carbon::parse('2018-07-25 12:45:16')->greaterThanOrEqualTo('2018-07-25 12:45:17'); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function greaterThanOrEqualTo($date): bool + { + $this->discourageNull($date); + $this->discourageBoolean($date); + + return $this >= $this->resolveCarbon($date); + } + + /** + * Determines if the instance is less (before) than another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->lt('2018-07-25 12:45:15'); // false + * Carbon::parse('2018-07-25 12:45:16')->lt('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->lt('2018-07-25 12:45:17'); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see lessThan() + * + * @return bool + */ + public function lt($date): bool + { + return $this->lessThan($date); + } + + /** + * Determines if the instance is less (before) than another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->lessThan('2018-07-25 12:45:15'); // false + * Carbon::parse('2018-07-25 12:45:16')->lessThan('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->lessThan('2018-07-25 12:45:17'); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function lessThan($date): bool + { + $this->discourageNull($date); + $this->discourageBoolean($date); + + return $this < $this->resolveCarbon($date); + } + + /** + * Determines if the instance is less (before) than another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->isBefore('2018-07-25 12:45:15'); // false + * Carbon::parse('2018-07-25 12:45:16')->isBefore('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->isBefore('2018-07-25 12:45:17'); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see lessThan() + * + * @return bool + */ + public function isBefore($date): bool + { + return $this->lessThan($date); + } + + /** + * Determines if the instance is less (before) or equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->lte('2018-07-25 12:45:15'); // false + * Carbon::parse('2018-07-25 12:45:16')->lte('2018-07-25 12:45:16'); // true + * Carbon::parse('2018-07-25 12:45:16')->lte('2018-07-25 12:45:17'); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see lessThanOrEqualTo() + * + * @return bool + */ + public function lte($date): bool + { + return $this->lessThanOrEqualTo($date); + } + + /** + * Determines if the instance is less (before) or equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->lessThanOrEqualTo('2018-07-25 12:45:15'); // false + * Carbon::parse('2018-07-25 12:45:16')->lessThanOrEqualTo('2018-07-25 12:45:16'); // true + * Carbon::parse('2018-07-25 12:45:16')->lessThanOrEqualTo('2018-07-25 12:45:17'); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function lessThanOrEqualTo($date): bool + { + $this->discourageNull($date); + $this->discourageBoolean($date); + + return $this <= $this->resolveCarbon($date); + } + + /** + * Determines if the instance is between two others. + * + * The third argument allow you to specify if bounds are included or not (true by default) + * but for when you including/excluding bounds may produce different results in your application, + * we recommend to use the explicit methods ->betweenIncluded() or ->betweenExcluded() instead. + * + * @example + * ``` + * Carbon::parse('2018-07-25')->between('2018-07-14', '2018-08-01'); // true + * Carbon::parse('2018-07-25')->between('2018-08-01', '2018-08-20'); // false + * Carbon::parse('2018-07-25')->between('2018-07-25', '2018-08-01'); // true + * Carbon::parse('2018-07-25')->between('2018-07-25', '2018-08-01', false); // false + * ``` + * + * @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): bool + { + $date1 = $this->resolveCarbon($date1); + $date2 = $this->resolveCarbon($date2); + + if ($date1->greaterThan($date2)) { + [$date1, $date2] = [$date2, $date1]; + } + + if ($equal) { + return $this >= $date1 && $this <= $date2; + } + + return $this > $date1 && $this < $date2; + } + + /** + * Determines if the instance is between two others, bounds included. + * + * @example + * ``` + * Carbon::parse('2018-07-25')->betweenIncluded('2018-07-14', '2018-08-01'); // true + * Carbon::parse('2018-07-25')->betweenIncluded('2018-08-01', '2018-08-20'); // false + * Carbon::parse('2018-07-25')->betweenIncluded('2018-07-25', '2018-08-01'); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date1 + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date2 + * + * @return bool + */ + public function betweenIncluded($date1, $date2): bool + { + return $this->between($date1, $date2, true); + } + + /** + * Determines if the instance is between two others, bounds excluded. + * + * @example + * ``` + * Carbon::parse('2018-07-25')->betweenExcluded('2018-07-14', '2018-08-01'); // true + * Carbon::parse('2018-07-25')->betweenExcluded('2018-08-01', '2018-08-20'); // false + * Carbon::parse('2018-07-25')->betweenExcluded('2018-07-25', '2018-08-01'); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date1 + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date2 + * + * @return bool + */ + public function betweenExcluded($date1, $date2): bool + { + return $this->between($date1, $date2, false); + } + + /** + * Determines if the instance is between two others + * + * @example + * ``` + * Carbon::parse('2018-07-25')->isBetween('2018-07-14', '2018-08-01'); // true + * Carbon::parse('2018-07-25')->isBetween('2018-08-01', '2018-08-20'); // false + * Carbon::parse('2018-07-25')->isBetween('2018-07-25', '2018-08-01'); // true + * Carbon::parse('2018-07-25')->isBetween('2018-07-25', '2018-08-01', false); // false + * ``` + * + * @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 isBetween($date1, $date2, $equal = true): bool + { + return $this->between($date1, $date2, $equal); + } + + /** + * Determines if the instance is a weekday. + * + * @example + * ``` + * Carbon::parse('2019-07-14')->isWeekday(); // false + * Carbon::parse('2019-07-15')->isWeekday(); // true + * ``` + * + * @return bool + */ + public function isWeekday() + { + return !$this->isWeekend(); + } + + /** + * Determines if the instance is a weekend day. + * + * @example + * ``` + * Carbon::parse('2019-07-14')->isWeekend(); // true + * Carbon::parse('2019-07-15')->isWeekend(); // false + * ``` + * + * @return bool + */ + public function isWeekend() + { + return \in_array($this->dayOfWeek, static::$weekendDays, true); + } + + /** + * Determines if the instance is yesterday. + * + * @example + * ``` + * Carbon::yesterday()->isYesterday(); // true + * Carbon::tomorrow()->isYesterday(); // false + * ``` + * + * @return bool + */ + public function isYesterday() + { + return $this->toDateString() === static::yesterday($this->getTimezone())->toDateString(); + } + + /** + * Determines if the instance is today. + * + * @example + * ``` + * Carbon::today()->isToday(); // true + * Carbon::tomorrow()->isToday(); // false + * ``` + * + * @return bool + */ + public function isToday() + { + return $this->toDateString() === $this->nowWithSameTz()->toDateString(); + } + + /** + * Determines if the instance is tomorrow. + * + * @example + * ``` + * Carbon::tomorrow()->isTomorrow(); // true + * Carbon::yesterday()->isTomorrow(); // false + * ``` + * + * @return bool + */ + public function isTomorrow() + { + return $this->toDateString() === static::tomorrow($this->getTimezone())->toDateString(); + } + + /** + * Determines if the instance is in the future, ie. greater (after) than now. + * + * @example + * ``` + * Carbon::now()->addHours(5)->isFuture(); // true + * Carbon::now()->subHours(5)->isFuture(); // false + * ``` + * + * @return bool + */ + public function isFuture() + { + return $this->greaterThan($this->nowWithSameTz()); + } + + /** + * Determines if the instance is in the past, ie. less (before) than now. + * + * @example + * ``` + * Carbon::now()->subHours(5)->isPast(); // true + * Carbon::now()->addHours(5)->isPast(); // false + * ``` + * + * @return bool + */ + public function isPast() + { + return $this->lessThan($this->nowWithSameTz()); + } + + /** + * Determines if the instance is a leap year. + * + * @example + * ``` + * Carbon::parse('2020-01-01')->isLeapYear(); // true + * Carbon::parse('2019-01-01')->isLeapYear(); // false + * ``` + * + * @return bool + */ + public function isLeapYear() + { + return $this->rawFormat('L') === '1'; + } + + /** + * Determines if the instance is a long year (using calendar year). + * + * ⚠️ This method completely ignores month and day to use the numeric year number, + * it's not correct if the exact date matters. For instance as `2019-12-30` is already + * in the first week of the 2020 year, if you want to know from this date if ISO week + * year 2020 is a long year, use `isLongIsoYear` instead. + * + * @example + * ``` + * Carbon::create(2015)->isLongYear(); // true + * Carbon::create(2016)->isLongYear(); // false + * ``` + * + * @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; + } + + /** + * Determines if the instance is a long year (using ISO 8601 year). + * + * @example + * ``` + * Carbon::parse('2015-01-01')->isLongIsoYear(); // true + * Carbon::parse('2016-01-01')->isLongIsoYear(); // true + * Carbon::parse('2016-01-03')->isLongIsoYear(); // false + * Carbon::parse('2019-12-29')->isLongIsoYear(); // false + * Carbon::parse('2019-12-30')->isLongIsoYear(); // true + * ``` + * + * @see https://en.wikipedia.org/wiki/ISO_8601#Week_dates + * + * @return bool + */ + public function isLongIsoYear() + { + return static::create($this->isoWeekYear, 12, 28, 0, 0, 0, $this->tz)->weekOfYear === 53; + } + + /** + * Compares the formatted values of the two dates. + * + * @example + * ``` + * Carbon::parse('2019-06-13')->isSameAs('Y-d', Carbon::parse('2019-12-13')); // true + * Carbon::parse('2019-06-13')->isSameAs('Y-d', Carbon::parse('2019-06-14')); // false + * ``` + * + * @param string $format date formats to compare. + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date instance to compare with or null to use current day. + * + * @return bool + */ + public function isSameAs($format, $date = null) + { + return $this->rawFormat($format) === $this->resolveCarbon($date)->rawFormat($format); + } + + /** + * Determines if the instance is in the current unit given. + * + * @example + * ``` + * Carbon::parse('2019-01-13')->isSameUnit('year', Carbon::parse('2019-12-25')); // true + * Carbon::parse('2018-12-13')->isSameUnit('year', Carbon::parse('2019-12-25')); // false + * ``` + * + * @param string $unit singular unit string + * @param \Carbon\Carbon|\DateTimeInterface|null $date instance to compare with or null to use current day. + * + * @throws BadComparisonUnitException + * + * @return bool + */ + public function isSameUnit($unit, $date = null) + { + $units = [ + // @call isSameUnit + 'year' => 'Y', + // @call isSameUnit + 'week' => 'o-W', + // @call isSameUnit + 'day' => 'Y-m-d', + // @call isSameUnit + 'hour' => 'Y-m-d H', + // @call isSameUnit + 'minute' => 'Y-m-d H:i', + // @call isSameUnit + 'second' => 'Y-m-d H:i:s', + // @call isSameUnit + 'micro' => 'Y-m-d H:i:s.u', + // @call isSameUnit + 'microsecond' => 'Y-m-d H:i:s.u', + ]; + + if (isset($units[$unit])) { + return $this->isSameAs($units[$unit], $date); + } + + if (isset($this->$unit)) { + return $this->resolveCarbon($date)->$unit === $this->$unit; + } + + if ($this->localStrictModeEnabled ?? static::isStrictModeEnabled()) { + throw new BadComparisonUnitException($unit); + } + + return false; + } + + /** + * Determines if the instance is in the current unit given. + * + * @example + * ``` + * Carbon::now()->isCurrentUnit('hour'); // true + * Carbon::now()->subHours(2)->isCurrentUnit('hour'); // false + * ``` + * + * @param string $unit The unit to test. + * + * @throws BadMethodCallException + * + * @return bool + */ + public function isCurrentUnit($unit) + { + return $this->{'isSame'.ucfirst($unit)}(); + } + + /** + * Checks if the passed in date is in the same quarter as the instance quarter (and year if needed). + * + * @example + * ``` + * Carbon::parse('2019-01-12')->isSameQuarter(Carbon::parse('2019-03-01')); // true + * Carbon::parse('2019-01-12')->isSameQuarter(Carbon::parse('2019-04-01')); // false + * Carbon::parse('2019-01-12')->isSameQuarter(Carbon::parse('2018-03-01')); // false + * Carbon::parse('2019-01-12')->isSameQuarter(Carbon::parse('2018-03-01'), false); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|string|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 = true) + { + $date = $this->resolveCarbon($date); + + return $this->quarter === $date->quarter && (!$ofSameYear || $this->isSameYear($date)); + } + + /** + * Checks if the passed in date is in the same month as the instance´s month. + * + * @example + * ``` + * Carbon::parse('2019-01-12')->isSameMonth(Carbon::parse('2019-01-01')); // true + * Carbon::parse('2019-01-12')->isSameMonth(Carbon::parse('2019-02-01')); // false + * Carbon::parse('2019-01-12')->isSameMonth(Carbon::parse('2018-01-01')); // false + * Carbon::parse('2019-01-12')->isSameMonth(Carbon::parse('2018-01-01'), false); // true + * ``` + * + * @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 = true) + { + return $this->isSameAs($ofSameYear ? 'Y-m' : 'm', $date); + } + + /** + * Checks if this day is a specific day of the week. + * + * @example + * ``` + * Carbon::parse('2019-07-17')->isDayOfWeek(Carbon::WEDNESDAY); // true + * Carbon::parse('2019-07-17')->isDayOfWeek(Carbon::FRIDAY); // false + * Carbon::parse('2019-07-17')->isDayOfWeek('Wednesday'); // true + * Carbon::parse('2019-07-17')->isDayOfWeek('Friday'); // false + * ``` + * + * @param int $dayOfWeek + * + * @return bool + */ + public function isDayOfWeek($dayOfWeek) + { + if (\is_string($dayOfWeek) && \defined($constant = static::class.'::'.strtoupper($dayOfWeek))) { + $dayOfWeek = \constant($constant); + } + + return $this->dayOfWeek === $dayOfWeek; + } + + /** + * Check if its the birthday. Compares the date/month values of the two dates. + * + * @example + * ``` + * Carbon::now()->subYears(5)->isBirthday(); // true + * Carbon::now()->subYears(5)->subDay()->isBirthday(); // false + * Carbon::parse('2019-06-05')->isBirthday(Carbon::parse('2001-06-05')); // true + * Carbon::parse('2019-06-05')->isBirthday(Carbon::parse('2001-06-06')); // false + * ``` + * + * @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 + * + * @example + * ``` + * Carbon::parse('2019-02-28')->isLastOfMonth(); // true + * Carbon::parse('2019-03-28')->isLastOfMonth(); // false + * Carbon::parse('2019-03-30')->isLastOfMonth(); // false + * Carbon::parse('2019-03-31')->isLastOfMonth(); // true + * Carbon::parse('2019-04-30')->isLastOfMonth(); // true + * ``` + * + * @return bool + */ + public function isLastOfMonth() + { + return $this->day === $this->daysInMonth; + } + + /** + * Check if the instance is start of day / midnight. + * + * @example + * ``` + * Carbon::parse('2019-02-28 00:00:00')->isStartOfDay(); // true + * Carbon::parse('2019-02-28 00:00:00.999999')->isStartOfDay(); // true + * Carbon::parse('2019-02-28 00:00:01')->isStartOfDay(); // false + * Carbon::parse('2019-02-28 00:00:00.000000')->isStartOfDay(true); // true + * Carbon::parse('2019-02-28 00:00:00.000012')->isStartOfDay(true); // false + * ``` + * + * @param bool $checkMicroseconds check time at microseconds precision + * + * @return bool + */ + public function isStartOfDay($checkMicroseconds = false) + { + /* @var CarbonInterface $this */ + return $checkMicroseconds + ? $this->rawFormat('H:i:s.u') === '00:00:00.000000' + : $this->rawFormat('H:i:s') === '00:00:00'; + } + + /** + * Check if the instance is end of day. + * + * @example + * ``` + * Carbon::parse('2019-02-28 23:59:59.999999')->isEndOfDay(); // true + * Carbon::parse('2019-02-28 23:59:59.123456')->isEndOfDay(); // true + * Carbon::parse('2019-02-28 23:59:59')->isEndOfDay(); // true + * Carbon::parse('2019-02-28 23:59:58.999999')->isEndOfDay(); // false + * Carbon::parse('2019-02-28 23:59:59.999999')->isEndOfDay(true); // true + * Carbon::parse('2019-02-28 23:59:59.123456')->isEndOfDay(true); // false + * Carbon::parse('2019-02-28 23:59:59')->isEndOfDay(true); // false + * ``` + * + * @param bool $checkMicroseconds check time at microseconds precision + * + * @return bool + */ + public function isEndOfDay($checkMicroseconds = false) + { + /* @var CarbonInterface $this */ + return $checkMicroseconds + ? $this->rawFormat('H:i:s.u') === '23:59:59.999999' + : $this->rawFormat('H:i:s') === '23:59:59'; + } + + /** + * Check if the instance is start of day / midnight. + * + * @example + * ``` + * Carbon::parse('2019-02-28 00:00:00')->isMidnight(); // true + * Carbon::parse('2019-02-28 00:00:00.999999')->isMidnight(); // true + * Carbon::parse('2019-02-28 00:00:01')->isMidnight(); // false + * ``` + * + * @return bool + */ + public function isMidnight() + { + return $this->isStartOfDay(); + } + + /** + * Check if the instance is midday. + * + * @example + * ``` + * Carbon::parse('2019-02-28 11:59:59.999999')->isMidday(); // false + * Carbon::parse('2019-02-28 12:00:00')->isMidday(); // true + * Carbon::parse('2019-02-28 12:00:00.999999')->isMidday(); // true + * Carbon::parse('2019-02-28 12:00:01')->isMidday(); // false + * ``` + * + * @return bool + */ + public function isMidday() + { + /* @var CarbonInterface $this */ + return $this->rawFormat('G:i:s') === static::$midDayAt.':00:00'; + } + + /** + * Checks if the (date)time string is in a given format. + * + * @example + * ``` + * Carbon::hasFormat('11:12:45', 'h:i:s'); // true + * Carbon::hasFormat('13:12:45', 'h:i:s'); // false + * ``` + * + * @param string $date + * @param string $format + * + * @return bool + */ + public static function hasFormat($date, $format) + { + // 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. + + return self::matchFormatPattern((string) $date, preg_quote((string) $format, '/'), static::$regexFormats); + } + + /** + * Checks if the (date)time string is in a given format. + * + * @example + * ``` + * Carbon::hasFormatWithModifiers('31/08/2015', 'd#m#Y'); // true + * Carbon::hasFormatWithModifiers('31/08/2015', 'm#d#Y'); // false + * ``` + * + * @param string $date + * @param string $format + * + * @return bool + */ + public static function hasFormatWithModifiers($date, $format): bool + { + return self::matchFormatPattern((string) $date, (string) $format, array_merge(static::$regexFormats, static::$regexFormatModifiers)); + } + + /** + * Checks if the (date)time string is in a given format and valid to create a + * new instance. + * + * @example + * ``` + * Carbon::canBeCreatedFromFormat('11:12:45', 'h:i:s'); // true + * Carbon::canBeCreatedFromFormat('13:12:45', 'h:i:s'); // false + * ``` + * + * @param string $date + * @param string $format + * + * @return bool + */ + public static function canBeCreatedFromFormat($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. + if (!static::rawCreateFromFormat($format, $date)) { + return false; + } + } catch (InvalidArgumentException $e) { + return false; + } + + return static::hasFormatWithModifiers($date, $format); + } + + /** + * Returns true if the current date matches the given string. + * + * @example + * ``` + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('2019')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('2018')); // false + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('2019-06')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('06-02')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('2019-06-02')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('Sunday')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('June')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('12:23')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('12:23:45')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('12:23:00')); // false + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('12h')); // true + * var_dump(Carbon::parse('2019-06-02 15:23:45')->is('3pm')); // true + * var_dump(Carbon::parse('2019-06-02 15:23:45')->is('3am')); // false + * ``` + * + * @param string $tester day name, month name, hour, date, etc. as string + * + * @return bool + */ + public function is(string $tester) + { + $tester = trim($tester); + + if (preg_match('/^\d+$/', $tester)) { + return $this->year === (int) $tester; + } + + if (preg_match('/^\d{3,}-\d{1,2}$/', $tester)) { + return $this->isSameMonth(static::parse($tester)); + } + + if (preg_match('/^\d{1,2}-\d{1,2}$/', $tester)) { + return $this->isSameDay(static::parse($this->year.'-'.$tester)); + } + + $modifier = preg_replace('/(\d)h$/i', '$1:00', $tester); + + /* @var CarbonInterface $max */ + $median = static::parse('5555-06-15 12:30:30.555555')->modify($modifier); + $current = $this->avoidMutation(); + /* @var CarbonInterface $other */ + $other = $this->avoidMutation()->modify($modifier); + + if ($current->eq($other)) { + return true; + } + + if (preg_match('/\d:\d{1,2}:\d{1,2}$/', $tester)) { + return $current->startOfSecond()->eq($other); + } + + if (preg_match('/\d:\d{1,2}$/', $tester)) { + return $current->startOfMinute()->eq($other); + } + + if (preg_match('/\d(?:h|am|pm)$/', $tester)) { + return $current->startOfHour()->eq($other); + } + + if (preg_match( + '/^(?:january|february|march|april|may|june|july|august|september|october|november|december)(?:\s+\d+)?$/i', + $tester + )) { + return $current->startOfMonth()->eq($other->startOfMonth()); + } + + $units = [ + 'month' => [1, 'year'], + 'day' => [1, 'month'], + 'hour' => [0, 'day'], + 'minute' => [0, 'hour'], + 'second' => [0, 'minute'], + 'microsecond' => [0, 'second'], + ]; + + foreach ($units as $unit => [$minimum, $startUnit]) { + if ($minimum === $median->$unit) { + $current = $current->startOf($startUnit); + + break; + } + } + + return $current->eq($other); + } + + /** + * Checks if the (date)time string is in a given format with + * given list of pattern replacements. + * + * @example + * ``` + * Carbon::hasFormat('11:12:45', 'h:i:s'); // true + * Carbon::hasFormat('13:12:45', 'h:i:s'); // false + * ``` + * + * @param string $date + * @param string $format + * @param array $replacements + * + * @return bool + */ + private static function matchFormatPattern(string $date, string $format, array $replacements): bool + { + // Preg quote, but remove escaped backslashes since we'll deal with escaped characters in the format string. + $regex = str_replace('\\\\', '\\', $format); + // Replace not-escaped letters + $regex = preg_replace_callback( + '/(?startOfTime ?? false; + } + + /** + * Returns true if the date was created using CarbonImmutable::endOfTime() + * + * @return bool + */ + public function isEndOfTime(): bool + { + return $this->endOfTime ?? false; + } + + private function discourageNull($value): void + { + if ($value === null) { + @trigger_error("Since 2.61.0, it's deprecated to compare a date to null, meaning of such comparison is ambiguous and will no longer be possible in 3.0.0, you should explicitly pass 'now' or make an other check to eliminate null values.", \E_USER_DEPRECATED); + } + } + + private function discourageBoolean($value): void + { + if (\is_bool($value)) { + @trigger_error("Since 2.61.0, it's deprecated to compare a date to true or false, meaning of such comparison is ambiguous and will no longer be possible in 3.0.0, you should explicitly pass 'now' or make an other check to eliminate boolean values.", \E_USER_DEPRECATED); + } + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Traits/Converter.php b/vendor/nesbot/carbon/src/Carbon/Traits/Converter.php new file mode 100644 index 0000000..fff8a60 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Traits/Converter.php @@ -0,0 +1,639 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use Carbon\Carbon; +use Carbon\CarbonImmutable; +use Carbon\CarbonInterface; +use Carbon\CarbonInterval; +use Carbon\CarbonPeriod; +use Carbon\CarbonPeriodImmutable; +use Carbon\Exceptions\UnitException; +use Closure; +use DateTime; +use DateTimeImmutable; +use ReturnTypeWillChange; + +/** + * Trait Converter. + * + * Change date into different string formats and types and + * handle the string cast. + * + * Depends on the following methods: + * + * @method static copy() + */ +trait Converter +{ + use ToStringFormat; + + /** + * Returns the formatted date string on success or FALSE on failure. + * + * @see https://php.net/manual/en/datetime.format.php + * + * @param string $format + * + * @return string + */ + #[ReturnTypeWillChange] + public function format($format) + { + $function = $this->localFormatFunction ?: static::$formatFunction; + + if (!$function) { + return $this->rawFormat($format); + } + + if (\is_string($function) && method_exists($this, $function)) { + $function = [$this, $function]; + } + + return $function(...\func_get_args()); + } + + /** + * @see https://php.net/manual/en/datetime.format.php + * + * @param string $format + * + * @return string + */ + public function rawFormat($format) + { + return parent::format($format); + } + + /** + * Format the instance as a string using the set format + * + * @example + * ``` + * echo Carbon::now(); // Carbon instances can be cast to string + * ``` + * + * @return string + */ + public function __toString() + { + $format = $this->localToStringFormat ?? static::$toStringFormat; + + return $format instanceof Closure + ? $format($this) + : $this->rawFormat($format ?: ( + \defined('static::DEFAULT_TO_STRING_FORMAT') + ? static::DEFAULT_TO_STRING_FORMAT + : CarbonInterface::DEFAULT_TO_STRING_FORMAT + )); + } + + /** + * Format the instance as date + * + * @example + * ``` + * echo Carbon::now()->toDateString(); + * ``` + * + * @return string + */ + public function toDateString() + { + return $this->rawFormat('Y-m-d'); + } + + /** + * Format the instance as a readable date + * + * @example + * ``` + * echo Carbon::now()->toFormattedDateString(); + * ``` + * + * @return string + */ + public function toFormattedDateString() + { + return $this->rawFormat('M j, Y'); + } + + /** + * Format the instance with the day, and a readable date + * + * @example + * ``` + * echo Carbon::now()->toFormattedDayDateString(); + * ``` + * + * @return string + */ + public function toFormattedDayDateString(): string + { + return $this->rawFormat('D, M j, Y'); + } + + /** + * Format the instance as time + * + * @example + * ``` + * echo Carbon::now()->toTimeString(); + * ``` + * + * @param string $unitPrecision + * + * @return string + */ + public function toTimeString($unitPrecision = 'second') + { + return $this->rawFormat(static::getTimeFormatByPrecision($unitPrecision)); + } + + /** + * Format the instance as date and time + * + * @example + * ``` + * echo Carbon::now()->toDateTimeString(); + * ``` + * + * @param string $unitPrecision + * + * @return string + */ + public function toDateTimeString($unitPrecision = 'second') + { + return $this->rawFormat('Y-m-d '.static::getTimeFormatByPrecision($unitPrecision)); + } + + /** + * Return a format from H:i to H:i:s.u according to given unit precision. + * + * @param string $unitPrecision "minute", "second", "millisecond" or "microsecond" + * + * @return string + */ + public static function getTimeFormatByPrecision($unitPrecision) + { + switch (static::singularUnit($unitPrecision)) { + case 'minute': + return 'H:i'; + case 'second': + return 'H:i:s'; + case 'm': + case 'millisecond': + return 'H:i:s.v'; + case 'µ': + case 'microsecond': + return 'H:i:s.u'; + } + + throw new UnitException('Precision unit expected among: minute, second, millisecond and microsecond.'); + } + + /** + * Format the instance as date and time T-separated with no timezone + * + * @example + * ``` + * echo Carbon::now()->toDateTimeLocalString(); + * echo "\n"; + * echo Carbon::now()->toDateTimeLocalString('minute'); // You can specify precision among: minute, second, millisecond and microsecond + * ``` + * + * @param string $unitPrecision + * + * @return string + */ + public function toDateTimeLocalString($unitPrecision = 'second') + { + return $this->rawFormat('Y-m-d\T'.static::getTimeFormatByPrecision($unitPrecision)); + } + + /** + * Format the instance with day, date and time + * + * @example + * ``` + * echo Carbon::now()->toDayDateTimeString(); + * ``` + * + * @return string + */ + public function toDayDateTimeString() + { + return $this->rawFormat('D, M j, Y g:i A'); + } + + /** + * Format the instance as ATOM + * + * @example + * ``` + * echo Carbon::now()->toAtomString(); + * ``` + * + * @return string + */ + public function toAtomString() + { + return $this->rawFormat(DateTime::ATOM); + } + + /** + * Format the instance as COOKIE + * + * @example + * ``` + * echo Carbon::now()->toCookieString(); + * ``` + * + * @return string + */ + public function toCookieString() + { + return $this->rawFormat(DateTime::COOKIE); + } + + /** + * Format the instance as ISO8601 + * + * @example + * ``` + * echo Carbon::now()->toIso8601String(); + * ``` + * + * @return string + */ + public function toIso8601String() + { + return $this->toAtomString(); + } + + /** + * Format the instance as RFC822 + * + * @example + * ``` + * echo Carbon::now()->toRfc822String(); + * ``` + * + * @return string + */ + public function toRfc822String() + { + return $this->rawFormat(DateTime::RFC822); + } + + /** + * Convert the instance to UTC and return as Zulu ISO8601 + * + * @example + * ``` + * echo Carbon::now()->toIso8601ZuluString(); + * ``` + * + * @param string $unitPrecision + * + * @return string + */ + public function toIso8601ZuluString($unitPrecision = 'second') + { + return $this->avoidMutation() + ->utc() + ->rawFormat('Y-m-d\T'.static::getTimeFormatByPrecision($unitPrecision).'\Z'); + } + + /** + * Format the instance as RFC850 + * + * @example + * ``` + * echo Carbon::now()->toRfc850String(); + * ``` + * + * @return string + */ + public function toRfc850String() + { + return $this->rawFormat(DateTime::RFC850); + } + + /** + * Format the instance as RFC1036 + * + * @example + * ``` + * echo Carbon::now()->toRfc1036String(); + * ``` + * + * @return string + */ + public function toRfc1036String() + { + return $this->rawFormat(DateTime::RFC1036); + } + + /** + * Format the instance as RFC1123 + * + * @example + * ``` + * echo Carbon::now()->toRfc1123String(); + * ``` + * + * @return string + */ + public function toRfc1123String() + { + return $this->rawFormat(DateTime::RFC1123); + } + + /** + * Format the instance as RFC2822 + * + * @example + * ``` + * echo Carbon::now()->toRfc2822String(); + * ``` + * + * @return string + */ + public function toRfc2822String() + { + return $this->rawFormat(DateTime::RFC2822); + } + + /** + * Format the instance as RFC3339 + * + * @param bool $extended + * + * @example + * ``` + * echo Carbon::now()->toRfc3339String() . "\n"; + * echo Carbon::now()->toRfc3339String(true) . "\n"; + * ``` + * + * @return string + */ + public function toRfc3339String($extended = false) + { + $format = DateTime::RFC3339; + if ($extended) { + $format = DateTime::RFC3339_EXTENDED; + } + + return $this->rawFormat($format); + } + + /** + * Format the instance as RSS + * + * @example + * ``` + * echo Carbon::now()->toRssString(); + * ``` + * + * @return string + */ + public function toRssString() + { + return $this->rawFormat(DateTime::RSS); + } + + /** + * Format the instance as W3C + * + * @example + * ``` + * echo Carbon::now()->toW3cString(); + * ``` + * + * @return string + */ + public function toW3cString() + { + return $this->rawFormat(DateTime::W3C); + } + + /** + * Format the instance as RFC7231 + * + * @example + * ``` + * echo Carbon::now()->toRfc7231String(); + * ``` + * + * @return string + */ + public function toRfc7231String() + { + return $this->avoidMutation() + ->setTimezone('GMT') + ->rawFormat(\defined('static::RFC7231_FORMAT') ? static::RFC7231_FORMAT : CarbonInterface::RFC7231_FORMAT); + } + + /** + * Get default array representation. + * + * @example + * ``` + * var_dump(Carbon::now()->toArray()); + * ``` + * + * @return array + */ + public function toArray() + { + return [ + '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->rawFormat(\defined('static::DEFAULT_TO_STRING_FORMAT') ? static::DEFAULT_TO_STRING_FORMAT : CarbonInterface::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->avoidMutation()->locale('en')->isoFormat('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ'); + } + + /** + * 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->isValid()) { + return null; + } + + $yearFormat = $this->year < 0 || $this->year > 9999 ? 'YYYYYY' : 'YYYY'; + $tzFormat = $keepOffset ? 'Z' : '[Z]'; + $date = $keepOffset ? $this : $this->avoidMutation()->utc(); + + return $date->isoFormat("$yearFormat-MM-DD[T]HH:mm:ss.SSSSSS$tzFormat"); + } + + /** + * 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->rawFormat('Y-m-d H:i:s.u'), $this->getTimezone()); + } + + /** + * Return native toDateTimeImmutable PHP object matching the current instance. + * + * @example + * ``` + * var_dump(Carbon::now()->toDateTimeImmutable()); + * ``` + * + * @return DateTimeImmutable + */ + public function toDateTimeImmutable() + { + return new DateTimeImmutable($this->rawFormat('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(); + } + + /** + * Create a iterable CarbonPeriod object from current date to a given end date (and optional interval). + * + * @param \DateTimeInterface|Carbon|CarbonImmutable|int|null $end period end date or recurrences count if int + * @param int|\DateInterval|string|null $interval period default interval or number of the given $unit + * @param string|null $unit if specified, $interval must be an integer + * + * @return CarbonPeriod + */ + public function toPeriod($end = null, $interval = null, $unit = null) + { + if ($unit) { + $interval = CarbonInterval::make("$interval ".static::pluralUnit($unit)); + } + + $period = ($this->isMutable() ? new CarbonPeriod() : new CarbonPeriodImmutable()) + ->setDateClass(static::class) + ->setStartDate($this); + + if ($interval) { + $period = $period->setDateInterval($interval); + } + + if (\is_int($end) || (\is_string($end) && ctype_digit($end))) { + $period = $period->setRecurrences($end); + } elseif ($end) { + $period = $period->setEndDate($end); + } + + return $period; + } + + /** + * Create a iterable CarbonPeriod object from current date to a given end date (and optional interval). + * + * @param \DateTimeInterface|Carbon|CarbonImmutable|null $end period end date + * @param int|\DateInterval|string|null $interval period default interval or number of the given $unit + * @param string|null $unit if specified, $interval must be an integer + * + * @return CarbonPeriod + */ + public function range($end = null, $interval = null, $unit = null) + { + return $this->toPeriod($end, $interval, $unit); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Traits/Creator.php b/vendor/nesbot/carbon/src/Carbon/Traits/Creator.php new file mode 100644 index 0000000..184f810 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Traits/Creator.php @@ -0,0 +1,974 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use Carbon\Carbon; +use Carbon\CarbonImmutable; +use Carbon\CarbonInterface; +use Carbon\Exceptions\InvalidDateException; +use Carbon\Exceptions\InvalidFormatException; +use Carbon\Exceptions\OutOfRangeException; +use Carbon\Translator; +use Closure; +use DateMalformedStringException; +use DateTimeInterface; +use DateTimeZone; +use Exception; +use ReturnTypeWillChange; + +/** + * Trait Creator. + * + * Static creators. + * + * Depends on the following methods: + * + * @method static Carbon|CarbonImmutable getTestNow() + */ +trait Creator +{ + use ObjectInitialisation; + + /** + * The errors that can occur. + * + * @var array + */ + protected static $lastErrors; + + /** + * 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 DateTimeInterface|string|null $time + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + */ + public function __construct($time = null, $tz = null) + { + if ($time instanceof DateTimeInterface) { + $time = $this->constructTimezoneFromDateTime($time, $tz)->format('Y-m-d H:i:s.u'); + } + + if (is_numeric($time) && (!\is_string($time) || !preg_match('/^\d{1,14}$/', $time))) { + $time = static::createFromTimestampUTC($time)->format('Y-m-d\TH:i:s.uP'); + } + + // 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 (method_exists(static::class, 'hasTestNow') && + method_exists(static::class, 'getTestNow') && + static::hasTestNow() && + ($isNow || static::hasRelativeKeywords($time)) + ) { + static::mockConstructorParameters($time, $tz); + } + + // Work-around for PHP bug https://bugs.php.net/bug.php?id=67127 + if (!str_contains((string) .1, '.')) { + $locale = setlocale(LC_NUMERIC, '0'); // @codeCoverageIgnore + setlocale(LC_NUMERIC, 'C'); // @codeCoverageIgnore + } + + try { + parent::__construct($time ?: 'now', static::safeCreateDateTimeZone($tz) ?: null); + } catch (Exception $exception) { + throw new InvalidFormatException($exception->getMessage(), 0, $exception); + } + + $this->constructedObjectId = spl_object_hash($this); + + if (isset($locale)) { + setlocale(LC_NUMERIC, $locale); // @codeCoverageIgnore + } + + self::setLastErrors(parent::getLastErrors()); + } + + /** + * Get timezone from a datetime instance. + * + * @param DateTimeInterface $date + * @param DateTimeZone|string|null $tz + * + * @return DateTimeInterface + */ + private function constructTimezoneFromDateTime(DateTimeInterface $date, &$tz) + { + if ($tz !== null) { + $safeTz = static::safeCreateDateTimeZone($tz); + + if ($safeTz) { + return $date->setTimezone($safeTz); + } + + return $date; + } + + $tz = $date->getTimezone(); + + return $date; + } + + /** + * Update constructedObjectId on cloned. + */ + public function __clone() + { + $this->constructedObjectId = spl_object_hash($this); + } + + /** + * Create a Carbon instance from a DateTime one. + * + * @param DateTimeInterface $date + * + * @return static + */ + public static function instance($date) + { + if ($date instanceof static) { + return clone $date; + } + + static::expectDateTime($date); + + $instance = new static($date->format('Y-m-d H:i:s.u'), $date->getTimezone()); + + if ($date instanceof CarbonInterface) { + $settings = $date->getSettings(); + + if (!$date->hasLocalTranslator()) { + unset($settings['locale']); + } + + $instance->settings($settings); + } + + return $instance; + } + + /** + * 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|DateTimeInterface|null $time + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + * + * @return static + */ + public static function rawParse($time = null, $tz = null) + { + if ($time instanceof DateTimeInterface) { + return static::instance($time); + } + + try { + return new static($time, $tz); + } catch (Exception $exception) { + // @codeCoverageIgnoreStart + try { + $date = @static::now($tz)->change($time); + } catch (DateMalformedStringException $ignoredException) { + $date = null; + } + // @codeCoverageIgnoreEnd + + if (!$date) { + throw new InvalidFormatException("Could not parse '$time': ".$exception->getMessage(), 0, $exception); + } + + return $date; + } + } + + /** + * 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|DateTimeInterface|null $time + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + * + * @return static + */ + public static function parse($time = null, $tz = null) + { + $function = static::$parseFunction; + + if (!$function) { + return static::rawParse($time, $tz); + } + + if (\is_string($function) && method_exists(static::class, $function)) { + $function = [static::class, $function]; + } + + return $function(...\func_get_args()); + } + + /** + * Create a carbon instance from a localized string (in French, Japanese, Arabic, etc.). + * + * @param string $time date/time string in the given language (may also contain English). + * @param string|null $locale if locale is null or not specified, current global locale will be + * used instead. + * @param DateTimeZone|string|null $tz optional timezone for the new instance. + * + * @throws InvalidFormatException + * + * @return static + */ + public static function parseFromLocale($time, $locale = null, $tz = null) + { + return static::rawParse(static::translateTimeString($time, $locale, 'en'), $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::rawParse('today', $tz); + } + + /** + * Create a Carbon instance for tomorrow. + * + * @param DateTimeZone|string|null $tz + * + * @return static + */ + public static function tomorrow($tz = null) + { + return static::rawParse('tomorrow', $tz); + } + + /** + * Create a Carbon instance for yesterday. + * + * @param DateTimeZone|string|null $tz + * + * @return static + */ + public static function yesterday($tz = null) + { + return static::rawParse('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); + } + + private static function assertBetween($unit, $value, $min, $max) + { + if (static::isStrictModeEnabled() && ($value < $min || $value > $max)) { + throw new OutOfRangeException($unit, $min, $max, $value); + } + } + + private static function createNowInstance($tz) + { + if (!static::hasTestNow()) { + return static::now($tz); + } + + $now = static::getTestNow(); + + if ($now instanceof Closure) { + return $now(static::now($tz)); + } + + return $now->avoidMutation()->tz($tz); + } + + /** + * 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 DateTimeInterface|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 InvalidFormatException + * + * @return static|false + */ + public static function create($year = 0, $month = 1, $day = 1, $hour = 0, $minute = 0, $second = 0, $tz = null) + { + if ((\is_string($year) && !is_numeric($year)) || $year instanceof DateTimeInterface) { + return static::parse($year, $tz ?: (\is_string($month) || $month instanceof DateTimeZone ? $month : null)); + } + + $defaults = null; + $getDefault = function ($unit) use ($tz, &$defaults) { + if ($defaults === null) { + $now = self::createNowInstance($tz); + + $defaults = array_combine([ + 'year', + 'month', + 'day', + 'hour', + 'minute', + 'second', + ], explode('-', $now->rawFormat('Y-n-j-G-i-s.u'))); + } + + return $defaults[$unit]; + }; + + $year = $year ?? $getDefault('year'); + $month = $month ?? $getDefault('month'); + $day = $day ?? $getDefault('day'); + $hour = $hour ?? $getDefault('hour'); + $minute = $minute ?? $getDefault('minute'); + $second = (float) ($second ?? $getDefault('second')); + + self::assertBetween('month', $month, 0, 99); + self::assertBetween('day', $day, 0, 99); + self::assertBetween('hour', $hour, 0, 99); + self::assertBetween('minute', $minute, 0, 99); + self::assertBetween('second', $second, 0, 99); + + $fixYear = null; + + if ($year < 0) { + $fixYear = $year; + $year = 0; + } elseif ($year > 9999) { + $fixYear = $year - 9999; + $year = 9999; + } + + $second = ($second < 10 ? '0' : '').number_format($second, 6); + $instance = static::rawCreateFromFormat('!Y-n-j G:i:s.u', sprintf('%s-%s-%s %s:%02s:%02s', $year, $month, $day, $hour, $minute, $second), $tz); + + if ($fixYear !== null) { + $instance = $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 InvalidDateException + * 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 InvalidDateException + * + * @return static|false + */ + public static function createSafe($year = null, $month = null, $day = null, $hour = null, $minute = null, $second = null, $tz = null) + { + $fields = static::getRangesByUnit(); + + foreach ($fields as $field => $range) { + if ($$field !== null && (!\is_int($$field) || $$field < $range[0] || $$field > $range[1])) { + if (static::isStrictModeEnabled()) { + throw new InvalidDateException($field, $$field); + } + + return false; + } + } + + $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)) { + if (static::isStrictModeEnabled()) { + throw new InvalidDateException($field, $$field); + } + + return false; + } + } + + return $instance; + } + + /** + * Create a new Carbon instance from a specific date and time using strict validation. + * + * @see create() + * + * @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 InvalidFormatException + * + * @return static + */ + public static function createStrict(?int $year = 0, ?int $month = 1, ?int $day = 1, ?int $hour = 0, ?int $minute = 0, ?int $second = 0, $tz = null): self + { + $initialStrictMode = static::isStrictModeEnabled(); + static::useStrictMode(true); + + try { + $date = static::create($year, $month, $day, $hour, $minute, $second, $tz); + } finally { + static::useStrictMode($initialStrictMode); + } + + return $date; + } + + /** + * 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 InvalidFormatException + * + * @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 + * + * @throws InvalidFormatException + * + * @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 InvalidFormatException + * + * @return static + */ + public static function createFromTime($hour = 0, $minute = 0, $second = 0, $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 InvalidFormatException + * + * @return static + */ + public static function createFromTimeString($time, $tz = null) + { + return static::today($tz)->setTimeFromTimeString($time); + } + + /** + * @param string $format Datetime format + * @param string $time + * @param DateTimeZone|string|false|null $originalTz + * + * @return DateTimeInterface|false + */ + private static function createFromFormatAndTimezone($format, $time, $originalTz) + { + // Work-around for https://bugs.php.net/bug.php?id=75577 + // @codeCoverageIgnoreStart + if (version_compare(PHP_VERSION, '7.3.0-dev', '<')) { + $format = str_replace('.v', '.u', $format); + } + // @codeCoverageIgnoreEnd + + if ($originalTz === null) { + return parent::createFromFormat($format, (string) $time); + } + + $tz = \is_int($originalTz) + ? @timezone_name_from_abbr('', (int) ($originalTz * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE), 1) + : $originalTz; + + $tz = static::safeCreateDateTimeZone($tz, $originalTz); + + if ($tz === false) { + return false; + } + + return parent::createFromFormat($format, (string) $time, $tz); + } + + /** + * Create a Carbon instance from a specific format. + * + * @param string $format Datetime format + * @param string $time + * @param DateTimeZone|string|false|null $tz + * + * @throws InvalidFormatException + * + * @return static|false + */ + public static function rawCreateFromFormat($format, $time, $tz = null) + { + // Work-around for https://bugs.php.net/bug.php?id=80141 + $format = preg_replace('/(?getTimezone(); + } + + // Set microseconds to zero to match behavior of DateTime::createFromFormat() + // See https://bugs.php.net/bug.php?id=74332 + $mock = $mock->copy()->microsecond(0); + + // 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 instanceof self ? $mock->rawFormat(static::MOCK_DATETIME_FORMAT) : $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 DateTimeInterface) { + $instance = static::instance($date); + $instance::setLastErrors($lastErrors); + + return $instance; + } + + if (static::isStrictModeEnabled()) { + throw new InvalidFormatException(implode(PHP_EOL, $lastErrors['errors'])); + } + + return false; + } + + /** + * Create a Carbon instance from a specific format. + * + * @param string $format Datetime format + * @param string $time + * @param DateTimeZone|string|false|null $tz + * + * @throws InvalidFormatException + * + * @return static|false + */ + #[ReturnTypeWillChange] + public static function createFromFormat($format, $time, $tz = null) + { + $function = static::$createFromFormatFunction; + + if (!$function) { + return static::rawCreateFromFormat($format, $time, $tz); + } + + if (\is_string($function) && method_exists(static::class, $function)) { + $function = [static::class, $function]; + } + + return $function(...\func_get_args()); + } + + /** + * Create a Carbon instance from a specific ISO format (same replacements as ->isoFormat()). + * + * @param string $format Datetime format + * @param string $time + * @param DateTimeZone|string|false|null $tz optional timezone + * @param string|null $locale locale to be used for LTS, LT, LL, LLL, etc. macro-formats (en by fault, unneeded if no such macro-format in use) + * @param \Symfony\Component\Translation\TranslatorInterface $translator optional custom translator to use for macro-formats + * + * @throws InvalidFormatException + * + * @return static|false + */ + public static function createFromIsoFormat($format, $time, $tz = null, $locale = 'en', $translator = null) + { + $format = preg_replace_callback('/(? static::getTranslationMessageWith($translator, 'formats.LT', $locale, 'h:mm A'), + 'LTS' => static::getTranslationMessageWith($translator, 'formats.LTS', $locale, 'h:mm:ss A'), + 'L' => static::getTranslationMessageWith($translator, 'formats.L', $locale, 'MM/DD/YYYY'), + 'LL' => static::getTranslationMessageWith($translator, 'formats.LL', $locale, 'MMMM D, YYYY'), + 'LLL' => static::getTranslationMessageWith($translator, 'formats.LLL', $locale, 'MMMM D, YYYY h:mm A'), + 'LLLL' => static::getTranslationMessageWith($translator, 'formats.LLLL', $locale, 'dddd, MMMM D, YYYY h:mm A'), + ]; + } + + return $formats[$code] ?? preg_replace_callback( + '/MMMM|MM|DD|dddd/', + function ($code) { + return mb_substr($code[0], 1); + }, + $formats[strtoupper($code)] ?? '' + ); + }, $format); + + $format = preg_replace_callback('/(? 'd', + 'OM' => 'M', + 'OY' => 'Y', + 'OH' => 'G', + 'Oh' => 'g', + 'Om' => 'i', + 'Os' => 's', + 'D' => 'd', + 'DD' => 'd', + 'Do' => 'd', + 'd' => '!', + 'dd' => '!', + 'ddd' => 'D', + 'dddd' => 'D', + 'DDD' => 'z', + 'DDDD' => 'z', + 'DDDo' => 'z', + 'e' => '!', + 'E' => '!', + 'H' => 'G', + 'HH' => 'H', + 'h' => 'g', + 'hh' => 'h', + 'k' => 'G', + 'kk' => 'G', + 'hmm' => 'gi', + 'hmmss' => 'gis', + 'Hmm' => 'Gi', + 'Hmmss' => 'Gis', + 'm' => 'i', + 'mm' => 'i', + 'a' => 'a', + 'A' => 'a', + 's' => 's', + 'ss' => 's', + 'S' => '*', + 'SS' => '*', + 'SSS' => '*', + 'SSSS' => '*', + 'SSSSS' => '*', + 'SSSSSS' => 'u', + 'SSSSSSS' => 'u*', + 'SSSSSSSS' => 'u*', + 'SSSSSSSSS' => 'u*', + 'M' => 'm', + 'MM' => 'm', + 'MMM' => 'M', + 'MMMM' => 'M', + 'Mo' => 'm', + 'Q' => '!', + 'Qo' => '!', + 'G' => '!', + 'GG' => '!', + 'GGG' => '!', + 'GGGG' => '!', + 'GGGGG' => '!', + 'g' => '!', + 'gg' => '!', + 'ggg' => '!', + 'gggg' => '!', + 'ggggg' => '!', + 'W' => '!', + 'WW' => '!', + 'Wo' => '!', + 'w' => '!', + 'ww' => '!', + 'wo' => '!', + 'x' => 'U???', + 'X' => 'U', + 'Y' => 'Y', + 'YY' => 'y', + 'YYYY' => 'Y', + 'YYYYY' => 'Y', + 'YYYYYY' => 'Y', + 'z' => 'e', + 'zz' => 'e', + 'Z' => 'e', + 'ZZ' => 'e', + ]; + } + + $format = $replacements[$code] ?? '?'; + + if ($format === '!') { + throw new InvalidFormatException("Format $code not supported for creation."); + } + + return $format; + }, $format); + + return static::rawCreateFromFormat($format, $time, $tz); + } + + /** + * Create a Carbon instance from a specific format and a string in a given language. + * + * @param string $format Datetime format + * @param string $locale + * @param string $time + * @param DateTimeZone|string|false|null $tz + * + * @throws InvalidFormatException + * + * @return static|false + */ + public static function createFromLocaleFormat($format, $locale, $time, $tz = null) + { + $format = preg_replace_callback( + '/(?:\\\\[a-zA-Z]|[bfkqCEJKQRV]){2,}/', + static function (array $match) use ($locale): string { + $word = str_replace('\\', '', $match[0]); + $translatedWord = static::translateTimeString($word, $locale, 'en'); + + return $word === $translatedWord + ? $match[0] + : preg_replace('/[a-zA-Z]/', '\\\\$0', $translatedWord); + }, + $format + ); + + return static::rawCreateFromFormat($format, static::translateTimeString($time, $locale, 'en'), $tz); + } + + /** + * Create a Carbon instance from a specific ISO format and a string in a given language. + * + * @param string $format Datetime ISO format + * @param string $locale + * @param string $time + * @param DateTimeZone|string|false|null $tz + * + * @throws InvalidFormatException + * + * @return static|false + */ + public static function createFromLocaleIsoFormat($format, $locale, $time, $tz = null) + { + $time = static::translateTimeString($time, $locale, 'en', CarbonInterface::TRANSLATE_MONTHS | CarbonInterface::TRANSLATE_DAYS | CarbonInterface::TRANSLATE_MERIDIEM); + + return static::createFromIsoFormat($format, $time, $tz, $locale); + } + + /** + * 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 + * + * @throws InvalidFormatException + * + * @return static|null + */ + public static function make($var) + { + if ($var instanceof DateTimeInterface) { + return static::instance($var); + } + + $date = null; + + if (\is_string($var)) { + $var = trim($var); + + if (!preg_match('/^P[\dT]/', $var) && + !preg_match('/^R\d/', $var) && + preg_match('/[a-z\d]/i', $var) + ) { + $date = static::parse($var); + } + } + + return $date; + } + + /** + * Set last errors. + * + * @param array|bool $lastErrors + * + * @return void + */ + private static function setLastErrors($lastErrors) + { + if (\is_array($lastErrors) || $lastErrors === false) { + static::$lastErrors = \is_array($lastErrors) ? $lastErrors : [ + 'warning_count' => 0, + 'warnings' => [], + 'error_count' => 0, + 'errors' => [], + ]; + } + } + + /** + * {@inheritdoc} + * + * @return array + */ + #[ReturnTypeWillChange] + public static function getLastErrors() + { + return static::$lastErrors; + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Traits/Date.php b/vendor/nesbot/carbon/src/Carbon/Traits/Date.php new file mode 100644 index 0000000..da3775c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Traits/Date.php @@ -0,0 +1,2746 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use BadMethodCallException; +use Carbon\Carbon; +use Carbon\CarbonInterface; +use Carbon\CarbonPeriod; +use Carbon\CarbonTimeZone; +use Carbon\Exceptions\BadComparisonUnitException; +use Carbon\Exceptions\ImmutableException; +use Carbon\Exceptions\InvalidTimeZoneException; +use Carbon\Exceptions\InvalidTypeException; +use Carbon\Exceptions\UnknownGetterException; +use Carbon\Exceptions\UnknownMethodException; +use Carbon\Exceptions\UnknownSetterException; +use Carbon\Exceptions\UnknownUnitException; +use Closure; +use DateInterval; +use DatePeriod; +use DateTime; +use DateTimeImmutable; +use DateTimeInterface; +use DateTimeZone; +use InvalidArgumentException; +use ReflectionException; +use ReturnTypeWillChange; +use Throwable; + +/** + * A simple API extension for DateTime. + * + * @mixin DeprecatedProperties + * + * + * + * @property int $year + * @property int $yearIso + * @property int $month + * @property int $day + * @property int $hour + * @property int $minute + * @property int $second + * @property int $micro + * @property int $microsecond + * @property int|float|string $timestamp seconds since the Unix Epoch + * @property string $englishDayOfWeek the day of week in English + * @property string $shortEnglishDayOfWeek the abbreviated day of week in English + * @property string $englishMonth the month in English + * @property string $shortEnglishMonth the abbreviated month in English + * @property int $milliseconds + * @property int $millisecond + * @property int $milli + * @property int $week 1 through 53 + * @property int $isoWeek 1 through 53 + * @property int $weekYear year according to week format + * @property int $isoWeekYear year according to ISO week format + * @property int $dayOfYear 1 through 366 + * @property int $age does a diffInYears() with default parameters + * @property int $offset the timezone offset in seconds from UTC + * @property int $offsetMinutes the timezone offset in minutes from UTC + * @property int $offsetHours the timezone offset in hours from UTC + * @property CarbonTimeZone $timezone the current timezone + * @property CarbonTimeZone $tz alias of $timezone + * @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 $weekOfYear ISO-8601 week number of year, weeks starting on Monday + * @property-read int $daysInMonth number of days in the given month + * @property-read string $latinMeridiem "am"/"pm" (Ante meridiem or Post meridiem latin lowercase mark) + * @property-read string $latinUpperMeridiem "AM"/"PM" (Ante meridiem or Post meridiem latin uppercase mark) + * @property-read string $timezoneAbbreviatedName the current timezone abbreviated name + * @property-read string $tzAbbrName alias of $timezoneAbbreviatedName + * @property-read string $dayName long name of weekday translated according to Carbon locale, in english if no translation available for current language + * @property-read string $shortDayName short name of weekday translated according to Carbon locale, in english if no translation available for current language + * @property-read string $minDayName very short name of weekday translated according to Carbon locale, in english if no translation available for current language + * @property-read string $monthName long name of month translated according to Carbon locale, in english if no translation available for current language + * @property-read string $shortMonthName short name of month translated according to Carbon locale, in english if no translation available for current language + * @property-read string $meridiem lowercase meridiem mark translated according to Carbon locale, in latin if no translation available for current language + * @property-read string $upperMeridiem uppercase meridiem mark translated according to Carbon locale, in latin if no translation available for current language + * @property-read int $noZeroHour current hour from 1 to 24 + * @property-read int $weeksInYear 51 through 53 + * @property-read int $isoWeeksInYear 51 through 53 + * @property-read int $weekOfMonth 1 through 5 + * @property-read int $weekNumberInMonth 1 through 5 + * @property-read int $firstWeekDay 0 through 6 + * @property-read int $lastWeekDay 0 through 6 + * @property-read int $daysInYear 365 or 366 + * @property-read int $quarter the quarter of this instance, 1 - 4 + * @property-read int $decade the decade of this instance + * @property-read int $century the century of this instance + * @property-read int $millennium the millennium of this instance + * @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 the current timezone name + * @property-read string $tzName alias of $timezoneName + * @property-read string $locale locale of the current instance + * + * @method bool isUtc() Check if the current instance has UTC timezone. (Both isUtc and isUTC cases are valid.) + * @method bool isLocal() Check if the current instance has non-UTC timezone. + * @method bool isValid() Check if the current instance is a valid date. + * @method bool isDST() Check if the current instance is in a daylight saving time. + * @method bool isSunday() Checks if the instance day is sunday. + * @method bool isMonday() Checks if the instance day is monday. + * @method bool isTuesday() Checks if the instance day is tuesday. + * @method bool isWednesday() Checks if the instance day is wednesday. + * @method bool isThursday() Checks if the instance day is thursday. + * @method bool isFriday() Checks if the instance day is friday. + * @method bool isSaturday() Checks if the instance day is saturday. + * @method bool isSameYear(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same year as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentYear() Checks if the instance is in the same year as the current moment. + * @method bool isNextYear() Checks if the instance is in the same year as the current moment next year. + * @method bool isLastYear() Checks if the instance is in the same year as the current moment last year. + * @method bool isSameWeek(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same week as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentWeek() Checks if the instance is in the same week as the current moment. + * @method bool isNextWeek() Checks if the instance is in the same week as the current moment next week. + * @method bool isLastWeek() Checks if the instance is in the same week as the current moment last week. + * @method bool isSameDay(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same day as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentDay() Checks if the instance is in the same day as the current moment. + * @method bool isNextDay() Checks if the instance is in the same day as the current moment next day. + * @method bool isLastDay() Checks if the instance is in the same day as the current moment last day. + * @method bool isSameHour(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same hour as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentHour() Checks if the instance is in the same hour as the current moment. + * @method bool isNextHour() Checks if the instance is in the same hour as the current moment next hour. + * @method bool isLastHour() Checks if the instance is in the same hour as the current moment last hour. + * @method bool isSameMinute(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same minute as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMinute() Checks if the instance is in the same minute as the current moment. + * @method bool isNextMinute() Checks if the instance is in the same minute as the current moment next minute. + * @method bool isLastMinute() Checks if the instance is in the same minute as the current moment last minute. + * @method bool isSameSecond(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same second as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentSecond() Checks if the instance is in the same second as the current moment. + * @method bool isNextSecond() Checks if the instance is in the same second as the current moment next second. + * @method bool isLastSecond() Checks if the instance is in the same second as the current moment last second. + * @method bool isSameMicro(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same microsecond as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMicro() Checks if the instance is in the same microsecond as the current moment. + * @method bool isNextMicro() Checks if the instance is in the same microsecond as the current moment next microsecond. + * @method bool isLastMicro() Checks if the instance is in the same microsecond as the current moment last microsecond. + * @method bool isSameMicrosecond(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same microsecond as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMicrosecond() Checks if the instance is in the same microsecond as the current moment. + * @method bool isNextMicrosecond() Checks if the instance is in the same microsecond as the current moment next microsecond. + * @method bool isLastMicrosecond() Checks if the instance is in the same microsecond as the current moment last microsecond. + * @method bool isCurrentMonth() Checks if the instance is in the same month as the current moment. + * @method bool isNextMonth() Checks if the instance is in the same month as the current moment next month. + * @method bool isLastMonth() Checks if the instance is in the same month as the current moment last month. + * @method bool isCurrentQuarter() Checks if the instance is in the same quarter as the current moment. + * @method bool isNextQuarter() Checks if the instance is in the same quarter as the current moment next quarter. + * @method bool isLastQuarter() Checks if the instance is in the same quarter as the current moment last quarter. + * @method bool isSameDecade(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same decade as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentDecade() Checks if the instance is in the same decade as the current moment. + * @method bool isNextDecade() Checks if the instance is in the same decade as the current moment next decade. + * @method bool isLastDecade() Checks if the instance is in the same decade as the current moment last decade. + * @method bool isSameCentury(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same century as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentCentury() Checks if the instance is in the same century as the current moment. + * @method bool isNextCentury() Checks if the instance is in the same century as the current moment next century. + * @method bool isLastCentury() Checks if the instance is in the same century as the current moment last century. + * @method bool isSameMillennium(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same millennium as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMillennium() Checks if the instance is in the same millennium as the current moment. + * @method bool isNextMillennium() Checks if the instance is in the same millennium as the current moment next millennium. + * @method bool isLastMillennium() Checks if the instance is in the same millennium as the current moment last millennium. + * @method CarbonInterface years(int $value) Set current instance year to the given value. + * @method CarbonInterface year(int $value) Set current instance year to the given value. + * @method CarbonInterface setYears(int $value) Set current instance year to the given value. + * @method CarbonInterface setYear(int $value) Set current instance year to the given value. + * @method CarbonInterface months(int $value) Set current instance month to the given value. + * @method CarbonInterface month(int $value) Set current instance month to the given value. + * @method CarbonInterface setMonths(int $value) Set current instance month to the given value. + * @method CarbonInterface setMonth(int $value) Set current instance month to the given value. + * @method CarbonInterface days(int $value) Set current instance day to the given value. + * @method CarbonInterface day(int $value) Set current instance day to the given value. + * @method CarbonInterface setDays(int $value) Set current instance day to the given value. + * @method CarbonInterface setDay(int $value) Set current instance day to the given value. + * @method CarbonInterface hours(int $value) Set current instance hour to the given value. + * @method CarbonInterface hour(int $value) Set current instance hour to the given value. + * @method CarbonInterface setHours(int $value) Set current instance hour to the given value. + * @method CarbonInterface setHour(int $value) Set current instance hour to the given value. + * @method CarbonInterface minutes(int $value) Set current instance minute to the given value. + * @method CarbonInterface minute(int $value) Set current instance minute to the given value. + * @method CarbonInterface setMinutes(int $value) Set current instance minute to the given value. + * @method CarbonInterface setMinute(int $value) Set current instance minute to the given value. + * @method CarbonInterface seconds(int $value) Set current instance second to the given value. + * @method CarbonInterface second(int $value) Set current instance second to the given value. + * @method CarbonInterface setSeconds(int $value) Set current instance second to the given value. + * @method CarbonInterface setSecond(int $value) Set current instance second to the given value. + * @method CarbonInterface millis(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface milli(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface setMillis(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface setMilli(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface milliseconds(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface millisecond(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface setMilliseconds(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface setMillisecond(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface micros(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface micro(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface setMicros(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface setMicro(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface microseconds(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface microsecond(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface setMicroseconds(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface setMicrosecond(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface addYears(int $value = 1) Add years (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addYear() Add one year to the instance (using date interval). + * @method CarbonInterface subYears(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subYear() Sub one year to the instance (using date interval). + * @method CarbonInterface addYearsWithOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addYearWithOverflow() Add one year to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subYearsWithOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subYearWithOverflow() Sub one year to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addYearsWithoutOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addYearWithoutOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearsWithoutOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearWithoutOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addYearsWithNoOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addYearWithNoOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearsWithNoOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearWithNoOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addYearsNoOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addYearNoOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearsNoOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearNoOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonths(int $value = 1) Add months (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMonth() Add one month to the instance (using date interval). + * @method CarbonInterface subMonths(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMonth() Sub one month to the instance (using date interval). + * @method CarbonInterface addMonthsWithOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addMonthWithOverflow() Add one month to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subMonthsWithOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subMonthWithOverflow() Sub one month to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addMonthsWithoutOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonthWithoutOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthsWithoutOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthWithoutOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonthsWithNoOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonthWithNoOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthsWithNoOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthWithNoOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonthsNoOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonthNoOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthsNoOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthNoOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDays(int $value = 1) Add days (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addDay() Add one day to the instance (using date interval). + * @method CarbonInterface subDays(int $value = 1) Sub days (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subDay() Sub one day to the instance (using date interval). + * @method CarbonInterface addHours(int $value = 1) Add hours (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addHour() Add one hour to the instance (using date interval). + * @method CarbonInterface subHours(int $value = 1) Sub hours (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subHour() Sub one hour to the instance (using date interval). + * @method CarbonInterface addMinutes(int $value = 1) Add minutes (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMinute() Add one minute to the instance (using date interval). + * @method CarbonInterface subMinutes(int $value = 1) Sub minutes (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMinute() Sub one minute to the instance (using date interval). + * @method CarbonInterface addSeconds(int $value = 1) Add seconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addSecond() Add one second to the instance (using date interval). + * @method CarbonInterface subSeconds(int $value = 1) Sub seconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subSecond() Sub one second to the instance (using date interval). + * @method CarbonInterface addMillis(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMilli() Add one millisecond to the instance (using date interval). + * @method CarbonInterface subMillis(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMilli() Sub one millisecond to the instance (using date interval). + * @method CarbonInterface addMilliseconds(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMillisecond() Add one millisecond to the instance (using date interval). + * @method CarbonInterface subMilliseconds(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMillisecond() Sub one millisecond to the instance (using date interval). + * @method CarbonInterface addMicros(int $value = 1) Add microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMicro() Add one microsecond to the instance (using date interval). + * @method CarbonInterface subMicros(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMicro() Sub one microsecond to the instance (using date interval). + * @method CarbonInterface addMicroseconds(int $value = 1) Add microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMicrosecond() Add one microsecond to the instance (using date interval). + * @method CarbonInterface subMicroseconds(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMicrosecond() Sub one microsecond to the instance (using date interval). + * @method CarbonInterface addMillennia(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMillennium() Add one millennium to the instance (using date interval). + * @method CarbonInterface subMillennia(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMillennium() Sub one millennium to the instance (using date interval). + * @method CarbonInterface addMillenniaWithOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addMillenniumWithOverflow() Add one millennium to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subMillenniaWithOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subMillenniumWithOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addMillenniaWithoutOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMillenniumWithoutOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniaWithoutOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniumWithoutOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMillenniaWithNoOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMillenniumWithNoOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniaWithNoOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniumWithNoOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMillenniaNoOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMillenniumNoOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniaNoOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniumNoOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturies(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addCentury() Add one century to the instance (using date interval). + * @method CarbonInterface subCenturies(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subCentury() Sub one century to the instance (using date interval). + * @method CarbonInterface addCenturiesWithOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addCenturyWithOverflow() Add one century to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subCenturiesWithOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subCenturyWithOverflow() Sub one century to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addCenturiesWithoutOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturyWithoutOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturiesWithoutOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturyWithoutOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturiesWithNoOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturyWithNoOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturiesWithNoOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturyWithNoOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturiesNoOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturyNoOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturiesNoOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturyNoOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecades(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addDecade() Add one decade to the instance (using date interval). + * @method CarbonInterface subDecades(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subDecade() Sub one decade to the instance (using date interval). + * @method CarbonInterface addDecadesWithOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addDecadeWithOverflow() Add one decade to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subDecadesWithOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subDecadeWithOverflow() Sub one decade to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addDecadesWithoutOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecadeWithoutOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadesWithoutOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadeWithoutOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecadesWithNoOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecadeWithNoOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadesWithNoOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadeWithNoOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecadesNoOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecadeNoOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadesNoOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadeNoOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuarters(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addQuarter() Add one quarter to the instance (using date interval). + * @method CarbonInterface subQuarters(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subQuarter() Sub one quarter to the instance (using date interval). + * @method CarbonInterface addQuartersWithOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addQuarterWithOverflow() Add one quarter to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subQuartersWithOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subQuarterWithOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addQuartersWithoutOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuarterWithoutOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuartersWithoutOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuarterWithoutOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuartersWithNoOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuarterWithNoOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuartersWithNoOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuarterWithNoOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuartersNoOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuarterNoOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuartersNoOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuarterNoOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addWeeks(int $value = 1) Add weeks (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addWeek() Add one week to the instance (using date interval). + * @method CarbonInterface subWeeks(int $value = 1) Sub weeks (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subWeek() Sub one week to the instance (using date interval). + * @method CarbonInterface addWeekdays(int $value = 1) Add weekdays (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addWeekday() Add one weekday to the instance (using date interval). + * @method CarbonInterface subWeekdays(int $value = 1) Sub weekdays (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subWeekday() Sub one weekday to the instance (using date interval). + * @method CarbonInterface addRealMicros(int $value = 1) Add microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealMicro() Add one microsecond to the instance (using timestamp). + * @method CarbonInterface subRealMicros(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealMicro() Sub one microsecond to the instance (using timestamp). + * @method CarbonPeriod microsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each microsecond or every X microseconds if a factor is given. + * @method CarbonInterface addRealMicroseconds(int $value = 1) Add microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealMicrosecond() Add one microsecond to the instance (using timestamp). + * @method CarbonInterface subRealMicroseconds(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealMicrosecond() Sub one microsecond to the instance (using timestamp). + * @method CarbonPeriod microsecondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each microsecond or every X microseconds if a factor is given. + * @method CarbonInterface addRealMillis(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealMilli() Add one millisecond to the instance (using timestamp). + * @method CarbonInterface subRealMillis(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealMilli() Sub one millisecond to the instance (using timestamp). + * @method CarbonPeriod millisUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millisecond or every X milliseconds if a factor is given. + * @method CarbonInterface addRealMilliseconds(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealMillisecond() Add one millisecond to the instance (using timestamp). + * @method CarbonInterface subRealMilliseconds(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealMillisecond() Sub one millisecond to the instance (using timestamp). + * @method CarbonPeriod millisecondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millisecond or every X milliseconds if a factor is given. + * @method CarbonInterface addRealSeconds(int $value = 1) Add seconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealSecond() Add one second to the instance (using timestamp). + * @method CarbonInterface subRealSeconds(int $value = 1) Sub seconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealSecond() Sub one second to the instance (using timestamp). + * @method CarbonPeriod secondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each second or every X seconds if a factor is given. + * @method CarbonInterface addRealMinutes(int $value = 1) Add minutes (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealMinute() Add one minute to the instance (using timestamp). + * @method CarbonInterface subRealMinutes(int $value = 1) Sub minutes (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealMinute() Sub one minute to the instance (using timestamp). + * @method CarbonPeriod minutesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each minute or every X minutes if a factor is given. + * @method CarbonInterface addRealHours(int $value = 1) Add hours (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealHour() Add one hour to the instance (using timestamp). + * @method CarbonInterface subRealHours(int $value = 1) Sub hours (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealHour() Sub one hour to the instance (using timestamp). + * @method CarbonPeriod hoursUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each hour or every X hours if a factor is given. + * @method CarbonInterface addRealDays(int $value = 1) Add days (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealDay() Add one day to the instance (using timestamp). + * @method CarbonInterface subRealDays(int $value = 1) Sub days (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealDay() Sub one day to the instance (using timestamp). + * @method CarbonPeriod daysUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each day or every X days if a factor is given. + * @method CarbonInterface addRealWeeks(int $value = 1) Add weeks (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealWeek() Add one week to the instance (using timestamp). + * @method CarbonInterface subRealWeeks(int $value = 1) Sub weeks (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealWeek() Sub one week to the instance (using timestamp). + * @method CarbonPeriod weeksUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each week or every X weeks if a factor is given. + * @method CarbonInterface addRealMonths(int $value = 1) Add months (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealMonth() Add one month to the instance (using timestamp). + * @method CarbonInterface subRealMonths(int $value = 1) Sub months (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealMonth() Sub one month to the instance (using timestamp). + * @method CarbonPeriod monthsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each month or every X months if a factor is given. + * @method CarbonInterface addRealQuarters(int $value = 1) Add quarters (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealQuarter() Add one quarter to the instance (using timestamp). + * @method CarbonInterface subRealQuarters(int $value = 1) Sub quarters (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealQuarter() Sub one quarter to the instance (using timestamp). + * @method CarbonPeriod quartersUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each quarter or every X quarters if a factor is given. + * @method CarbonInterface addRealYears(int $value = 1) Add years (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealYear() Add one year to the instance (using timestamp). + * @method CarbonInterface subRealYears(int $value = 1) Sub years (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealYear() Sub one year to the instance (using timestamp). + * @method CarbonPeriod yearsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each year or every X years if a factor is given. + * @method CarbonInterface addRealDecades(int $value = 1) Add decades (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealDecade() Add one decade to the instance (using timestamp). + * @method CarbonInterface subRealDecades(int $value = 1) Sub decades (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealDecade() Sub one decade to the instance (using timestamp). + * @method CarbonPeriod decadesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each decade or every X decades if a factor is given. + * @method CarbonInterface addRealCenturies(int $value = 1) Add centuries (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealCentury() Add one century to the instance (using timestamp). + * @method CarbonInterface subRealCenturies(int $value = 1) Sub centuries (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealCentury() Sub one century to the instance (using timestamp). + * @method CarbonPeriod centuriesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each century or every X centuries if a factor is given. + * @method CarbonInterface addRealMillennia(int $value = 1) Add millennia (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealMillennium() Add one millennium to the instance (using timestamp). + * @method CarbonInterface subRealMillennia(int $value = 1) Sub millennia (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealMillennium() Sub one millennium to the instance (using timestamp). + * @method CarbonPeriod millenniaUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millennium or every X millennia if a factor is given. + * @method CarbonInterface roundYear(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. + * @method CarbonInterface roundYears(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. + * @method CarbonInterface floorYear(float $precision = 1) Truncate the current instance year with given precision. + * @method CarbonInterface floorYears(float $precision = 1) Truncate the current instance year with given precision. + * @method CarbonInterface ceilYear(float $precision = 1) Ceil the current instance year with given precision. + * @method CarbonInterface ceilYears(float $precision = 1) Ceil the current instance year with given precision. + * @method CarbonInterface roundMonth(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. + * @method CarbonInterface roundMonths(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. + * @method CarbonInterface floorMonth(float $precision = 1) Truncate the current instance month with given precision. + * @method CarbonInterface floorMonths(float $precision = 1) Truncate the current instance month with given precision. + * @method CarbonInterface ceilMonth(float $precision = 1) Ceil the current instance month with given precision. + * @method CarbonInterface ceilMonths(float $precision = 1) Ceil the current instance month with given precision. + * @method CarbonInterface roundDay(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method CarbonInterface roundDays(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method CarbonInterface floorDay(float $precision = 1) Truncate the current instance day with given precision. + * @method CarbonInterface floorDays(float $precision = 1) Truncate the current instance day with given precision. + * @method CarbonInterface ceilDay(float $precision = 1) Ceil the current instance day with given precision. + * @method CarbonInterface ceilDays(float $precision = 1) Ceil the current instance day with given precision. + * @method CarbonInterface roundHour(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. + * @method CarbonInterface roundHours(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. + * @method CarbonInterface floorHour(float $precision = 1) Truncate the current instance hour with given precision. + * @method CarbonInterface floorHours(float $precision = 1) Truncate the current instance hour with given precision. + * @method CarbonInterface ceilHour(float $precision = 1) Ceil the current instance hour with given precision. + * @method CarbonInterface ceilHours(float $precision = 1) Ceil the current instance hour with given precision. + * @method CarbonInterface roundMinute(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. + * @method CarbonInterface roundMinutes(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. + * @method CarbonInterface floorMinute(float $precision = 1) Truncate the current instance minute with given precision. + * @method CarbonInterface floorMinutes(float $precision = 1) Truncate the current instance minute with given precision. + * @method CarbonInterface ceilMinute(float $precision = 1) Ceil the current instance minute with given precision. + * @method CarbonInterface ceilMinutes(float $precision = 1) Ceil the current instance minute with given precision. + * @method CarbonInterface roundSecond(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. + * @method CarbonInterface roundSeconds(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. + * @method CarbonInterface floorSecond(float $precision = 1) Truncate the current instance second with given precision. + * @method CarbonInterface floorSeconds(float $precision = 1) Truncate the current instance second with given precision. + * @method CarbonInterface ceilSecond(float $precision = 1) Ceil the current instance second with given precision. + * @method CarbonInterface ceilSeconds(float $precision = 1) Ceil the current instance second with given precision. + * @method CarbonInterface roundMillennium(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. + * @method CarbonInterface roundMillennia(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. + * @method CarbonInterface floorMillennium(float $precision = 1) Truncate the current instance millennium with given precision. + * @method CarbonInterface floorMillennia(float $precision = 1) Truncate the current instance millennium with given precision. + * @method CarbonInterface ceilMillennium(float $precision = 1) Ceil the current instance millennium with given precision. + * @method CarbonInterface ceilMillennia(float $precision = 1) Ceil the current instance millennium with given precision. + * @method CarbonInterface roundCentury(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. + * @method CarbonInterface roundCenturies(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. + * @method CarbonInterface floorCentury(float $precision = 1) Truncate the current instance century with given precision. + * @method CarbonInterface floorCenturies(float $precision = 1) Truncate the current instance century with given precision. + * @method CarbonInterface ceilCentury(float $precision = 1) Ceil the current instance century with given precision. + * @method CarbonInterface ceilCenturies(float $precision = 1) Ceil the current instance century with given precision. + * @method CarbonInterface roundDecade(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. + * @method CarbonInterface roundDecades(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. + * @method CarbonInterface floorDecade(float $precision = 1) Truncate the current instance decade with given precision. + * @method CarbonInterface floorDecades(float $precision = 1) Truncate the current instance decade with given precision. + * @method CarbonInterface ceilDecade(float $precision = 1) Ceil the current instance decade with given precision. + * @method CarbonInterface ceilDecades(float $precision = 1) Ceil the current instance decade with given precision. + * @method CarbonInterface roundQuarter(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. + * @method CarbonInterface roundQuarters(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. + * @method CarbonInterface floorQuarter(float $precision = 1) Truncate the current instance quarter with given precision. + * @method CarbonInterface floorQuarters(float $precision = 1) Truncate the current instance quarter with given precision. + * @method CarbonInterface ceilQuarter(float $precision = 1) Ceil the current instance quarter with given precision. + * @method CarbonInterface ceilQuarters(float $precision = 1) Ceil the current instance quarter with given precision. + * @method CarbonInterface roundMillisecond(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. + * @method CarbonInterface roundMilliseconds(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. + * @method CarbonInterface floorMillisecond(float $precision = 1) Truncate the current instance millisecond with given precision. + * @method CarbonInterface floorMilliseconds(float $precision = 1) Truncate the current instance millisecond with given precision. + * @method CarbonInterface ceilMillisecond(float $precision = 1) Ceil the current instance millisecond with given precision. + * @method CarbonInterface ceilMilliseconds(float $precision = 1) Ceil the current instance millisecond with given precision. + * @method CarbonInterface roundMicrosecond(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. + * @method CarbonInterface roundMicroseconds(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. + * @method CarbonInterface floorMicrosecond(float $precision = 1) Truncate the current instance microsecond with given precision. + * @method CarbonInterface floorMicroseconds(float $precision = 1) Truncate the current instance microsecond with given precision. + * @method CarbonInterface ceilMicrosecond(float $precision = 1) Ceil the current instance microsecond with given precision. + * @method CarbonInterface ceilMicroseconds(float $precision = 1) Ceil the current instance microsecond with given precision. + * @method string shortAbsoluteDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'Absolute' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longAbsoluteDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'Absolute' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'Relative' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'Relative' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeToNowDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'RelativeToNow' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeToNowDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'RelativeToNow' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeToOtherDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'RelativeToOther' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeToOtherDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'RelativeToOther' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * + * + */ +trait Date +{ + use Boundaries; + use Comparison; + use Converter; + use Creator; + use Difference; + use Macro; + use MagicParameter; + use Modifiers; + use Mutability; + use ObjectInitialisation; + use Options; + use Rounding; + use Serialization; + use Test; + use Timestamp; + use Units; + use Week; + + /** + * Names of days of the week. + * + * @var array + */ + protected static $days = [ + // @call isDayOfWeek + CarbonInterface::SUNDAY => 'Sunday', + // @call isDayOfWeek + CarbonInterface::MONDAY => 'Monday', + // @call isDayOfWeek + CarbonInterface::TUESDAY => 'Tuesday', + // @call isDayOfWeek + CarbonInterface::WEDNESDAY => 'Wednesday', + // @call isDayOfWeek + CarbonInterface::THURSDAY => 'Thursday', + // @call isDayOfWeek + CarbonInterface::FRIDAY => 'Friday', + // @call isDayOfWeek + CarbonInterface::SATURDAY => 'Saturday', + ]; + + /** + * Will UTF8 encoding be used to print localized date/time ? + * + * @var bool + */ + protected static $utf8 = false; + + /** + * List of unit and magic methods associated as doc-comments. + * + * @var array + */ + protected static $units = [ + // @call setUnit + // @call addUnit + 'year', + // @call setUnit + // @call addUnit + 'month', + // @call setUnit + // @call addUnit + 'day', + // @call setUnit + // @call addUnit + 'hour', + // @call setUnit + // @call addUnit + 'minute', + // @call setUnit + // @call addUnit + 'second', + // @call setUnit + // @call addUnit + 'milli', + // @call setUnit + // @call addUnit + 'millisecond', + // @call setUnit + // @call addUnit + 'micro', + // @call setUnit + // @call addUnit + 'microsecond', + ]; + + /** + * Creates a DateTimeZone from a string, DateTimeZone or integer offset. + * + * @param DateTimeZone|string|int|null $object original value to get CarbonTimeZone from it. + * @param DateTimeZone|string|int|null $objectDump dump of the object for error messages. + * + * @throws InvalidTimeZoneException + * + * @return CarbonTimeZone|false + */ + protected static function safeCreateDateTimeZone($object, $objectDump = null) + { + return CarbonTimeZone::instance($object, $objectDump); + } + + /** + * Get the TimeZone associated with the Carbon instance (as CarbonTimeZone). + * + * @return CarbonTimeZone + * + * @link https://php.net/manual/en/datetime.gettimezone.php + */ + #[ReturnTypeWillChange] + public function getTimezone() + { + return CarbonTimeZone::instance(parent::getTimezone()); + } + + /** + * List of minimum and maximums for each unit. + * + * @param int $daysInMonth + * + * @return array + */ + protected static function getRangesByUnit(int $daysInMonth = 31): array + { + return [ + // @call roundUnit + 'year' => [1, 9999], + // @call roundUnit + 'month' => [1, static::MONTHS_PER_YEAR], + // @call roundUnit + 'day' => [1, $daysInMonth], + // @call roundUnit + 'hour' => [0, static::HOURS_PER_DAY - 1], + // @call roundUnit + 'minute' => [0, static::MINUTES_PER_HOUR - 1], + // @call roundUnit + 'second' => [0, static::SECONDS_PER_MINUTE - 1], + ]; + } + + /** + * Get a copy of the instance. + * + * @return static + */ + public function copy() + { + return clone $this; + } + + /** + * @alias copy + * + * Get a copy of the instance. + * + * @return static + */ + public function clone() + { + return clone $this; + } + + /** + * Clone the current instance if it's mutable. + * + * This method is convenient to ensure you don't mutate the initial object + * but avoid to make a useless copy of it if it's already immutable. + * + * @return static + */ + public function avoidMutation(): self + { + if ($this instanceof DateTimeImmutable) { + return $this; + } + + 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. + * + * @param mixed $date + * @param string|array $other + * + * @throws InvalidTypeException + */ + protected static function expectDateTime($date, $other = []) + { + $message = 'Expected '; + foreach ((array) $other as $expect) { + $message .= "$expect, "; + } + + if (!$date instanceof DateTime && !$date instanceof DateTimeInterface) { + throw new InvalidTypeException( + $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|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, ['null', 'string']); + + return $date instanceof self ? $date : static::instance($date); + } + + /** + * Return the Carbon instance passed through, a now instance in UTC + * if null given or parse the input if string given (using current timezone + * then switching to UTC). + * + * @param Carbon|DateTimeInterface|string|null $date + * + * @return static + */ + protected function resolveUTC($date = null): self + { + if (!$date) { + return static::now('UTC'); + } + + if (\is_string($date)) { + return static::parse($date, $this->getTimezone())->utc(); + } + + static::expectDateTime($date, ['null', 'string']); + + return $date instanceof self ? $date : static::instance($date)->utc(); + } + + /** + * 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\CarbonPeriod|\Carbon\CarbonInterval|\DateInterval|\DatePeriod|DateTimeInterface|string|null $date + * + * @return static + */ + public function carbonize($date = null) + { + if ($date instanceof DateInterval) { + return $this->avoidMutation()->add($date); + } + + if ($date instanceof DatePeriod || $date instanceof CarbonPeriod) { + $date = $date->getStartDate(); + } + + return $this->resolveCarbon($date); + } + + /////////////////////////////////////////////////////////////////// + ///////////////////////// GETTERS AND SETTERS ///////////////////// + /////////////////////////////////////////////////////////////////// + + /** + * Get a part of the Carbon object + * + * @param string $name + * + * @throws UnknownGetterException + * + * @return string|int|bool|DateTimeZone|null + */ + public function __get($name) + { + return $this->get($name); + } + + /** + * Get a part of the Carbon object + * + * @param string $name + * + * @throws UnknownGetterException + * + * @return string|int|bool|DateTimeZone|null + */ + public function get($name) + { + static $formats = [ + // @property int + 'year' => 'Y', + // @property int + 'yearIso' => 'o', + // @property int + // @call isSameUnit + 'month' => 'n', + // @property int + 'day' => 'j', + // @property int + 'hour' => 'G', + // @property int + 'minute' => 'i', + // @property int + 'second' => 's', + // @property int + 'micro' => 'u', + // @property int + 'microsecond' => 'u', + // @property-read int 0 (for Sunday) through 6 (for Saturday) + 'dayOfWeek' => 'w', + // @property-read int 1 (for Monday) through 7 (for Sunday) + 'dayOfWeekIso' => 'N', + // @property-read int ISO-8601 week number of year, weeks starting on Monday + 'weekOfYear' => 'W', + // @property-read int number of days in the given month + 'daysInMonth' => 't', + // @property int|float|string seconds since the Unix Epoch + 'timestamp' => 'U', + // @property-read string "am"/"pm" (Ante meridiem or Post meridiem latin lowercase mark) + 'latinMeridiem' => 'a', + // @property-read string "AM"/"PM" (Ante meridiem or Post meridiem latin uppercase mark) + 'latinUpperMeridiem' => 'A', + // @property string the day of week in English + 'englishDayOfWeek' => 'l', + // @property string the abbreviated day of week in English + 'shortEnglishDayOfWeek' => 'D', + // @property string the month in English + 'englishMonth' => 'F', + // @property string the abbreviated month in English + 'shortEnglishMonth' => 'M', + // @property string the day of week in current locale LC_TIME + // @deprecated + // reason: It uses OS language package and strftime() which is deprecated since PHP 8.1. + // replacement: Use ->isoFormat('MMM') instead. + // since: 2.55.0 + 'localeDayOfWeek' => '%A', + // @property string the abbreviated day of week in current locale LC_TIME + // @deprecated + // reason: It uses OS language package and strftime() which is deprecated since PHP 8.1. + // replacement: Use ->isoFormat('dddd') instead. + // since: 2.55.0 + 'shortLocaleDayOfWeek' => '%a', + // @property string the month in current locale LC_TIME + // @deprecated + // reason: It uses OS language package and strftime() which is deprecated since PHP 8.1. + // replacement: Use ->isoFormat('ddd') instead. + // since: 2.55.0 + 'localeMonth' => '%B', + // @property string the abbreviated month in current locale LC_TIME + // @deprecated + // reason: It uses OS language package and strftime() which is deprecated since PHP 8.1. + // replacement: Use ->isoFormat('MMMM') instead. + // since: 2.55.0 + 'shortLocaleMonth' => '%b', + // @property-read string $timezoneAbbreviatedName the current timezone abbreviated name + 'timezoneAbbreviatedName' => 'T', + // @property-read string $tzAbbrName alias of $timezoneAbbreviatedName + 'tzAbbrName' => 'T', + ]; + + switch (true) { + case isset($formats[$name]): + $format = $formats[$name]; + $method = str_starts_with($format, '%') ? 'formatLocalized' : 'rawFormat'; + $value = $this->$method($format); + + return is_numeric($value) ? (int) $value : $value; + + // @property-read string long name of weekday translated according to Carbon locale, in english if no translation available for current language + case $name === 'dayName': + return $this->getTranslatedDayName(); + // @property-read string short name of weekday translated according to Carbon locale, in english if no translation available for current language + case $name === 'shortDayName': + return $this->getTranslatedShortDayName(); + // @property-read string very short name of weekday translated according to Carbon locale, in english if no translation available for current language + case $name === 'minDayName': + return $this->getTranslatedMinDayName(); + // @property-read string long name of month translated according to Carbon locale, in english if no translation available for current language + case $name === 'monthName': + return $this->getTranslatedMonthName(); + // @property-read string short name of month translated according to Carbon locale, in english if no translation available for current language + case $name === 'shortMonthName': + return $this->getTranslatedShortMonthName(); + // @property-read string lowercase meridiem mark translated according to Carbon locale, in latin if no translation available for current language + case $name === 'meridiem': + return $this->meridiem(true); + // @property-read string uppercase meridiem mark translated according to Carbon locale, in latin if no translation available for current language + case $name === 'upperMeridiem': + return $this->meridiem(); + // @property-read int current hour from 1 to 24 + case $name === 'noZeroHour': + return $this->hour ?: 24; + // @property int + case $name === 'milliseconds': + // @property int + case $name === 'millisecond': + // @property int + case $name === 'milli': + return (int) floor(((int) $this->rawFormat('u')) / 1000); + + // @property int 1 through 53 + case $name === 'week': + return (int) $this->week(); + + // @property int 1 through 53 + case $name === 'isoWeek': + return (int) $this->isoWeek(); + + // @property int year according to week format + case $name === 'weekYear': + return (int) $this->weekYear(); + + // @property int year according to ISO week format + case $name === 'isoWeekYear': + return (int) $this->isoWeekYear(); + + // @property-read int 51 through 53 + case $name === 'weeksInYear': + return $this->weeksInYear(); + + // @property-read int 51 through 53 + case $name === 'isoWeeksInYear': + return $this->isoWeeksInYear(); + + // @property-read int 1 through 5 + case $name === 'weekOfMonth': + return (int) ceil($this->day / static::DAYS_PER_WEEK); + + // @property-read int 1 through 5 + case $name === 'weekNumberInMonth': + return (int) ceil(($this->day + $this->avoidMutation()->startOfMonth()->dayOfWeekIso - 1) / static::DAYS_PER_WEEK); + + // @property-read int 0 through 6 + case $name === 'firstWeekDay': + return $this->localTranslator ? ($this->getTranslationMessage('first_day_of_week') ?? 0) : static::getWeekStartsAt(); + + // @property-read int 0 through 6 + case $name === 'lastWeekDay': + return $this->localTranslator ? (($this->getTranslationMessage('first_day_of_week') ?? 0) + static::DAYS_PER_WEEK - 1) % static::DAYS_PER_WEEK : static::getWeekEndsAt(); + + // @property int 1 through 366 + case $name === 'dayOfYear': + return 1 + (int) ($this->rawFormat('z')); + + // @property-read int 365 or 366 + case $name === 'daysInYear': + return $this->isLeapYear() ? 366 : 365; + + // @property int does a diffInYears() with default parameters + case $name === 'age': + return $this->diffInYears(); + + // @property-read int the quarter of this instance, 1 - 4 + // @call isSameUnit + case $name === 'quarter': + return (int) ceil($this->month / static::MONTHS_PER_QUARTER); + + // @property-read int the decade of this instance + // @call isSameUnit + case $name === 'decade': + return (int) ceil($this->year / static::YEARS_PER_DECADE); + + // @property-read int the century of this instance + // @call isSameUnit + case $name === 'century': + $factor = 1; + $year = $this->year; + if ($year < 0) { + $year = -$year; + $factor = -1; + } + + return (int) ($factor * ceil($year / static::YEARS_PER_CENTURY)); + + // @property-read int the millennium of this instance + // @call isSameUnit + case $name === 'millennium': + $factor = 1; + $year = $this->year; + if ($year < 0) { + $year = -$year; + $factor = -1; + } + + return (int) ($factor * ceil($year / static::YEARS_PER_MILLENNIUM)); + + // @property int the timezone offset in seconds from UTC + case $name === 'offset': + return $this->getOffset(); + + // @property int the timezone offset in minutes from UTC + case $name === 'offsetMinutes': + return $this->getOffset() / static::SECONDS_PER_MINUTE; + + // @property int the timezone offset in hours from UTC + case $name === 'offsetHours': + return $this->getOffset() / static::SECONDS_PER_MINUTE / static::MINUTES_PER_HOUR; + + // @property-read bool daylight savings time indicator, true if DST, false otherwise + case $name === 'dst': + return $this->rawFormat('I') === '1'; + + // @property-read bool checks if the timezone is local, true if local, false otherwise + case $name === 'local': + return $this->getOffset() === $this->avoidMutation()->setTimezone(date_default_timezone_get())->getOffset(); + + // @property-read bool checks if the timezone is UTC, true if UTC, false otherwise + case $name === 'utc': + return $this->getOffset() === 0; + + // @property CarbonTimeZone $timezone the current timezone + // @property CarbonTimeZone $tz alias of $timezone + case $name === 'timezone' || $name === 'tz': + return CarbonTimeZone::instance($this->getTimezone()); + + // @property-read string $timezoneName the current timezone name + // @property-read string $tzName alias of $timezoneName + case $name === 'timezoneName' || $name === 'tzName': + return $this->getTimezone()->getName(); + + // @property-read string locale of the current instance + case $name === 'locale': + return $this->getTranslatorLocale(); + + default: + $macro = $this->getLocalMacro('get'.ucfirst($name)); + + if ($macro) { + return $this->executeCallableWithContext($macro); + } + + throw new UnknownGetterException($name); + } + } + + /** + * Check if an attribute exists on the object + * + * @param string $name + * + * @return bool + */ + public function __isset($name) + { + try { + $this->__get($name); + } catch (UnknownGetterException | ReflectionException $e) { + return false; + } + + return true; + } + + /** + * Set a part of the Carbon object + * + * @param string $name + * @param string|int|DateTimeZone $value + * + * @throws UnknownSetterException|ReflectionException + * + * @return void + */ + public function __set($name, $value) + { + if ($this->constructedObjectId === spl_object_hash($this)) { + $this->set($name, $value); + + return; + } + + $this->$name = $value; + } + + /** + * Set a part of the Carbon object + * + * @param string|array $name + * @param string|int|DateTimeZone $value + * + * @throws ImmutableException|UnknownSetterException + * + * @return $this + */ + public function set($name, $value = null) + { + if ($this->isImmutable()) { + throw new ImmutableException(sprintf('%s class', static::class)); + } + + if (\is_array($name)) { + foreach ($name as $key => $value) { + $this->set($key, $value); + } + + return $this; + } + + switch ($name) { + case 'milliseconds': + case 'millisecond': + case 'milli': + case 'microseconds': + case 'microsecond': + case 'micro': + if (str_starts_with($name, 'milli')) { + $value *= 1000; + } + + while ($value < 0) { + $this->subSecond(); + $value += static::MICROSECONDS_PER_SECOND; + } + + while ($value >= static::MICROSECONDS_PER_SECOND) { + $this->addSecond(); + $value -= static::MICROSECONDS_PER_SECOND; + } + + $this->modify($this->rawFormat('H:i:s.').str_pad((string) round($value), 6, '0', STR_PAD_LEFT)); + + break; + + case 'year': + case 'month': + case 'day': + case 'hour': + case 'minute': + case 'second': + [$year, $month, $day, $hour, $minute, $second] = array_map('intval', explode('-', $this->rawFormat('Y-n-j-G-i-s'))); + $$name = $value; + $this->setDateTime($year, $month, $day, $hour, $minute, $second); + + break; + + case 'week': + $this->week($value); + + break; + + case 'isoWeek': + $this->isoWeek($value); + + break; + + case 'weekYear': + $this->weekYear($value); + + break; + + case 'isoWeekYear': + $this->isoWeekYear($value); + + break; + + case 'dayOfYear': + $this->addDays($value - $this->dayOfYear); + + break; + + case 'timestamp': + $this->setTimestamp($value); + + break; + + case 'offset': + $this->setTimezone(static::safeCreateDateTimeZone($value / static::SECONDS_PER_MINUTE / static::MINUTES_PER_HOUR)); + + break; + + case 'offsetMinutes': + $this->setTimezone(static::safeCreateDateTimeZone($value / static::MINUTES_PER_HOUR)); + + break; + + case 'offsetHours': + $this->setTimezone(static::safeCreateDateTimeZone($value)); + + break; + + case 'timezone': + case 'tz': + $this->setTimezone($value); + + break; + + default: + $macro = $this->getLocalMacro('set'.ucfirst($name)); + + if ($macro) { + $this->executeCallableWithContext($macro, $value); + + break; + } + + if ($this->localStrictModeEnabled ?? static::isStrictModeEnabled()) { + throw new UnknownSetterException($name); + } + + $this->$name = $value; + } + + return $this; + } + + protected function getTranslatedFormByRegExp($baseKey, $keySuffix, $context, $subKey, $defaultValue) + { + $key = $baseKey.$keySuffix; + $standaloneKey = "{$key}_standalone"; + $baseTranslation = $this->getTranslationMessage($key); + + if ($baseTranslation instanceof Closure) { + return $baseTranslation($this, $context, $subKey) ?: $defaultValue; + } + + if ( + $this->getTranslationMessage("$standaloneKey.$subKey") && + (!$context || (($regExp = $this->getTranslationMessage("{$baseKey}_regexp")) && !preg_match($regExp, $context))) + ) { + $key = $standaloneKey; + } + + return $this->getTranslationMessage("$key.$subKey", null, $defaultValue); + } + + /** + * Get the translation of the current week day name (with context for languages with multiple forms). + * + * @param string|null $context whole format string + * @param string $keySuffix "", "_short" or "_min" + * @param string|null $defaultValue default value if translation missing + * + * @return string + */ + public function getTranslatedDayName($context = null, $keySuffix = '', $defaultValue = null) + { + return $this->getTranslatedFormByRegExp('weekdays', $keySuffix, $context, $this->dayOfWeek, $defaultValue ?: $this->englishDayOfWeek); + } + + /** + * Get the translation of the current short week day name (with context for languages with multiple forms). + * + * @param string|null $context whole format string + * + * @return string + */ + public function getTranslatedShortDayName($context = null) + { + return $this->getTranslatedDayName($context, '_short', $this->shortEnglishDayOfWeek); + } + + /** + * Get the translation of the current abbreviated week day name (with context for languages with multiple forms). + * + * @param string|null $context whole format string + * + * @return string + */ + public function getTranslatedMinDayName($context = null) + { + return $this->getTranslatedDayName($context, '_min', $this->shortEnglishDayOfWeek); + } + + /** + * Get the translation of the current month day name (with context for languages with multiple forms). + * + * @param string|null $context whole format string + * @param string $keySuffix "" or "_short" + * @param string|null $defaultValue default value if translation missing + * + * @return string + */ + public function getTranslatedMonthName($context = null, $keySuffix = '', $defaultValue = null) + { + return $this->getTranslatedFormByRegExp('months', $keySuffix, $context, $this->month - 1, $defaultValue ?: $this->englishMonth); + } + + /** + * Get the translation of the current short month day name (with context for languages with multiple forms). + * + * @param string|null $context whole format string + * + * @return string + */ + public function getTranslatedShortMonthName($context = null) + { + return $this->getTranslatedMonthName($context, '_short', $this->shortEnglishMonth); + } + + /** + * Get/set the day of year. + * + * @param int|null $value new value for day of year if using as setter. + * + * @return static|int + */ + public function dayOfYear($value = null) + { + $dayOfYear = $this->dayOfYear; + + return $value === null ? $dayOfYear : $this->addDays($value - $dayOfYear); + } + + /** + * Get/set the weekday from 0 (Sunday) to 6 (Saturday). + * + * @param int|null $value new value for weekday if using as setter. + * + * @return static|int + */ + public function weekday($value = null) + { + if ($value === null) { + return $this->dayOfWeek; + } + + $firstDay = (int) ($this->getTranslationMessage('first_day_of_week') ?? 0); + $dayOfWeek = ($this->dayOfWeek + 7 - $firstDay) % 7; + + return $this->addDays((($value + 7 - $firstDay) % 7) - $dayOfWeek); + } + + /** + * Get/set the ISO weekday from 1 (Monday) to 7 (Sunday). + * + * @param int|null $value new value for weekday if using as setter. + * + * @return static|int + */ + public function isoWeekday($value = null) + { + $dayOfWeekIso = $this->dayOfWeekIso; + + return $value === null ? $dayOfWeekIso : $this->addDays($value - $dayOfWeekIso); + } + + /** + * Return the number of days since the start of the week (using the current locale or the first parameter + * if explicitly given). + * + * @param int|null $weekStartsAt optional start allow you to specify the day of week to use to start the week, + * if not provided, start of week is inferred from the locale + * (Sunday for en_US, Monday for de_DE, etc.) + * + * @return int + */ + public function getDaysFromStartOfWeek(int $weekStartsAt = null): int + { + $firstDay = (int) ($weekStartsAt ?? $this->getTranslationMessage('first_day_of_week') ?? 0); + + return ($this->dayOfWeek + 7 - $firstDay) % 7; + } + + /** + * Set the day (keeping the current time) to the start of the week + the number of days passed as the first + * parameter. First day of week is driven by the locale unless explicitly set with the second parameter. + * + * @param int $numberOfDays number of days to add after the start of the current week + * @param int|null $weekStartsAt optional start allow you to specify the day of week to use to start the week, + * if not provided, start of week is inferred from the locale + * (Sunday for en_US, Monday for de_DE, etc.) + * + * @return static + */ + public function setDaysFromStartOfWeek(int $numberOfDays, int $weekStartsAt = null) + { + return $this->addDays($numberOfDays - $this->getDaysFromStartOfWeek($weekStartsAt)); + } + + /** + * Set any unit to a new value without overflowing current other unit given. + * + * @param string $valueUnit unit name to modify + * @param int $value new value for the input unit + * @param string $overflowUnit unit name to not overflow + * + * @return static + */ + public function setUnitNoOverflow($valueUnit, $value, $overflowUnit) + { + try { + $original = $this->avoidMutation(); + /** @var static $date */ + $date = $this->$valueUnit($value); + $end = $original->avoidMutation()->endOf($overflowUnit); + $start = $original->avoidMutation()->startOf($overflowUnit); + if ($date < $start) { + $date = $date->setDateTimeFrom($start); + } elseif ($date > $end) { + $date = $date->setDateTimeFrom($end); + } + + return $date; + } catch (BadMethodCallException | ReflectionException $exception) { + throw new UnknownUnitException($valueUnit, 0, $exception); + } + } + + /** + * Add any unit to a new value without overflowing current other unit given. + * + * @param string $valueUnit unit name to modify + * @param int $value amount to add to the input unit + * @param string $overflowUnit unit name to not overflow + * + * @return static + */ + public function addUnitNoOverflow($valueUnit, $value, $overflowUnit) + { + return $this->setUnitNoOverflow($valueUnit, $this->$valueUnit + $value, $overflowUnit); + } + + /** + * Subtract any unit to a new value without overflowing current other unit given. + * + * @param string $valueUnit unit name to modify + * @param int $value amount to subtract to the input unit + * @param string $overflowUnit unit name to not overflow + * + * @return static + */ + public function subUnitNoOverflow($valueUnit, $value, $overflowUnit) + { + return $this->setUnitNoOverflow($valueUnit, $this->$valueUnit - $value, $overflowUnit); + } + + /** + * Returns the minutes offset to UTC if no arguments passed, else set the timezone with given minutes shift passed. + * + * @param int|null $minuteOffset + * + * @return int|static + */ + public function utcOffset(int $minuteOffset = null) + { + if (\func_num_args() < 1) { + return $this->offsetMinutes; + } + + return $this->setTimezone(CarbonTimeZone::createFromMinuteOffset($minuteOffset)); + } + + /** + * Set the date with gregorian year, month and day numbers. + * + * @see https://php.net/manual/en/datetime.setdate.php + * + * @param int $year + * @param int $month + * @param int $day + * + * @return static + */ + #[ReturnTypeWillChange] + public function setDate($year, $month, $day) + { + return parent::setDate((int) $year, (int) $month, (int) $day); + } + + /** + * Set a date according to the ISO 8601 standard - using weeks and day offsets rather than specific dates. + * + * @see https://php.net/manual/en/datetime.setisodate.php + * + * @param int $year + * @param int $week + * @param int $day + * + * @return static + */ + #[ReturnTypeWillChange] + public function setISODate($year, $week, $day = 1) + { + return parent::setISODate((int) $year, (int) $week, (int) $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 + * @param int $microseconds + * + * @return static + */ + public function setDateTime($year, $month, $day, $hour, $minute, $second = 0, $microseconds = 0) + { + return $this->setDate($year, $month, $day)->setTime((int) $hour, (int) $minute, (int) $second, (int) $microseconds); + } + + /** + * Resets the current time of the DateTime object to a different time. + * + * @see https://php.net/manual/en/datetime.settime.php + * + * @param int $hour + * @param int $minute + * @param int $second + * @param int $microseconds + * + * @return static + */ + #[ReturnTypeWillChange] + public function setTime($hour, $minute, $second = 0, $microseconds = 0) + { + return parent::setTime((int) $hour, (int) $minute, (int) $second, (int) $microseconds); + } + + /** + * Set the instance's timestamp. + * + * Timestamp input can be given as int, float or a string containing one or more numbers. + * + * @param float|int|string $unixTimestamp + * + * @return static + */ + #[ReturnTypeWillChange] + public function setTimestamp($unixTimestamp) + { + [$timestamp, $microseconds] = self::getIntegerAndDecimalParts($unixTimestamp); + + return parent::setTimestamp((int) $timestamp)->setMicroseconds((int) $microseconds); + } + + /** + * Set the time by time string. + * + * @param string $time + * + * @return static + */ + public function setTimeFromTimeString($time) + { + if (!str_contains($time, ':')) { + $time .= ':0'; + } + + return $this->modify($time); + } + + /** + * @alias setTimezone + * + * @param DateTimeZone|string $value + * + * @return static + */ + public function timezone($value) + { + return $this->setTimezone($value); + } + + /** + * Set the timezone or returns the timezone name if no arguments passed. + * + * @param DateTimeZone|string $value + * + * @return static|string + */ + public function tz($value = null) + { + if (\func_num_args() < 1) { + return $this->tzName; + } + + return $this->setTimezone($value); + } + + /** + * Set the instance's timezone from a string or object. + * + * @param DateTimeZone|string $value + * + * @return static + */ + #[ReturnTypeWillChange] + public function setTimezone($value) + { + $tz = static::safeCreateDateTimeZone($value); + + if ($tz === false && !self::isStrictModeEnabled()) { + $tz = new CarbonTimeZone(); + } + + return parent::setTimezone($tz); + } + + /** + * Set the instance's timezone from a string or object and add/subtract the offset difference. + * + * @param DateTimeZone|string $value + * + * @return static + */ + public function shiftTimezone($value) + { + $dateTimeString = $this->format('Y-m-d H:i:s.u'); + + return $this + ->setTimezone($value) + ->modify($dateTimeString); + } + + /** + * Set the instance's timezone to UTC. + * + * @return static + */ + public function utc() + { + return $this->setTimezone('UTC'); + } + + /** + * Set the year, month, and date for this instance to that of the passed instance. + * + * @param Carbon|DateTimeInterface $date now if null + * + * @return static + */ + public function setDateFrom($date = null) + { + $date = $this->resolveCarbon($date); + + return $this->setDate($date->year, $date->month, $date->day); + } + + /** + * Set the hour, minute, second and microseconds for this instance to that of the passed instance. + * + * @param Carbon|DateTimeInterface $date now if null + * + * @return static + */ + public function setTimeFrom($date = null) + { + $date = $this->resolveCarbon($date); + + return $this->setTime($date->hour, $date->minute, $date->second, $date->microsecond); + } + + /** + * Set the date and time for this instance to that of the passed instance. + * + * @param Carbon|DateTimeInterface $date + * + * @return static + */ + public function setDateTimeFrom($date = null) + { + $date = $this->resolveCarbon($date); + + return $this->modify($date->rawFormat('Y-m-d H:i:s.u')); + } + + /** + * Get the days of the week + * + * @return array + */ + public static function getDays() + { + return static::$days; + } + + /////////////////////////////////////////////////////////////////// + /////////////////////// WEEK SPECIAL DAYS ///////////////////////// + /////////////////////////////////////////////////////////////////// + + private static function getFirstDayOfWeek(): int + { + return (int) static::getTranslationMessageWith( + static::getTranslator(), + 'first_day_of_week' + ); + } + + /** + * Get the first day of week + * + * @return int + */ + public static function getWeekStartsAt() + { + if (static::$weekStartsAt === static::WEEK_DAY_AUTO) { + return self::getFirstDayOfWeek(); + } + + return static::$weekStartsAt; + } + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * Use $weekEndsAt optional parameter instead when using endOfWeek method. You can also use the + * 'first_day_of_week' locale setting to change the start of week according to current locale + * selected and implicitly the end of week. + * + * Set the first day of week + * + * @param int|string $day week start day (or 'auto' to get the first day of week from Carbon::getLocale() culture). + * + * @return void + */ + public static function setWeekStartsAt($day) + { + static::$weekStartsAt = $day === static::WEEK_DAY_AUTO ? $day : max(0, (7 + $day) % 7); + } + + /** + * Get the last day of week + * + * @return int + */ + public static function getWeekEndsAt() + { + if (static::$weekStartsAt === static::WEEK_DAY_AUTO) { + return (int) (static::DAYS_PER_WEEK - 1 + self::getFirstDayOfWeek()) % static::DAYS_PER_WEEK; + } + + return static::$weekEndsAt; + } + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * Use $weekStartsAt optional parameter instead when using startOfWeek, floorWeek, ceilWeek + * or roundWeek method. You can also use the 'first_day_of_week' locale setting to change the + * start of week according to current locale selected and implicitly the end of week. + * + * Set the last day of week + * + * @param int|string $day week end day (or 'auto' to get the day before the first day of week + * from Carbon::getLocale() culture). + * + * @return void + */ + public static function setWeekEndsAt($day) + { + static::$weekEndsAt = $day === static::WEEK_DAY_AUTO ? $day : max(0, (7 + $day) % 7); + } + + /** + * Get weekend days + * + * @return array + */ + public static function getWeekendDays() + { + return static::$weekendDays; + } + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather consider week-end is always saturday and sunday, and if you have some custom + * week-end days to handle, give to those days an other name and create a macro for them: + * + * ``` + * Carbon::macro('isDayOff', function ($date) { + * return $date->isSunday() || $date->isMonday(); + * }); + * Carbon::macro('isNotDayOff', function ($date) { + * return !$date->isDayOff(); + * }); + * if ($someDate->isDayOff()) ... + * if ($someDate->isNotDayOff()) ... + * // Add 5 not-off days + * $count = 5; + * while ($someDate->isDayOff() || ($count-- > 0)) { + * $someDate->addDay(); + * } + * ``` + * + * Set weekend days + * + * @param array $days + * + * @return void + */ + public static function setWeekendDays($days) + { + static::$weekendDays = $days; + } + + /** + * 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 (!$time || 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; + } + + /////////////////////////////////////////////////////////////////// + /////////////////////// STRING FORMATTING ///////////////////////// + /////////////////////////////////////////////////////////////////// + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use UTF-8 language packages on every machine. + * + * 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() https://php.net/setlocale. + * + * @deprecated It uses OS language package and strftime() which is deprecated since PHP 8.1. + * Use ->isoFormat() instead. + * Deprecated since 2.55.0 + * + * @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()); + $formatted = ($this->localStrictModeEnabled ?? static::isStrictModeEnabled()) + ? strftime($format, $time) + : @strftime($format, $time); + + return static::$utf8 + ? ( + \function_exists('mb_convert_encoding') + ? mb_convert_encoding($formatted, 'UTF-8', mb_list_encodings()) + : utf8_encode($formatted) + ) + : $formatted; + } + + /** + * Returns list of locale formats for ISO formatting. + * + * @param string|null $locale current locale used if null + * + * @return array + */ + public function getIsoFormats($locale = null) + { + return [ + 'LT' => $this->getTranslationMessage('formats.LT', $locale, 'h:mm A'), + 'LTS' => $this->getTranslationMessage('formats.LTS', $locale, 'h:mm:ss A'), + 'L' => $this->getTranslationMessage('formats.L', $locale, 'MM/DD/YYYY'), + 'LL' => $this->getTranslationMessage('formats.LL', $locale, 'MMMM D, YYYY'), + 'LLL' => $this->getTranslationMessage('formats.LLL', $locale, 'MMMM D, YYYY h:mm A'), + 'LLLL' => $this->getTranslationMessage('formats.LLLL', $locale, 'dddd, MMMM D, YYYY h:mm A'), + 'l' => $this->getTranslationMessage('formats.l', $locale), + 'll' => $this->getTranslationMessage('formats.ll', $locale), + 'lll' => $this->getTranslationMessage('formats.lll', $locale), + 'llll' => $this->getTranslationMessage('formats.llll', $locale), + ]; + } + + /** + * Returns list of calendar formats for ISO formatting. + * + * @param string|null $locale current locale used if null + * + * @return array + */ + public function getCalendarFormats($locale = null) + { + return [ + 'sameDay' => $this->getTranslationMessage('calendar.sameDay', $locale, '[Today at] LT'), + 'nextDay' => $this->getTranslationMessage('calendar.nextDay', $locale, '[Tomorrow at] LT'), + 'nextWeek' => $this->getTranslationMessage('calendar.nextWeek', $locale, 'dddd [at] LT'), + 'lastDay' => $this->getTranslationMessage('calendar.lastDay', $locale, '[Yesterday at] LT'), + 'lastWeek' => $this->getTranslationMessage('calendar.lastWeek', $locale, '[Last] dddd [at] LT'), + 'sameElse' => $this->getTranslationMessage('calendar.sameElse', $locale, 'L'), + ]; + } + + /** + * Returns list of locale units for ISO formatting. + * + * @return array + */ + public static function getIsoUnits() + { + static $units = null; + + if ($units === null) { + $units = [ + 'OD' => ['getAltNumber', ['day']], + 'OM' => ['getAltNumber', ['month']], + 'OY' => ['getAltNumber', ['year']], + 'OH' => ['getAltNumber', ['hour']], + 'Oh' => ['getAltNumber', ['h']], + 'Om' => ['getAltNumber', ['minute']], + 'Os' => ['getAltNumber', ['second']], + 'D' => 'day', + 'DD' => ['rawFormat', ['d']], + 'Do' => ['ordinal', ['day', 'D']], + 'd' => 'dayOfWeek', + 'dd' => function (CarbonInterface $date, $originalFormat = null) { + return $date->getTranslatedMinDayName($originalFormat); + }, + 'ddd' => function (CarbonInterface $date, $originalFormat = null) { + return $date->getTranslatedShortDayName($originalFormat); + }, + 'dddd' => function (CarbonInterface $date, $originalFormat = null) { + return $date->getTranslatedDayName($originalFormat); + }, + 'DDD' => 'dayOfYear', + 'DDDD' => ['getPaddedUnit', ['dayOfYear', 3]], + 'DDDo' => ['ordinal', ['dayOfYear', 'DDD']], + 'e' => ['weekday', []], + 'E' => 'dayOfWeekIso', + 'H' => ['rawFormat', ['G']], + 'HH' => ['rawFormat', ['H']], + 'h' => ['rawFormat', ['g']], + 'hh' => ['rawFormat', ['h']], + 'k' => 'noZeroHour', + 'kk' => ['getPaddedUnit', ['noZeroHour']], + 'hmm' => ['rawFormat', ['gi']], + 'hmmss' => ['rawFormat', ['gis']], + 'Hmm' => ['rawFormat', ['Gi']], + 'Hmmss' => ['rawFormat', ['Gis']], + 'm' => 'minute', + 'mm' => ['rawFormat', ['i']], + 'a' => 'meridiem', + 'A' => 'upperMeridiem', + 's' => 'second', + 'ss' => ['getPaddedUnit', ['second']], + 'S' => function (CarbonInterface $date) { + return (string) floor($date->micro / 100000); + }, + 'SS' => function (CarbonInterface $date) { + return str_pad((string) floor($date->micro / 10000), 2, '0', STR_PAD_LEFT); + }, + 'SSS' => function (CarbonInterface $date) { + return str_pad((string) floor($date->micro / 1000), 3, '0', STR_PAD_LEFT); + }, + 'SSSS' => function (CarbonInterface $date) { + return str_pad((string) floor($date->micro / 100), 4, '0', STR_PAD_LEFT); + }, + 'SSSSS' => function (CarbonInterface $date) { + return str_pad((string) floor($date->micro / 10), 5, '0', STR_PAD_LEFT); + }, + 'SSSSSS' => ['getPaddedUnit', ['micro', 6]], + 'SSSSSSS' => function (CarbonInterface $date) { + return str_pad((string) floor($date->micro * 10), 7, '0', STR_PAD_LEFT); + }, + 'SSSSSSSS' => function (CarbonInterface $date) { + return str_pad((string) floor($date->micro * 100), 8, '0', STR_PAD_LEFT); + }, + 'SSSSSSSSS' => function (CarbonInterface $date) { + return str_pad((string) floor($date->micro * 1000), 9, '0', STR_PAD_LEFT); + }, + 'M' => 'month', + 'MM' => ['rawFormat', ['m']], + 'MMM' => function (CarbonInterface $date, $originalFormat = null) { + $month = $date->getTranslatedShortMonthName($originalFormat); + $suffix = $date->getTranslationMessage('mmm_suffix'); + if ($suffix && $month !== $date->monthName) { + $month .= $suffix; + } + + return $month; + }, + 'MMMM' => function (CarbonInterface $date, $originalFormat = null) { + return $date->getTranslatedMonthName($originalFormat); + }, + 'Mo' => ['ordinal', ['month', 'M']], + 'Q' => 'quarter', + 'Qo' => ['ordinal', ['quarter', 'M']], + 'G' => 'isoWeekYear', + 'GG' => ['getPaddedUnit', ['isoWeekYear']], + 'GGG' => ['getPaddedUnit', ['isoWeekYear', 3]], + 'GGGG' => ['getPaddedUnit', ['isoWeekYear', 4]], + 'GGGGG' => ['getPaddedUnit', ['isoWeekYear', 5]], + 'g' => 'weekYear', + 'gg' => ['getPaddedUnit', ['weekYear']], + 'ggg' => ['getPaddedUnit', ['weekYear', 3]], + 'gggg' => ['getPaddedUnit', ['weekYear', 4]], + 'ggggg' => ['getPaddedUnit', ['weekYear', 5]], + 'W' => 'isoWeek', + 'WW' => ['getPaddedUnit', ['isoWeek']], + 'Wo' => ['ordinal', ['isoWeek', 'W']], + 'w' => 'week', + 'ww' => ['getPaddedUnit', ['week']], + 'wo' => ['ordinal', ['week', 'w']], + 'x' => ['valueOf', []], + 'X' => 'timestamp', + 'Y' => 'year', + 'YY' => ['rawFormat', ['y']], + 'YYYY' => ['getPaddedUnit', ['year', 4]], + 'YYYYY' => ['getPaddedUnit', ['year', 5]], + 'YYYYYY' => function (CarbonInterface $date) { + return ($date->year < 0 ? '' : '+').$date->getPaddedUnit('year', 6); + }, + 'z' => ['rawFormat', ['T']], + 'zz' => 'tzName', + 'Z' => ['getOffsetString', []], + 'ZZ' => ['getOffsetString', ['']], + ]; + } + + return $units; + } + + /** + * Returns a unit of the instance padded with 0 by default or any other string if specified. + * + * @param string $unit Carbon unit name + * @param int $length Length of the output (2 by default) + * @param string $padString String to use for padding ("0" by default) + * @param int $padType Side(s) to pad (STR_PAD_LEFT by default) + * + * @return string + */ + public function getPaddedUnit($unit, $length = 2, $padString = '0', $padType = STR_PAD_LEFT) + { + return ($this->$unit < 0 ? '-' : '').str_pad((string) abs($this->$unit), $length, $padString, $padType); + } + + /** + * Return a property with its ordinal. + * + * @param string $key + * @param string|null $period + * + * @return string + */ + public function ordinal(string $key, ?string $period = null): string + { + $number = $this->$key; + $result = $this->translate('ordinal', [ + ':number' => $number, + ':period' => (string) $period, + ]); + + return (string) ($result === 'ordinal' ? $number : $result); + } + + /** + * Return the meridiem of the current time in the current locale. + * + * @param bool $isLower if true, returns lowercase variant if available in the current locale. + * + * @return string + */ + public function meridiem(bool $isLower = false): string + { + $hour = $this->hour; + $index = $hour < 12 ? 0 : 1; + + if ($isLower) { + $key = 'meridiem.'.($index + 2); + $result = $this->translate($key); + + if ($result !== $key) { + return $result; + } + } + + $key = "meridiem.$index"; + $result = $this->translate($key); + if ($result === $key) { + $result = $this->translate('meridiem', [ + ':hour' => $this->hour, + ':minute' => $this->minute, + ':isLower' => $isLower, + ]); + + if ($result === 'meridiem') { + return $isLower ? $this->latinMeridiem : $this->latinUpperMeridiem; + } + } elseif ($isLower) { + $result = mb_strtolower($result); + } + + return $result; + } + + /** + * Returns the alternative number for a given date property if available in the current locale. + * + * @param string $key date property + * + * @return string + */ + public function getAltNumber(string $key): string + { + return $this->translateNumber(\strlen($key) > 1 ? $this->$key : $this->rawFormat('h')); + } + + /** + * Format in the current language using ISO replacement patterns. + * + * @param string $format + * @param string|null $originalFormat provide context if a chunk has been passed alone + * + * @return string + */ + public function isoFormat(string $format, ?string $originalFormat = null): string + { + $result = ''; + $length = mb_strlen($format); + $originalFormat = $originalFormat ?: $format; + $inEscaped = false; + $formats = null; + $units = null; + + for ($i = 0; $i < $length; $i++) { + $char = mb_substr($format, $i, 1); + + if ($char === '\\') { + $result .= mb_substr($format, ++$i, 1); + + continue; + } + + if ($char === '[' && !$inEscaped) { + $inEscaped = true; + + continue; + } + + if ($char === ']' && $inEscaped) { + $inEscaped = false; + + continue; + } + + if ($inEscaped) { + $result .= $char; + + continue; + } + + $input = mb_substr($format, $i); + + if (preg_match('/^(LTS|LT|l{1,4}|L{1,4})/', $input, $match)) { + if ($formats === null) { + $formats = $this->getIsoFormats(); + } + + $code = $match[0]; + $sequence = $formats[$code] ?? preg_replace_callback( + '/MMMM|MM|DD|dddd/', + function ($code) { + return mb_substr($code[0], 1); + }, + $formats[strtoupper($code)] ?? '' + ); + $rest = mb_substr($format, $i + mb_strlen($code)); + $format = mb_substr($format, 0, $i).$sequence.$rest; + $length = mb_strlen($format); + $input = $sequence.$rest; + } + + if (preg_match('/^'.CarbonInterface::ISO_FORMAT_REGEXP.'/', $input, $match)) { + $code = $match[0]; + + if ($units === null) { + $units = static::getIsoUnits(); + } + + $sequence = $units[$code] ?? ''; + + if ($sequence instanceof Closure) { + $sequence = $sequence($this, $originalFormat); + } elseif (\is_array($sequence)) { + try { + $sequence = $this->{$sequence[0]}(...$sequence[1]); + } catch (ReflectionException | InvalidArgumentException | BadMethodCallException $e) { + $sequence = ''; + } + } elseif (\is_string($sequence)) { + $sequence = $this->$sequence ?? $code; + } + + $format = mb_substr($format, 0, $i).$sequence.mb_substr($format, $i + mb_strlen($code)); + $i += mb_strlen((string) $sequence) - 1; + $length = mb_strlen($format); + $char = $sequence; + } + + $result .= $char; + } + + return $result; + } + + /** + * List of replacements from date() format to isoFormat(). + * + * @return array + */ + public static function getFormatsToIsoReplacements() + { + static $replacements = null; + + if ($replacements === null) { + $replacements = [ + 'd' => true, + 'D' => 'ddd', + 'j' => true, + 'l' => 'dddd', + 'N' => true, + 'S' => function ($date) { + $day = $date->rawFormat('j'); + + return str_replace((string) $day, '', $date->isoFormat('Do')); + }, + 'w' => true, + 'z' => true, + 'W' => true, + 'F' => 'MMMM', + 'm' => true, + 'M' => 'MMM', + 'n' => true, + 't' => true, + 'L' => true, + 'o' => true, + 'Y' => true, + 'y' => true, + 'a' => 'a', + 'A' => 'A', + 'B' => true, + 'g' => true, + 'G' => true, + 'h' => true, + 'H' => true, + 'i' => true, + 's' => true, + 'u' => true, + 'v' => true, + 'E' => true, + 'I' => true, + 'O' => true, + 'P' => true, + 'Z' => true, + 'c' => true, + 'r' => true, + 'U' => true, + ]; + } + + return $replacements; + } + + /** + * Format as ->format() do (using date replacements patterns from https://php.net/manual/en/function.date.php) + * but translate words whenever possible (months, day names, etc.) using the current locale. + * + * @param string $format + * + * @return string + */ + public function translatedFormat(string $format): string + { + $replacements = static::getFormatsToIsoReplacements(); + $context = ''; + $isoFormat = ''; + $length = mb_strlen($format); + + for ($i = 0; $i < $length; $i++) { + $char = mb_substr($format, $i, 1); + + if ($char === '\\') { + $replacement = mb_substr($format, $i, 2); + $isoFormat .= $replacement; + $i++; + + continue; + } + + if (!isset($replacements[$char])) { + $replacement = preg_match('/^[A-Za-z]$/', $char) ? "\\$char" : $char; + $isoFormat .= $replacement; + $context .= $replacement; + + continue; + } + + $replacement = $replacements[$char]; + + if ($replacement === true) { + static $contextReplacements = null; + + if ($contextReplacements === null) { + $contextReplacements = [ + 'm' => 'MM', + 'd' => 'DD', + 't' => 'D', + 'j' => 'D', + 'N' => 'e', + 'w' => 'e', + 'n' => 'M', + 'o' => 'YYYY', + 'Y' => 'YYYY', + 'y' => 'YY', + 'g' => 'h', + 'G' => 'H', + 'h' => 'hh', + 'H' => 'HH', + 'i' => 'mm', + 's' => 'ss', + ]; + } + + $isoFormat .= '['.$this->rawFormat($char).']'; + $context .= $contextReplacements[$char] ?? ' '; + + continue; + } + + if ($replacement instanceof Closure) { + $replacement = '['.$replacement($this).']'; + $isoFormat .= $replacement; + $context .= $replacement; + + continue; + } + + $isoFormat .= $replacement; + $context .= $replacement; + } + + return $this->isoFormat($isoFormat, $context); + } + + /** + * Returns the offset hour and minute formatted with +/- and a given separator (":" by default). + * For example, if the time zone is 9 hours 30 minutes, you'll get "+09:30", with "@@" as first + * argument, "+09@@30", with "" as first argument, "+0930". Negative offset will return something + * like "-12:00". + * + * @param string $separator string to place between hours and minutes (":" by default) + * + * @return string + */ + public function getOffsetString($separator = ':') + { + $second = $this->getOffset(); + $symbol = $second < 0 ? '-' : '+'; + $minute = abs($second) / static::SECONDS_PER_MINUTE; + $hour = str_pad((string) floor($minute / static::MINUTES_PER_HOUR), 2, '0', STR_PAD_LEFT); + $minute = str_pad((string) (((int) $minute) % static::MINUTES_PER_HOUR), 2, '0', STR_PAD_LEFT); + + return "$symbol$hour$separator$minute"; + } + + protected static function executeStaticCallable($macro, ...$parameters) + { + return static::bindMacroContext(null, function () use (&$macro, &$parameters) { + if ($macro instanceof Closure) { + $boundMacro = @Closure::bind($macro, null, static::class); + + return ($boundMacro ?: $macro)(...$parameters); + } + + return $macro(...$parameters); + }); + } + + /** + * Dynamically handle calls to the class. + * + * @param string $method magic method name called + * @param array $parameters parameters list + * + * @throws BadMethodCallException + * + * @return mixed + */ + public static function __callStatic($method, $parameters) + { + if (!static::hasMacro($method)) { + foreach (static::getGenericMacros() as $callback) { + try { + return static::executeStaticCallable($callback, $method, ...$parameters); + } catch (BadMethodCallException $exception) { + continue; + } + } + if (static::isStrictModeEnabled()) { + throw new UnknownMethodException(sprintf('%s::%s', static::class, $method)); + } + + return null; + } + + return static::executeStaticCallable(static::$globalMacros[$method], ...$parameters); + } + + /** + * Set specified unit to new given value. + * + * @param string $unit year, month, day, hour, minute, second or microsecond + * @param int $value new value for given unit + * + * @return static + */ + public function setUnit($unit, $value = null) + { + $unit = static::singularUnit($unit); + $dateUnits = ['year', 'month', 'day']; + if (\in_array($unit, $dateUnits)) { + return $this->setDate(...array_map(function ($name) use ($unit, $value) { + return (int) ($name === $unit ? $value : $this->$name); + }, $dateUnits)); + } + + $units = ['hour', 'minute', 'second', 'micro']; + if ($unit === 'millisecond' || $unit === 'milli') { + $value *= 1000; + $unit = 'micro'; + } elseif ($unit === 'microsecond') { + $unit = 'micro'; + } + + return $this->setTime(...array_map(function ($name) use ($unit, $value) { + return (int) ($name === $unit ? $value : $this->$name); + }, $units)); + } + + /** + * Returns standardized singular of a given singular/plural unit name (in English). + * + * @param string $unit + * + * @return string + */ + public static function singularUnit(string $unit): string + { + $unit = rtrim(mb_strtolower($unit), 's'); + + if ($unit === 'centurie') { + return 'century'; + } + + if ($unit === 'millennia') { + return 'millennium'; + } + + return $unit; + } + + /** + * Returns standardized plural of a given singular/plural unit name (in English). + * + * @param string $unit + * + * @return string + */ + public static function pluralUnit(string $unit): string + { + $unit = rtrim(strtolower($unit), 's'); + + if ($unit === 'century') { + return 'centuries'; + } + + if ($unit === 'millennium' || $unit === 'millennia') { + return 'millennia'; + } + + return "{$unit}s"; + } + + protected function executeCallable($macro, ...$parameters) + { + if ($macro instanceof Closure) { + $boundMacro = @$macro->bindTo($this, static::class) ?: @$macro->bindTo(null, static::class); + + return ($boundMacro ?: $macro)(...$parameters); + } + + return $macro(...$parameters); + } + + protected function executeCallableWithContext($macro, ...$parameters) + { + return static::bindMacroContext($this, function () use (&$macro, &$parameters) { + return $this->executeCallable($macro, ...$parameters); + }); + } + + protected static function getGenericMacros() + { + foreach (static::$globalGenericMacros as $list) { + foreach ($list as $macro) { + yield $macro; + } + } + } + + /** + * Dynamically handle calls to the class. + * + * @param string $method magic method name called + * @param array $parameters parameters list + * + * @throws UnknownMethodException|BadMethodCallException|ReflectionException|Throwable + * + * @return mixed + */ + public function __call($method, $parameters) + { + $diffSizes = [ + // @mode diffForHumans + 'short' => true, + // @mode diffForHumans + 'long' => false, + ]; + $diffSyntaxModes = [ + // @call diffForHumans + 'Absolute' => CarbonInterface::DIFF_ABSOLUTE, + // @call diffForHumans + 'Relative' => CarbonInterface::DIFF_RELATIVE_AUTO, + // @call diffForHumans + 'RelativeToNow' => CarbonInterface::DIFF_RELATIVE_TO_NOW, + // @call diffForHumans + 'RelativeToOther' => CarbonInterface::DIFF_RELATIVE_TO_OTHER, + ]; + $sizePattern = implode('|', array_keys($diffSizes)); + $syntaxPattern = implode('|', array_keys($diffSyntaxModes)); + + if (preg_match("/^(?$sizePattern)(?$syntaxPattern)DiffForHumans$/", $method, $match)) { + $dates = array_filter($parameters, function ($parameter) { + return $parameter instanceof DateTimeInterface; + }); + $other = null; + + if (\count($dates)) { + $key = key($dates); + $other = current($dates); + array_splice($parameters, $key, 1); + } + + return $this->diffForHumans($other, $diffSyntaxModes[$match['syntax']], $diffSizes[$match['size']], ...$parameters); + } + + $roundedValue = $this->callRoundMethod($method, $parameters); + + if ($roundedValue !== null) { + return $roundedValue; + } + + $unit = rtrim($method, 's'); + + if (str_starts_with($unit, 'is')) { + $word = substr($unit, 2); + + if (\in_array($word, static::$days, true)) { + return $this->isDayOfWeek($word); + } + + switch ($word) { + // @call is Check if the current instance has UTC timezone. (Both isUtc and isUTC cases are valid.) + case 'Utc': + case 'UTC': + return $this->utc; + // @call is Check if the current instance has non-UTC timezone. + case 'Local': + return $this->local; + // @call is Check if the current instance is a valid date. + case 'Valid': + return $this->year !== 0; + // @call is Check if the current instance is in a daylight saving time. + case 'DST': + return $this->dst; + } + } + + $action = substr($unit, 0, 3); + $overflow = null; + + if ($action === 'set') { + $unit = strtolower(substr($unit, 3)); + } + + if (\in_array($unit, static::$units, true)) { + return $this->setUnit($unit, ...$parameters); + } + + if ($action === 'add' || $action === 'sub') { + $unit = substr($unit, 3); + + if (str_starts_with($unit, 'Real')) { + $unit = static::singularUnit(substr($unit, 4)); + + return $this->{"{$action}RealUnit"}($unit, ...$parameters); + } + + if (preg_match('/^(Month|Quarter|Year|Decade|Century|Centurie|Millennium|Millennia)s?(No|With|Without|WithNo)Overflow$/', $unit, $match)) { + $unit = $match[1]; + $overflow = $match[2] === 'With'; + } + + $unit = static::singularUnit($unit); + } + + if (static::isModifiableUnit($unit)) { + return $this->{"{$action}Unit"}($unit, $this->getMagicParameter($parameters, 0, 'value', 1), $overflow); + } + + $sixFirstLetters = substr($unit, 0, 6); + $factor = -1; + + if ($sixFirstLetters === 'isLast') { + $sixFirstLetters = 'isNext'; + $factor = 1; + } + + if ($sixFirstLetters === 'isNext') { + $lowerUnit = strtolower(substr($unit, 6)); + + if (static::isModifiableUnit($lowerUnit)) { + return $this->copy()->addUnit($lowerUnit, $factor, false)->isSameUnit($lowerUnit, ...$parameters); + } + } + + if ($sixFirstLetters === 'isSame') { + try { + return $this->isSameUnit(strtolower(substr($unit, 6)), ...$parameters); + } catch (BadComparisonUnitException $exception) { + // Try next + } + } + + if (str_starts_with($unit, 'isCurrent')) { + try { + return $this->isCurrentUnit(strtolower(substr($unit, 9))); + } catch (BadComparisonUnitException | BadMethodCallException $exception) { + // Try next + } + } + + if (str_ends_with($method, 'Until')) { + try { + $unit = static::singularUnit(substr($method, 0, -5)); + + return $this->range( + $this->getMagicParameter($parameters, 0, 'endDate', $this), + $this->getMagicParameter($parameters, 1, 'factor', 1), + $unit + ); + } catch (InvalidArgumentException $exception) { + // Try macros + } + } + + return static::bindMacroContext($this, function () use (&$method, &$parameters) { + $macro = $this->getLocalMacro($method); + + if (!$macro) { + foreach ([$this->localGenericMacros ?: [], static::getGenericMacros()] as $list) { + foreach ($list as $callback) { + try { + return $this->executeCallable($callback, $method, ...$parameters); + } catch (BadMethodCallException $exception) { + continue; + } + } + } + + if ($this->localStrictModeEnabled ?? static::isStrictModeEnabled()) { + throw new UnknownMethodException($method); + } + + return null; + } + + return $this->executeCallable($macro, ...$parameters); + }); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Traits/DeprecatedProperties.php b/vendor/nesbot/carbon/src/Carbon/Traits/DeprecatedProperties.php new file mode 100644 index 0000000..5acc6f5 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Traits/DeprecatedProperties.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +trait DeprecatedProperties +{ + /** + * the day of week in current locale LC_TIME + * + * @var string + * + * @deprecated It uses OS language package and strftime() which is deprecated since PHP 8.1. + * Use ->isoFormat('MMM') instead. + * Deprecated since 2.55.0 + */ + public $localeDayOfWeek; + + /** + * the abbreviated day of week in current locale LC_TIME + * + * @var string + * + * @deprecated It uses OS language package and strftime() which is deprecated since PHP 8.1. + * Use ->isoFormat('dddd') instead. + * Deprecated since 2.55.0 + */ + public $shortLocaleDayOfWeek; + + /** + * the month in current locale LC_TIME + * + * @var string + * + * @deprecated It uses OS language package and strftime() which is deprecated since PHP 8.1. + * Use ->isoFormat('ddd') instead. + * Deprecated since 2.55.0 + */ + public $localeMonth; + + /** + * the abbreviated month in current locale LC_TIME + * + * @var string + * + * @deprecated It uses OS language package and strftime() which is deprecated since PHP 8.1. + * Use ->isoFormat('MMMM') instead. + * Deprecated since 2.55.0 + */ + public $shortLocaleMonth; +} diff --git a/vendor/nesbot/carbon/src/Carbon/Traits/Difference.php b/vendor/nesbot/carbon/src/Carbon/Traits/Difference.php new file mode 100644 index 0000000..a9868eb --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Traits/Difference.php @@ -0,0 +1,1182 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use Carbon\Carbon; +use Carbon\CarbonImmutable; +use Carbon\CarbonInterface; +use Carbon\CarbonInterval; +use Carbon\CarbonPeriod; +use Carbon\Translator; +use Closure; +use DateInterval; +use DateTimeInterface; +use ReturnTypeWillChange; + +/** + * Trait Difference. + * + * Depends on the following methods: + * + * @method bool lessThan($date) + * @method static copy() + * @method static resolveCarbon($date = null) + * @method static Translator translator() + */ +trait Difference +{ + /** + * @codeCoverageIgnore + * + * @param CarbonInterval $diff + */ + protected static function fixNegativeMicroseconds(CarbonInterval $diff) + { + 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--; + } + } + } + } + } + + return; + } + + $diff->f *= -1; + $diff->invert(); + } + + /** + * @param DateInterval $diff + * @param bool $absolute + * + * @return CarbonInterval + */ + protected static function fixDiffInterval(DateInterval $diff, $absolute, array $skip = []) + { + $diff = CarbonInterval::instance($diff, $skip); + + // Work-around for https://bugs.php.net/bug.php?id=77145 + // @codeCoverageIgnoreStart + 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) { + static::fixNegativeMicroseconds($diff); + } + // @codeCoverageIgnoreEnd + + if ($absolute && $diff->invert) { + $diff->invert(); + } + + return $diff; + } + + /** + * Get the difference as a DateInterval instance. + * Return relative interval (negative if $absolute flag is not set to true and the given date is before + * current one). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return DateInterval + */ + #[ReturnTypeWillChange] + public function diff($date = null, $absolute = false) + { + $other = $this->resolveCarbon($date); + + // Work-around for https://bugs.php.net/bug.php?id=81458 + // It was initially introduced for https://bugs.php.net/bug.php?id=80998 + // The very specific case of 80998 was fixed in PHP 8.1beta3, but it introduced 81458 + // So we still need to keep this for now + // @codeCoverageIgnoreStart + if (version_compare(PHP_VERSION, '8.1.0-dev', '>=') && $other->tz !== $this->tz) { + $other = $other->avoidMutation()->tz($this->tz); + } + // @codeCoverageIgnoreEnd + + return parent::diff($other, (bool) $absolute); + } + + /** + * Get the difference as a CarbonInterval instance. + * Return relative interval (negative if $absolute flag is not set to true and the given date is before + * current one). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return CarbonInterval + */ + public function diffAsCarbonInterval($date = null, $absolute = true, array $skip = []) + { + return static::fixDiffInterval($this->diff($this->resolveCarbon($date), $absolute), $absolute, $skip); + } + + /** + * Get the difference in years + * + * @param \Carbon\CarbonInterface|\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 quarters rounded down. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInQuarters($date = null, $absolute = true) + { + return (int) ($this->diffInMonths($date, $absolute) / static::MONTHS_PER_QUARTER); + } + + /** + * Get the difference in months rounded down. + * + * @param \Carbon\CarbonInterface|\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)->avoidMutation()->tz($this->tz); + + [$yearStart, $monthStart, $dayStart] = explode('-', $this->format('Y-m-dHisu')); + [$yearEnd, $monthEnd, $dayEnd] = explode('-', $date->format('Y-m-dHisu')); + + $diff = (((int) $yearEnd) - ((int) $yearStart)) * static::MONTHS_PER_YEAR + + ((int) $monthEnd) - ((int) $monthStart); + + if ($diff > 0) { + $diff -= ($dayStart > $dayEnd ? 1 : 0); + } elseif ($diff < 0) { + $diff += ($dayStart < $dayEnd ? 1 : 0); + } + + return $absolute ? abs($diff) : $diff; + } + + /** + * Get the difference in weeks rounded down. + * + * @param \Carbon\CarbonInterface|\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 rounded down. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInDays($date = null, $absolute = true) + { + return $this->getIntervalDayDiff($this->diff($this->resolveCarbon($date), $absolute)); + } + + /** + * Get the difference in days using a filter closure rounded down. + * + * @param Closure $callback + * @param \Carbon\CarbonInterface|\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 rounded down. + * + * @param Closure $callback + * @param \Carbon\CarbonInterface|\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\CarbonInterface|\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; + } + + $options = CarbonPeriod::EXCLUDE_END_DATE | ($this->isMutable() ? 0 : CarbonPeriod::IMMUTABLE); + $diff = $ci->toPeriod($start, $end, $options)->filter($callback)->count(); + + return $inverse && !$absolute ? -$diff : $diff; + } + + /** + * Get the difference in weekdays rounded down. + * + * @param \Carbon\CarbonInterface|\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 (CarbonInterface $date) { + return $date->isWeekday(); + }, $date, $absolute); + } + + /** + * Get the difference in weekend days using a filter rounded down. + * + * @param \Carbon\CarbonInterface|\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 (CarbonInterface $date) { + return $date->isWeekend(); + }, $date, $absolute); + } + + /** + * Get the difference in hours rounded down. + * + * @param \Carbon\CarbonInterface|\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 rounded down using timestamps. + * + * @param \Carbon\CarbonInterface|\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 rounded down. + * + * @param \Carbon\CarbonInterface|\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 rounded down using timestamps. + * + * @param \Carbon\CarbonInterface|\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 rounded down. + * + * @param \Carbon\CarbonInterface|\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($date); + + if ($diff->days === 0) { + $diff = static::fixDiffInterval($diff, $absolute); + } + + $value = (((($diff->m || $diff->y ? $diff->days : $diff->d) * static::HOURS_PER_DAY) + + $diff->h) * static::MINUTES_PER_HOUR + + $diff->i) * static::SECONDS_PER_MINUTE + + $diff->s; + + return $absolute || !$diff->invert ? $value : -$value; + } + + /** + * Get the difference in microseconds. + * + * @param \Carbon\CarbonInterface|\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($date); + $value = (int) round(((((($diff->m || $diff->y ? $diff->days : $diff->d) * static::HOURS_PER_DAY) + + $diff->h) * static::MINUTES_PER_HOUR + + $diff->i) * static::SECONDS_PER_MINUTE + + ($diff->f + $diff->s)) * static::MICROSECONDS_PER_SECOND); + + return $absolute || !$diff->invert ? $value : -$value; + } + + /** + * Get the difference in milliseconds rounded down. + * + * @param \Carbon\CarbonInterface|\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 seconds using timestamps. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInRealSeconds($date = null, $absolute = true) + { + /** @var CarbonInterface $date */ + $date = $this->resolveCarbon($date); + $value = $date->getTimestamp() - $this->getTimestamp(); + + return $absolute ? abs($value) : $value; + } + + /** + * Get the difference in microseconds using timestamps. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInRealMicroseconds($date = null, $absolute = true) + { + /** @var CarbonInterface $date */ + $date = $this->resolveCarbon($date); + $value = ($date->timestamp - $this->timestamp) * static::MICROSECONDS_PER_SECOND + + $date->micro - $this->micro; + + return $absolute ? abs($value) : $value; + } + + /** + * Get the difference in milliseconds rounded down using timestamps. + * + * @param \Carbon\CarbonInterface|\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 seconds as float (microsecond-precision). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInSeconds($date = null, $absolute = true) + { + return (float) ($this->diffInMicroseconds($date, $absolute) / static::MICROSECONDS_PER_SECOND); + } + + /** + * Get the difference in minutes as float (microsecond-precision). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInMinutes($date = null, $absolute = true) + { + return $this->floatDiffInSeconds($date, $absolute) / static::SECONDS_PER_MINUTE; + } + + /** + * Get the difference in hours as float (microsecond-precision). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInHours($date = null, $absolute = true) + { + return $this->floatDiffInMinutes($date, $absolute) / static::MINUTES_PER_HOUR; + } + + /** + * Get the difference in days as float (microsecond-precision). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInDays($date = null, $absolute = true) + { + $hoursDiff = $this->floatDiffInHours($date, $absolute); + $interval = $this->diff($date, $absolute); + + if ($interval->y === 0 && $interval->m === 0 && $interval->d === 0) { + return $hoursDiff / static::HOURS_PER_DAY; + } + + $daysDiff = $this->getIntervalDayDiff($interval); + + return $daysDiff + fmod($hoursDiff, static::HOURS_PER_DAY) / static::HOURS_PER_DAY; + } + + /** + * Get the difference in weeks as float (microsecond-precision). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInWeeks($date = null, $absolute = true) + { + return $this->floatDiffInDays($date, $absolute) / static::DAYS_PER_WEEK; + } + + /** + * Get the difference in months as float (microsecond-precision). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInMonths($date = null, $absolute = true) + { + $start = $this; + $end = $this->resolveCarbon($date); + $ascending = ($start <= $end); + $sign = $absolute || $ascending ? 1 : -1; + if (!$ascending) { + [$start, $end] = [$end, $start]; + } + $monthsDiff = $start->diffInMonths($end); + /** @var Carbon|CarbonImmutable $floorEnd */ + $floorEnd = $start->avoidMutation()->addMonths($monthsDiff); + + if ($floorEnd >= $end) { + return $sign * $monthsDiff; + } + + /** @var Carbon|CarbonImmutable $startOfMonthAfterFloorEnd */ + $startOfMonthAfterFloorEnd = $floorEnd->avoidMutation()->addMonth()->startOfMonth(); + + if ($startOfMonthAfterFloorEnd > $end) { + return $sign * ($monthsDiff + $floorEnd->floatDiffInDays($end) / $floorEnd->daysInMonth); + } + + return $sign * ($monthsDiff + $floorEnd->floatDiffInDays($startOfMonthAfterFloorEnd) / $floorEnd->daysInMonth + $startOfMonthAfterFloorEnd->floatDiffInDays($end) / $end->daysInMonth); + } + + /** + * Get the difference in year as float (microsecond-precision). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInYears($date = null, $absolute = true) + { + $start = $this; + $end = $this->resolveCarbon($date); + $ascending = ($start <= $end); + $sign = $absolute || $ascending ? 1 : -1; + if (!$ascending) { + [$start, $end] = [$end, $start]; + } + $yearsDiff = $start->diffInYears($end); + /** @var Carbon|CarbonImmutable $floorEnd */ + $floorEnd = $start->avoidMutation()->addYears($yearsDiff); + + if ($floorEnd >= $end) { + return $sign * $yearsDiff; + } + + /** @var Carbon|CarbonImmutable $startOfYearAfterFloorEnd */ + $startOfYearAfterFloorEnd = $floorEnd->avoidMutation()->addYear()->startOfYear(); + + if ($startOfYearAfterFloorEnd > $end) { + return $sign * ($yearsDiff + $floorEnd->floatDiffInDays($end) / $floorEnd->daysInYear); + } + + return $sign * ($yearsDiff + $floorEnd->floatDiffInDays($startOfYearAfterFloorEnd) / $floorEnd->daysInYear + $startOfYearAfterFloorEnd->floatDiffInDays($end) / $end->daysInYear); + } + + /** + * Get the difference in seconds as float (microsecond-precision) using timestamps. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInRealSeconds($date = null, $absolute = true) + { + return $this->diffInRealMicroseconds($date, $absolute) / static::MICROSECONDS_PER_SECOND; + } + + /** + * Get the difference in minutes as float (microsecond-precision) using timestamps. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInRealMinutes($date = null, $absolute = true) + { + return $this->floatDiffInRealSeconds($date, $absolute) / static::SECONDS_PER_MINUTE; + } + + /** + * Get the difference in hours as float (microsecond-precision) using timestamps. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInRealHours($date = null, $absolute = true) + { + return $this->floatDiffInRealMinutes($date, $absolute) / static::MINUTES_PER_HOUR; + } + + /** + * Get the difference in days as float (microsecond-precision). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInRealDays($date = null, $absolute = true) + { + $date = $this->resolveUTC($date); + $utc = $this->avoidMutation()->utc(); + $hoursDiff = $utc->floatDiffInRealHours($date, $absolute); + + return ($hoursDiff < 0 ? -1 : 1) * $utc->diffInDays($date) + fmod($hoursDiff, static::HOURS_PER_DAY) / static::HOURS_PER_DAY; + } + + /** + * Get the difference in weeks as float (microsecond-precision). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInRealWeeks($date = null, $absolute = true) + { + return $this->floatDiffInRealDays($date, $absolute) / static::DAYS_PER_WEEK; + } + + /** + * Get the difference in months as float (microsecond-precision) using timestamps. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInRealMonths($date = null, $absolute = true) + { + $start = $this; + $end = $this->resolveCarbon($date); + $ascending = ($start <= $end); + $sign = $absolute || $ascending ? 1 : -1; + if (!$ascending) { + [$start, $end] = [$end, $start]; + } + $monthsDiff = $start->diffInMonths($end); + /** @var Carbon|CarbonImmutable $floorEnd */ + $floorEnd = $start->avoidMutation()->addMonths($monthsDiff); + + if ($floorEnd >= $end) { + return $sign * $monthsDiff; + } + + /** @var Carbon|CarbonImmutable $startOfMonthAfterFloorEnd */ + $startOfMonthAfterFloorEnd = $floorEnd->avoidMutation()->addMonth()->startOfMonth(); + + if ($startOfMonthAfterFloorEnd > $end) { + return $sign * ($monthsDiff + $floorEnd->floatDiffInRealDays($end) / $floorEnd->daysInMonth); + } + + return $sign * ($monthsDiff + $floorEnd->floatDiffInRealDays($startOfMonthAfterFloorEnd) / $floorEnd->daysInMonth + $startOfMonthAfterFloorEnd->floatDiffInRealDays($end) / $end->daysInMonth); + } + + /** + * Get the difference in year as float (microsecond-precision) using timestamps. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInRealYears($date = null, $absolute = true) + { + $start = $this; + $end = $this->resolveCarbon($date); + $ascending = ($start <= $end); + $sign = $absolute || $ascending ? 1 : -1; + if (!$ascending) { + [$start, $end] = [$end, $start]; + } + $yearsDiff = $start->diffInYears($end); + /** @var Carbon|CarbonImmutable $floorEnd */ + $floorEnd = $start->avoidMutation()->addYears($yearsDiff); + + if ($floorEnd >= $end) { + return $sign * $yearsDiff; + } + + /** @var Carbon|CarbonImmutable $startOfYearAfterFloorEnd */ + $startOfYearAfterFloorEnd = $floorEnd->avoidMutation()->addYear()->startOfYear(); + + if ($startOfYearAfterFloorEnd > $end) { + return $sign * ($yearsDiff + $floorEnd->floatDiffInRealDays($end) / $floorEnd->daysInYear); + } + + return $sign * ($yearsDiff + $floorEnd->floatDiffInRealDays($startOfYearAfterFloorEnd) / $floorEnd->daysInYear + $startOfYearAfterFloorEnd->floatDiffInRealDays($end) / $end->daysInYear); + } + + /** + * The number of seconds since midnight. + * + * @return int + */ + public function secondsSinceMidnight() + { + return $this->diffInSeconds($this->avoidMutation()->startOfDay()); + } + + /** + * The number of seconds until 23:59:59. + * + * @return int + */ + public function secondsUntilEndOfDay() + { + return $this->diffInSeconds($this->avoidMutation()->endOfDay()); + } + + /** + * Get the difference in a human readable format in the current locale from current instance to an other + * instance given (or now if null given). + * + * @example + * ``` + * echo Carbon::tomorrow()->diffForHumans() . "\n"; + * echo Carbon::tomorrow()->diffForHumans(['parts' => 2]) . "\n"; + * echo Carbon::tomorrow()->diffForHumans(['parts' => 3, 'join' => true]) . "\n"; + * echo Carbon::tomorrow()->diffForHumans(Carbon::yesterday()) . "\n"; + * echo Carbon::tomorrow()->diffForHumans(Carbon::yesterday(), ['short' => true]) . "\n"; + * ``` + * + * @param Carbon|\DateTimeInterface|string|array|null $other if array passed, will be used as parameters array, see $syntax below; + * if null passed, now will be used as comparison reference; + * if any other type, it will be converted to date and used as reference. + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'skip' entry, list of units to skip (array of strings or a single string, + * ` it can be the unit name (singular or plural) or its shortcut + * ` (y, m, w, d, h, min, s, ms, µs). + * - 'aUnit' entry, prefer "an hour" over "1 hour" if true + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * - 'other' entry (see above) + * - 'minimumUnit' entry determines the smallest unit of time to display can be long or + * ` short form of the units, e.g. 'hour' or 'h' (default value: s) + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single unit) + * @param int $options human diff options + * + * @return string + */ + public function diffForHumans($other = null, $syntax = null, $short = false, $parts = 1, $options = null) + { + /* @var CarbonInterface $this */ + if (\is_array($other)) { + $other['syntax'] = \array_key_exists('syntax', $other) ? $other['syntax'] : $syntax; + $syntax = $other; + $other = $syntax['other'] ?? null; + } + + $intSyntax = &$syntax; + if (\is_array($syntax)) { + $syntax['syntax'] = $syntax['syntax'] ?? null; + $intSyntax = &$syntax['syntax']; + } + $intSyntax = (int) ($intSyntax ?? static::DIFF_RELATIVE_AUTO); + $intSyntax = $intSyntax === static::DIFF_RELATIVE_AUTO && $other === null ? static::DIFF_RELATIVE_TO_NOW : $intSyntax; + + $parts = min(7, max(1, (int) $parts)); + $skip = \is_array($syntax) ? ($syntax['skip'] ?? []) : []; + + return $this->diffAsCarbonInterval($other, false, (array) $skip) + ->setLocalTranslator($this->getLocalTranslator()) + ->forHumans($syntax, (bool) $short, $parts, $options ?? $this->localHumanDiffOptions ?? static::getHumanDiffOptions()); + } + + /** + * @alias diffForHumans + * + * Get the difference in a human readable format in the current locale from current instance to an other + * instance given (or now if null given). + * + * @param Carbon|\DateTimeInterface|string|array|null $other if array passed, will be used as parameters array, see $syntax below; + * if null passed, now will be used as comparison reference; + * if any other type, it will be converted to date and used as reference. + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * - 'other' entry (see above) + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single unit) + * @param int $options human diff options + * + * @return string + */ + public function from($other = null, $syntax = null, $short = false, $parts = 1, $options = null) + { + return $this->diffForHumans($other, $syntax, $short, $parts, $options); + } + + /** + * @alias diffForHumans + * + * Get the difference in a human readable format in the current locale from current instance to an other + * instance given (or now if null given). + */ + public function since($other = null, $syntax = null, $short = false, $parts = 1, $options = null) + { + return $this->diffForHumans($other, $syntax, $short, $parts, $options); + } + + /** + * 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|\DateTimeInterface|string|array|null $other if array passed, will be used as parameters array, see $syntax below; + * if null passed, now will be used as comparison reference; + * if any other type, it will be converted to date and used as reference. + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * - 'other' entry (see above) + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single unit) + * @param int $options human diff options + * + * @return string + */ + public function to($other = null, $syntax = null, $short = false, $parts = 1, $options = null) + { + if (!$syntax && !$other) { + $syntax = CarbonInterface::DIFF_RELATIVE_TO_NOW; + } + + return $this->resolveCarbon($other)->diffForHumans($this, $syntax, $short, $parts, $options); + } + + /** + * @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|\DateTimeInterface|string|array|null $other if array passed, will be used as parameters array, see $syntax below; + * if null passed, now will be used as comparison reference; + * if any other type, it will be converted to date and used as reference. + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * - 'other' entry (see above) + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single unit) + * @param int $options human diff options + * + * @return string + */ + public function until($other = null, $syntax = null, $short = false, $parts = 1, $options = null) + { + return $this->to($other, $syntax, $short, $parts, $options); + } + + /** + * Get the difference in a human readable format in the current locale from current + * instance to now. + * + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single unit) + * @param int $options human diff options + * + * @return string + */ + public function fromNow($syntax = null, $short = false, $parts = 1, $options = null) + { + $other = null; + + if ($syntax instanceof DateTimeInterface) { + [$other, $syntax, $short, $parts, $options] = array_pad(\func_get_args(), 5, null); + } + + return $this->from($other, $syntax, $short, $parts, $options); + } + + /** + * Get the difference in a human readable format in the current locale from an other + * instance given to now + * + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single part) + * @param int $options human diff options + * + * @return string + */ + public function toNow($syntax = null, $short = false, $parts = 1, $options = null) + { + return $this->to(null, $syntax, $short, $parts, $options); + } + + /** + * Get the difference in a human readable format in the current locale from an other + * instance given to now + * + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single part) + * @param int $options human diff options + * + * @return string + */ + public function ago($syntax = null, $short = false, $parts = 1, $options = null) + { + $other = null; + + if ($syntax instanceof DateTimeInterface) { + [$other, $syntax, $short, $parts, $options] = array_pad(\func_get_args(), 5, null); + } + + return $this->from($other, $syntax, $short, $parts, $options); + } + + /** + * Get the difference in a human readable format in the current locale from current instance to an other + * instance given (or now if null given). + * + * @return string + */ + public function timespan($other = null, $timezone = null) + { + if (!$other instanceof DateTimeInterface) { + $other = static::parse($other, $timezone); + } + + return $this->diffForHumans($other, [ + 'join' => ', ', + 'syntax' => CarbonInterface::DIFF_ABSOLUTE, + 'options' => CarbonInterface::NO_ZERO_DIFF, + 'parts' => -1, + ]); + } + + /** + * Returns either day of week + time (e.g. "Last Friday at 3:30 PM") if reference time is within 7 days, + * or a calendar date (e.g. "10/29/2017") otherwise. + * + * Language, date and time formats will change according to the current locale. + * + * @param Carbon|\DateTimeInterface|string|null $referenceTime + * @param array $formats + * + * @return string + */ + public function calendar($referenceTime = null, array $formats = []) + { + /** @var CarbonInterface $current */ + $current = $this->avoidMutation()->startOfDay(); + /** @var CarbonInterface $other */ + $other = $this->resolveCarbon($referenceTime)->avoidMutation()->setTimezone($this->getTimezone())->startOfDay(); + $diff = $other->diffInDays($current, false); + $format = $diff < -6 ? 'sameElse' : ( + $diff < -1 ? 'lastWeek' : ( + $diff < 0 ? 'lastDay' : ( + $diff < 1 ? 'sameDay' : ( + $diff < 2 ? 'nextDay' : ( + $diff < 7 ? 'nextWeek' : 'sameElse' + ) + ) + ) + ) + ); + $format = array_merge($this->getCalendarFormats(), $formats)[$format]; + if ($format instanceof Closure) { + $format = $format($current, $other) ?? ''; + } + + return $this->isoFormat((string) $format); + } + + private function getIntervalDayDiff(DateInterval $interval): int + { + $daysDiff = (int) $interval->format('%a'); + $sign = $interval->format('%r') === '-' ? -1 : 1; + + if (\is_int($interval->days) && + $interval->y === 0 && + $interval->m === 0 && + version_compare(PHP_VERSION, '8.1.0-dev', '<') && + abs($interval->d - $daysDiff) === 1 + ) { + $daysDiff = abs($interval->d); // @codeCoverageIgnore + } + + return $daysDiff * $sign; + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Traits/IntervalRounding.php b/vendor/nesbot/carbon/src/Carbon/Traits/IntervalRounding.php new file mode 100644 index 0000000..4cd66b6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Traits/IntervalRounding.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use Carbon\CarbonInterval; +use Carbon\Exceptions\InvalidIntervalException; +use DateInterval; + +/** + * Trait to call rounding methods to interval or the interval of a period. + */ +trait IntervalRounding +{ + protected function callRoundMethod(string $method, array $parameters) + { + $action = substr($method, 0, 4); + + if ($action !== 'ceil') { + $action = substr($method, 0, 5); + } + + if (\in_array($action, ['round', 'floor', 'ceil'])) { + return $this->{$action.'Unit'}(substr($method, \strlen($action)), ...$parameters); + } + + return null; + } + + protected function roundWith($precision, $function) + { + $unit = 'second'; + + if ($precision instanceof DateInterval) { + $precision = (string) CarbonInterval::instance($precision); + } + + if (\is_string($precision) && preg_match('/^\s*(?\d+)?\s*(?\w+)(?\W.*)?$/', $precision, $match)) { + if (trim($match['other'] ?? '') !== '') { + throw new InvalidIntervalException('Rounding is only possible with single unit intervals.'); + } + + $precision = (int) ($match['precision'] ?: 1); + $unit = $match['unit']; + } + + return $this->roundUnit($unit, $precision, $function); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Traits/IntervalStep.php b/vendor/nesbot/carbon/src/Carbon/Traits/IntervalStep.php new file mode 100644 index 0000000..82d7c32 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Traits/IntervalStep.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use Carbon\Carbon; +use Carbon\CarbonImmutable; +use Carbon\CarbonInterface; +use Closure; +use DateTimeImmutable; +use DateTimeInterface; + +trait IntervalStep +{ + /** + * Step to apply instead of a fixed interval to get the new date. + * + * @var Closure|null + */ + protected $step; + + /** + * Get the dynamic step in use. + * + * @return Closure + */ + public function getStep(): ?Closure + { + return $this->step; + } + + /** + * Set a step to apply instead of a fixed interval to get the new date. + * + * Or pass null to switch to fixed interval. + * + * @param Closure|null $step + */ + public function setStep(?Closure $step): void + { + $this->step = $step; + } + + /** + * Take a date and apply either the step if set, or the current interval else. + * + * The interval/step is applied negatively (typically subtraction instead of addition) if $negated is true. + * + * @param DateTimeInterface $dateTime + * @param bool $negated + * + * @return CarbonInterface + */ + public function convertDate(DateTimeInterface $dateTime, bool $negated = false): CarbonInterface + { + /** @var CarbonInterface $carbonDate */ + $carbonDate = $dateTime instanceof CarbonInterface ? $dateTime : $this->resolveCarbon($dateTime); + + if ($this->step) { + return $carbonDate->setDateTimeFrom(($this->step)($carbonDate->avoidMutation(), $negated)); + } + + if ($negated) { + return $carbonDate->rawSub($this); + } + + return $carbonDate->rawAdd($this); + } + + /** + * Convert DateTimeImmutable instance to CarbonImmutable instance and DateTime instance to Carbon instance. + * + * @param DateTimeInterface $dateTime + * + * @return Carbon|CarbonImmutable + */ + private function resolveCarbon(DateTimeInterface $dateTime) + { + if ($dateTime instanceof DateTimeImmutable) { + return CarbonImmutable::instance($dateTime); + } + + return Carbon::instance($dateTime); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Traits/Localization.php b/vendor/nesbot/carbon/src/Carbon/Traits/Localization.php new file mode 100644 index 0000000..ff24956 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Traits/Localization.php @@ -0,0 +1,838 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use Carbon\CarbonInterface; +use Carbon\Exceptions\InvalidTypeException; +use Carbon\Exceptions\NotLocaleAwareException; +use Carbon\Language; +use Carbon\Translator; +use Carbon\TranslatorStrongTypeInterface; +use Closure; +use Symfony\Component\Translation\TranslatorBagInterface; +use Symfony\Component\Translation\TranslatorInterface; +use Symfony\Contracts\Translation\LocaleAwareInterface; +use Symfony\Contracts\Translation\TranslatorInterface as ContractsTranslatorInterface; + +if (interface_exists('Symfony\\Contracts\\Translation\\TranslatorInterface') && + !interface_exists('Symfony\\Component\\Translation\\TranslatorInterface') +) { + class_alias( + 'Symfony\\Contracts\\Translation\\TranslatorInterface', + 'Symfony\\Component\\Translation\\TranslatorInterface' + ); +} + +/** + * Trait Localization. + * + * Embed default and locale translators and translation base methods. + */ +trait Localization +{ + /** + * Default translator. + * + * @var \Symfony\Component\Translation\TranslatorInterface + */ + protected static $translator; + + /** + * Specific translator of the current instance. + * + * @var \Symfony\Component\Translation\TranslatorInterface + */ + protected $localTranslator; + + /** + * Options for diffForHumans(). + * + * @var int + */ + protected static $humanDiffOptions = CarbonInterface::NO_ZERO_DIFF; + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @see settings + * + * @param int $humanDiffOptions + */ + public static function setHumanDiffOptions($humanDiffOptions) + { + static::$humanDiffOptions = $humanDiffOptions; + } + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @see settings + * + * @param int $humanDiffOption + */ + public static function enableHumanDiffOption($humanDiffOption) + { + static::$humanDiffOptions = static::getHumanDiffOptions() | $humanDiffOption; + } + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @see settings + * + * @param int $humanDiffOption + */ + public static function disableHumanDiffOption($humanDiffOption) + { + static::$humanDiffOptions = static::getHumanDiffOptions() & ~$humanDiffOption; + } + + /** + * Return default humanDiff() options (merged flags as integer). + * + * @return int + */ + public static function getHumanDiffOptions() + { + return static::$humanDiffOptions; + } + + /** + * Get the default translator instance in use. + * + * @return \Symfony\Component\Translation\TranslatorInterface + */ + public static function getTranslator() + { + return static::translator(); + } + + /** + * Set the default translator instance to use. + * + * @param \Symfony\Component\Translation\TranslatorInterface $translator + * + * @return void + */ + public static function setTranslator(TranslatorInterface $translator) + { + static::$translator = $translator; + } + + /** + * Return true if the current instance has its own translator. + * + * @return bool + */ + public function hasLocalTranslator() + { + return isset($this->localTranslator); + } + + /** + * Get the translator of the current instance or the default if none set. + * + * @return \Symfony\Component\Translation\TranslatorInterface + */ + public function getLocalTranslator() + { + return $this->localTranslator ?: static::translator(); + } + + /** + * Set the translator for the current instance. + * + * @param \Symfony\Component\Translation\TranslatorInterface $translator + * + * @return $this + */ + public function setLocalTranslator(TranslatorInterface $translator) + { + $this->localTranslator = $translator; + + return $this; + } + + /** + * Returns raw translation message for a given key. + * + * @param \Symfony\Component\Translation\TranslatorInterface $translator the translator to use + * @param string $key key to find + * @param string|null $locale current locale used if null + * @param string|null $default default value if translation returns the key + * + * @return string + */ + public static function getTranslationMessageWith($translator, string $key, ?string $locale = null, ?string $default = null) + { + if (!($translator instanceof TranslatorBagInterface && $translator instanceof TranslatorInterface)) { + throw new InvalidTypeException( + 'Translator does not implement '.TranslatorInterface::class.' and '.TranslatorBagInterface::class.'. '. + (\is_object($translator) ? \get_class($translator) : \gettype($translator)).' has been given.' + ); + } + + if (!$locale && $translator instanceof LocaleAwareInterface) { + $locale = $translator->getLocale(); + } + + $result = self::getFromCatalogue($translator, $translator->getCatalogue($locale), $key); + + return $result === $key ? $default : $result; + } + + /** + * Returns raw translation message for a given key. + * + * @param string $key key to find + * @param string|null $locale current locale used if null + * @param string|null $default default value if translation returns the key + * @param \Symfony\Component\Translation\TranslatorInterface $translator an optional translator to use + * + * @return string + */ + public function getTranslationMessage(string $key, ?string $locale = null, ?string $default = null, $translator = null) + { + return static::getTranslationMessageWith($translator ?: $this->getLocalTranslator(), $key, $locale, $default); + } + + /** + * Translate using translation string or callback available. + * + * @param \Symfony\Component\Translation\TranslatorInterface $translator + * @param string $key + * @param array $parameters + * @param null $number + * + * @return string + */ + public static function translateWith(TranslatorInterface $translator, string $key, array $parameters = [], $number = null): string + { + $message = static::getTranslationMessageWith($translator, $key, null, $key); + if ($message instanceof Closure) { + return (string) $message(...array_values($parameters)); + } + + if ($number !== null) { + $parameters['%count%'] = $number; + } + if (isset($parameters['%count%'])) { + $parameters[':count'] = $parameters['%count%']; + } + + // @codeCoverageIgnoreStart + $choice = $translator instanceof ContractsTranslatorInterface + ? $translator->trans($key, $parameters) + : $translator->transChoice($key, $number, $parameters); + // @codeCoverageIgnoreEnd + + return (string) $choice; + } + + /** + * Translate using translation string or callback available. + * + * @param string $key + * @param array $parameters + * @param string|int|float|null $number + * @param \Symfony\Component\Translation\TranslatorInterface|null $translator + * @param bool $altNumbers + * + * @return string + */ + public function translate(string $key, array $parameters = [], $number = null, ?TranslatorInterface $translator = null, bool $altNumbers = false): string + { + $translation = static::translateWith($translator ?: $this->getLocalTranslator(), $key, $parameters, $number); + + if ($number !== null && $altNumbers) { + return str_replace($number, $this->translateNumber($number), $translation); + } + + return $translation; + } + + /** + * Returns the alternative number for a given integer if available in the current locale. + * + * @param int $number + * + * @return string + */ + public function translateNumber(int $number): string + { + $translateKey = "alt_numbers.$number"; + $symbol = $this->translate($translateKey); + + if ($symbol !== $translateKey) { + return $symbol; + } + + if ($number > 99 && $this->translate('alt_numbers.99') !== 'alt_numbers.99') { + $start = ''; + foreach ([10000, 1000, 100] as $exp) { + $key = "alt_numbers_pow.$exp"; + if ($number >= $exp && $number < $exp * 10 && ($pow = $this->translate($key)) !== $key) { + $unit = floor($number / $exp); + $number -= $unit * $exp; + $start .= ($unit > 1 ? $this->translate("alt_numbers.$unit") : '').$pow; + } + } + $result = ''; + while ($number) { + $chunk = $number % 100; + $result = $this->translate("alt_numbers.$chunk").$result; + $number = floor($number / 100); + } + + return "$start$result"; + } + + if ($number > 9 && $this->translate('alt_numbers.9') !== 'alt_numbers.9') { + $result = ''; + while ($number) { + $chunk = $number % 10; + $result = $this->translate("alt_numbers.$chunk").$result; + $number = floor($number / 10); + } + + return $result; + } + + return (string) $number; + } + + /** + * Translate a time string from a locale to an other. + * + * @param string $timeString date/time/duration string to translate (may also contain English) + * @param string|null $from input locale of the $timeString parameter (`Carbon::getLocale()` by default) + * @param string|null $to output locale of the result returned (`"en"` by default) + * @param int $mode specify what to translate with options: + * - CarbonInterface::TRANSLATE_ALL (default) + * - CarbonInterface::TRANSLATE_MONTHS + * - CarbonInterface::TRANSLATE_DAYS + * - CarbonInterface::TRANSLATE_UNITS + * - CarbonInterface::TRANSLATE_MERIDIEM + * You can use pipe to group: CarbonInterface::TRANSLATE_MONTHS | CarbonInterface::TRANSLATE_DAYS + * + * @return string + */ + public static function translateTimeString($timeString, $from = null, $to = null, $mode = CarbonInterface::TRANSLATE_ALL) + { + // Fallback source and destination locales + $from = $from ?: static::getLocale(); + $to = $to ?: 'en'; + + if ($from === $to) { + return $timeString; + } + + // Standardize apostrophe + $timeString = strtr($timeString, ['’' => "'"]); + + $fromTranslations = []; + $toTranslations = []; + + foreach (['from', 'to'] as $key) { + $language = $$key; + $translator = Translator::get($language); + $translations = $translator->getMessages(); + + if (!isset($translations[$language])) { + return $timeString; + } + + $translationKey = $key.'Translations'; + $messages = $translations[$language]; + $months = $messages['months'] ?? []; + $weekdays = $messages['weekdays'] ?? []; + $meridiem = $messages['meridiem'] ?? ['AM', 'PM']; + + if (isset($messages['ordinal_words'])) { + $timeString = self::replaceOrdinalWords( + $timeString, + $key === 'from' ? array_flip($messages['ordinal_words']) : $messages['ordinal_words'] + ); + } + + if ($key === 'from') { + foreach (['months', 'weekdays'] as $variable) { + $list = $messages[$variable.'_standalone'] ?? null; + + if ($list) { + foreach ($$variable as $index => &$name) { + $name .= '|'.$messages[$variable.'_standalone'][$index]; + } + } + } + } + + $$translationKey = array_merge( + $mode & CarbonInterface::TRANSLATE_MONTHS ? static::getTranslationArray($months, 12, $timeString) : [], + $mode & CarbonInterface::TRANSLATE_MONTHS ? static::getTranslationArray($messages['months_short'] ?? [], 12, $timeString) : [], + $mode & CarbonInterface::TRANSLATE_DAYS ? static::getTranslationArray($weekdays, 7, $timeString) : [], + $mode & CarbonInterface::TRANSLATE_DAYS ? static::getTranslationArray($messages['weekdays_short'] ?? [], 7, $timeString) : [], + $mode & CarbonInterface::TRANSLATE_DIFF ? static::translateWordsByKeys([ + 'diff_now', + 'diff_today', + 'diff_yesterday', + 'diff_tomorrow', + 'diff_before_yesterday', + 'diff_after_tomorrow', + ], $messages, $key) : [], + $mode & CarbonInterface::TRANSLATE_UNITS ? static::translateWordsByKeys([ + 'year', + 'month', + 'week', + 'day', + 'hour', + 'minute', + 'second', + ], $messages, $key) : [], + $mode & CarbonInterface::TRANSLATE_MERIDIEM ? array_map(function ($hour) use ($meridiem) { + if (\is_array($meridiem)) { + return $meridiem[$hour < 12 ? 0 : 1]; + } + + return $meridiem($hour, 0, false); + }, range(0, 23)) : [] + ); + } + + return substr(preg_replace_callback('/(?<=[\d\s+.\/,_-])('.implode('|', $fromTranslations).')(?=[\d\s+.\/,_-])/iu', function ($match) use ($fromTranslations, $toTranslations) { + [$chunk] = $match; + + foreach ($fromTranslations as $index => $word) { + if (preg_match("/^$word\$/iu", $chunk)) { + return $toTranslations[$index] ?? ''; + } + } + + return $chunk; // @codeCoverageIgnore + }, " $timeString "), 1, -1); + } + + /** + * Translate a time string from the current locale (`$date->locale()`) to an other. + * + * @param string $timeString time string to translate + * @param string|null $to output locale of the result returned ("en" by default) + * + * @return string + */ + public function translateTimeStringTo($timeString, $to = null) + { + return static::translateTimeString($timeString, $this->getTranslatorLocale(), $to); + } + + /** + * Get/set the locale for the current instance. + * + * @param string|null $locale + * @param string ...$fallbackLocales + * + * @return $this|string + */ + public function locale(string $locale = null, ...$fallbackLocales) + { + if ($locale === null) { + return $this->getTranslatorLocale(); + } + + if (!$this->localTranslator || $this->getTranslatorLocale($this->localTranslator) !== $locale) { + $translator = Translator::get($locale); + + if (!empty($fallbackLocales)) { + $translator->setFallbackLocales($fallbackLocales); + + foreach ($fallbackLocales as $fallbackLocale) { + $messages = Translator::get($fallbackLocale)->getMessages(); + + if (isset($messages[$fallbackLocale])) { + $translator->setMessages($fallbackLocale, $messages[$fallbackLocale]); + } + } + } + + $this->localTranslator = $translator; + } + + return $this; + } + + /** + * Get the current translator locale. + * + * @return string + */ + public static function getLocale() + { + return static::getLocaleAwareTranslator()->getLocale(); + } + + /** + * Set the current translator locale and indicate if the source locale file exists. + * Pass 'auto' as locale to use closest language from the current LC_TIME locale. + * + * @param string $locale locale ex. en + * + * @return bool + */ + public static function setLocale($locale) + { + return static::getLocaleAwareTranslator()->setLocale($locale) !== false; + } + + /** + * Set the fallback locale. + * + * @see https://symfony.com/doc/current/components/translation.html#fallback-locales + * + * @param string $locale + */ + public static function setFallbackLocale($locale) + { + $translator = static::getTranslator(); + + if (method_exists($translator, 'setFallbackLocales')) { + $translator->setFallbackLocales([$locale]); + + if ($translator instanceof Translator) { + $preferredLocale = $translator->getLocale(); + $translator->setMessages($preferredLocale, array_replace_recursive( + $translator->getMessages()[$locale] ?? [], + Translator::get($locale)->getMessages()[$locale] ?? [], + $translator->getMessages($preferredLocale) + )); + } + } + } + + /** + * Get the fallback locale. + * + * @see https://symfony.com/doc/current/components/translation.html#fallback-locales + * + * @return string|null + */ + public static function getFallbackLocale() + { + $translator = static::getTranslator(); + + if (method_exists($translator, 'getFallbackLocales')) { + return $translator->getFallbackLocales()[0] ?? null; + } + + return null; + } + + /** + * 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 + * @param callable $func + * + * @return mixed + */ + public static function executeWithLocale($locale, $func) + { + $currentLocale = static::getLocale(); + $result = $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 = static::translateWith($translator, 'y')) !== 'y' && $y !== static::translateWith($translator, 'year'))) || ( + ($y = static::translateWith($translator, 'd')) !== 'd' && + $y !== static::translateWith($translator, 'day') + ) || ( + ($y = static::translateWith($translator, 'h')) !== 'h' && + $y !== static::translateWith($translator, '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) { + if (!$newLocale) { + return false; + } + + foreach (['ago', 'from_now', 'before', 'after'] as $key) { + if ($translator instanceof TranslatorBagInterface && + self::getFromCatalogue($translator, $translator->getCatalogue($newLocale), $key) instanceof Closure + ) { + continue; + } + + if ($translator->trans($key) === $key) { + return false; + } + } + + return true; + }); + } + + /** + * 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::getLocaleAwareTranslator(); + + return $translator instanceof Translator + ? $translator->getAvailableLocales() + : [$translator->getLocale()]; + } + + /** + * Returns list of Language object for each available locale. This object allow you to get the ISO name, native + * name, region and variant of the locale. + * + * @return Language[] + */ + public static function getAvailableLocalesInfo() + { + $languages = []; + foreach (static::getAvailableLocales() as $id) { + $languages[$id] = new Language($id); + } + + return $languages; + } + + /** + * Initialize the default 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 locale of a given translator. + * + * If null or omitted, current local translator is used. + * If no local translator is in use, current global translator is used. + * + * @param null $translator + * + * @return string|null + */ + protected function getTranslatorLocale($translator = null): ?string + { + if (\func_num_args() === 0) { + $translator = $this->getLocalTranslator(); + } + + $translator = static::getLocaleAwareTranslator($translator); + + return $translator ? $translator->getLocale() : null; + } + + /** + * Throw an error if passed object is not LocaleAwareInterface. + * + * @param LocaleAwareInterface|null $translator + * + * @return LocaleAwareInterface|null + */ + protected static function getLocaleAwareTranslator($translator = null) + { + if (\func_num_args() === 0) { + $translator = static::translator(); + } + + if ($translator && !($translator instanceof LocaleAwareInterface || method_exists($translator, 'getLocale'))) { + throw new NotLocaleAwareException($translator); // @codeCoverageIgnore + } + + return $translator; + } + + /** + * @param mixed $translator + * @param \Symfony\Component\Translation\MessageCatalogueInterface $catalogue + * + * @return mixed + */ + private static function getFromCatalogue($translator, $catalogue, string $id, string $domain = 'messages') + { + return $translator instanceof TranslatorStrongTypeInterface + ? $translator->getFromCatalogue($catalogue, $id, $domain) // @codeCoverageIgnore + : $catalogue->get($id, $domain); + } + + /** + * Return the word cleaned from its translation codes. + * + * @param string $word + * + * @return string + */ + private static function cleanWordFromTranslationString($word) + { + $word = str_replace([':count', '%count', ':time'], '', $word); + $word = strtr($word, ['’' => "'"]); + $word = preg_replace('/({\d+(,(\d+|Inf))?}|[\[\]]\d+(,(\d+|Inf))?[\[\]])/', '', $word); + + return trim($word); + } + + /** + * Translate a list of words. + * + * @param string[] $keys keys to translate. + * @param string[] $messages messages bag handling translations. + * @param string $key 'to' (to get the translation) or 'from' (to get the detection RegExp pattern). + * + * @return string[] + */ + private static function translateWordsByKeys($keys, $messages, $key): array + { + return array_map(function ($wordKey) use ($messages, $key) { + $message = $key === 'from' && isset($messages[$wordKey.'_regexp']) + ? $messages[$wordKey.'_regexp'] + : ($messages[$wordKey] ?? null); + + if (!$message) { + return '>>DO NOT REPLACE<<'; + } + + $parts = explode('|', $message); + + return $key === 'to' + ? self::cleanWordFromTranslationString(end($parts)) + : '(?:'.implode('|', array_map([static::class, 'cleanWordFromTranslationString'], $parts)).')'; + }, $keys); + } + + /** + * Get an array of translations based on the current date. + * + * @param callable $translation + * @param int $length + * @param string $timeString + * + * @return string[] + */ + private static function getTranslationArray($translation, $length, $timeString): array + { + $filler = '>>DO NOT REPLACE<<'; + + if (\is_array($translation)) { + return array_pad($translation, $length, $filler); + } + + $list = []; + $date = static::now(); + + for ($i = 0; $i < $length; $i++) { + $list[] = $translation($date, $timeString, $i) ?? $filler; + } + + return $list; + } + + private static function replaceOrdinalWords(string $timeString, array $ordinalWords): string + { + return preg_replace_callback('/(? + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +/** + * Trait Macros. + * + * Allows users to register macros within the Carbon class. + */ +trait Macro +{ + use Mixin; + + /** + * The registered macros. + * + * @var array + */ + protected static $globalMacros = []; + + /** + * The registered generic macros. + * + * @var array + */ + protected static $globalGenericMacros = []; + + /** + * Register a custom macro. + * + * @example + * ``` + * $userSettings = [ + * 'locale' => 'pt', + * 'timezone' => 'America/Sao_Paulo', + * ]; + * Carbon::macro('userFormat', function () use ($userSettings) { + * return $this->copy()->locale($userSettings['locale'])->tz($userSettings['timezone'])->calendar(); + * }); + * echo Carbon::yesterday()->hours(11)->userFormat(); + * ``` + * + * @param string $name + * @param object|callable $macro + * + * @return void + */ + public static function macro($name, $macro) + { + static::$globalMacros[$name] = $macro; + } + + /** + * Remove all macros and generic macros. + */ + public static function resetMacros() + { + static::$globalMacros = []; + static::$globalGenericMacros = []; + } + + /** + * Register a custom macro. + * + * @param object|callable $macro + * @param int $priority marco with higher priority is tried first + * + * @return void + */ + public static function genericMacro($macro, $priority = 0) + { + if (!isset(static::$globalGenericMacros[$priority])) { + static::$globalGenericMacros[$priority] = []; + krsort(static::$globalGenericMacros, SORT_NUMERIC); + } + + static::$globalGenericMacros[$priority][] = $macro; + } + + /** + * Checks if macro is registered globally. + * + * @param string $name + * + * @return bool + */ + public static function hasMacro($name) + { + return isset(static::$globalMacros[$name]); + } + + /** + * Get the raw callable macro registered globally for a given name. + * + * @param string $name + * + * @return callable|null + */ + public static function getMacro($name) + { + return static::$globalMacros[$name] ?? null; + } + + /** + * Checks if macro is registered globally or locally. + * + * @param string $name + * + * @return bool + */ + public function hasLocalMacro($name) + { + return ($this->localMacros && isset($this->localMacros[$name])) || static::hasMacro($name); + } + + /** + * Get the raw callable macro registered globally or locally for a given name. + * + * @param string $name + * + * @return callable|null + */ + public function getLocalMacro($name) + { + return ($this->localMacros ?? [])[$name] ?? static::getMacro($name); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Traits/MagicParameter.php b/vendor/nesbot/carbon/src/Carbon/Traits/MagicParameter.php new file mode 100644 index 0000000..310a44d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Traits/MagicParameter.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 Carbon\Traits; + +/** + * Trait MagicParameter. + * + * Allows to retrieve parameter in magic calls by index or name. + */ +trait MagicParameter +{ + private function getMagicParameter(array $parameters, int $index, string $key, $default) + { + if (\array_key_exists($index, $parameters)) { + return $parameters[$index]; + } + + if (\array_key_exists($key, $parameters)) { + return $parameters[$key]; + } + + return $default; + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Traits/Mixin.php b/vendor/nesbot/carbon/src/Carbon/Traits/Mixin.php new file mode 100644 index 0000000..595c287 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Traits/Mixin.php @@ -0,0 +1,181 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use Closure; +use Generator; +use ReflectionClass; +use ReflectionException; +use ReflectionMethod; +use Throwable; + +/** + * Trait Mixin. + * + * Allows mixing in entire classes with multiple macros. + */ +trait Mixin +{ + /** + * Stack of macro instance contexts. + * + * @var array + */ + protected static $macroContextStack = []; + + /** + * Mix another object into the class. + * + * @example + * ``` + * Carbon::mixin(new class { + * public function addMoon() { + * return function () { + * return $this->addDays(30); + * }; + * } + * public function subMoon() { + * return function () { + * return $this->subDays(30); + * }; + * } + * }); + * $fullMoon = Carbon::create('2018-12-22'); + * $nextFullMoon = $fullMoon->addMoon(); + * $blackMoon = Carbon::create('2019-01-06'); + * $previousBlackMoon = $blackMoon->subMoon(); + * echo "$nextFullMoon\n"; + * echo "$previousBlackMoon\n"; + * ``` + * + * @param object|string $mixin + * + * @throws ReflectionException + * + * @return void + */ + public static function mixin($mixin) + { + \is_string($mixin) && trait_exists($mixin) + ? self::loadMixinTrait($mixin) + : self::loadMixinClass($mixin); + } + + /** + * @param object|string $mixin + * + * @throws ReflectionException + */ + private static function loadMixinClass($mixin) + { + $methods = (new ReflectionClass($mixin))->getMethods( + ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED + ); + + foreach ($methods as $method) { + if ($method->isConstructor() || $method->isDestructor()) { + continue; + } + + $method->setAccessible(true); + + static::macro($method->name, $method->invoke($mixin)); + } + } + + /** + * @param string $trait + */ + private static function loadMixinTrait($trait) + { + $context = eval(self::getAnonymousClassCodeForTrait($trait)); + $className = \get_class($context); + + foreach (self::getMixableMethods($context) as $name) { + $closureBase = Closure::fromCallable([$context, $name]); + + static::macro($name, function () use ($closureBase, $className) { + /** @phpstan-ignore-next-line */ + $context = isset($this) ? $this->cast($className) : new $className(); + + try { + // @ is required to handle error if not converted into exceptions + $closure = @$closureBase->bindTo($context); + } catch (Throwable $throwable) { // @codeCoverageIgnore + $closure = $closureBase; // @codeCoverageIgnore + } + + // in case of errors not converted into exceptions + $closure = $closure ?: $closureBase; + + return $closure(...\func_get_args()); + }); + } + } + + private static function getAnonymousClassCodeForTrait(string $trait) + { + return 'return new class() extends '.static::class.' {use '.$trait.';};'; + } + + private static function getMixableMethods(self $context): Generator + { + foreach (get_class_methods($context) as $name) { + if (method_exists(static::class, $name)) { + continue; + } + + yield $name; + } + } + + /** + * Stack a Carbon context from inside calls of self::this() and execute a given action. + * + * @param static|null $context + * @param callable $callable + * + * @throws Throwable + * + * @return mixed + */ + protected static function bindMacroContext($context, callable $callable) + { + static::$macroContextStack[] = $context; + + try { + return $callable(); + } finally { + array_pop(static::$macroContextStack); + } + } + + /** + * Return the current context from inside a macro callee or a null if static. + * + * @return static|null + */ + protected static function context() + { + return end(static::$macroContextStack) ?: null; + } + + /** + * Return the current context from inside a macro callee or a new one if static. + * + * @return static + */ + protected static function this() + { + return end(static::$macroContextStack) ?: new static(); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Traits/Modifiers.php b/vendor/nesbot/carbon/src/Carbon/Traits/Modifiers.php new file mode 100644 index 0000000..39343d8 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Traits/Modifiers.php @@ -0,0 +1,472 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use Carbon\CarbonInterface; +use ReturnTypeWillChange; + +/** + * Trait Modifiers. + * + * Returns dates relative to current date using modifier short-hand. + */ +trait Modifiers +{ + /** + * Midday/noon hour. + * + * @var int + */ + protected static $midDayAt = 12; + + /** + * get midday/noon hour + * + * @return int + */ + public static function getMidDayAt() + { + return static::$midDayAt; + } + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather consider mid-day is always 12pm, then if you need to test if it's an other + * hour, test it explicitly: + * $date->format('G') == 13 + * or to set explicitly to a given hour: + * $date->setTime(13, 0, 0, 0) + * + * Set midday/noon hour + * + * @param int $hour midday hour + * + * @return void + */ + public static function setMidDayAt($hour) + { + static::$midDayAt = $hour; + } + + /** + * Modify to midday, default to self::$midDayAt + * + * @return static + */ + public function midDay() + { + return $this->setTime(static::$midDayAt, 0, 0, 0); + } + + /** + * Modify to the next occurrence of a given modifier such as a day of + * the week. If no modifier 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 string|int|null $modifier + * + * @return static|false + */ + public function next($modifier = null) + { + if ($modifier === null) { + $modifier = $this->dayOfWeek; + } + + return $this->change( + 'next '.(\is_string($modifier) ? $modifier : static::$days[$modifier]) + ); + } + + /** + * Go forward or backward to the next week- or weekend-day. + * + * @param bool $weekday + * @param bool $forward + * + * @return static + */ + private function nextOrPreviousDay($weekday = true, $forward = true) + { + /** @var CarbonInterface $date */ + $date = $this; + $step = $forward ? 1 : -1; + + do { + $date = $date->addDays($step); + } while ($weekday ? $date->isWeekend() : $date->isWeekday()); + + return $date; + } + + /** + * Go forward to the next weekday. + * + * @return static + */ + public function nextWeekday() + { + return $this->nextOrPreviousDay(); + } + + /** + * Go backward to the previous weekday. + * + * @return static + */ + public function previousWeekday() + { + return $this->nextOrPreviousDay(true, false); + } + + /** + * Go forward to the next weekend day. + * + * @return static + */ + public function nextWeekendDay() + { + return $this->nextOrPreviousDay(false); + } + + /** + * Go backward to the previous weekend day. + * + * @return static + */ + public function previousWeekendDay() + { + return $this->nextOrPreviousDay(false, false); + } + + /** + * Modify to the previous occurrence of a given modifier such as a 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 string|int|null $modifier + * + * @return static|false + */ + public function previous($modifier = null) + { + if ($modifier === null) { + $modifier = $this->dayOfWeek; + } + + return $this->change( + 'last '.(\is_string($modifier) ? $modifier : static::$days[$modifier]) + ); + } + + /** + * 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) + { + $date = $this->startOfDay(); + + if ($dayOfWeek === null) { + return $date->day(1); + } + + return $date->modify('first '.static::$days[$dayOfWeek].' of '.$date->rawFormat('F').' '.$date->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) + { + $date = $this->startOfDay(); + + if ($dayOfWeek === null) { + return $date->day($date->daysInMonth); + } + + return $date->modify('last '.static::$days[$dayOfWeek].' of '.$date->rawFormat('F').' '.$date->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->avoidMutation()->firstOfMonth(); + $check = $date->rawFormat('Y-m'); + $date = $date->modify('+'.$nth.' '.static::$days[$dayOfWeek]); + + return $date->rawFormat('Y-m') === $check ? $this->modify((string) $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->avoidMutation()->day(1)->month($this->quarter * static::MONTHS_PER_QUARTER); + $lastMonth = $date->month; + $year = $date->year; + $date = $date->firstOfQuarter()->modify('+'.$nth.' '.static::$days[$dayOfWeek]); + + return ($lastMonth < $date->month || $year !== $date->year) ? false : $this->modify((string) $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->avoidMutation()->firstOfYear()->modify('+'.$nth.' '.static::$days[$dayOfWeek]); + + return $this->year === $date->year ? $this->modify((string) $date) : false; + } + + /** + * Modify the current instance to the average of a given instance (default now) and the current instance + * (second-precision). + * + * @param \Carbon\Carbon|\DateTimeInterface|null $date + * + * @return static + */ + public function average($date = null) + { + return $this->addRealMicroseconds((int) ($this->diffInRealMicroseconds($this->resolveCarbon($date), false) / 2)); + } + + /** + * Get the closest date from the instance (second-precision). + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date1 + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date2 + * + * @return static + */ + public function closest($date1, $date2) + { + return $this->diffInRealMicroseconds($date1) < $this->diffInRealMicroseconds($date2) ? $date1 : $date2; + } + + /** + * Get the farthest date from the instance (second-precision). + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date1 + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date2 + * + * @return static + */ + public function farthest($date1, $date2) + { + return $this->diffInRealMicroseconds($date1) > $this->diffInRealMicroseconds($date2) ? $date1 : $date2; + } + + /** + * Get the minimum instance between a given instance (default now) and the current instance. + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $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|mixed $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); + } + + /** + * Calls \DateTime::modify if mutable or \DateTimeImmutable::modify else. + * + * @see https://php.net/manual/en/datetime.modify.php + * + * @return static|false + */ + #[ReturnTypeWillChange] + public function modify($modify) + { + return parent::modify((string) $modify); + } + + /** + * Similar to native modify() method of DateTime but can handle more grammars. + * + * @example + * ``` + * echo Carbon::now()->change('next 2pm'); + * ``` + * + * @link https://php.net/manual/en/datetime.modify.php + * + * @param string $modifier + * + * @return static|false + */ + public function change($modifier) + { + return $this->modify(preg_replace_callback('/^(next|previous|last)\s+(\d{1,2}(h|am|pm|:\d{1,2}(:\d{1,2})?))$/i', function ($match) { + $match[2] = str_replace('h', ':00', $match[2]); + $test = $this->avoidMutation()->modify($match[2]); + $method = $match[1] === 'next' ? 'lt' : 'gt'; + $match[1] = $test->$method($this) ? $match[1].' day' : 'today'; + + return $match[1].' '.$match[2]; + }, strtr(trim($modifier), [ + ' at ' => ' ', + 'just now' => 'now', + 'after tomorrow' => 'tomorrow +1 day', + 'before yesterday' => 'yesterday -1 day', + ]))); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Traits/Mutability.php b/vendor/nesbot/carbon/src/Carbon/Traits/Mutability.php new file mode 100644 index 0000000..561c867 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Traits/Mutability.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use Carbon\Carbon; +use Carbon\CarbonImmutable; + +/** + * Trait Mutability. + * + * Utils to know if the current object is mutable or immutable and convert it. + */ +trait Mutability +{ + use Cast; + + /** + * Returns true if the current class/instance is mutable. + * + * @return bool + */ + public static function isMutable() + { + return false; + } + + /** + * Returns true if the current class/instance is immutable. + * + * @return bool + */ + public static function isImmutable() + { + return !static::isMutable(); + } + + /** + * Return a mutable copy of the instance. + * + * @return Carbon + */ + public function toMutable() + { + /** @var Carbon $date */ + $date = $this->cast(Carbon::class); + + return $date; + } + + /** + * Return a immutable copy of the instance. + * + * @return CarbonImmutable + */ + public function toImmutable() + { + /** @var CarbonImmutable $date */ + $date = $this->cast(CarbonImmutable::class); + + return $date; + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Traits/ObjectInitialisation.php b/vendor/nesbot/carbon/src/Carbon/Traits/ObjectInitialisation.php new file mode 100644 index 0000000..c77a102 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Traits/ObjectInitialisation.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +trait ObjectInitialisation +{ + /** + * True when parent::__construct has been called. + * + * @var string + */ + protected $constructedObjectId; +} diff --git a/vendor/nesbot/carbon/src/Carbon/Traits/Options.php b/vendor/nesbot/carbon/src/Carbon/Traits/Options.php new file mode 100644 index 0000000..6f02194 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Traits/Options.php @@ -0,0 +1,471 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use Carbon\CarbonInterface; +use DateTimeInterface; +use Throwable; + +/** + * Trait Options. + * + * Embed base methods to change settings of Carbon classes. + * + * Depends on the following methods: + * + * @method static shiftTimezone($timezone) Set the timezone + */ +trait Options +{ + use Localization; + + /** + * Customizable PHP_INT_SIZE override. + * + * @var int + */ + public static $PHPIntSize = PHP_INT_SIZE; + + /** + * First day of week. + * + * @var int|string + */ + protected static $weekStartsAt = CarbonInterface::MONDAY; + + /** + * Last day of week. + * + * @var int|string + */ + protected static $weekEndsAt = CarbonInterface::SUNDAY; + + /** + * Days of weekend. + * + * @var array + */ + protected static $weekendDays = [ + CarbonInterface::SATURDAY, + CarbonInterface::SUNDAY, + ]; + + /** + * Format regex patterns. + * + * @var array + */ + protected static $regexFormats = [ + 'd' => '(3[01]|[12][0-9]|0[1-9])', + 'D' => '(Sun|Mon|Tue|Wed|Thu|Fri|Sat)', + 'j' => '([123][0-9]|[1-9])', + 'l' => '([a-zA-Z]{2,})', + 'N' => '([1-7])', + 'S' => '(st|nd|rd|th)', + '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]|0?[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])', + 'p' => '(Z|[+-](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]|0[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, d M Y H:i:s O + ]; + + /** + * Format modifiers (such as available in createFromFormat) regex patterns. + * + * @var array + */ + protected static $regexFormatModifiers = [ + '*' => '.+', + ' ' => '[ ]', + '#' => '[;:\\/.,()-]', + '?' => '([^a]|[a])', + '!' => '', + '|' => '', + '+' => '', + ]; + + /** + * Indicates if months should be calculated with overflow. + * Global setting. + * + * @var bool + */ + protected static $monthsOverflow = true; + + /** + * Indicates if years should be calculated with overflow. + * Global setting. + * + * @var bool + */ + protected static $yearsOverflow = true; + + /** + * Indicates if the strict mode is in use. + * Global setting. + * + * @var bool + */ + protected static $strictModeEnabled = true; + + /** + * Function to call instead of format. + * + * @var string|callable|null + */ + protected static $formatFunction; + + /** + * Function to call instead of createFromFormat. + * + * @var string|callable|null + */ + protected static $createFromFormatFunction; + + /** + * Function to call instead of parse. + * + * @var string|callable|null + */ + protected static $parseFunction; + + /** + * Indicates if months should be calculated with overflow. + * Specific setting. + * + * @var bool|null + */ + protected $localMonthsOverflow; + + /** + * Indicates if years should be calculated with overflow. + * Specific setting. + * + * @var bool|null + */ + protected $localYearsOverflow; + + /** + * Indicates if the strict mode is in use. + * Specific setting. + * + * @var bool|null + */ + protected $localStrictModeEnabled; + + /** + * Options for diffForHumans and forHumans methods. + * + * @var bool|null + */ + protected $localHumanDiffOptions; + + /** + * Format to use on string cast. + * + * @var string|null + */ + protected $localToStringFormat; + + /** + * Format to use on JSON serialization. + * + * @var string|null + */ + protected $localSerializer; + + /** + * Instance-specific macros. + * + * @var array|null + */ + protected $localMacros; + + /** + * Instance-specific generic macros. + * + * @var array|null + */ + protected $localGenericMacros; + + /** + * Function to call instead of format. + * + * @var string|callable|null + */ + protected $localFormatFunction; + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @see settings + * + * Enable the strict mode (or disable with passing false). + * + * @param bool $strictModeEnabled + */ + public static function useStrictMode($strictModeEnabled = true) + { + static::$strictModeEnabled = $strictModeEnabled; + } + + /** + * Returns true if the strict mode is globally in use, false else. + * (It can be overridden in specific instances.) + * + * @return bool + */ + public static function isStrictModeEnabled() + { + return static::$strictModeEnabled; + } + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addMonthsWithOverflow/addMonthsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @see settings + * + * Indicates if months should be calculated with overflow. + * + * @param bool $monthsOverflow + * + * @return void + */ + public static function useMonthsOverflow($monthsOverflow = true) + { + static::$monthsOverflow = $monthsOverflow; + } + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addMonthsWithOverflow/addMonthsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @see settings + * + * Reset the month overflow behavior. + * + * @return void + */ + public static function resetMonthsOverflow() + { + static::$monthsOverflow = true; + } + + /** + * Get the month overflow global behavior (can be overridden in specific instances). + * + * @return bool + */ + public static function shouldOverflowMonths() + { + return static::$monthsOverflow; + } + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addYearsWithOverflow/addYearsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @see settings + * + * Indicates if years should be calculated with overflow. + * + * @param bool $yearsOverflow + * + * @return void + */ + public static function useYearsOverflow($yearsOverflow = true) + { + static::$yearsOverflow = $yearsOverflow; + } + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addYearsWithOverflow/addYearsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @see settings + * + * Reset the month overflow behavior. + * + * @return void + */ + public static function resetYearsOverflow() + { + static::$yearsOverflow = true; + } + + /** + * Get the month overflow global behavior (can be overridden in specific instances). + * + * @return bool + */ + public static function shouldOverflowYears() + { + return static::$yearsOverflow; + } + + /** + * Set specific options. + * - strictMode: true|false|null + * - monthOverflow: true|false|null + * - yearOverflow: true|false|null + * - humanDiffOptions: int|null + * - toStringFormat: string|Closure|null + * - toJsonFormat: string|Closure|null + * - locale: string|null + * - timezone: \DateTimeZone|string|int|null + * - macros: array|null + * - genericMacros: array|null + * + * @param array $settings + * + * @return $this|static + */ + public function settings(array $settings) + { + $this->localStrictModeEnabled = $settings['strictMode'] ?? null; + $this->localMonthsOverflow = $settings['monthOverflow'] ?? null; + $this->localYearsOverflow = $settings['yearOverflow'] ?? null; + $this->localHumanDiffOptions = $settings['humanDiffOptions'] ?? null; + $this->localToStringFormat = $settings['toStringFormat'] ?? null; + $this->localSerializer = $settings['toJsonFormat'] ?? null; + $this->localMacros = $settings['macros'] ?? null; + $this->localGenericMacros = $settings['genericMacros'] ?? null; + $this->localFormatFunction = $settings['formatFunction'] ?? null; + + if (isset($settings['locale'])) { + $locales = $settings['locale']; + + if (!\is_array($locales)) { + $locales = [$locales]; + } + + $this->locale(...$locales); + } + + if (isset($settings['innerTimezone'])) { + return $this->setTimezone($settings['innerTimezone']); + } + + if (isset($settings['timezone'])) { + return $this->shiftTimezone($settings['timezone']); + } + + return $this; + } + + /** + * Returns current local settings. + * + * @return array + */ + public function getSettings() + { + $settings = []; + $map = [ + 'localStrictModeEnabled' => 'strictMode', + 'localMonthsOverflow' => 'monthOverflow', + 'localYearsOverflow' => 'yearOverflow', + 'localHumanDiffOptions' => 'humanDiffOptions', + 'localToStringFormat' => 'toStringFormat', + 'localSerializer' => 'toJsonFormat', + 'localMacros' => 'macros', + 'localGenericMacros' => 'genericMacros', + 'locale' => 'locale', + 'tzName' => 'timezone', + 'localFormatFunction' => 'formatFunction', + ]; + + foreach ($map as $property => $key) { + $value = $this->$property ?? null; + + if ($value !== null) { + $settings[$key] = $value; + } + } + + return $settings; + } + + /** + * Show truthy properties on var_dump(). + * + * @return array + */ + public function __debugInfo() + { + $infos = array_filter(get_object_vars($this), function ($var) { + return $var; + }); + + foreach (['dumpProperties', 'constructedObjectId', 'constructed'] as $property) { + if (isset($infos[$property])) { + unset($infos[$property]); + } + } + + $this->addExtraDebugInfos($infos); + + return $infos; + } + + protected function addExtraDebugInfos(&$infos): void + { + if ($this instanceof DateTimeInterface) { + try { + if (!isset($infos['date'])) { + $infos['date'] = $this->format(CarbonInterface::MOCK_DATETIME_FORMAT); + } + + if (!isset($infos['timezone'])) { + $infos['timezone'] = $this->tzName; + } + } catch (Throwable $exception) { + // noop + } + } + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Traits/Rounding.php b/vendor/nesbot/carbon/src/Carbon/Traits/Rounding.php new file mode 100644 index 0000000..f98c2a3 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Traits/Rounding.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 Carbon\Traits; + +use Carbon\CarbonInterface; +use Carbon\Exceptions\UnknownUnitException; + +/** + * Trait Rounding. + * + * Round, ceil, floor units. + * + * Depends on the following methods: + * + * @method static copy() + * @method static startOfWeek(int $weekStartsAt = null) + */ +trait Rounding +{ + use IntervalRounding; + + /** + * Round the current instance at the given unit with given precision if specified and the given function. + * + * @param string $unit + * @param float|int $precision + * @param string $function + * + * @return CarbonInterface + */ + public function roundUnit($unit, $precision = 1, $function = 'round') + { + $metaUnits = [ + // @call roundUnit + 'millennium' => [static::YEARS_PER_MILLENNIUM, 'year'], + // @call roundUnit + 'century' => [static::YEARS_PER_CENTURY, 'year'], + // @call roundUnit + 'decade' => [static::YEARS_PER_DECADE, 'year'], + // @call roundUnit + 'quarter' => [static::MONTHS_PER_QUARTER, 'month'], + // @call roundUnit + 'millisecond' => [1000, 'microsecond'], + ]; + $normalizedUnit = static::singularUnit($unit); + $ranges = array_merge(static::getRangesByUnit($this->daysInMonth), [ + // @call roundUnit + 'microsecond' => [0, 999999], + ]); + $factor = 1; + $initialMonth = $this->month; + + if ($normalizedUnit === 'week') { + $normalizedUnit = 'day'; + $precision *= static::DAYS_PER_WEEK; + } + + if (isset($metaUnits[$normalizedUnit])) { + [$factor, $normalizedUnit] = $metaUnits[$normalizedUnit]; + } + + $precision *= $factor; + + if (!isset($ranges[$normalizedUnit])) { + throw new UnknownUnitException($unit); + } + + $found = false; + $fraction = 0; + $arguments = null; + $initialValue = null; + $factor = $this->year < 0 ? -1 : 1; + $changes = []; + $minimumInc = null; + + foreach ($ranges as $unit => [$minimum, $maximum]) { + if ($normalizedUnit === $unit) { + $arguments = [$this->$unit, $minimum]; + $initialValue = $this->$unit; + $fraction = $precision - floor($precision); + $found = true; + + continue; + } + + if ($found) { + $delta = $maximum + 1 - $minimum; + $factor /= $delta; + $fraction *= $delta; + $inc = ($this->$unit - $minimum) * $factor; + + if ($inc !== 0.0) { + $minimumInc = $minimumInc ?? ($arguments[0] / pow(2, 52)); + + // If value is still the same when adding a non-zero increment/decrement, + // it means precision got lost in the addition + if (abs($inc) < $minimumInc) { + $inc = $minimumInc * ($inc < 0 ? -1 : 1); + } + + // If greater than $precision, assume precision loss caused an overflow + if ($function !== 'floor' || abs($arguments[0] + $inc - $initialValue) >= $precision) { + $arguments[0] += $inc; + } + } + + $changes[$unit] = round( + $minimum + ($fraction ? $fraction * $function(($this->$unit - $minimum) / $fraction) : 0) + ); + + // Cannot use modulo as it lose double precision + while ($changes[$unit] >= $delta) { + $changes[$unit] -= $delta; + } + + $fraction -= floor($fraction); + } + } + + [$value, $minimum] = $arguments; + $normalizedValue = floor($function(($value - $minimum) / $precision) * $precision + $minimum); + + /** @var CarbonInterface $result */ + $result = $this->$normalizedUnit($normalizedValue); + + foreach ($changes as $unit => $value) { + $result = $result->$unit($value); + } + + return $normalizedUnit === 'month' && $precision <= 1 && abs($result->month - $initialMonth) === 2 + // Re-run the change in case an overflow occurred + ? $result->$normalizedUnit($normalizedValue) + : $result; + } + + /** + * Truncate the current instance at the given unit with given precision if specified. + * + * @param string $unit + * @param float|int $precision + * + * @return CarbonInterface + */ + public function floorUnit($unit, $precision = 1) + { + return $this->roundUnit($unit, $precision, 'floor'); + } + + /** + * Ceil the current instance at the given unit with given precision if specified. + * + * @param string $unit + * @param float|int $precision + * + * @return CarbonInterface + */ + public function ceilUnit($unit, $precision = 1) + { + return $this->roundUnit($unit, $precision, 'ceil'); + } + + /** + * Round the current instance second with given precision if specified. + * + * @param float|int|string|\DateInterval|null $precision + * @param string $function + * + * @return CarbonInterface + */ + public function round($precision = 1, $function = 'round') + { + return $this->roundWith($precision, $function); + } + + /** + * Round the current instance second with given precision if specified. + * + * @param float|int|string|\DateInterval|null $precision + * + * @return CarbonInterface + */ + public function floor($precision = 1) + { + return $this->round($precision, 'floor'); + } + + /** + * Ceil the current instance second with given precision if specified. + * + * @param float|int|string|\DateInterval|null $precision + * + * @return CarbonInterface + */ + public function ceil($precision = 1) + { + return $this->round($precision, 'ceil'); + } + + /** + * Round the current instance week. + * + * @param int $weekStartsAt optional start allow you to specify the day of week to use to start the week + * + * @return CarbonInterface + */ + public function roundWeek($weekStartsAt = null) + { + return $this->closest( + $this->avoidMutation()->floorWeek($weekStartsAt), + $this->avoidMutation()->ceilWeek($weekStartsAt) + ); + } + + /** + * Truncate the current instance week. + * + * @param int $weekStartsAt optional start allow you to specify the day of week to use to start the week + * + * @return CarbonInterface + */ + public function floorWeek($weekStartsAt = null) + { + return $this->startOfWeek($weekStartsAt); + } + + /** + * Ceil the current instance week. + * + * @param int $weekStartsAt optional start allow you to specify the day of week to use to start the week + * + * @return CarbonInterface + */ + public function ceilWeek($weekStartsAt = null) + { + if ($this->isMutable()) { + $startOfWeek = $this->avoidMutation()->startOfWeek($weekStartsAt); + + return $startOfWeek != $this ? + $this->startOfWeek($weekStartsAt)->addWeek() : + $this; + } + + $startOfWeek = $this->startOfWeek($weekStartsAt); + + return $startOfWeek != $this ? + $startOfWeek->addWeek() : + $this->avoidMutation(); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Traits/Serialization.php b/vendor/nesbot/carbon/src/Carbon/Traits/Serialization.php new file mode 100644 index 0000000..53fead6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Traits/Serialization.php @@ -0,0 +1,326 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use Carbon\Exceptions\InvalidFormatException; +use ReturnTypeWillChange; +use Throwable; + +/** + * Trait Serialization. + * + * Serialization and JSON stuff. + * + * Depends on the following properties: + * + * @property int $year + * @property int $month + * @property int $daysInMonth + * @property int $quarter + * + * Depends on the following methods: + * + * @method string|static locale(string $locale = null, string ...$fallbackLocales) + * @method string toJSON() + */ +trait Serialization +{ + use ObjectInitialisation; + + /** + * The custom Carbon JSON serializer. + * + * @var callable|null + */ + protected static $serializer; + + /** + * List of key to use for dump/serialization. + * + * @var string[] + */ + protected $dumpProperties = ['date', 'timezone_type', 'timezone']; + + /** + * Locale to dump comes here before serialization. + * + * @var string|null + */ + protected $dumpLocale; + + /** + * Embed date properties to dump in a dedicated variables so it won't overlap native + * DateTime ones. + * + * @var array|null + */ + protected $dumpDateProperties; + + /** + * 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 InvalidFormatException + * + * @return static + */ + public static function fromSerialized($value) + { + $instance = @unserialize((string) $value); + + if (!$instance instanceof static) { + throw new InvalidFormatException("Invalid serialized value: $value"); + } + + return $instance; + } + + /** + * The __set_state handler. + * + * @param string|array $dump + * + * @return static + */ + #[ReturnTypeWillChange] + public static function __set_state($dump) + { + if (\is_string($dump)) { + return static::parse($dump); + } + + /** @var \DateTimeInterface $date */ + $date = get_parent_class(static::class) && method_exists(parent::class, '__set_state') + ? parent::__set_state((array) $dump) + : (object) $dump; + + return static::instance($date); + } + + /** + * Returns the list of properties to dump on serialize() called on. + * + * Only used by PHP < 7.4. + * + * @return array + */ + public function __sleep() + { + $properties = $this->getSleepProperties(); + + if ($this->localTranslator ?? null) { + $properties[] = 'dumpLocale'; + $this->dumpLocale = $this->locale ?? null; + } + + return $properties; + } + + /** + * Returns the values to dump on serialize() called on. + * + * Only used by PHP >= 7.4. + * + * @return array + */ + public function __serialize(): array + { + // @codeCoverageIgnoreStart + if (isset($this->timezone_type)) { + return [ + 'date' => $this->date ?? null, + 'timezone_type' => $this->timezone_type, + 'timezone' => $this->timezone ?? null, + ]; + } + // @codeCoverageIgnoreEnd + + $timezone = $this->getTimezone(); + $export = [ + 'date' => $this->format('Y-m-d H:i:s.u'), + 'timezone_type' => $timezone->getType(), + 'timezone' => $timezone->getName(), + ]; + + // @codeCoverageIgnoreStart + if (\extension_loaded('msgpack') && isset($this->constructedObjectId)) { + $export['dumpDateProperties'] = [ + 'date' => $this->format('Y-m-d H:i:s.u'), + 'timezone' => serialize($this->timezone ?? null), + ]; + } + // @codeCoverageIgnoreEnd + + if ($this->localTranslator ?? null) { + $export['dumpLocale'] = $this->locale ?? null; + } + + return $export; + } + + /** + * Set locale if specified on unserialize() called. + * + * Only used by PHP < 7.4. + * + * @return void + */ + #[ReturnTypeWillChange] + public function __wakeup() + { + if (parent::class && method_exists(parent::class, '__wakeup')) { + // @codeCoverageIgnoreStart + try { + parent::__wakeup(); + } catch (Throwable $exception) { + try { + // FatalError occurs when calling msgpack_unpack() in PHP 7.4 or later. + ['date' => $date, 'timezone' => $timezone] = $this->dumpDateProperties; + parent::__construct($date, unserialize($timezone)); + } catch (Throwable $ignoredException) { + throw $exception; + } + } + // @codeCoverageIgnoreEnd + } + + $this->constructedObjectId = spl_object_hash($this); + + if (isset($this->dumpLocale)) { + $this->locale($this->dumpLocale); + $this->dumpLocale = null; + } + + $this->cleanupDumpProperties(); + } + + /** + * Set locale if specified on unserialize() called. + * + * Only used by PHP >= 7.4. + * + * @return void + */ + public function __unserialize(array $data): void + { + // @codeCoverageIgnoreStart + try { + $this->__construct($data['date'] ?? null, $data['timezone'] ?? null); + } catch (Throwable $exception) { + if (!isset($data['dumpDateProperties']['date'], $data['dumpDateProperties']['timezone'])) { + throw $exception; + } + + try { + // FatalError occurs when calling msgpack_unpack() in PHP 7.4 or later. + ['date' => $date, 'timezone' => $timezone] = $data['dumpDateProperties']; + $this->__construct($date, unserialize($timezone)); + } catch (Throwable $ignoredException) { + throw $exception; + } + } + // @codeCoverageIgnoreEnd + + if (isset($data['dumpLocale'])) { + $this->locale($data['dumpLocale']); + } + } + + /** + * Prepare the object for JSON serialization. + * + * @return array|string + */ + #[ReturnTypeWillChange] + public function jsonSerialize() + { + $serializer = $this->localSerializer ?? static::$serializer; + + if ($serializer) { + return \is_string($serializer) + ? $this->rawFormat($serializer) + : $serializer($this); + } + + return $this->toJSON(); + } + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather transform Carbon object before the serialization. + * + * JSON serialize all Carbon instances using the given callback. + * + * @param callable $callback + * + * @return void + */ + public static function serializeUsing($callback) + { + static::$serializer = $callback; + } + + /** + * Cleanup properties attached to the public scope of DateTime when a dump of the date is requested. + * foreach ($date as $_) {} + * serializer($date) + * var_export($date) + * get_object_vars($date) + */ + public function cleanupDumpProperties() + { + // @codeCoverageIgnoreStart + if (PHP_VERSION < 8.2) { + foreach ($this->dumpProperties as $property) { + if (isset($this->$property)) { + unset($this->$property); + } + } + } + // @codeCoverageIgnoreEnd + + return $this; + } + + private function getSleepProperties(): array + { + $properties = $this->dumpProperties; + + // @codeCoverageIgnoreStart + if (!\extension_loaded('msgpack')) { + return $properties; + } + + if (isset($this->constructedObjectId)) { + $this->dumpDateProperties = [ + 'date' => $this->format('Y-m-d H:i:s.u'), + 'timezone' => serialize($this->timezone ?? null), + ]; + + $properties[] = 'dumpDateProperties'; + } + + return $properties; + // @codeCoverageIgnoreEnd + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Traits/Test.php b/vendor/nesbot/carbon/src/Carbon/Traits/Test.php new file mode 100644 index 0000000..ffd01bd --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Traits/Test.php @@ -0,0 +1,229 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use Carbon\CarbonInterface; +use Carbon\CarbonTimeZone; +use Closure; +use DateTimeImmutable; +use DateTimeInterface; +use InvalidArgumentException; +use Throwable; + +trait Test +{ + /////////////////////////////////////////////////////////////////// + ///////////////////////// TESTING AIDS //////////////////////////// + /////////////////////////////////////////////////////////////////// + + /** + * A test Carbon instance to be returned when now instances are created. + * + * @var Closure|static|null + */ + protected static $testNow; + + /** + * The timezone to resto to when clearing the time mock. + * + * @var string|null + */ + protected static $testDefaultTimezone; + + /** + * 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. + * + * Only the moment is mocked with setTestNow(), the timezone will still be the one passed + * as parameter of date_default_timezone_get() as a fallback (see setTestNowAndTimezone()). + * + * To clear the test instance call this method using the default + * parameter of null. + * + * /!\ Use this method for unit tests only. + * + * @param DateTimeInterface|Closure|static|string|false|null $testNow real or mock Carbon instance + */ + public static function setTestNow($testNow = null) + { + static::$testNow = $testNow instanceof self || $testNow instanceof Closure + ? $testNow + : static::make($testNow); + } + + /** + * 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(). + * + * It will also align default timezone (e.g. call date_default_timezone_set()) with + * the second argument or if null, with the timezone of the given date object. + * + * To clear the test instance call this method using the default + * parameter of null. + * + * /!\ Use this method for unit tests only. + * + * @param DateTimeInterface|Closure|static|string|false|null $testNow real or mock Carbon instance + */ + public static function setTestNowAndTimezone($testNow = null, $tz = null) + { + if ($testNow) { + self::$testDefaultTimezone = self::$testDefaultTimezone ?? date_default_timezone_get(); + } + + $useDateInstanceTimezone = $testNow instanceof DateTimeInterface; + + if ($useDateInstanceTimezone) { + self::setDefaultTimezone($testNow->getTimezone()->getName(), $testNow); + } + + static::setTestNow($testNow); + + if (!$useDateInstanceTimezone) { + $now = static::getMockedTestNow(\func_num_args() === 1 ? null : $tz); + $tzName = $now ? $now->tzName : null; + self::setDefaultTimezone($tzName ?? self::$testDefaultTimezone ?? 'UTC', $now); + } + + if (!$testNow) { + self::$testDefaultTimezone = null; + } + } + + /** + * Temporarily sets a static date to be used within the callback. + * Using setTestNow to set the date, executing the callback, then + * clearing the test instance. + * + * /!\ Use this method for unit tests only. + * + * @template T + * + * @param DateTimeInterface|Closure|static|string|false|null $testNow real or mock Carbon instance + * @param Closure(): T $callback + * + * @return mixed + * @phpstan-return T + */ + public static function withTestNow($testNow, $callback) + { + static::setTestNow($testNow); + + try { + $result = $callback(); + } finally { + static::setTestNow(); + } + + return $result; + } + + /** + * Get the Carbon instance (real or mock) to be returned when a "now" + * instance is created. + * + * @return Closure|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; + } + + /** + * Get the mocked date passed in setTestNow() and if it's a Closure, execute it. + * + * @param string|\DateTimeZone $tz + * + * @return \Carbon\CarbonImmutable|\Carbon\Carbon|null + */ + protected static function getMockedTestNow($tz) + { + $testNow = static::getTestNow(); + + if ($testNow instanceof Closure) { + $realNow = new DateTimeImmutable('now'); + $testNow = $testNow(static::parse( + $realNow->format('Y-m-d H:i:s.u'), + $tz ?: $realNow->getTimezone() + )); + } + /* @var \Carbon\CarbonImmutable|\Carbon\Carbon|null $testNow */ + + return $testNow instanceof CarbonInterface + ? $testNow->avoidMutation()->tz($tz) + : $testNow; + } + + protected static function mockConstructorParameters(&$time, $tz) + { + /** @var \Carbon\CarbonImmutable|\Carbon\Carbon $testInstance */ + $testInstance = clone static::getMockedTestNow($tz); + + if (static::hasRelativeKeywords($time)) { + $testInstance = $testInstance->modify($time); + } + + $time = $testInstance instanceof self + ? $testInstance->rawFormat(static::MOCK_DATETIME_FORMAT) + : $testInstance->format(static::MOCK_DATETIME_FORMAT); + } + + private static function setDefaultTimezone($timezone, DateTimeInterface $date = null) + { + $previous = null; + $success = false; + + try { + $success = date_default_timezone_set($timezone); + } catch (Throwable $exception) { + $previous = $exception; + } + + if (!$success) { + $suggestion = @CarbonTimeZone::create($timezone)->toRegionName($date); + + throw new InvalidArgumentException( + "Timezone ID '$timezone' is invalid". + ($suggestion && $suggestion !== $timezone ? ", did you mean '$suggestion'?" : '.')."\n". + "It must be one of the IDs from DateTimeZone::listIdentifiers(),\n". + 'For the record, hours/minutes offset are relevant only for a particular moment, '. + 'but not as a default timezone.', + 0, + $previous + ); + } + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Traits/Timestamp.php b/vendor/nesbot/carbon/src/Carbon/Traits/Timestamp.php new file mode 100644 index 0000000..88a465c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Traits/Timestamp.php @@ -0,0 +1,198 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +/** + * Trait Timestamp. + */ +trait Timestamp +{ + /** + * Create a Carbon instance from a timestamp and set the timezone (use default one if not specified). + * + * Timestamp input can be given as int, float or a string containing one or more numbers. + * + * @param float|int|string $timestamp + * @param \DateTimeZone|string|null $tz + * + * @return static + */ + public static function createFromTimestamp($timestamp, $tz = null) + { + return static::createFromTimestampUTC($timestamp)->setTimezone($tz); + } + + /** + * Create a Carbon instance from an timestamp keeping the timezone to UTC. + * + * Timestamp input can be given as int, float or a string containing one or more numbers. + * + * @param float|int|string $timestamp + * + * @return static + */ + public static function createFromTimestampUTC($timestamp) + { + [$integer, $decimal] = self::getIntegerAndDecimalParts($timestamp); + $delta = floor($decimal / static::MICROSECONDS_PER_SECOND); + $integer += $delta; + $decimal -= $delta * static::MICROSECONDS_PER_SECOND; + $decimal = str_pad((string) $decimal, 6, '0', STR_PAD_LEFT); + + return static::rawCreateFromFormat('U u', "$integer $decimal"); + } + + /** + * Create a Carbon instance from a timestamp in milliseconds. + * + * Timestamp input can be given as int, float or a string containing one or more numbers. + * + * @param float|int|string $timestamp + * + * @return static + */ + public static function createFromTimestampMsUTC($timestamp) + { + [$milliseconds, $microseconds] = self::getIntegerAndDecimalParts($timestamp, 3); + $sign = $milliseconds < 0 || ($milliseconds === 0.0 && $microseconds < 0) ? -1 : 1; + $milliseconds = abs($milliseconds); + $microseconds = $sign * abs($microseconds) + static::MICROSECONDS_PER_MILLISECOND * ($milliseconds % static::MILLISECONDS_PER_SECOND); + $seconds = $sign * floor($milliseconds / static::MILLISECONDS_PER_SECOND); + $delta = floor($microseconds / static::MICROSECONDS_PER_SECOND); + $seconds += $delta; + $microseconds -= $delta * static::MICROSECONDS_PER_SECOND; + $microseconds = str_pad($microseconds, 6, '0', STR_PAD_LEFT); + + return static::rawCreateFromFormat('U u', "$seconds $microseconds"); + } + + /** + * Create a Carbon instance from a timestamp in milliseconds. + * + * Timestamp input can be given as int, float or a string containing one or more numbers. + * + * @param float|int|string $timestamp + * @param \DateTimeZone|string|null $tz + * + * @return static + */ + public static function createFromTimestampMs($timestamp, $tz = null) + { + return static::createFromTimestampMsUTC($timestamp) + ->setTimezone($tz); + } + + /** + * Set the instance's timestamp. + * + * Timestamp input can be given as int, float or a string containing one or more numbers. + * + * @param float|int|string $unixTimestamp + * + * @return static + */ + public function timestamp($unixTimestamp) + { + return $this->setTimestamp($unixTimestamp); + } + + /** + * Returns a timestamp rounded with the given precision (6 by default). + * + * @example getPreciseTimestamp() 1532087464437474 (microsecond maximum precision) + * @example getPreciseTimestamp(6) 1532087464437474 + * @example getPreciseTimestamp(5) 153208746443747 (1/100000 second precision) + * @example getPreciseTimestamp(4) 15320874644375 (1/10000 second precision) + * @example getPreciseTimestamp(3) 1532087464437 (millisecond precision) + * @example getPreciseTimestamp(2) 153208746444 (1/100 second precision) + * @example getPreciseTimestamp(1) 15320874644 (1/10 second precision) + * @example getPreciseTimestamp(0) 1532087464 (second precision) + * @example getPreciseTimestamp(-1) 153208746 (10 second precision) + * @example getPreciseTimestamp(-2) 15320875 (100 second precision) + * + * @param int $precision + * + * @return float + */ + public function getPreciseTimestamp($precision = 6) + { + return round(((float) $this->rawFormat('Uu')) / pow(10, 6 - $precision)); + } + + /** + * Returns the milliseconds timestamps used amongst other by Date javascript objects. + * + * @return float + */ + public function valueOf() + { + return $this->getPreciseTimestamp(3); + } + + /** + * Returns the timestamp with millisecond precision. + * + * @return int + */ + public function getTimestampMs() + { + return (int) $this->getPreciseTimestamp(3); + } + + /** + * @alias getTimestamp + * + * Returns the UNIX timestamp for the current date. + * + * @return int + */ + public function unix() + { + return $this->getTimestamp(); + } + + /** + * Return an array with integer part digits and decimals digits split from one or more positive numbers + * (such as timestamps) as string with the given number of decimals (6 by default). + * + * By splitting integer and decimal, this method obtain a better precision than + * number_format when the input is a string. + * + * @param float|int|string $numbers one or more numbers + * @param int $decimals number of decimals precision (6 by default) + * + * @return array 0-index is integer part, 1-index is decimal part digits + */ + private static function getIntegerAndDecimalParts($numbers, $decimals = 6) + { + if (\is_int($numbers) || \is_float($numbers)) { + $numbers = number_format($numbers, $decimals, '.', ''); + } + + $sign = str_starts_with($numbers, '-') ? -1 : 1; + $integer = 0; + $decimal = 0; + + foreach (preg_split('`[^\d.]+`', $numbers) as $chunk) { + [$integerPart, $decimalPart] = explode('.', "$chunk."); + + $integer += (int) $integerPart; + $decimal += (float) ("0.$decimalPart"); + } + + $overflow = floor($decimal); + $integer += $overflow; + $decimal -= $overflow; + + return [$sign * $integer, $decimal === 0.0 ? 0.0 : $sign * round($decimal * pow(10, $decimals))]; + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Traits/ToStringFormat.php b/vendor/nesbot/carbon/src/Carbon/Traits/ToStringFormat.php new file mode 100644 index 0000000..a81164f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Traits/ToStringFormat.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use Closure; + +/** + * Trait ToStringFormat. + * + * Handle global format customization for string cast of the object. + */ +trait ToStringFormat +{ + /** + * Format to use for __toString method when type juggling occurs. + * + * @var string|Closure|null + */ + protected static $toStringFormat; + + /** + * Reset the format used to the default when type juggling a Carbon instance to a string + * + * @return void + */ + public static function resetToStringFormat() + { + static::setToStringFormat(null); + } + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather let Carbon object being cast to string with DEFAULT_TO_STRING_FORMAT, and + * use other method or custom format passed to format() method if you need to dump another string + * format. + * + * Set the default format used when type juggling a Carbon instance to a string. + * + * @param string|Closure|null $format + * + * @return void + */ + public static function setToStringFormat($format) + { + static::$toStringFormat = $format; + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Traits/Units.php b/vendor/nesbot/carbon/src/Carbon/Traits/Units.php new file mode 100644 index 0000000..7aadd65 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Traits/Units.php @@ -0,0 +1,412 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use Carbon\CarbonConverterInterface; +use Carbon\CarbonInterface; +use Carbon\CarbonInterval; +use Carbon\Exceptions\UnitException; +use Closure; +use DateInterval; +use DateMalformedStringException; +use ReturnTypeWillChange; + +/** + * Trait Units. + * + * Add, subtract and set units. + */ +trait Units +{ + /** + * Add seconds to the instance using timestamp. Positive $value travels + * forward while negative $value travels into the past. + * + * @param string $unit + * @param int $value + * + * @return static + */ + public function addRealUnit($unit, $value = 1) + { + switch ($unit) { + // @call addRealUnit + case 'micro': + + // @call addRealUnit + case 'microsecond': + /* @var CarbonInterface $this */ + $diff = $this->microsecond + $value; + $time = $this->getTimestamp(); + $seconds = (int) floor($diff / static::MICROSECONDS_PER_SECOND); + $time += $seconds; + $diff -= $seconds * static::MICROSECONDS_PER_SECOND; + $microtime = str_pad((string) $diff, 6, '0', STR_PAD_LEFT); + $tz = $this->tz; + + return $this->tz('UTC')->modify("@$time.$microtime")->tz($tz); + + // @call addRealUnit + case 'milli': + // @call addRealUnit + case 'millisecond': + return $this->addRealUnit('microsecond', $value * static::MICROSECONDS_PER_MILLISECOND); + + // @call addRealUnit + case 'second': + break; + + // @call addRealUnit + case 'minute': + $value *= static::SECONDS_PER_MINUTE; + + break; + + // @call addRealUnit + case 'hour': + $value *= static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE; + + break; + + // @call addRealUnit + case 'day': + $value *= static::HOURS_PER_DAY * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE; + + break; + + // @call addRealUnit + case 'week': + $value *= static::DAYS_PER_WEEK * static::HOURS_PER_DAY * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE; + + break; + + // @call addRealUnit + case 'month': + $value *= 30 * static::HOURS_PER_DAY * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE; + + break; + + // @call addRealUnit + case 'quarter': + $value *= static::MONTHS_PER_QUARTER * 30 * static::HOURS_PER_DAY * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE; + + break; + + // @call addRealUnit + case 'year': + $value *= 365 * static::HOURS_PER_DAY * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE; + + break; + + // @call addRealUnit + case 'decade': + $value *= static::YEARS_PER_DECADE * 365 * static::HOURS_PER_DAY * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE; + + break; + + // @call addRealUnit + case 'century': + $value *= static::YEARS_PER_CENTURY * 365 * static::HOURS_PER_DAY * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE; + + break; + + // @call addRealUnit + case 'millennium': + $value *= static::YEARS_PER_MILLENNIUM * 365 * static::HOURS_PER_DAY * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE; + + break; + + default: + if ($this->localStrictModeEnabled ?? static::isStrictModeEnabled()) { + throw new UnitException("Invalid unit for real timestamp add/sub: '$unit'"); + } + + return $this; + } + + /* @var CarbonInterface $this */ + return $this->setTimestamp((int) ($this->getTimestamp() + $value)); + } + + public function subRealUnit($unit, $value = 1) + { + return $this->addRealUnit($unit, -$value); + } + + /** + * Returns true if a property can be changed via setter. + * + * @param string $unit + * + * @return bool + */ + public static function isModifiableUnit($unit) + { + static $modifiableUnits = [ + // @call addUnit + 'millennium', + // @call addUnit + 'century', + // @call addUnit + 'decade', + // @call addUnit + 'quarter', + // @call addUnit + 'week', + // @call addUnit + 'weekday', + ]; + + return \in_array($unit, $modifiableUnits, true) || \in_array($unit, static::$units, true); + } + + /** + * Call native PHP DateTime/DateTimeImmutable add() method. + * + * @param DateInterval $interval + * + * @return static + */ + public function rawAdd(DateInterval $interval) + { + return parent::add($interval); + } + + /** + * Add given units or interval to the current instance. + * + * @example $date->add('hour', 3) + * @example $date->add(15, 'days') + * @example $date->add(CarbonInterval::days(4)) + * + * @param string|DateInterval|Closure|CarbonConverterInterface $unit + * @param int $value + * @param bool|null $overflow + * + * @return static + */ + #[ReturnTypeWillChange] + public function add($unit, $value = 1, $overflow = null) + { + if (\is_string($unit) && \func_num_args() === 1) { + $unit = CarbonInterval::make($unit); + } + + if ($unit instanceof CarbonConverterInterface) { + return $this->resolveCarbon($unit->convertDate($this, false)); + } + + if ($unit instanceof Closure) { + return $this->resolveCarbon($unit($this, false)); + } + + if ($unit instanceof DateInterval) { + return parent::add($unit); + } + + if (is_numeric($unit)) { + [$value, $unit] = [$unit, $value]; + } + + return $this->addUnit($unit, $value, $overflow); + } + + /** + * Add given units to the current instance. + * + * @param string $unit + * @param int $value + * @param bool|null $overflow + * + * @return static + */ + public function addUnit($unit, $value = 1, $overflow = null) + { + $originalArgs = \func_get_args(); + + $date = $this; + + if (!is_numeric($value) || !(float) $value) { + return $date->isMutable() ? $date : $date->avoidMutation(); + } + + $unit = self::singularUnit($unit); + $metaUnits = [ + 'millennium' => [static::YEARS_PER_MILLENNIUM, 'year'], + 'century' => [static::YEARS_PER_CENTURY, 'year'], + 'decade' => [static::YEARS_PER_DECADE, 'year'], + 'quarter' => [static::MONTHS_PER_QUARTER, 'month'], + ]; + + if (isset($metaUnits[$unit])) { + [$factor, $unit] = $metaUnits[$unit]; + $value *= $factor; + } + + if ($unit === 'weekday') { + $weekendDays = static::getWeekendDays(); + + if ($weekendDays !== [static::SATURDAY, static::SUNDAY]) { + $absoluteValue = abs($value); + $sign = $value / max(1, $absoluteValue); + $weekDaysCount = 7 - min(6, \count(array_unique($weekendDays))); + $weeks = floor($absoluteValue / $weekDaysCount); + + for ($diff = $absoluteValue % $weekDaysCount; $diff; $diff--) { + /** @var static $date */ + $date = $date->addDays($sign); + + while (\in_array($date->dayOfWeek, $weekendDays, true)) { + $date = $date->addDays($sign); + } + } + + $value = $weeks * $sign; + $unit = 'week'; + } + + $timeString = $date->toTimeString(); + } elseif ($canOverflow = (\in_array($unit, [ + 'month', + 'year', + ]) && ($overflow === false || ( + $overflow === null && + ($ucUnit = ucfirst($unit).'s') && + !($this->{'local'.$ucUnit.'Overflow'} ?? static::{'shouldOverflow'.$ucUnit}()) + )))) { + $day = $date->day; + } + + $value = (int) $value; + + if ($unit === 'milli' || $unit === 'millisecond') { + $unit = 'microsecond'; + $value *= static::MICROSECONDS_PER_MILLISECOND; + } + + // Work-around for bug https://bugs.php.net/bug.php?id=75642 + if ($unit === 'micro' || $unit === 'microsecond') { + $microseconds = $this->micro + $value; + $second = (int) floor($microseconds / static::MICROSECONDS_PER_SECOND); + $microseconds %= static::MICROSECONDS_PER_SECOND; + if ($microseconds < 0) { + $microseconds += static::MICROSECONDS_PER_SECOND; + } + $date = $date->microseconds($microseconds); + $unit = 'second'; + $value = $second; + } + + try { + $date = $date->modify("$value $unit"); + + if (isset($timeString)) { + $date = $date->setTimeFromTimeString($timeString); + } elseif (isset($canOverflow, $day) && $canOverflow && $day !== $date->day) { + $date = $date->modify('last day of previous month'); + } + } catch (DateMalformedStringException $ignoredException) { // @codeCoverageIgnore + $date = null; // @codeCoverageIgnore + } + + if (!$date) { + throw new UnitException('Unable to add unit '.var_export($originalArgs, true)); + } + + return $date; + } + + /** + * Subtract given units to the current instance. + * + * @param string $unit + * @param int $value + * @param bool|null $overflow + * + * @return static + */ + public function subUnit($unit, $value = 1, $overflow = null) + { + return $this->addUnit($unit, -$value, $overflow); + } + + /** + * Call native PHP DateTime/DateTimeImmutable sub() method. + * + * @param DateInterval $interval + * + * @return static + */ + public function rawSub(DateInterval $interval) + { + return parent::sub($interval); + } + + /** + * Subtract given units or interval to the current instance. + * + * @example $date->sub('hour', 3) + * @example $date->sub(15, 'days') + * @example $date->sub(CarbonInterval::days(4)) + * + * @param string|DateInterval|Closure|CarbonConverterInterface $unit + * @param int $value + * @param bool|null $overflow + * + * @return static + */ + #[ReturnTypeWillChange] + public function sub($unit, $value = 1, $overflow = null) + { + if (\is_string($unit) && \func_num_args() === 1) { + $unit = CarbonInterval::make($unit); + } + + if ($unit instanceof CarbonConverterInterface) { + return $this->resolveCarbon($unit->convertDate($this, true)); + } + + if ($unit instanceof Closure) { + return $this->resolveCarbon($unit($this, true)); + } + + if ($unit instanceof DateInterval) { + return parent::sub($unit); + } + + if (is_numeric($unit)) { + [$value, $unit] = [$unit, $value]; + } + + return $this->addUnit($unit, -(float) $value, $overflow); + } + + /** + * Subtract given units or interval to the current instance. + * + * @see sub() + * + * @param string|DateInterval $unit + * @param int $value + * @param bool|null $overflow + * + * @return static + */ + public function subtract($unit, $value = 1, $overflow = null) + { + if (\is_string($unit) && \func_num_args() === 1) { + $unit = CarbonInterval::make($unit); + } + + return $this->sub($unit, $value, $overflow); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Traits/Week.php b/vendor/nesbot/carbon/src/Carbon/Traits/Week.php new file mode 100644 index 0000000..6f14814 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Traits/Week.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 Carbon\Traits; + +/** + * Trait Week. + * + * week and ISO week number, year and count in year. + * + * Depends on the following properties: + * + * @property int $daysInYear + * @property int $dayOfWeek + * @property int $dayOfYear + * @property int $year + * + * Depends on the following methods: + * + * @method static addWeeks(int $weeks = 1) + * @method static copy() + * @method static dayOfYear(int $dayOfYear) + * @method string getTranslationMessage(string $key, ?string $locale = null, ?string $default = null, $translator = null) + * @method static next(int|string $day = null) + * @method static startOfWeek(int $day = 1) + * @method static subWeeks(int $weeks = 1) + * @method static year(int $year = null) + */ +trait Week +{ + /** + * Set/get the week number of year using given first day of week and first + * day of year included in the first week. Or use ISO format if no settings + * given. + * + * @param int|null $year if null, act as a getter, if not null, set the year and return current instance. + * @param int|null $dayOfWeek first date of week from 0 (Sunday) to 6 (Saturday) + * @param int|null $dayOfYear first day of year included in the week #1 + * + * @return int|static + */ + public function isoWeekYear($year = null, $dayOfWeek = null, $dayOfYear = null) + { + return $this->weekYear( + $year, + $dayOfWeek ?? 1, + $dayOfYear ?? 4 + ); + } + + /** + * Set/get the week number of year using given first day of week and first + * day of year included in the first week. Or use US format if no settings + * given (Sunday / Jan 6). + * + * @param int|null $year if null, act as a getter, if not null, set the year and return current instance. + * @param int|null $dayOfWeek first date of week from 0 (Sunday) to 6 (Saturday) + * @param int|null $dayOfYear first day of year included in the week #1 + * + * @return int|static + */ + public function weekYear($year = null, $dayOfWeek = null, $dayOfYear = null) + { + $dayOfWeek = $dayOfWeek ?? $this->getTranslationMessage('first_day_of_week') ?? 0; + $dayOfYear = $dayOfYear ?? $this->getTranslationMessage('day_of_first_week_of_year') ?? 1; + + if ($year !== null) { + $year = (int) round($year); + + if ($this->weekYear(null, $dayOfWeek, $dayOfYear) === $year) { + return $this->avoidMutation(); + } + + $week = $this->week(null, $dayOfWeek, $dayOfYear); + $day = $this->dayOfWeek; + $date = $this->year($year); + switch ($date->weekYear(null, $dayOfWeek, $dayOfYear) - $year) { + case 1: + $date = $date->subWeeks(26); + + break; + case -1: + $date = $date->addWeeks(26); + + break; + } + + $date = $date->addWeeks($week - $date->week(null, $dayOfWeek, $dayOfYear))->startOfWeek($dayOfWeek); + + if ($date->dayOfWeek === $day) { + return $date; + } + + return $date->next($day); + } + + $year = $this->year; + $day = $this->dayOfYear; + $date = $this->avoidMutation()->dayOfYear($dayOfYear)->startOfWeek($dayOfWeek); + + if ($date->year === $year && $day < $date->dayOfYear) { + return $year - 1; + } + + $date = $this->avoidMutation()->addYear()->dayOfYear($dayOfYear)->startOfWeek($dayOfWeek); + + if ($date->year === $year && $day >= $date->dayOfYear) { + return $year + 1; + } + + return $year; + } + + /** + * Get the number of weeks of the current week-year using given first day of week and first + * day of year included in the first week. Or use ISO format if no settings + * given. + * + * @param int|null $dayOfWeek first date of week from 0 (Sunday) to 6 (Saturday) + * @param int|null $dayOfYear first day of year included in the week #1 + * + * @return int + */ + public function isoWeeksInYear($dayOfWeek = null, $dayOfYear = null) + { + return $this->weeksInYear( + $dayOfWeek ?? 1, + $dayOfYear ?? 4 + ); + } + + /** + * Get the number of weeks of the current week-year using given first day of week and first + * day of year included in the first week. Or use US format if no settings + * given (Sunday / Jan 6). + * + * @param int|null $dayOfWeek first date of week from 0 (Sunday) to 6 (Saturday) + * @param int|null $dayOfYear first day of year included in the week #1 + * + * @return int + */ + public function weeksInYear($dayOfWeek = null, $dayOfYear = null) + { + $dayOfWeek = $dayOfWeek ?? $this->getTranslationMessage('first_day_of_week') ?? 0; + $dayOfYear = $dayOfYear ?? $this->getTranslationMessage('day_of_first_week_of_year') ?? 1; + $year = $this->year; + $start = $this->avoidMutation()->dayOfYear($dayOfYear)->startOfWeek($dayOfWeek); + $startDay = $start->dayOfYear; + if ($start->year !== $year) { + $startDay -= $start->daysInYear; + } + $end = $this->avoidMutation()->addYear()->dayOfYear($dayOfYear)->startOfWeek($dayOfWeek); + $endDay = $end->dayOfYear; + if ($end->year !== $year) { + $endDay += $this->daysInYear; + } + + return (int) round(($endDay - $startDay) / 7); + } + + /** + * Get/set the week number using given first day of week and first + * day of year included in the first week. Or use US format if no settings + * given (Sunday / Jan 6). + * + * @param int|null $week + * @param int|null $dayOfWeek + * @param int|null $dayOfYear + * + * @return int|static + */ + public function week($week = null, $dayOfWeek = null, $dayOfYear = null) + { + $date = $this; + $dayOfWeek = $dayOfWeek ?? $this->getTranslationMessage('first_day_of_week') ?? 0; + $dayOfYear = $dayOfYear ?? $this->getTranslationMessage('day_of_first_week_of_year') ?? 1; + + if ($week !== null) { + return $date->addWeeks(round($week) - $this->week(null, $dayOfWeek, $dayOfYear)); + } + + $start = $date->avoidMutation()->dayOfYear($dayOfYear)->startOfWeek($dayOfWeek); + $end = $date->avoidMutation()->startOfWeek($dayOfWeek); + if ($start > $end) { + $start = $start->subWeeks(26)->dayOfYear($dayOfYear)->startOfWeek($dayOfWeek); + } + $week = (int) ($start->diffInDays($end) / 7 + 1); + + return $week > $end->weeksInYear($dayOfWeek, $dayOfYear) ? 1 : $week; + } + + /** + * Get/set the week number using given first day of week and first + * day of year included in the first week. Or use ISO format if no settings + * given. + * + * @param int|null $week + * @param int|null $dayOfWeek + * @param int|null $dayOfYear + * + * @return int|static + */ + public function isoWeek($week = null, $dayOfWeek = null, $dayOfYear = null) + { + return $this->week( + $week, + $dayOfWeek ?? 1, + $dayOfYear ?? 4 + ); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Translator.php b/vendor/nesbot/carbon/src/Carbon/Translator.php new file mode 100644 index 0000000..491c9e7 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Translator.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +use ReflectionMethod; +use Symfony\Component\Translation; +use Symfony\Contracts\Translation\TranslatorInterface; + +$transMethod = new ReflectionMethod( + class_exists(TranslatorInterface::class) + ? TranslatorInterface::class + : Translation\Translator::class, + 'trans' +); + +require $transMethod->hasReturnType() + ? __DIR__.'/../../lazy/Carbon/TranslatorStrongType.php' + : __DIR__.'/../../lazy/Carbon/TranslatorWeakType.php'; + +class Translator extends LazyTranslator +{ + // Proxy dynamically loaded LazyTranslator in a static way +} diff --git a/vendor/nesbot/carbon/src/Carbon/TranslatorImmutable.php b/vendor/nesbot/carbon/src/Carbon/TranslatorImmutable.php new file mode 100644 index 0000000..ad36c67 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/TranslatorImmutable.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +use Carbon\Exceptions\ImmutableException; +use Symfony\Component\Config\ConfigCacheFactoryInterface; +use Symfony\Component\Translation\Formatter\MessageFormatterInterface; + +class TranslatorImmutable extends Translator +{ + /** @var bool */ + private $constructed = false; + + public function __construct($locale, MessageFormatterInterface $formatter = null, $cacheDir = null, $debug = false) + { + parent::__construct($locale, $formatter, $cacheDir, $debug); + $this->constructed = true; + } + + /** + * @codeCoverageIgnore + */ + public function setDirectories(array $directories) + { + $this->disallowMutation(__METHOD__); + + return parent::setDirectories($directories); + } + + public function setLocale($locale) + { + $this->disallowMutation(__METHOD__); + + return parent::setLocale($locale); + } + + /** + * @codeCoverageIgnore + */ + public function setMessages($locale, $messages) + { + $this->disallowMutation(__METHOD__); + + return parent::setMessages($locale, $messages); + } + + /** + * @codeCoverageIgnore + */ + public function setTranslations($messages) + { + $this->disallowMutation(__METHOD__); + + return parent::setTranslations($messages); + } + + /** + * @codeCoverageIgnore + */ + public function setConfigCacheFactory(ConfigCacheFactoryInterface $configCacheFactory) + { + $this->disallowMutation(__METHOD__); + + parent::setConfigCacheFactory($configCacheFactory); + } + + public function resetMessages($locale = null) + { + $this->disallowMutation(__METHOD__); + + return parent::resetMessages($locale); + } + + /** + * @codeCoverageIgnore + */ + public function setFallbackLocales(array $locales) + { + $this->disallowMutation(__METHOD__); + + parent::setFallbackLocales($locales); + } + + private function disallowMutation($method) + { + if ($this->constructed) { + throw new ImmutableException($method.' not allowed on '.static::class); + } + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/TranslatorStrongTypeInterface.php b/vendor/nesbot/carbon/src/Carbon/TranslatorStrongTypeInterface.php new file mode 100644 index 0000000..ef4dee8 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/TranslatorStrongTypeInterface.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +use Symfony\Component\Translation\MessageCatalogueInterface; + +/** + * Mark translator using strong type from symfony/translation >= 6. + */ +interface TranslatorStrongTypeInterface +{ + public function getFromCatalogue(MessageCatalogueInterface $catalogue, string $id, string $domain = 'messages'); +} diff --git a/vendor/nikic/fast-route/.gitignore b/vendor/nikic/fast-route/.gitignore new file mode 100644 index 0000000..e378a07 --- /dev/null +++ b/vendor/nikic/fast-route/.gitignore @@ -0,0 +1,5 @@ +/vendor/ +.idea/ + +# ignore lock file since we have no extra dependencies +composer.lock diff --git a/vendor/nikic/fast-route/.hhconfig b/vendor/nikic/fast-route/.hhconfig new file mode 100644 index 0000000..0c2153c --- /dev/null +++ b/vendor/nikic/fast-route/.hhconfig @@ -0,0 +1 @@ +assume_php=false diff --git a/vendor/nikic/fast-route/.travis.yml b/vendor/nikic/fast-route/.travis.yml new file mode 100644 index 0000000..10f8381 --- /dev/null +++ b/vendor/nikic/fast-route/.travis.yml @@ -0,0 +1,20 @@ +sudo: false +language: php + +php: + - 5.4 + - 5.5 + - 5.6 + - 7.0 + - 7.1 + - 7.2 + - hhvm + +script: + - ./vendor/bin/phpunit + +before_install: + - travis_retry composer self-update + +install: + - composer install diff --git a/vendor/nikic/fast-route/FastRoute.hhi b/vendor/nikic/fast-route/FastRoute.hhi new file mode 100644 index 0000000..8d50738 --- /dev/null +++ b/vendor/nikic/fast-route/FastRoute.hhi @@ -0,0 +1,126 @@ +; + } + + class RouteCollector { + public function __construct(RouteParser $routeParser, DataGenerator $dataGenerator); + public function addRoute(mixed $httpMethod, string $route, mixed $handler): void; + public function getData(): array; + } + + class Route { + public function __construct(string $httpMethod, mixed $handler, string $regex, array $variables); + public function matches(string $str): bool; + } + + interface DataGenerator { + public function addRoute(string $httpMethod, array $routeData, mixed $handler); + public function getData(): array; + } + + interface Dispatcher { + const int NOT_FOUND = 0; + const int FOUND = 1; + const int METHOD_NOT_ALLOWED = 2; + public function dispatch(string $httpMethod, string $uri): array; + } + + function simpleDispatcher( + (function(RouteCollector): void) $routeDefinitionCallback, + shape( + ?'routeParser' => classname, + ?'dataGenerator' => classname, + ?'dispatcher' => classname, + ?'routeCollector' => classname, + ) $options = shape()): Dispatcher; + + function cachedDispatcher( + (function(RouteCollector): void) $routeDefinitionCallback, + shape( + ?'routeParser' => classname, + ?'dataGenerator' => classname, + ?'dispatcher' => classname, + ?'routeCollector' => classname, + ?'cacheDisabled' => bool, + ?'cacheFile' => string, + ) $options = shape()): Dispatcher; +} + +namespace FastRoute\DataGenerator { + abstract class RegexBasedAbstract implements \FastRoute\DataGenerator { + protected abstract function getApproxChunkSize(); + protected abstract function processChunk($regexToRoutesMap); + + public function addRoute(string $httpMethod, array $routeData, mixed $handler): void; + public function getData(): array; + } + + class CharCountBased extends RegexBasedAbstract { + protected function getApproxChunkSize(): int; + protected function processChunk(array $regexToRoutesMap): array; + } + + class GroupCountBased extends RegexBasedAbstract { + protected function getApproxChunkSize(): int; + protected function processChunk(array $regexToRoutesMap): array; + } + + class GroupPosBased extends RegexBasedAbstract { + protected function getApproxChunkSize(): int; + protected function processChunk(array $regexToRoutesMap): array; + } + + class MarkBased extends RegexBasedAbstract { + protected function getApproxChunkSize(): int; + protected function processChunk(array $regexToRoutesMap): array; + } +} + +namespace FastRoute\Dispatcher { + abstract class RegexBasedAbstract implements \FastRoute\Dispatcher { + protected abstract function dispatchVariableRoute(array $routeData, string $uri): array; + + public function dispatch(string $httpMethod, string $uri): array; + } + + class GroupPosBased extends RegexBasedAbstract { + public function __construct(array $data); + protected function dispatchVariableRoute(array $routeData, string $uri): array; + } + + class GroupCountBased extends RegexBasedAbstract { + public function __construct(array $data); + protected function dispatchVariableRoute(array $routeData, string $uri): array; + } + + class CharCountBased extends RegexBasedAbstract { + public function __construct(array $data); + protected function dispatchVariableRoute(array $routeData, string $uri): array; + } + + class MarkBased extends RegexBasedAbstract { + public function __construct(array $data); + protected function dispatchVariableRoute(array $routeData, string $uri): array; + } +} + +namespace FastRoute\RouteParser { + class Std implements \FastRoute\RouteParser { + const string VARIABLE_REGEX = <<<'REGEX' +\{ + \s* ([a-zA-Z][a-zA-Z0-9_]*) \s* + (?: + : \s* ([^{}]*(?:\{(?-1)\}[^{}]*)*) + )? +\} +REGEX; + const string DEFAULT_DISPATCH_REGEX = '[^/]+'; + public function parse(string $route): array; + } +} diff --git a/vendor/nikic/fast-route/LICENSE b/vendor/nikic/fast-route/LICENSE new file mode 100644 index 0000000..478e764 --- /dev/null +++ b/vendor/nikic/fast-route/LICENSE @@ -0,0 +1,31 @@ +Copyright (c) 2013 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. diff --git a/vendor/nikic/fast-route/README.md b/vendor/nikic/fast-route/README.md new file mode 100644 index 0000000..91bd466 --- /dev/null +++ b/vendor/nikic/fast-route/README.md @@ -0,0 +1,313 @@ +FastRoute - Fast request router for PHP +======================================= + +This library provides a fast implementation of a regular expression based router. [Blog post explaining how the +implementation works and why it is fast.][blog_post] + +Install +------- + +To install with composer: + +```sh +composer require nikic/fast-route +``` + +Requires PHP 5.4 or newer. + +Usage +----- + +Here's a basic usage example: + +```php +addRoute('GET', '/users', 'get_all_users_handler'); + // {id} must be a number (\d+) + $r->addRoute('GET', '/user/{id:\d+}', 'get_user_handler'); + // The /{title} suffix is optional + $r->addRoute('GET', '/articles/{id:\d+}[/{title}]', 'get_article_handler'); +}); + +// Fetch method and URI from somewhere +$httpMethod = $_SERVER['REQUEST_METHOD']; +$uri = $_SERVER['REQUEST_URI']; + +// Strip query string (?foo=bar) and decode URI +if (false !== $pos = strpos($uri, '?')) { + $uri = substr($uri, 0, $pos); +} +$uri = rawurldecode($uri); + +$routeInfo = $dispatcher->dispatch($httpMethod, $uri); +switch ($routeInfo[0]) { + case FastRoute\Dispatcher::NOT_FOUND: + // ... 404 Not Found + break; + case FastRoute\Dispatcher::METHOD_NOT_ALLOWED: + $allowedMethods = $routeInfo[1]; + // ... 405 Method Not Allowed + break; + case FastRoute\Dispatcher::FOUND: + $handler = $routeInfo[1]; + $vars = $routeInfo[2]; + // ... call $handler with $vars + break; +} +``` + +### Defining routes + +The routes are defined by calling the `FastRoute\simpleDispatcher()` function, which accepts +a callable taking a `FastRoute\RouteCollector` instance. The routes are added by calling +`addRoute()` on the collector instance: + +```php +$r->addRoute($method, $routePattern, $handler); +``` + +The `$method` is an uppercase HTTP method string for which a certain route should match. It +is possible to specify multiple valid methods using an array: + +```php +// These two calls +$r->addRoute('GET', '/test', 'handler'); +$r->addRoute('POST', '/test', 'handler'); +// Are equivalent to this one call +$r->addRoute(['GET', 'POST'], '/test', 'handler'); +``` + +By default the `$routePattern` uses a syntax where `{foo}` specifies a placeholder with name `foo` +and matching the regex `[^/]+`. To adjust the pattern the placeholder matches, you can specify +a custom pattern by writing `{bar:[0-9]+}`. Some examples: + +```php +// Matches /user/42, but not /user/xyz +$r->addRoute('GET', '/user/{id:\d+}', 'handler'); + +// Matches /user/foobar, but not /user/foo/bar +$r->addRoute('GET', '/user/{name}', 'handler'); + +// Matches /user/foo/bar as well +$r->addRoute('GET', '/user/{name:.+}', 'handler'); +``` + +Custom patterns for route placeholders cannot use capturing groups. For example `{lang:(en|de)}` +is not a valid placeholder, because `()` is a capturing group. Instead you can use either +`{lang:en|de}` or `{lang:(?:en|de)}`. + +Furthermore parts of the route enclosed in `[...]` are considered optional, so that `/foo[bar]` +will match both `/foo` and `/foobar`. Optional parts are only supported in a trailing position, +not in the middle of a route. + +```php +// This route +$r->addRoute('GET', '/user/{id:\d+}[/{name}]', 'handler'); +// Is equivalent to these two routes +$r->addRoute('GET', '/user/{id:\d+}', 'handler'); +$r->addRoute('GET', '/user/{id:\d+}/{name}', 'handler'); + +// Multiple nested optional parts are possible as well +$r->addRoute('GET', '/user[/{id:\d+}[/{name}]]', 'handler'); + +// This route is NOT valid, because optional parts can only occur at the end +$r->addRoute('GET', '/user[/{id:\d+}]/{name}', 'handler'); +``` + +The `$handler` parameter does not necessarily have to be a callback, it could also be a controller +class name or any other kind of data you wish to associate with the route. FastRoute only tells you +which handler corresponds to your URI, how you interpret it is up to you. + +#### Shorcut methods for common request methods + +For the `GET`, `POST`, `PUT`, `PATCH`, `DELETE` and `HEAD` request methods shortcut methods are available. For example: + +```php +$r->get('/get-route', 'get_handler'); +$r->post('/post-route', 'post_handler'); +``` + +Is equivalent to: + +```php +$r->addRoute('GET', '/get-route', 'get_handler'); +$r->addRoute('POST', '/post-route', 'post_handler'); +``` + +#### Route Groups + +Additionally, you can specify routes inside of a group. All routes defined inside a group will have a common prefix. + +For example, defining your routes as: + +```php +$r->addGroup('/admin', function (RouteCollector $r) { + $r->addRoute('GET', '/do-something', 'handler'); + $r->addRoute('GET', '/do-another-thing', 'handler'); + $r->addRoute('GET', '/do-something-else', 'handler'); +}); +``` + +Will have the same result as: + + ```php +$r->addRoute('GET', '/admin/do-something', 'handler'); +$r->addRoute('GET', '/admin/do-another-thing', 'handler'); +$r->addRoute('GET', '/admin/do-something-else', 'handler'); + ``` + +Nested groups are also supported, in which case the prefixes of all the nested groups are combined. + +### Caching + +The reason `simpleDispatcher` accepts a callback for defining the routes is to allow seamless +caching. By using `cachedDispatcher` instead of `simpleDispatcher` you can cache the generated +routing data and construct the dispatcher from the cached information: + +```php +addRoute('GET', '/user/{name}/{id:[0-9]+}', 'handler0'); + $r->addRoute('GET', '/user/{id:[0-9]+}', 'handler1'); + $r->addRoute('GET', '/user/{name}', 'handler2'); +}, [ + 'cacheFile' => __DIR__ . '/route.cache', /* required */ + 'cacheDisabled' => IS_DEBUG_ENABLED, /* optional, enabled by default */ +]); +``` + +The second parameter to the function is an options array, which can be used to specify the cache +file location, among other things. + +### Dispatching a URI + +A URI is dispatched by calling the `dispatch()` method of the created dispatcher. This method +accepts the HTTP method and a URI. Getting those two bits of information (and normalizing them +appropriately) is your job - this library is not bound to the PHP web SAPIs. + +The `dispatch()` method returns an array whose first element contains a status code. It is one +of `Dispatcher::NOT_FOUND`, `Dispatcher::METHOD_NOT_ALLOWED` and `Dispatcher::FOUND`. For the +method not allowed status the second array element contains a list of HTTP methods allowed for +the supplied URI. For example: + + [FastRoute\Dispatcher::METHOD_NOT_ALLOWED, ['GET', 'POST']] + +> **NOTE:** The HTTP specification requires that a `405 Method Not Allowed` response include the +`Allow:` header to detail available methods for the requested resource. Applications using FastRoute +should use the second array element to add this header when relaying a 405 response. + +For the found status the second array element is the handler that was associated with the route +and the third array element is a dictionary of placeholder names to their values. For example: + + /* Routing against GET /user/nikic/42 */ + + [FastRoute\Dispatcher::FOUND, 'handler0', ['name' => 'nikic', 'id' => '42']] + +### Overriding the route parser and dispatcher + +The routing process makes use of three components: A route parser, a data generator and a +dispatcher. The three components adhere to the following interfaces: + +```php + 'FastRoute\\RouteParser\\Std', + 'dataGenerator' => 'FastRoute\\DataGenerator\\GroupCountBased', + 'dispatcher' => 'FastRoute\\Dispatcher\\GroupCountBased', +]); +``` + +The above options array corresponds to the defaults. By replacing `GroupCountBased` by +`GroupPosBased` you could switch to a different dispatching strategy. + +### A Note on HEAD Requests + +The HTTP spec requires servers to [support both GET and HEAD methods][2616-511]: + +> The methods GET and HEAD MUST be supported by all general-purpose servers + +To avoid forcing users to manually register HEAD routes for each resource we fallback to matching an +available GET route for a given resource. The PHP web SAPI transparently removes the entity body +from HEAD responses so this behavior has no effect on the vast majority of users. + +However, implementers using FastRoute outside the web SAPI environment (e.g. a custom server) MUST +NOT send entity bodies generated in response to HEAD requests. If you are a non-SAPI user this is +*your responsibility*; FastRoute has no purview to prevent you from breaking HTTP in such cases. + +Finally, note that applications MAY always specify their own HEAD method route for a given +resource to bypass this behavior entirely. + +### Credits + +This library is based on a router that [Levi Morrison][levi] implemented for the Aerys server. + +A large number of tests, as well as HTTP compliance considerations, were provided by [Daniel Lowrey][rdlowrey]. + + +[2616-511]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.1 "RFC 2616 Section 5.1.1" +[blog_post]: http://nikic.github.io/2014/02/18/Fast-request-routing-using-regular-expressions.html +[levi]: https://github.com/morrisonlevi +[rdlowrey]: https://github.com/rdlowrey diff --git a/vendor/nikic/fast-route/phpunit.xml b/vendor/nikic/fast-route/phpunit.xml new file mode 100644 index 0000000..3c807b6 --- /dev/null +++ b/vendor/nikic/fast-route/phpunit.xml @@ -0,0 +1,24 @@ + + + + + + ./test/ + + + + + + ./src/ + + + diff --git a/vendor/nikic/fast-route/psalm.xml b/vendor/nikic/fast-route/psalm.xml new file mode 100644 index 0000000..0dca5d7 --- /dev/null +++ b/vendor/nikic/fast-route/psalm.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/nikic/fast-route/src/BadRouteException.php b/vendor/nikic/fast-route/src/BadRouteException.php new file mode 100644 index 0000000..62262ec --- /dev/null +++ b/vendor/nikic/fast-route/src/BadRouteException.php @@ -0,0 +1,7 @@ + $route) { + $suffixLen++; + $suffix .= "\t"; + + $regexes[] = '(?:' . $regex . '/(\t{' . $suffixLen . '})\t{' . ($count - $suffixLen) . '})'; + $routeMap[$suffix] = [$route->handler, $route->variables]; + } + + $regex = '~^(?|' . implode('|', $regexes) . ')$~'; + return ['regex' => $regex, 'suffix' => '/' . $suffix, 'routeMap' => $routeMap]; + } +} diff --git a/vendor/nikic/fast-route/src/DataGenerator/GroupCountBased.php b/vendor/nikic/fast-route/src/DataGenerator/GroupCountBased.php new file mode 100644 index 0000000..54d9a05 --- /dev/null +++ b/vendor/nikic/fast-route/src/DataGenerator/GroupCountBased.php @@ -0,0 +1,30 @@ + $route) { + $numVariables = count($route->variables); + $numGroups = max($numGroups, $numVariables); + + $regexes[] = $regex . str_repeat('()', $numGroups - $numVariables); + $routeMap[$numGroups + 1] = [$route->handler, $route->variables]; + + ++$numGroups; + } + + $regex = '~^(?|' . implode('|', $regexes) . ')$~'; + return ['regex' => $regex, 'routeMap' => $routeMap]; + } +} diff --git a/vendor/nikic/fast-route/src/DataGenerator/GroupPosBased.php b/vendor/nikic/fast-route/src/DataGenerator/GroupPosBased.php new file mode 100644 index 0000000..fc4dc0a --- /dev/null +++ b/vendor/nikic/fast-route/src/DataGenerator/GroupPosBased.php @@ -0,0 +1,27 @@ + $route) { + $regexes[] = $regex; + $routeMap[$offset] = [$route->handler, $route->variables]; + + $offset += count($route->variables); + } + + $regex = '~^(?:' . implode('|', $regexes) . ')$~'; + return ['regex' => $regex, 'routeMap' => $routeMap]; + } +} diff --git a/vendor/nikic/fast-route/src/DataGenerator/MarkBased.php b/vendor/nikic/fast-route/src/DataGenerator/MarkBased.php new file mode 100644 index 0000000..0aebed9 --- /dev/null +++ b/vendor/nikic/fast-route/src/DataGenerator/MarkBased.php @@ -0,0 +1,27 @@ + $route) { + $regexes[] = $regex . '(*MARK:' . $markName . ')'; + $routeMap[$markName] = [$route->handler, $route->variables]; + + ++$markName; + } + + $regex = '~^(?|' . implode('|', $regexes) . ')$~'; + return ['regex' => $regex, 'routeMap' => $routeMap]; + } +} diff --git a/vendor/nikic/fast-route/src/DataGenerator/RegexBasedAbstract.php b/vendor/nikic/fast-route/src/DataGenerator/RegexBasedAbstract.php new file mode 100644 index 0000000..6457290 --- /dev/null +++ b/vendor/nikic/fast-route/src/DataGenerator/RegexBasedAbstract.php @@ -0,0 +1,186 @@ +isStaticRoute($routeData)) { + $this->addStaticRoute($httpMethod, $routeData, $handler); + } else { + $this->addVariableRoute($httpMethod, $routeData, $handler); + } + } + + /** + * @return mixed[] + */ + public function getData() + { + if (empty($this->methodToRegexToRoutesMap)) { + return [$this->staticRoutes, []]; + } + + return [$this->staticRoutes, $this->generateVariableRouteData()]; + } + + /** + * @return mixed[] + */ + private function generateVariableRouteData() + { + $data = []; + foreach ($this->methodToRegexToRoutesMap as $method => $regexToRoutesMap) { + $chunkSize = $this->computeChunkSize(count($regexToRoutesMap)); + $chunks = array_chunk($regexToRoutesMap, $chunkSize, true); + $data[$method] = array_map([$this, 'processChunk'], $chunks); + } + return $data; + } + + /** + * @param int + * @return int + */ + private function computeChunkSize($count) + { + $numParts = max(1, round($count / $this->getApproxChunkSize())); + return (int) ceil($count / $numParts); + } + + /** + * @param mixed[] + * @return bool + */ + private function isStaticRoute($routeData) + { + return count($routeData) === 1 && is_string($routeData[0]); + } + + private function addStaticRoute($httpMethod, $routeData, $handler) + { + $routeStr = $routeData[0]; + + if (isset($this->staticRoutes[$httpMethod][$routeStr])) { + throw new BadRouteException(sprintf( + 'Cannot register two routes matching "%s" for method "%s"', + $routeStr, $httpMethod + )); + } + + if (isset($this->methodToRegexToRoutesMap[$httpMethod])) { + foreach ($this->methodToRegexToRoutesMap[$httpMethod] as $route) { + if ($route->matches($routeStr)) { + throw new BadRouteException(sprintf( + 'Static route "%s" is shadowed by previously defined variable route "%s" for method "%s"', + $routeStr, $route->regex, $httpMethod + )); + } + } + } + + $this->staticRoutes[$httpMethod][$routeStr] = $handler; + } + + private function addVariableRoute($httpMethod, $routeData, $handler) + { + list($regex, $variables) = $this->buildRegexForRoute($routeData); + + if (isset($this->methodToRegexToRoutesMap[$httpMethod][$regex])) { + throw new BadRouteException(sprintf( + 'Cannot register two routes matching "%s" for method "%s"', + $regex, $httpMethod + )); + } + + $this->methodToRegexToRoutesMap[$httpMethod][$regex] = new Route( + $httpMethod, $handler, $regex, $variables + ); + } + + /** + * @param mixed[] + * @return mixed[] + */ + private function buildRegexForRoute($routeData) + { + $regex = ''; + $variables = []; + foreach ($routeData as $part) { + if (is_string($part)) { + $regex .= preg_quote($part, '~'); + continue; + } + + list($varName, $regexPart) = $part; + + if (isset($variables[$varName])) { + throw new BadRouteException(sprintf( + 'Cannot use the same placeholder "%s" twice', $varName + )); + } + + if ($this->regexHasCapturingGroups($regexPart)) { + throw new BadRouteException(sprintf( + 'Regex "%s" for parameter "%s" contains a capturing group', + $regexPart, $varName + )); + } + + $variables[$varName] = $varName; + $regex .= '(' . $regexPart . ')'; + } + + return [$regex, $variables]; + } + + /** + * @param string + * @return bool + */ + private function regexHasCapturingGroups($regex) + { + if (false === strpos($regex, '(')) { + // Needs to have at least a ( to contain a capturing group + return false; + } + + // Semi-accurate detection for capturing groups + return (bool) preg_match( + '~ + (?: + \(\?\( + | \[ [^\]\\\\]* (?: \\\\ . [^\]\\\\]* )* \] + | \\\\ . + ) (*SKIP)(*FAIL) | + \( + (?! + \? (?! <(?![!=]) | P< | \' ) + | \* + ) + ~x', + $regex + ); + } +} diff --git a/vendor/nikic/fast-route/src/Dispatcher.php b/vendor/nikic/fast-route/src/Dispatcher.php new file mode 100644 index 0000000..4ae72a3 --- /dev/null +++ b/vendor/nikic/fast-route/src/Dispatcher.php @@ -0,0 +1,26 @@ + 'value', ...]] + * + * @param string $httpMethod + * @param string $uri + * + * @return array + */ + public function dispatch($httpMethod, $uri); +} diff --git a/vendor/nikic/fast-route/src/Dispatcher/CharCountBased.php b/vendor/nikic/fast-route/src/Dispatcher/CharCountBased.php new file mode 100644 index 0000000..ef1eec1 --- /dev/null +++ b/vendor/nikic/fast-route/src/Dispatcher/CharCountBased.php @@ -0,0 +1,31 @@ +staticRouteMap, $this->variableRouteData) = $data; + } + + protected function dispatchVariableRoute($routeData, $uri) + { + foreach ($routeData as $data) { + if (!preg_match($data['regex'], $uri . $data['suffix'], $matches)) { + continue; + } + + list($handler, $varNames) = $data['routeMap'][end($matches)]; + + $vars = []; + $i = 0; + foreach ($varNames as $varName) { + $vars[$varName] = $matches[++$i]; + } + return [self::FOUND, $handler, $vars]; + } + + return [self::NOT_FOUND]; + } +} diff --git a/vendor/nikic/fast-route/src/Dispatcher/GroupCountBased.php b/vendor/nikic/fast-route/src/Dispatcher/GroupCountBased.php new file mode 100644 index 0000000..493e7a9 --- /dev/null +++ b/vendor/nikic/fast-route/src/Dispatcher/GroupCountBased.php @@ -0,0 +1,31 @@ +staticRouteMap, $this->variableRouteData) = $data; + } + + protected function dispatchVariableRoute($routeData, $uri) + { + foreach ($routeData as $data) { + if (!preg_match($data['regex'], $uri, $matches)) { + continue; + } + + list($handler, $varNames) = $data['routeMap'][count($matches)]; + + $vars = []; + $i = 0; + foreach ($varNames as $varName) { + $vars[$varName] = $matches[++$i]; + } + return [self::FOUND, $handler, $vars]; + } + + return [self::NOT_FOUND]; + } +} diff --git a/vendor/nikic/fast-route/src/Dispatcher/GroupPosBased.php b/vendor/nikic/fast-route/src/Dispatcher/GroupPosBased.php new file mode 100644 index 0000000..498220e --- /dev/null +++ b/vendor/nikic/fast-route/src/Dispatcher/GroupPosBased.php @@ -0,0 +1,33 @@ +staticRouteMap, $this->variableRouteData) = $data; + } + + protected function dispatchVariableRoute($routeData, $uri) + { + foreach ($routeData as $data) { + if (!preg_match($data['regex'], $uri, $matches)) { + continue; + } + + // find first non-empty match + for ($i = 1; '' === $matches[$i]; ++$i); + + list($handler, $varNames) = $data['routeMap'][$i]; + + $vars = []; + foreach ($varNames as $varName) { + $vars[$varName] = $matches[$i++]; + } + return [self::FOUND, $handler, $vars]; + } + + return [self::NOT_FOUND]; + } +} diff --git a/vendor/nikic/fast-route/src/Dispatcher/MarkBased.php b/vendor/nikic/fast-route/src/Dispatcher/MarkBased.php new file mode 100644 index 0000000..22eb09b --- /dev/null +++ b/vendor/nikic/fast-route/src/Dispatcher/MarkBased.php @@ -0,0 +1,31 @@ +staticRouteMap, $this->variableRouteData) = $data; + } + + protected function dispatchVariableRoute($routeData, $uri) + { + foreach ($routeData as $data) { + if (!preg_match($data['regex'], $uri, $matches)) { + continue; + } + + list($handler, $varNames) = $data['routeMap'][$matches['MARK']]; + + $vars = []; + $i = 0; + foreach ($varNames as $varName) { + $vars[$varName] = $matches[++$i]; + } + return [self::FOUND, $handler, $vars]; + } + + return [self::NOT_FOUND]; + } +} diff --git a/vendor/nikic/fast-route/src/Dispatcher/RegexBasedAbstract.php b/vendor/nikic/fast-route/src/Dispatcher/RegexBasedAbstract.php new file mode 100644 index 0000000..206e879 --- /dev/null +++ b/vendor/nikic/fast-route/src/Dispatcher/RegexBasedAbstract.php @@ -0,0 +1,88 @@ +staticRouteMap[$httpMethod][$uri])) { + $handler = $this->staticRouteMap[$httpMethod][$uri]; + return [self::FOUND, $handler, []]; + } + + $varRouteData = $this->variableRouteData; + if (isset($varRouteData[$httpMethod])) { + $result = $this->dispatchVariableRoute($varRouteData[$httpMethod], $uri); + if ($result[0] === self::FOUND) { + return $result; + } + } + + // For HEAD requests, attempt fallback to GET + if ($httpMethod === 'HEAD') { + if (isset($this->staticRouteMap['GET'][$uri])) { + $handler = $this->staticRouteMap['GET'][$uri]; + return [self::FOUND, $handler, []]; + } + if (isset($varRouteData['GET'])) { + $result = $this->dispatchVariableRoute($varRouteData['GET'], $uri); + if ($result[0] === self::FOUND) { + return $result; + } + } + } + + // If nothing else matches, try fallback routes + if (isset($this->staticRouteMap['*'][$uri])) { + $handler = $this->staticRouteMap['*'][$uri]; + return [self::FOUND, $handler, []]; + } + if (isset($varRouteData['*'])) { + $result = $this->dispatchVariableRoute($varRouteData['*'], $uri); + if ($result[0] === self::FOUND) { + return $result; + } + } + + // Find allowed methods for this URI by matching against all other HTTP methods as well + $allowedMethods = []; + + foreach ($this->staticRouteMap as $method => $uriMap) { + if ($method !== $httpMethod && isset($uriMap[$uri])) { + $allowedMethods[] = $method; + } + } + + foreach ($varRouteData as $method => $routeData) { + if ($method === $httpMethod) { + continue; + } + + $result = $this->dispatchVariableRoute($routeData, $uri); + if ($result[0] === self::FOUND) { + $allowedMethods[] = $method; + } + } + + // If there are no allowed methods the route simply does not exist + if ($allowedMethods) { + return [self::METHOD_NOT_ALLOWED, $allowedMethods]; + } + + return [self::NOT_FOUND]; + } +} diff --git a/vendor/nikic/fast-route/src/Route.php b/vendor/nikic/fast-route/src/Route.php new file mode 100644 index 0000000..e1bf7dd --- /dev/null +++ b/vendor/nikic/fast-route/src/Route.php @@ -0,0 +1,47 @@ +httpMethod = $httpMethod; + $this->handler = $handler; + $this->regex = $regex; + $this->variables = $variables; + } + + /** + * Tests whether this route matches the given string. + * + * @param string $str + * + * @return bool + */ + public function matches($str) + { + $regex = '~^' . $this->regex . '$~'; + return (bool) preg_match($regex, $str); + } +} diff --git a/vendor/nikic/fast-route/src/RouteCollector.php b/vendor/nikic/fast-route/src/RouteCollector.php new file mode 100644 index 0000000..c1c1762 --- /dev/null +++ b/vendor/nikic/fast-route/src/RouteCollector.php @@ -0,0 +1,152 @@ +routeParser = $routeParser; + $this->dataGenerator = $dataGenerator; + $this->currentGroupPrefix = ''; + } + + /** + * Adds a route to the collection. + * + * The syntax used in the $route string depends on the used route parser. + * + * @param string|string[] $httpMethod + * @param string $route + * @param mixed $handler + */ + public function addRoute($httpMethod, $route, $handler) + { + $route = $this->currentGroupPrefix . $route; + $routeDatas = $this->routeParser->parse($route); + foreach ((array) $httpMethod as $method) { + foreach ($routeDatas as $routeData) { + $this->dataGenerator->addRoute($method, $routeData, $handler); + } + } + } + + /** + * Create a route group with a common prefix. + * + * All routes created in the passed callback will have the given group prefix prepended. + * + * @param string $prefix + * @param callable $callback + */ + public function addGroup($prefix, callable $callback) + { + $previousGroupPrefix = $this->currentGroupPrefix; + $this->currentGroupPrefix = $previousGroupPrefix . $prefix; + $callback($this); + $this->currentGroupPrefix = $previousGroupPrefix; + } + + /** + * Adds a GET route to the collection + * + * This is simply an alias of $this->addRoute('GET', $route, $handler) + * + * @param string $route + * @param mixed $handler + */ + public function get($route, $handler) + { + $this->addRoute('GET', $route, $handler); + } + + /** + * Adds a POST route to the collection + * + * This is simply an alias of $this->addRoute('POST', $route, $handler) + * + * @param string $route + * @param mixed $handler + */ + public function post($route, $handler) + { + $this->addRoute('POST', $route, $handler); + } + + /** + * Adds a PUT route to the collection + * + * This is simply an alias of $this->addRoute('PUT', $route, $handler) + * + * @param string $route + * @param mixed $handler + */ + public function put($route, $handler) + { + $this->addRoute('PUT', $route, $handler); + } + + /** + * Adds a DELETE route to the collection + * + * This is simply an alias of $this->addRoute('DELETE', $route, $handler) + * + * @param string $route + * @param mixed $handler + */ + public function delete($route, $handler) + { + $this->addRoute('DELETE', $route, $handler); + } + + /** + * Adds a PATCH route to the collection + * + * This is simply an alias of $this->addRoute('PATCH', $route, $handler) + * + * @param string $route + * @param mixed $handler + */ + public function patch($route, $handler) + { + $this->addRoute('PATCH', $route, $handler); + } + + /** + * Adds a HEAD route to the collection + * + * This is simply an alias of $this->addRoute('HEAD', $route, $handler) + * + * @param string $route + * @param mixed $handler + */ + public function head($route, $handler) + { + $this->addRoute('HEAD', $route, $handler); + } + + /** + * Returns the collected route data, as provided by the data generator. + * + * @return array + */ + public function getData() + { + return $this->dataGenerator->getData(); + } +} diff --git a/vendor/nikic/fast-route/src/RouteParser.php b/vendor/nikic/fast-route/src/RouteParser.php new file mode 100644 index 0000000..6a7685c --- /dev/null +++ b/vendor/nikic/fast-route/src/RouteParser.php @@ -0,0 +1,37 @@ + $segment) { + if ($segment === '' && $n !== 0) { + throw new BadRouteException('Empty optional part'); + } + + $currentRoute .= $segment; + $routeDatas[] = $this->parsePlaceholders($currentRoute); + } + return $routeDatas; + } + + /** + * Parses a route string that does not contain optional segments. + * + * @param string + * @return mixed[] + */ + private function parsePlaceholders($route) + { + if (!preg_match_all( + '~' . self::VARIABLE_REGEX . '~x', $route, $matches, + PREG_OFFSET_CAPTURE | PREG_SET_ORDER + )) { + return [$route]; + } + + $offset = 0; + $routeData = []; + foreach ($matches as $set) { + if ($set[0][1] > $offset) { + $routeData[] = substr($route, $offset, $set[0][1] - $offset); + } + $routeData[] = [ + $set[1][0], + isset($set[2]) ? trim($set[2][0]) : self::DEFAULT_DISPATCH_REGEX + ]; + $offset = $set[0][1] + strlen($set[0][0]); + } + + if ($offset !== strlen($route)) { + $routeData[] = substr($route, $offset); + } + + return $routeData; + } +} diff --git a/vendor/nikic/fast-route/src/bootstrap.php b/vendor/nikic/fast-route/src/bootstrap.php new file mode 100644 index 0000000..0bce3a4 --- /dev/null +++ b/vendor/nikic/fast-route/src/bootstrap.php @@ -0,0 +1,12 @@ + 'FastRoute\\RouteParser\\Std', + 'dataGenerator' => 'FastRoute\\DataGenerator\\GroupCountBased', + 'dispatcher' => 'FastRoute\\Dispatcher\\GroupCountBased', + 'routeCollector' => 'FastRoute\\RouteCollector', + ]; + + /** @var RouteCollector $routeCollector */ + $routeCollector = new $options['routeCollector']( + new $options['routeParser'], new $options['dataGenerator'] + ); + $routeDefinitionCallback($routeCollector); + + return new $options['dispatcher']($routeCollector->getData()); + } + + /** + * @param callable $routeDefinitionCallback + * @param array $options + * + * @return Dispatcher + */ + function cachedDispatcher(callable $routeDefinitionCallback, array $options = []) + { + $options += [ + 'routeParser' => 'FastRoute\\RouteParser\\Std', + 'dataGenerator' => 'FastRoute\\DataGenerator\\GroupCountBased', + 'dispatcher' => 'FastRoute\\Dispatcher\\GroupCountBased', + 'routeCollector' => 'FastRoute\\RouteCollector', + 'cacheDisabled' => false, + ]; + + if (!isset($options['cacheFile'])) { + throw new \LogicException('Must specify "cacheFile" option'); + } + + if (!$options['cacheDisabled'] && file_exists($options['cacheFile'])) { + $dispatchData = require $options['cacheFile']; + if (!is_array($dispatchData)) { + throw new \RuntimeException('Invalid cache file "' . $options['cacheFile'] . '"'); + } + return new $options['dispatcher']($dispatchData); + } + + $routeCollector = new $options['routeCollector']( + new $options['routeParser'], new $options['dataGenerator'] + ); + $routeDefinitionCallback($routeCollector); + + /** @var RouteCollector $routeCollector */ + $dispatchData = $routeCollector->getData(); + if (!$options['cacheDisabled']) { + file_put_contents( + $options['cacheFile'], + ' $this->getDataGeneratorClass(), + 'dispatcher' => $this->getDispatcherClass() + ]; + } + + /** + * @dataProvider provideFoundDispatchCases + */ + public function testFoundDispatches($method, $uri, $callback, $handler, $argDict) + { + $dispatcher = \FastRoute\simpleDispatcher($callback, $this->generateDispatcherOptions()); + $info = $dispatcher->dispatch($method, $uri); + $this->assertSame($dispatcher::FOUND, $info[0]); + $this->assertSame($handler, $info[1]); + $this->assertSame($argDict, $info[2]); + } + + /** + * @dataProvider provideNotFoundDispatchCases + */ + public function testNotFoundDispatches($method, $uri, $callback) + { + $dispatcher = \FastRoute\simpleDispatcher($callback, $this->generateDispatcherOptions()); + $routeInfo = $dispatcher->dispatch($method, $uri); + $this->assertArrayNotHasKey(1, $routeInfo, + 'NOT_FOUND result must only contain a single element in the returned info array' + ); + $this->assertSame($dispatcher::NOT_FOUND, $routeInfo[0]); + } + + /** + * @dataProvider provideMethodNotAllowedDispatchCases + */ + public function testMethodNotAllowedDispatches($method, $uri, $callback, $availableMethods) + { + $dispatcher = \FastRoute\simpleDispatcher($callback, $this->generateDispatcherOptions()); + $routeInfo = $dispatcher->dispatch($method, $uri); + $this->assertArrayHasKey(1, $routeInfo, + 'METHOD_NOT_ALLOWED result must return an array of allowed methods at index 1' + ); + + list($routedStatus, $methodArray) = $dispatcher->dispatch($method, $uri); + $this->assertSame($dispatcher::METHOD_NOT_ALLOWED, $routedStatus); + $this->assertSame($availableMethods, $methodArray); + } + + /** + * @expectedException \FastRoute\BadRouteException + * @expectedExceptionMessage Cannot use the same placeholder "test" twice + */ + public function testDuplicateVariableNameError() + { + \FastRoute\simpleDispatcher(function (RouteCollector $r) { + $r->addRoute('GET', '/foo/{test}/{test:\d+}', 'handler0'); + }, $this->generateDispatcherOptions()); + } + + /** + * @expectedException \FastRoute\BadRouteException + * @expectedExceptionMessage Cannot register two routes matching "/user/([^/]+)" for method "GET" + */ + public function testDuplicateVariableRoute() + { + \FastRoute\simpleDispatcher(function (RouteCollector $r) { + $r->addRoute('GET', '/user/{id}', 'handler0'); // oops, forgot \d+ restriction ;) + $r->addRoute('GET', '/user/{name}', 'handler1'); + }, $this->generateDispatcherOptions()); + } + + /** + * @expectedException \FastRoute\BadRouteException + * @expectedExceptionMessage Cannot register two routes matching "/user" for method "GET" + */ + public function testDuplicateStaticRoute() + { + \FastRoute\simpleDispatcher(function (RouteCollector $r) { + $r->addRoute('GET', '/user', 'handler0'); + $r->addRoute('GET', '/user', 'handler1'); + }, $this->generateDispatcherOptions()); + } + + /** + * @expectedException \FastRoute\BadRouteException + * @expectedExceptionMessage Static route "/user/nikic" is shadowed by previously defined variable route "/user/([^/]+)" for method "GET" + */ + public function testShadowedStaticRoute() + { + \FastRoute\simpleDispatcher(function (RouteCollector $r) { + $r->addRoute('GET', '/user/{name}', 'handler0'); + $r->addRoute('GET', '/user/nikic', 'handler1'); + }, $this->generateDispatcherOptions()); + } + + /** + * @expectedException \FastRoute\BadRouteException + * @expectedExceptionMessage Regex "(en|de)" for parameter "lang" contains a capturing group + */ + public function testCapturing() + { + \FastRoute\simpleDispatcher(function (RouteCollector $r) { + $r->addRoute('GET', '/{lang:(en|de)}', 'handler0'); + }, $this->generateDispatcherOptions()); + } + + public function provideFoundDispatchCases() + { + $cases = []; + + // 0 --------------------------------------------------------------------------------------> + + $callback = function (RouteCollector $r) { + $r->addRoute('GET', '/resource/123/456', 'handler0'); + }; + + $method = 'GET'; + $uri = '/resource/123/456'; + $handler = 'handler0'; + $argDict = []; + + $cases[] = [$method, $uri, $callback, $handler, $argDict]; + + // 1 --------------------------------------------------------------------------------------> + + $callback = function (RouteCollector $r) { + $r->addRoute('GET', '/handler0', 'handler0'); + $r->addRoute('GET', '/handler1', 'handler1'); + $r->addRoute('GET', '/handler2', 'handler2'); + }; + + $method = 'GET'; + $uri = '/handler2'; + $handler = 'handler2'; + $argDict = []; + + $cases[] = [$method, $uri, $callback, $handler, $argDict]; + + // 2 --------------------------------------------------------------------------------------> + + $callback = function (RouteCollector $r) { + $r->addRoute('GET', '/user/{name}/{id:[0-9]+}', 'handler0'); + $r->addRoute('GET', '/user/{id:[0-9]+}', 'handler1'); + $r->addRoute('GET', '/user/{name}', 'handler2'); + }; + + $method = 'GET'; + $uri = '/user/rdlowrey'; + $handler = 'handler2'; + $argDict = ['name' => 'rdlowrey']; + + $cases[] = [$method, $uri, $callback, $handler, $argDict]; + + // 3 --------------------------------------------------------------------------------------> + + // reuse $callback from #2 + + $method = 'GET'; + $uri = '/user/12345'; + $handler = 'handler1'; + $argDict = ['id' => '12345']; + + $cases[] = [$method, $uri, $callback, $handler, $argDict]; + + // 4 --------------------------------------------------------------------------------------> + + // reuse $callback from #3 + + $method = 'GET'; + $uri = '/user/NaN'; + $handler = 'handler2'; + $argDict = ['name' => 'NaN']; + + $cases[] = [$method, $uri, $callback, $handler, $argDict]; + + // 5 --------------------------------------------------------------------------------------> + + // reuse $callback from #4 + + $method = 'GET'; + $uri = '/user/rdlowrey/12345'; + $handler = 'handler0'; + $argDict = ['name' => 'rdlowrey', 'id' => '12345']; + + $cases[] = [$method, $uri, $callback, $handler, $argDict]; + + // 6 --------------------------------------------------------------------------------------> + + $callback = function (RouteCollector $r) { + $r->addRoute('GET', '/user/{id:[0-9]+}', 'handler0'); + $r->addRoute('GET', '/user/12345/extension', 'handler1'); + $r->addRoute('GET', '/user/{id:[0-9]+}.{extension}', 'handler2'); + }; + + $method = 'GET'; + $uri = '/user/12345.svg'; + $handler = 'handler2'; + $argDict = ['id' => '12345', 'extension' => 'svg']; + + $cases[] = [$method, $uri, $callback, $handler, $argDict]; + + // 7 ----- Test GET method fallback on HEAD route miss ------------------------------------> + + $callback = function (RouteCollector $r) { + $r->addRoute('GET', '/user/{name}', 'handler0'); + $r->addRoute('GET', '/user/{name}/{id:[0-9]+}', 'handler1'); + $r->addRoute('GET', '/static0', 'handler2'); + $r->addRoute('GET', '/static1', 'handler3'); + $r->addRoute('HEAD', '/static1', 'handler4'); + }; + + $method = 'HEAD'; + $uri = '/user/rdlowrey'; + $handler = 'handler0'; + $argDict = ['name' => 'rdlowrey']; + + $cases[] = [$method, $uri, $callback, $handler, $argDict]; + + // 8 ----- Test GET method fallback on HEAD route miss ------------------------------------> + + // reuse $callback from #7 + + $method = 'HEAD'; + $uri = '/user/rdlowrey/1234'; + $handler = 'handler1'; + $argDict = ['name' => 'rdlowrey', 'id' => '1234']; + + $cases[] = [$method, $uri, $callback, $handler, $argDict]; + + // 9 ----- Test GET method fallback on HEAD route miss ------------------------------------> + + // reuse $callback from #8 + + $method = 'HEAD'; + $uri = '/static0'; + $handler = 'handler2'; + $argDict = []; + + $cases[] = [$method, $uri, $callback, $handler, $argDict]; + + // 10 ---- Test existing HEAD route used if available (no fallback) -----------------------> + + // reuse $callback from #9 + + $method = 'HEAD'; + $uri = '/static1'; + $handler = 'handler4'; + $argDict = []; + + $cases[] = [$method, $uri, $callback, $handler, $argDict]; + + // 11 ---- More specified routes are not shadowed by less specific of another method ------> + + $callback = function (RouteCollector $r) { + $r->addRoute('GET', '/user/{name}', 'handler0'); + $r->addRoute('POST', '/user/{name:[a-z]+}', 'handler1'); + }; + + $method = 'POST'; + $uri = '/user/rdlowrey'; + $handler = 'handler1'; + $argDict = ['name' => 'rdlowrey']; + + $cases[] = [$method, $uri, $callback, $handler, $argDict]; + + // 12 ---- Handler of more specific routes is used, if it occurs first --------------------> + + $callback = function (RouteCollector $r) { + $r->addRoute('GET', '/user/{name}', 'handler0'); + $r->addRoute('POST', '/user/{name:[a-z]+}', 'handler1'); + $r->addRoute('POST', '/user/{name}', 'handler2'); + }; + + $method = 'POST'; + $uri = '/user/rdlowrey'; + $handler = 'handler1'; + $argDict = ['name' => 'rdlowrey']; + + $cases[] = [$method, $uri, $callback, $handler, $argDict]; + + // 13 ---- Route with constant suffix -----------------------------------------------------> + + $callback = function (RouteCollector $r) { + $r->addRoute('GET', '/user/{name}', 'handler0'); + $r->addRoute('GET', '/user/{name}/edit', 'handler1'); + }; + + $method = 'GET'; + $uri = '/user/rdlowrey/edit'; + $handler = 'handler1'; + $argDict = ['name' => 'rdlowrey']; + + $cases[] = [$method, $uri, $callback, $handler, $argDict]; + + // 14 ---- Handle multiple methods with the same handler ----------------------------------> + + $callback = function (RouteCollector $r) { + $r->addRoute(['GET', 'POST'], '/user', 'handlerGetPost'); + $r->addRoute(['DELETE'], '/user', 'handlerDelete'); + $r->addRoute([], '/user', 'handlerNone'); + }; + + $argDict = []; + $cases[] = ['GET', '/user', $callback, 'handlerGetPost', $argDict]; + $cases[] = ['POST', '/user', $callback, 'handlerGetPost', $argDict]; + $cases[] = ['DELETE', '/user', $callback, 'handlerDelete', $argDict]; + + // 17 ---- + + $callback = function (RouteCollector $r) { + $r->addRoute('POST', '/user.json', 'handler0'); + $r->addRoute('GET', '/{entity}.json', 'handler1'); + }; + + $cases[] = ['GET', '/user.json', $callback, 'handler1', ['entity' => 'user']]; + + // 18 ---- + + $callback = function (RouteCollector $r) { + $r->addRoute('GET', '', 'handler0'); + }; + + $cases[] = ['GET', '', $callback, 'handler0', []]; + + // 19 ---- + + $callback = function (RouteCollector $r) { + $r->addRoute('HEAD', '/a/{foo}', 'handler0'); + $r->addRoute('GET', '/b/{foo}', 'handler1'); + }; + + $cases[] = ['HEAD', '/b/bar', $callback, 'handler1', ['foo' => 'bar']]; + + // 20 ---- + + $callback = function (RouteCollector $r) { + $r->addRoute('HEAD', '/a', 'handler0'); + $r->addRoute('GET', '/b', 'handler1'); + }; + + $cases[] = ['HEAD', '/b', $callback, 'handler1', []]; + + // 21 ---- + + $callback = function (RouteCollector $r) { + $r->addRoute('GET', '/foo', 'handler0'); + $r->addRoute('HEAD', '/{bar}', 'handler1'); + }; + + $cases[] = ['HEAD', '/foo', $callback, 'handler1', ['bar' => 'foo']]; + + // 22 ---- + + $callback = function (RouteCollector $r) { + $r->addRoute('*', '/user', 'handler0'); + $r->addRoute('*', '/{user}', 'handler1'); + $r->addRoute('GET', '/user', 'handler2'); + }; + + $cases[] = ['GET', '/user', $callback, 'handler2', []]; + + // 23 ---- + + $callback = function (RouteCollector $r) { + $r->addRoute('*', '/user', 'handler0'); + $r->addRoute('GET', '/user', 'handler1'); + }; + + $cases[] = ['POST', '/user', $callback, 'handler0', []]; + + // 24 ---- + + $cases[] = ['HEAD', '/user', $callback, 'handler1', []]; + + // 25 ---- + + $callback = function (RouteCollector $r) { + $r->addRoute('GET', '/{bar}', 'handler0'); + $r->addRoute('*', '/foo', 'handler1'); + }; + + $cases[] = ['GET', '/foo', $callback, 'handler0', ['bar' => 'foo']]; + + // 26 ---- + + $callback = function(RouteCollector $r) { + $r->addRoute('GET', '/user', 'handler0'); + $r->addRoute('*', '/{foo:.*}', 'handler1'); + }; + + $cases[] = ['POST', '/bar', $callback, 'handler1', ['foo' => 'bar']]; + + // x --------------------------------------------------------------------------------------> + + return $cases; + } + + public function provideNotFoundDispatchCases() + { + $cases = []; + + // 0 --------------------------------------------------------------------------------------> + + $callback = function (RouteCollector $r) { + $r->addRoute('GET', '/resource/123/456', 'handler0'); + }; + + $method = 'GET'; + $uri = '/not-found'; + + $cases[] = [$method, $uri, $callback]; + + // 1 --------------------------------------------------------------------------------------> + + // reuse callback from #0 + $method = 'POST'; + $uri = '/not-found'; + + $cases[] = [$method, $uri, $callback]; + + // 2 --------------------------------------------------------------------------------------> + + // reuse callback from #1 + $method = 'PUT'; + $uri = '/not-found'; + + $cases[] = [$method, $uri, $callback]; + + // 3 --------------------------------------------------------------------------------------> + + $callback = function (RouteCollector $r) { + $r->addRoute('GET', '/handler0', 'handler0'); + $r->addRoute('GET', '/handler1', 'handler1'); + $r->addRoute('GET', '/handler2', 'handler2'); + }; + + $method = 'GET'; + $uri = '/not-found'; + + $cases[] = [$method, $uri, $callback]; + + // 4 --------------------------------------------------------------------------------------> + + $callback = function (RouteCollector $r) { + $r->addRoute('GET', '/user/{name}/{id:[0-9]+}', 'handler0'); + $r->addRoute('GET', '/user/{id:[0-9]+}', 'handler1'); + $r->addRoute('GET', '/user/{name}', 'handler2'); + }; + + $method = 'GET'; + $uri = '/not-found'; + + $cases[] = [$method, $uri, $callback]; + + // 5 --------------------------------------------------------------------------------------> + + // reuse callback from #4 + $method = 'GET'; + $uri = '/user/rdlowrey/12345/not-found'; + + $cases[] = [$method, $uri, $callback]; + + // 6 --------------------------------------------------------------------------------------> + + // reuse callback from #5 + $method = 'HEAD'; + + $cases[] = [$method, $uri, $callback]; + + // x --------------------------------------------------------------------------------------> + + return $cases; + } + + public function provideMethodNotAllowedDispatchCases() + { + $cases = []; + + // 0 --------------------------------------------------------------------------------------> + + $callback = function (RouteCollector $r) { + $r->addRoute('GET', '/resource/123/456', 'handler0'); + }; + + $method = 'POST'; + $uri = '/resource/123/456'; + $allowedMethods = ['GET']; + + $cases[] = [$method, $uri, $callback, $allowedMethods]; + + // 1 --------------------------------------------------------------------------------------> + + $callback = function (RouteCollector $r) { + $r->addRoute('GET', '/resource/123/456', 'handler0'); + $r->addRoute('POST', '/resource/123/456', 'handler1'); + $r->addRoute('PUT', '/resource/123/456', 'handler2'); + $r->addRoute('*', '/', 'handler3'); + }; + + $method = 'DELETE'; + $uri = '/resource/123/456'; + $allowedMethods = ['GET', 'POST', 'PUT']; + + $cases[] = [$method, $uri, $callback, $allowedMethods]; + + // 2 --------------------------------------------------------------------------------------> + + $callback = function (RouteCollector $r) { + $r->addRoute('GET', '/user/{name}/{id:[0-9]+}', 'handler0'); + $r->addRoute('POST', '/user/{name}/{id:[0-9]+}', 'handler1'); + $r->addRoute('PUT', '/user/{name}/{id:[0-9]+}', 'handler2'); + $r->addRoute('PATCH', '/user/{name}/{id:[0-9]+}', 'handler3'); + }; + + $method = 'DELETE'; + $uri = '/user/rdlowrey/42'; + $allowedMethods = ['GET', 'POST', 'PUT', 'PATCH']; + + $cases[] = [$method, $uri, $callback, $allowedMethods]; + + // 3 --------------------------------------------------------------------------------------> + + $callback = function (RouteCollector $r) { + $r->addRoute('POST', '/user/{name}', 'handler1'); + $r->addRoute('PUT', '/user/{name:[a-z]+}', 'handler2'); + $r->addRoute('PATCH', '/user/{name:[a-z]+}', 'handler3'); + }; + + $method = 'GET'; + $uri = '/user/rdlowrey'; + $allowedMethods = ['POST', 'PUT', 'PATCH']; + + $cases[] = [$method, $uri, $callback, $allowedMethods]; + + // 4 --------------------------------------------------------------------------------------> + + $callback = function (RouteCollector $r) { + $r->addRoute(['GET', 'POST'], '/user', 'handlerGetPost'); + $r->addRoute(['DELETE'], '/user', 'handlerDelete'); + $r->addRoute([], '/user', 'handlerNone'); + }; + + $cases[] = ['PUT', '/user', $callback, ['GET', 'POST', 'DELETE']]; + + // 5 + + $callback = function (RouteCollector $r) { + $r->addRoute('POST', '/user.json', 'handler0'); + $r->addRoute('GET', '/{entity}.json', 'handler1'); + }; + + $cases[] = ['PUT', '/user.json', $callback, ['POST', 'GET']]; + + // x --------------------------------------------------------------------------------------> + + return $cases; + } +} diff --git a/vendor/nikic/fast-route/test/Dispatcher/GroupCountBasedTest.php b/vendor/nikic/fast-route/test/Dispatcher/GroupCountBasedTest.php new file mode 100644 index 0000000..f821ef5 --- /dev/null +++ b/vendor/nikic/fast-route/test/Dispatcher/GroupCountBasedTest.php @@ -0,0 +1,16 @@ +markTestSkipped('PHP 5.6 required for MARK support'); + } + } + + protected function getDispatcherClass() + { + return 'FastRoute\\Dispatcher\\MarkBased'; + } + + protected function getDataGeneratorClass() + { + return 'FastRoute\\DataGenerator\\MarkBased'; + } +} diff --git a/vendor/nikic/fast-route/test/HackTypechecker/HackTypecheckerTest.php b/vendor/nikic/fast-route/test/HackTypechecker/HackTypecheckerTest.php new file mode 100644 index 0000000..b6fc53f --- /dev/null +++ b/vendor/nikic/fast-route/test/HackTypechecker/HackTypecheckerTest.php @@ -0,0 +1,44 @@ +markTestSkipped('HHVM only'); + } + if (!version_compare(HHVM_VERSION, '3.9.0', '>=')) { + $this->markTestSkipped('classname requires HHVM 3.9+'); + } + + // The typechecker recurses the whole tree, so it makes sure + // that everything in fixtures/ is valid when this runs. + + $output = []; + $exit_code = null; + exec( + 'hh_server --check ' . escapeshellarg(__DIR__ . '/../../') . ' 2>&1', + $output, + $exit_code + ); + if ($exit_code === self::SERVER_ALREADY_RUNNING_CODE) { + $this->assertTrue( + $recurse, + 'Typechecker still running after running hh_client stop' + ); + // Server already running - 3.10 => 3.11 regression: + // https://github.com/facebook/hhvm/issues/6646 + exec('hh_client stop 2>/dev/null'); + $this->testTypechecks(/* recurse = */ false); + return; + + } + $this->assertSame(0, $exit_code, implode("\n", $output)); + } +} diff --git a/vendor/nikic/fast-route/test/HackTypechecker/fixtures/all_options.php b/vendor/nikic/fast-route/test/HackTypechecker/fixtures/all_options.php new file mode 100644 index 0000000..05a9af2 --- /dev/null +++ b/vendor/nikic/fast-route/test/HackTypechecker/fixtures/all_options.php @@ -0,0 +1,29 @@ + {}, + shape( + 'routeParser' => \FastRoute\RouteParser\Std::class, + 'dataGenerator' => \FastRoute\DataGenerator\GroupCountBased::class, + 'dispatcher' => \FastRoute\Dispatcher\GroupCountBased::class, + 'routeCollector' => \FastRoute\RouteCollector::class, + ), + ); +} + +function all_options_cached(): \FastRoute\Dispatcher { + return \FastRoute\cachedDispatcher( + $collector ==> {}, + shape( + 'routeParser' => \FastRoute\RouteParser\Std::class, + 'dataGenerator' => \FastRoute\DataGenerator\GroupCountBased::class, + 'dispatcher' => \FastRoute\Dispatcher\GroupCountBased::class, + 'routeCollector' => \FastRoute\RouteCollector::class, + 'cacheFile' => '/dev/null', + 'cacheDisabled' => false, + ), + ); +} diff --git a/vendor/nikic/fast-route/test/HackTypechecker/fixtures/empty_options.php b/vendor/nikic/fast-route/test/HackTypechecker/fixtures/empty_options.php new file mode 100644 index 0000000..61eb541 --- /dev/null +++ b/vendor/nikic/fast-route/test/HackTypechecker/fixtures/empty_options.php @@ -0,0 +1,11 @@ + {}, shape()); +} + +function empty_options_cached(): \FastRoute\Dispatcher { + return \FastRoute\cachedDispatcher($collector ==> {}, shape()); +} diff --git a/vendor/nikic/fast-route/test/HackTypechecker/fixtures/no_options.php b/vendor/nikic/fast-route/test/HackTypechecker/fixtures/no_options.php new file mode 100644 index 0000000..44b5422 --- /dev/null +++ b/vendor/nikic/fast-route/test/HackTypechecker/fixtures/no_options.php @@ -0,0 +1,11 @@ + {}); +} + +function no_options_cached(): \FastRoute\Dispatcher { + return \FastRoute\cachedDispatcher($collector ==> {}); +} diff --git a/vendor/nikic/fast-route/test/RouteCollectorTest.php b/vendor/nikic/fast-route/test/RouteCollectorTest.php new file mode 100644 index 0000000..cc54407 --- /dev/null +++ b/vendor/nikic/fast-route/test/RouteCollectorTest.php @@ -0,0 +1,108 @@ +delete('/delete', 'delete'); + $r->get('/get', 'get'); + $r->head('/head', 'head'); + $r->patch('/patch', 'patch'); + $r->post('/post', 'post'); + $r->put('/put', 'put'); + + $expected = [ + ['DELETE', '/delete', 'delete'], + ['GET', '/get', 'get'], + ['HEAD', '/head', 'head'], + ['PATCH', '/patch', 'patch'], + ['POST', '/post', 'post'], + ['PUT', '/put', 'put'], + ]; + + $this->assertSame($expected, $r->routes); + } + + public function testGroups() + { + $r = new DummyRouteCollector(); + + $r->delete('/delete', 'delete'); + $r->get('/get', 'get'); + $r->head('/head', 'head'); + $r->patch('/patch', 'patch'); + $r->post('/post', 'post'); + $r->put('/put', 'put'); + + $r->addGroup('/group-one', function (DummyRouteCollector $r) { + $r->delete('/delete', 'delete'); + $r->get('/get', 'get'); + $r->head('/head', 'head'); + $r->patch('/patch', 'patch'); + $r->post('/post', 'post'); + $r->put('/put', 'put'); + + $r->addGroup('/group-two', function (DummyRouteCollector $r) { + $r->delete('/delete', 'delete'); + $r->get('/get', 'get'); + $r->head('/head', 'head'); + $r->patch('/patch', 'patch'); + $r->post('/post', 'post'); + $r->put('/put', 'put'); + }); + }); + + $r->addGroup('/admin', function (DummyRouteCollector $r) { + $r->get('-some-info', 'admin-some-info'); + }); + $r->addGroup('/admin-', function (DummyRouteCollector $r) { + $r->get('more-info', 'admin-more-info'); + }); + + $expected = [ + ['DELETE', '/delete', 'delete'], + ['GET', '/get', 'get'], + ['HEAD', '/head', 'head'], + ['PATCH', '/patch', 'patch'], + ['POST', '/post', 'post'], + ['PUT', '/put', 'put'], + ['DELETE', '/group-one/delete', 'delete'], + ['GET', '/group-one/get', 'get'], + ['HEAD', '/group-one/head', 'head'], + ['PATCH', '/group-one/patch', 'patch'], + ['POST', '/group-one/post', 'post'], + ['PUT', '/group-one/put', 'put'], + ['DELETE', '/group-one/group-two/delete', 'delete'], + ['GET', '/group-one/group-two/get', 'get'], + ['HEAD', '/group-one/group-two/head', 'head'], + ['PATCH', '/group-one/group-two/patch', 'patch'], + ['POST', '/group-one/group-two/post', 'post'], + ['PUT', '/group-one/group-two/put', 'put'], + ['GET', '/admin-some-info', 'admin-some-info'], + ['GET', '/admin-more-info', 'admin-more-info'], + ]; + + $this->assertSame($expected, $r->routes); + } +} + +class DummyRouteCollector extends RouteCollector +{ + public $routes = []; + + public function __construct() + { + } + + public function addRoute($method, $route, $handler) + { + $route = $this->currentGroupPrefix . $route; + $this->routes[] = [$method, $route, $handler]; + } +} diff --git a/vendor/nikic/fast-route/test/RouteParser/StdTest.php b/vendor/nikic/fast-route/test/RouteParser/StdTest.php new file mode 100644 index 0000000..e13e4de --- /dev/null +++ b/vendor/nikic/fast-route/test/RouteParser/StdTest.php @@ -0,0 +1,154 @@ +parse($routeString); + $this->assertSame($expectedRouteDatas, $routeDatas); + } + + /** @dataProvider provideTestParseError */ + public function testParseError($routeString, $expectedExceptionMessage) + { + $parser = new Std(); + $this->setExpectedException('FastRoute\\BadRouteException', $expectedExceptionMessage); + $parser->parse($routeString); + } + + public function provideTestParse() + { + return [ + [ + '/test', + [ + ['/test'], + ] + ], + [ + '/test/{param}', + [ + ['/test/', ['param', '[^/]+']], + ] + ], + [ + '/te{ param }st', + [ + ['/te', ['param', '[^/]+'], 'st'] + ] + ], + [ + '/test/{param1}/test2/{param2}', + [ + ['/test/', ['param1', '[^/]+'], '/test2/', ['param2', '[^/]+']] + ] + ], + [ + '/test/{param:\d+}', + [ + ['/test/', ['param', '\d+']] + ] + ], + [ + '/test/{ param : \d{1,9} }', + [ + ['/test/', ['param', '\d{1,9}']] + ] + ], + [ + '/test[opt]', + [ + ['/test'], + ['/testopt'], + ] + ], + [ + '/test[/{param}]', + [ + ['/test'], + ['/test/', ['param', '[^/]+']], + ] + ], + [ + '/{param}[opt]', + [ + ['/', ['param', '[^/]+']], + ['/', ['param', '[^/]+'], 'opt'] + ] + ], + [ + '/test[/{name}[/{id:[0-9]+}]]', + [ + ['/test'], + ['/test/', ['name', '[^/]+']], + ['/test/', ['name', '[^/]+'], '/', ['id', '[0-9]+']], + ] + ], + [ + '', + [ + [''], + ] + ], + [ + '[test]', + [ + [''], + ['test'], + ] + ], + [ + '/{foo-bar}', + [ + ['/', ['foo-bar', '[^/]+']] + ] + ], + [ + '/{_foo:.*}', + [ + ['/', ['_foo', '.*']] + ] + ], + ]; + } + + public function provideTestParseError() + { + return [ + [ + '/test[opt', + "Number of opening '[' and closing ']' does not match" + ], + [ + '/test[opt[opt2]', + "Number of opening '[' and closing ']' does not match" + ], + [ + '/testopt]', + "Number of opening '[' and closing ']' does not match" + ], + [ + '/test[]', + 'Empty optional part' + ], + [ + '/test[[opt]]', + 'Empty optional part' + ], + [ + '[[test]]', + 'Empty optional part' + ], + [ + '/test[/opt]/required', + 'Optional segments can only occur at the end of a route' + ], + ]; + } +} diff --git a/vendor/nikic/fast-route/test/bootstrap.php b/vendor/nikic/fast-route/test/bootstrap.php new file mode 100644 index 0000000..3023f41 --- /dev/null +++ b/vendor/nikic/fast-route/test/bootstrap.php @@ -0,0 +1,11 @@ + + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace PhpOption; + +use Traversable; + +/** + * @template T + * + * @extends Option + */ +final class LazyOption extends Option +{ + /** @var callable(mixed...):(Option) */ + private $callback; + + /** @var array */ + private $arguments; + + /** @var Option|null */ + private $option; + + /** + * @template S + * @param callable(mixed...):(Option) $callback + * @param array $arguments + * + * @return LazyOption + */ + public static function create($callback, array $arguments = []): self + { + return new self($callback, $arguments); + } + + /** + * @param callable(mixed...):(Option) $callback + * @param array $arguments + */ + public function __construct($callback, array $arguments = []) + { + if (!is_callable($callback)) { + throw new \InvalidArgumentException('Invalid callback given'); + } + + $this->callback = $callback; + $this->arguments = $arguments; + } + + public function isDefined(): bool + { + return $this->option()->isDefined(); + } + + public function isEmpty(): bool + { + return $this->option()->isEmpty(); + } + + public function get() + { + return $this->option()->get(); + } + + public function getOrElse($default) + { + return $this->option()->getOrElse($default); + } + + public function getOrCall($callable) + { + return $this->option()->getOrCall($callable); + } + + public function getOrThrow(\Exception $ex) + { + return $this->option()->getOrThrow($ex); + } + + public function orElse(Option $else) + { + return $this->option()->orElse($else); + } + + public function ifDefined($callable) + { + $this->option()->forAll($callable); + } + + public function forAll($callable) + { + return $this->option()->forAll($callable); + } + + public function map($callable) + { + return $this->option()->map($callable); + } + + public function flatMap($callable) + { + return $this->option()->flatMap($callable); + } + + public function filter($callable) + { + return $this->option()->filter($callable); + } + + public function filterNot($callable) + { + return $this->option()->filterNot($callable); + } + + public function select($value) + { + return $this->option()->select($value); + } + + public function reject($value) + { + return $this->option()->reject($value); + } + + /** + * @return Traversable + */ + public function getIterator(): Traversable + { + return $this->option()->getIterator(); + } + + public function foldLeft($initialValue, $callable) + { + return $this->option()->foldLeft($initialValue, $callable); + } + + public function foldRight($initialValue, $callable) + { + return $this->option()->foldRight($initialValue, $callable); + } + + /** + * @return Option + */ + private function option(): Option + { + if (null === $this->option) { + /** @var mixed */ + $option = call_user_func_array($this->callback, $this->arguments); + if ($option instanceof Option) { + $this->option = $option; + } else { + throw new \RuntimeException(sprintf('Expected instance of %s', Option::class)); + } + } + + return $this->option; + } +} diff --git a/vendor/phpoption/phpoption/src/PhpOption/None.php b/vendor/phpoption/phpoption/src/PhpOption/None.php new file mode 100644 index 0000000..4b85d22 --- /dev/null +++ b/vendor/phpoption/phpoption/src/PhpOption/None.php @@ -0,0 +1,136 @@ + + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace PhpOption; + +use EmptyIterator; + +/** + * @extends Option + */ +final class None extends Option +{ + /** @var None|null */ + private static $instance; + + /** + * @return None + */ + public static function create(): self + { + if (null === self::$instance) { + self::$instance = new self(); + } + + return self::$instance; + } + + public function get() + { + throw new \RuntimeException('None has no value.'); + } + + public function getOrCall($callable) + { + return $callable(); + } + + public function getOrElse($default) + { + return $default; + } + + public function getOrThrow(\Exception $ex) + { + throw $ex; + } + + public function isEmpty(): bool + { + return true; + } + + public function isDefined(): bool + { + return false; + } + + public function orElse(Option $else) + { + return $else; + } + + public function ifDefined($callable) + { + // Just do nothing in that case. + } + + public function forAll($callable) + { + return $this; + } + + public function map($callable) + { + return $this; + } + + public function flatMap($callable) + { + return $this; + } + + public function filter($callable) + { + return $this; + } + + public function filterNot($callable) + { + return $this; + } + + public function select($value) + { + return $this; + } + + public function reject($value) + { + return $this; + } + + public function getIterator(): EmptyIterator + { + return new EmptyIterator(); + } + + public function foldLeft($initialValue, $callable) + { + return $initialValue; + } + + public function foldRight($initialValue, $callable) + { + return $initialValue; + } + + private function __construct() + { + } +} diff --git a/vendor/phpoption/phpoption/src/PhpOption/Option.php b/vendor/phpoption/phpoption/src/PhpOption/Option.php new file mode 100644 index 0000000..172924c --- /dev/null +++ b/vendor/phpoption/phpoption/src/PhpOption/Option.php @@ -0,0 +1,434 @@ + + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace PhpOption; + +use ArrayAccess; +use IteratorAggregate; + +/** + * @template T + * + * @implements IteratorAggregate + */ +abstract class Option implements IteratorAggregate +{ + /** + * Creates an option given a return value. + * + * This is intended for consuming existing APIs and allows you to easily + * convert them to an option. By default, we treat ``null`` as the None + * case, and everything else as Some. + * + * @template S + * + * @param S $value The actual return value. + * @param S $noneValue The value which should be considered "None"; null by + * default. + * + * @return Option + */ + public static function fromValue($value, $noneValue = null) + { + if ($value === $noneValue) { + return None::create(); + } + + return new Some($value); + } + + /** + * Creates an option from an array's value. + * + * If the key does not exist in the array, the array is not actually an + * array, or the array's value at the given key is null, None is returned. + * Otherwise, Some is returned wrapping the value at the given key. + * + * @template S + * + * @param array|ArrayAccess|null $array A potential array or \ArrayAccess value. + * @param string $key The key to check. + * + * @return Option + */ + public static function fromArraysValue($array, $key) + { + if (!(is_array($array) || $array instanceof ArrayAccess) || !isset($array[$key])) { + return None::create(); + } + + return new Some($array[$key]); + } + + /** + * Creates a lazy-option with the given callback. + * + * This is also a helper constructor for lazy-consuming existing APIs where + * the return value is not yet an option. By default, we treat ``null`` as + * None case, and everything else as Some. + * + * @template S + * + * @param callable $callback The callback to evaluate. + * @param array $arguments The arguments for the callback. + * @param S $noneValue The value which should be considered "None"; + * null by default. + * + * @return LazyOption + */ + public static function fromReturn($callback, array $arguments = [], $noneValue = null) + { + return new LazyOption(static function () use ($callback, $arguments, $noneValue) { + /** @var mixed */ + $return = call_user_func_array($callback, $arguments); + + if ($return === $noneValue) { + return None::create(); + } + + return new Some($return); + }); + } + + /** + * Option factory, which creates new option based on passed value. + * + * If value is already an option, it simply returns. If value is callable, + * LazyOption with passed callback created and returned. If Option + * returned from callback, it returns directly. On other case value passed + * to Option::fromValue() method. + * + * @template S + * + * @param Option|callable|S $value + * @param S $noneValue Used when $value is mixed or + * callable, for None-check. + * + * @return Option|LazyOption + */ + public static function ensure($value, $noneValue = null) + { + if ($value instanceof self) { + return $value; + } elseif (is_callable($value)) { + return new LazyOption(static function () use ($value, $noneValue) { + /** @var mixed */ + $return = $value(); + + if ($return instanceof self) { + return $return; + } else { + return self::fromValue($return, $noneValue); + } + }); + } else { + return self::fromValue($value, $noneValue); + } + } + + /** + * Lift a function so that it accepts Option as parameters. + * + * We return a new closure that wraps the original callback. If any of the + * parameters passed to the lifted function is empty, the function will + * return a value of None. Otherwise, we will pass all parameters to the + * original callback and return the value inside a new Option, unless an + * Option is returned from the function, in which case, we use that. + * + * @template S + * + * @param callable $callback + * @param mixed $noneValue + * + * @return callable + */ + public static function lift($callback, $noneValue = null) + { + return static function () use ($callback, $noneValue) { + /** @var array */ + $args = func_get_args(); + + $reduced_args = array_reduce( + $args, + /** @param bool $status */ + static function ($status, self $o) { + return $o->isEmpty() ? true : $status; + }, + false + ); + // if at least one parameter is empty, return None + if ($reduced_args) { + return None::create(); + } + + $args = array_map( + /** @return T */ + static function (self $o) { + // it is safe to do so because the fold above checked + // that all arguments are of type Some + /** @var T */ + return $o->get(); + }, + $args + ); + + return self::ensure(call_user_func_array($callback, $args), $noneValue); + }; + } + + /** + * Returns the value if available, or throws an exception otherwise. + * + * @throws \RuntimeException If value is not available. + * + * @return T + */ + abstract public function get(); + + /** + * Returns the value if available, or the default value if not. + * + * @template S + * + * @param S $default + * + * @return T|S + */ + abstract public function getOrElse($default); + + /** + * Returns the value if available, or the results of the callable. + * + * This is preferable over ``getOrElse`` if the computation of the default + * value is expensive. + * + * @template S + * + * @param callable():S $callable + * + * @return T|S + */ + abstract public function getOrCall($callable); + + /** + * Returns the value if available, or throws the passed exception. + * + * @param \Exception $ex + * + * @return T + */ + abstract public function getOrThrow(\Exception $ex); + + /** + * Returns true if no value is available, false otherwise. + * + * @return bool + */ + abstract public function isEmpty(); + + /** + * Returns true if a value is available, false otherwise. + * + * @return bool + */ + abstract public function isDefined(); + + /** + * Returns this option if non-empty, or the passed option otherwise. + * + * This can be used to try multiple alternatives, and is especially useful + * with lazy evaluating options: + * + * ```php + * $repo->findSomething() + * ->orElse(new LazyOption(array($repo, 'findSomethingElse'))) + * ->orElse(new LazyOption(array($repo, 'createSomething'))); + * ``` + * + * @param Option $else + * + * @return Option + */ + abstract public function orElse(self $else); + + /** + * This is similar to map() below except that the return value has no meaning; + * the passed callable is simply executed if the option is non-empty, and + * ignored if the option is empty. + * + * In all cases, the return value of the callable is discarded. + * + * ```php + * $comment->getMaybeFile()->ifDefined(function($file) { + * // Do something with $file here. + * }); + * ``` + * + * If you're looking for something like ``ifEmpty``, you can use ``getOrCall`` + * and ``getOrElse`` in these cases. + * + * @deprecated Use forAll() instead. + * + * @param callable(T):mixed $callable + * + * @return void + */ + abstract public function ifDefined($callable); + + /** + * This is similar to map() except that the return value of the callable has no meaning. + * + * The passed callable is simply executed if the option is non-empty, and ignored if the + * option is empty. This method is preferred for callables with side-effects, while map() + * is intended for callables without side-effects. + * + * @param callable(T):mixed $callable + * + * @return Option + */ + abstract public function forAll($callable); + + /** + * Applies the callable to the value of the option if it is non-empty, + * and returns the return value of the callable wrapped in Some(). + * + * If the option is empty, then the callable is not applied. + * + * ```php + * (new Some("foo"))->map('strtoupper')->get(); // "FOO" + * ``` + * + * @template S + * + * @param callable(T):S $callable + * + * @return Option + */ + abstract public function map($callable); + + /** + * Applies the callable to the value of the option if it is non-empty, and + * returns the return value of the callable directly. + * + * In contrast to ``map``, the return value of the callable is expected to + * be an Option itself; it is not automatically wrapped in Some(). + * + * @template S + * + * @param callable(T):Option $callable must return an Option + * + * @return Option + */ + abstract public function flatMap($callable); + + /** + * If the option is empty, it is returned immediately without applying the callable. + * + * If the option is non-empty, the callable is applied, and if it returns true, + * the option itself is returned; otherwise, None is returned. + * + * @param callable(T):bool $callable + * + * @return Option + */ + abstract public function filter($callable); + + /** + * If the option is empty, it is returned immediately without applying the callable. + * + * If the option is non-empty, the callable is applied, and if it returns false, + * the option itself is returned; otherwise, None is returned. + * + * @param callable(T):bool $callable + * + * @return Option + */ + abstract public function filterNot($callable); + + /** + * If the option is empty, it is returned immediately. + * + * If the option is non-empty, and its value does not equal the passed value + * (via a shallow comparison ===), then None is returned. Otherwise, the + * Option is returned. + * + * In other words, this will filter all but the passed value. + * + * @param T $value + * + * @return Option + */ + abstract public function select($value); + + /** + * If the option is empty, it is returned immediately. + * + * If the option is non-empty, and its value does equal the passed value (via + * a shallow comparison ===), then None is returned; otherwise, the Option is + * returned. + * + * In other words, this will let all values through except the passed value. + * + * @param T $value + * + * @return Option + */ + abstract public function reject($value); + + /** + * Binary operator for the initial value and the option's value. + * + * If empty, the initial value is returned. If non-empty, the callable + * receives the initial value and the option's value as arguments. + * + * ```php + * + * $some = new Some(5); + * $none = None::create(); + * $result = $some->foldLeft(1, function($a, $b) { return $a + $b; }); // int(6) + * $result = $none->foldLeft(1, function($a, $b) { return $a + $b; }); // int(1) + * + * // This can be used instead of something like the following: + * $option = Option::fromValue($integerOrNull); + * $result = 1; + * if ( ! $option->isEmpty()) { + * $result += $option->get(); + * } + * ``` + * + * @template S + * + * @param S $initialValue + * @param callable(S, T):S $callable + * + * @return S + */ + abstract public function foldLeft($initialValue, $callable); + + /** + * foldLeft() but with reversed arguments for the callable. + * + * @template S + * + * @param S $initialValue + * @param callable(T, S):S $callable + * + * @return S + */ + abstract public function foldRight($initialValue, $callable); +} diff --git a/vendor/phpoption/phpoption/src/PhpOption/Some.php b/vendor/phpoption/phpoption/src/PhpOption/Some.php new file mode 100644 index 0000000..032632e --- /dev/null +++ b/vendor/phpoption/phpoption/src/PhpOption/Some.php @@ -0,0 +1,169 @@ + + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace PhpOption; + +use ArrayIterator; + +/** + * @template T + * + * @extends Option + */ +final class Some extends Option +{ + /** @var T */ + private $value; + + /** + * @param T $value + */ + public function __construct($value) + { + $this->value = $value; + } + + /** + * @template U + * + * @param U $value + * + * @return Some + */ + public static function create($value): self + { + return new self($value); + } + + public function isDefined(): bool + { + return true; + } + + public function isEmpty(): bool + { + return false; + } + + public function get() + { + return $this->value; + } + + public function getOrElse($default) + { + return $this->value; + } + + public function getOrCall($callable) + { + return $this->value; + } + + public function getOrThrow(\Exception $ex) + { + return $this->value; + } + + public function orElse(Option $else) + { + return $this; + } + + public function ifDefined($callable) + { + $this->forAll($callable); + } + + public function forAll($callable) + { + $callable($this->value); + + return $this; + } + + public function map($callable) + { + return new self($callable($this->value)); + } + + public function flatMap($callable) + { + /** @var mixed */ + $rs = $callable($this->value); + if (!$rs instanceof Option) { + throw new \RuntimeException('Callables passed to flatMap() must return an Option. Maybe you should use map() instead?'); + } + + return $rs; + } + + public function filter($callable) + { + if (true === $callable($this->value)) { + return $this; + } + + return None::create(); + } + + public function filterNot($callable) + { + if (false === $callable($this->value)) { + return $this; + } + + return None::create(); + } + + public function select($value) + { + if ($this->value === $value) { + return $this; + } + + return None::create(); + } + + public function reject($value) + { + if ($this->value === $value) { + return None::create(); + } + + return $this; + } + + /** + * @return ArrayIterator + */ + public function getIterator(): ArrayIterator + { + return new ArrayIterator([$this->value]); + } + + public function foldLeft($initialValue, $callable) + { + return $callable($initialValue, $this->value); + } + + public function foldRight($initialValue, $callable) + { + return $callable($this->value, $initialValue); + } +} diff --git a/vendor/psr/container/.gitignore b/vendor/psr/container/.gitignore new file mode 100644 index 0000000..b2395aa --- /dev/null +++ b/vendor/psr/container/.gitignore @@ -0,0 +1,3 @@ +composer.lock +composer.phar +/vendor/ diff --git a/vendor/psr/container/LICENSE b/vendor/psr/container/LICENSE new file mode 100644 index 0000000..2877a48 --- /dev/null +++ b/vendor/psr/container/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2013-2016 container-interop +Copyright (c) 2016 PHP Framework Interoperability Group + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/psr/container/README.md b/vendor/psr/container/README.md new file mode 100644 index 0000000..1b9d9e5 --- /dev/null +++ b/vendor/psr/container/README.md @@ -0,0 +1,13 @@ +Container interface +============== + +This repository holds all interfaces related to [PSR-11 (Container Interface)][psr-url]. + +Note that this is not a Container implementation of its own. It is merely abstractions that describe the components of a Dependency Injection Container. + +The installable [package][package-url] and [implementations][implementation-url] are listed on Packagist. + +[psr-url]: https://www.php-fig.org/psr/psr-11/ +[package-url]: https://packagist.org/packages/psr/container +[implementation-url]: https://packagist.org/providers/psr/container-implementation + diff --git a/vendor/psr/container/src/ContainerExceptionInterface.php b/vendor/psr/container/src/ContainerExceptionInterface.php new file mode 100644 index 0000000..0f213f2 --- /dev/null +++ b/vendor/psr/container/src/ContainerExceptionInterface.php @@ -0,0 +1,12 @@ + `RequestInterface`, `ServerRequestInterface`, `ResponseInterface` extend `MessageInterface` because the `Request` and the `Response` are `HTTP Messages`. +> When using `ServerRequestInterface`, both `RequestInterface` and `Psr\Http\Message\MessageInterface` methods are considered. + diff --git a/vendor/psr/http-message/docs/PSR7-Usage.md b/vendor/psr/http-message/docs/PSR7-Usage.md new file mode 100644 index 0000000..b6d048a --- /dev/null +++ b/vendor/psr/http-message/docs/PSR7-Usage.md @@ -0,0 +1,159 @@ +### PSR-7 Usage + +All PSR-7 applications comply with these interfaces +They were created to establish a standard between middleware implementations. + +> `RequestInterface`, `ServerRequestInterface`, `ResponseInterface` extend `MessageInterface` because the `Request` and the `Response` are `HTTP Messages`. +> When using `ServerRequestInterface`, both `RequestInterface` and `Psr\Http\Message\MessageInterface` methods are considered. + + +The following examples will illustrate how basic operations are done in PSR-7. + +##### Examples + + +For this examples to work (at least) a PSR-7 implementation package is required. (eg: zendframework/zend-diactoros, guzzlehttp/psr7, slim/slim, etc) +All PSR-7 implementations should have the same behaviour. + +The following will be assumed: +`$request` is an object of `Psr\Http\Message\RequestInterface` and + +`$response` is an object implementing `Psr\Http\Message\RequestInterface` + + +### Working with HTTP Headers + +#### Adding headers to response: + +```php +$response->withHeader('My-Custom-Header', 'My Custom Message'); +``` + +#### Appending values to headers + +```php +$response->withAddedHeader('My-Custom-Header', 'The second message'); +``` + +#### Checking if header exists: + +```php +$request->hasHeader('My-Custom-Header'); // will return false +$response->hasHeader('My-Custom-Header'); // will return true +``` + +> Note: My-Custom-Header was only added in the Response + +#### Getting comma-separated values from a header (also applies to request) + +```php +// getting value from request headers +$request->getHeaderLine('Content-Type'); // will return: "text/html; charset=UTF-8" +// getting value from response headers +$response->getHeaderLine('My-Custom-Header'); // will return: "My Custom Message; The second message" +``` + +#### Getting array of value from a header (also applies to request) +```php +// getting value from request headers +$request->getHeader('Content-Type'); // will return: ["text/html", "charset=UTF-8"] +// getting value from response headers +$response->getHeader('My-Custom-Header'); // will return: ["My Custom Message", "The second message"] +``` + +#### Removing headers from HTTP Messages +```php +// removing a header from Request, removing deprecated "Content-MD5" header +$request->withoutHeader('Content-MD5'); + +// removing a header from Response +// effect: the browser won't know the size of the stream +// the browser will download the stream till it ends +$response->withoutHeader('Content-Length'); +``` + +### Working with HTTP Message Body + +When working with the PSR-7 there are two methods of implementation: +#### 1. Getting the body separately + +> This method makes the body handling easier to understand and is useful when repeatedly calling body methods. (You only call `getBody()` once). Using this method mistakes like `$response->write()` are also prevented. + +```php +$body = $response->getBody(); +// operations on body, eg. read, write, seek +// ... +// replacing the old body +$response->withBody($body); +// this last statement is optional as we working with objects +// in this case the "new" body is same with the "old" one +// the $body variable has the same value as the one in $request, only the reference is passed +``` + +#### 2. Working directly on response + +> This method is useful when only performing few operations as the `$request->getBody()` statement fragment is required + +```php +$response->getBody()->write('hello'); +``` + +### Getting the body contents + +The following snippet gets the contents of a stream contents. +> Note: Streams must be rewinded, if content was written into streams, it will be ignored when calling `getContents()` because the stream pointer is set to the last character, which is `\0` - meaning end of stream. +```php +$body = $response->getBody(); +$body->rewind(); // or $body->seek(0); +$bodyText = $body->getContents(); +``` +> Note: If `$body->seek(1)` is called before `$body->getContents()`, the first character will be ommited as the starting pointer is set to `1`, not `0`. This is why using `$body->rewind()` is recommended. + +### Append to body + +```php +$response->getBody()->write('Hello'); // writing directly +$body = $request->getBody(); // which is a `StreamInterface` +$body->write('xxxxx'); +``` + +### Prepend to body +Prepending is different when it comes to streams. The content must be copied before writing the content to be prepended. +The following example will explain the behaviour of streams. + +```php +// assuming our response is initially empty +$body = $repsonse->getBody(); +// writing the string "abcd" +$body->write('abcd'); + +// seeking to start of stream +$body->seek(0); +// writing 'ef' +$body->write('ef'); // at this point the stream contains "efcd" +``` + +#### Prepending by rewriting separately + +```php +// assuming our response body stream only contains: "abcd" +$body = $response->getBody(); +$body->rewind(); +$contents = $body->getContents(); // abcd +// seeking the stream to beginning +$body->rewind(); +$body->write('ef'); // stream contains "efcd" +$body->write($contents); // stream contains "efabcd" +``` + +> Note: `getContents()` seeks the stream while reading it, therefore if the second `rewind()` method call was not present the stream would have resulted in `abcdefabcd` because the `write()` method appends to stream if not preceeded by `rewind()` or `seek(0)`. + +#### Prepending by using contents as a string +```php +$body = $response->getBody(); +$body->rewind(); +$contents = $body->getContents(); // efabcd +$contents = 'ef'.$contents; +$body->rewind(); +$body->write($contents); +``` diff --git a/vendor/psr/http-message/src/MessageInterface.php b/vendor/psr/http-message/src/MessageInterface.php new file mode 100644 index 0000000..a83c985 --- /dev/null +++ b/vendor/psr/http-message/src/MessageInterface.php @@ -0,0 +1,187 @@ +getHeaders() as $name => $values) { + * echo $name . ": " . implode(", ", $values); + * } + * + * // Emit headers iteratively: + * foreach ($message->getHeaders() as $name => $values) { + * foreach ($values as $value) { + * header(sprintf('%s: %s', $name, $value), false); + * } + * } + * + * While header names are not case-sensitive, getHeaders() will preserve the + * exact case in which headers were originally specified. + * + * @return string[][] Returns an associative array of the message's headers. Each + * key MUST be a header name, and each value MUST be an array of strings + * for that header. + */ + public function getHeaders(): array; + + /** + * Checks if a header exists by the given case-insensitive name. + * + * @param string $name Case-insensitive header field name. + * @return bool Returns true if any header names match the given header + * name using a case-insensitive string comparison. Returns false if + * no matching header name is found in the message. + */ + public function hasHeader(string $name): bool; + + /** + * Retrieves a message header value by the given case-insensitive name. + * + * This method returns an array of all the header values of the given + * case-insensitive header name. + * + * If the header does not appear in the message, this method MUST return an + * empty array. + * + * @param string $name Case-insensitive header field name. + * @return string[] An array of string values as provided for the given + * header. If the header does not appear in the message, this method MUST + * return an empty array. + */ + public function getHeader(string $name): array; + + /** + * Retrieves a comma-separated string of the values for a single header. + * + * This method returns all of the header values of the given + * case-insensitive header name as a string concatenated together using + * a comma. + * + * NOTE: Not all header values may be appropriately represented using + * comma concatenation. For such headers, use getHeader() instead + * and supply your own delimiter when concatenating. + * + * If the header does not appear in the message, this method MUST return + * an empty string. + * + * @param string $name Case-insensitive header field name. + * @return string A string of values as provided for the given header + * concatenated together using a comma. If the header does not appear in + * the message, this method MUST return an empty string. + */ + public function getHeaderLine(string $name): string; + + /** + * Return an instance with the provided value replacing the specified header. + * + * While header names are case-insensitive, the casing of the header will + * be preserved by this function, and returned from getHeaders(). + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * new and/or updated header and value. + * + * @param string $name Case-insensitive header field name. + * @param string|string[] $value Header value(s). + * @return static + * @throws \InvalidArgumentException for invalid header names or values. + */ + public function withHeader(string $name, $value): MessageInterface; + + /** + * Return an instance with the specified header appended with the given value. + * + * Existing values for the specified header will be maintained. The new + * value(s) will be appended to the existing list. If the header did not + * exist previously, it will be added. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * new header and/or value. + * + * @param string $name Case-insensitive header field name to add. + * @param string|string[] $value Header value(s). + * @return static + * @throws \InvalidArgumentException for invalid header names or values. + */ + public function withAddedHeader(string $name, $value): MessageInterface; + + /** + * Return an instance without the specified header. + * + * Header resolution MUST be done without case-sensitivity. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that removes + * the named header. + * + * @param string $name Case-insensitive header field name to remove. + * @return static + */ + public function withoutHeader(string $name): MessageInterface; + + /** + * Gets the body of the message. + * + * @return StreamInterface Returns the body as a stream. + */ + public function getBody(): StreamInterface; + + /** + * Return an instance with the specified message body. + * + * The body MUST be a StreamInterface object. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return a new instance that has the + * new body stream. + * + * @param StreamInterface $body Body. + * @return static + * @throws \InvalidArgumentException When the body is not valid. + */ + public function withBody(StreamInterface $body): MessageInterface; +} diff --git a/vendor/psr/http-message/src/RequestInterface.php b/vendor/psr/http-message/src/RequestInterface.php new file mode 100644 index 0000000..33f85e5 --- /dev/null +++ b/vendor/psr/http-message/src/RequestInterface.php @@ -0,0 +1,130 @@ +getQuery()` + * or from the `QUERY_STRING` server param. + * + * @return array + */ + public function getQueryParams(): array; + + /** + * Return an instance with the specified query string arguments. + * + * These values SHOULD remain immutable over the course of the incoming + * request. They MAY be injected during instantiation, such as from PHP's + * $_GET superglobal, or MAY be derived from some other value such as the + * URI. In cases where the arguments are parsed from the URI, the data + * MUST be compatible with what PHP's parse_str() would return for + * purposes of how duplicate query parameters are handled, and how nested + * sets are handled. + * + * Setting query string arguments MUST NOT change the URI stored by the + * request, nor the values in the server params. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated query string arguments. + * + * @param array $query Array of query string arguments, typically from + * $_GET. + * @return static + */ + public function withQueryParams(array $query): ServerRequestInterface; + + /** + * Retrieve normalized file upload data. + * + * This method returns upload metadata in a normalized tree, with each leaf + * an instance of Psr\Http\Message\UploadedFileInterface. + * + * These values MAY be prepared from $_FILES or the message body during + * instantiation, or MAY be injected via withUploadedFiles(). + * + * @return array An array tree of UploadedFileInterface instances; an empty + * array MUST be returned if no data is present. + */ + public function getUploadedFiles(): array; + + /** + * Create a new instance with the specified uploaded files. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated body parameters. + * + * @param array $uploadedFiles An array tree of UploadedFileInterface instances. + * @return static + * @throws \InvalidArgumentException if an invalid structure is provided. + */ + public function withUploadedFiles(array $uploadedFiles): ServerRequestInterface; + + /** + * Retrieve any parameters provided in the request body. + * + * If the request Content-Type is either application/x-www-form-urlencoded + * or multipart/form-data, and the request method is POST, this method MUST + * return the contents of $_POST. + * + * Otherwise, this method may return any results of deserializing + * the request body content; as parsing returns structured content, the + * potential types MUST be arrays or objects only. A null value indicates + * the absence of body content. + * + * @return null|array|object The deserialized body parameters, if any. + * These will typically be an array or object. + */ + public function getParsedBody(); + + /** + * Return an instance with the specified body parameters. + * + * These MAY be injected during instantiation. + * + * If the request Content-Type is either application/x-www-form-urlencoded + * or multipart/form-data, and the request method is POST, use this method + * ONLY to inject the contents of $_POST. + * + * The data IS NOT REQUIRED to come from $_POST, but MUST be the results of + * deserializing the request body content. Deserialization/parsing returns + * structured data, and, as such, this method ONLY accepts arrays or objects, + * or a null value if nothing was available to parse. + * + * As an example, if content negotiation determines that the request data + * is a JSON payload, this method could be used to create a request + * instance with the deserialized parameters. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated body parameters. + * + * @param null|array|object $data The deserialized body data. This will + * typically be in an array or object. + * @return static + * @throws \InvalidArgumentException if an unsupported argument type is + * provided. + */ + public function withParsedBody($data): ServerRequestInterface; + + /** + * Retrieve attributes derived from the request. + * + * The request "attributes" may be used to allow injection of any + * parameters derived from the request: e.g., the results of path + * match operations; the results of decrypting cookies; the results of + * deserializing non-form-encoded message bodies; etc. Attributes + * will be application and request specific, and CAN be mutable. + * + * @return array Attributes derived from the request. + */ + public function getAttributes(): array; + + /** + * Retrieve a single derived request attribute. + * + * Retrieves a single derived request attribute as described in + * getAttributes(). If the attribute has not been previously set, returns + * the default value as provided. + * + * This method obviates the need for a hasAttribute() method, as it allows + * specifying a default value to return if the attribute is not found. + * + * @see getAttributes() + * @param string $name The attribute name. + * @param mixed $default Default value to return if the attribute does not exist. + * @return mixed + */ + public function getAttribute(string $name, $default = null); + + /** + * Return an instance with the specified derived request attribute. + * + * This method allows setting a single derived request attribute as + * described in getAttributes(). + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated attribute. + * + * @see getAttributes() + * @param string $name The attribute name. + * @param mixed $value The value of the attribute. + * @return static + */ + public function withAttribute(string $name, $value): ServerRequestInterface; + + /** + * Return an instance that removes the specified derived request attribute. + * + * This method allows removing a single derived request attribute as + * described in getAttributes(). + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that removes + * the attribute. + * + * @see getAttributes() + * @param string $name The attribute name. + * @return static + */ + public function withoutAttribute(string $name): ServerRequestInterface; +} diff --git a/vendor/psr/http-message/src/StreamInterface.php b/vendor/psr/http-message/src/StreamInterface.php new file mode 100644 index 0000000..a62aabb --- /dev/null +++ b/vendor/psr/http-message/src/StreamInterface.php @@ -0,0 +1,158 @@ + + * [user-info@]host[:port] + * + * + * If the port component is not set or is the standard port for the current + * scheme, it SHOULD NOT be included. + * + * @see https://tools.ietf.org/html/rfc3986#section-3.2 + * @return string The URI authority, in "[user-info@]host[:port]" format. + */ + public function getAuthority(): string; + + /** + * Retrieve the user information component of the URI. + * + * If no user information is present, this method MUST return an empty + * string. + * + * If a user is present in the URI, this will return that value; + * additionally, if the password is also present, it will be appended to the + * user value, with a colon (":") separating the values. + * + * The trailing "@" character is not part of the user information and MUST + * NOT be added. + * + * @return string The URI user information, in "username[:password]" format. + */ + public function getUserInfo(): string; + + /** + * Retrieve the host component of the URI. + * + * If no host is present, this method MUST return an empty string. + * + * The value returned MUST be normalized to lowercase, per RFC 3986 + * Section 3.2.2. + * + * @see http://tools.ietf.org/html/rfc3986#section-3.2.2 + * @return string The URI host. + */ + public function getHost(): string; + + /** + * Retrieve the port component of the URI. + * + * If a port is present, and it is non-standard for the current scheme, + * this method MUST return it as an integer. If the port is the standard port + * used with the current scheme, this method SHOULD return null. + * + * If no port is present, and no scheme is present, this method MUST return + * a null value. + * + * If no port is present, but a scheme is present, this method MAY return + * the standard port for that scheme, but SHOULD return null. + * + * @return null|int The URI port. + */ + public function getPort(): ?int; + + /** + * Retrieve the path component of the URI. + * + * The path can either be empty or absolute (starting with a slash) or + * rootless (not starting with a slash). Implementations MUST support all + * three syntaxes. + * + * Normally, the empty path "" and absolute path "/" are considered equal as + * defined in RFC 7230 Section 2.7.3. But this method MUST NOT automatically + * do this normalization because in contexts with a trimmed base path, e.g. + * the front controller, this difference becomes significant. It's the task + * of the user to handle both "" and "/". + * + * The value returned MUST be percent-encoded, but MUST NOT double-encode + * any characters. To determine what characters to encode, please refer to + * RFC 3986, Sections 2 and 3.3. + * + * As an example, if the value should include a slash ("/") not intended as + * delimiter between path segments, that value MUST be passed in encoded + * form (e.g., "%2F") to the instance. + * + * @see https://tools.ietf.org/html/rfc3986#section-2 + * @see https://tools.ietf.org/html/rfc3986#section-3.3 + * @return string The URI path. + */ + public function getPath(): string; + + /** + * Retrieve the query string of the URI. + * + * If no query string is present, this method MUST return an empty string. + * + * The leading "?" character is not part of the query and MUST NOT be + * added. + * + * The value returned MUST be percent-encoded, but MUST NOT double-encode + * any characters. To determine what characters to encode, please refer to + * RFC 3986, Sections 2 and 3.4. + * + * As an example, if a value in a key/value pair of the query string should + * include an ampersand ("&") not intended as a delimiter between values, + * that value MUST be passed in encoded form (e.g., "%26") to the instance. + * + * @see https://tools.ietf.org/html/rfc3986#section-2 + * @see https://tools.ietf.org/html/rfc3986#section-3.4 + * @return string The URI query string. + */ + public function getQuery(): string; + + /** + * Retrieve the fragment component of the URI. + * + * If no fragment is present, this method MUST return an empty string. + * + * The leading "#" character is not part of the fragment and MUST NOT be + * added. + * + * The value returned MUST be percent-encoded, but MUST NOT double-encode + * any characters. To determine what characters to encode, please refer to + * RFC 3986, Sections 2 and 3.5. + * + * @see https://tools.ietf.org/html/rfc3986#section-2 + * @see https://tools.ietf.org/html/rfc3986#section-3.5 + * @return string The URI fragment. + */ + public function getFragment(): string; + + /** + * Return an instance with the specified scheme. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified scheme. + * + * Implementations MUST support the schemes "http" and "https" case + * insensitively, and MAY accommodate other schemes if required. + * + * An empty scheme is equivalent to removing the scheme. + * + * @param string $scheme The scheme to use with the new instance. + * @return static A new instance with the specified scheme. + * @throws \InvalidArgumentException for invalid or unsupported schemes. + */ + public function withScheme(string $scheme): UriInterface; + + /** + * Return an instance with the specified user information. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified user information. + * + * Password is optional, but the user information MUST include the + * user; an empty string for the user is equivalent to removing user + * information. + * + * @param string $user The user name to use for authority. + * @param null|string $password The password associated with $user. + * @return static A new instance with the specified user information. + */ + public function withUserInfo(string $user, ?string $password = null): UriInterface; + + /** + * Return an instance with the specified host. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified host. + * + * An empty host value is equivalent to removing the host. + * + * @param string $host The hostname to use with the new instance. + * @return static A new instance with the specified host. + * @throws \InvalidArgumentException for invalid hostnames. + */ + public function withHost(string $host): UriInterface; + + /** + * Return an instance with the specified port. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified port. + * + * Implementations MUST raise an exception for ports outside the + * established TCP and UDP port ranges. + * + * A null value provided for the port is equivalent to removing the port + * information. + * + * @param null|int $port The port to use with the new instance; a null value + * removes the port information. + * @return static A new instance with the specified port. + * @throws \InvalidArgumentException for invalid ports. + */ + public function withPort(?int $port): UriInterface; + + /** + * Return an instance with the specified path. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified path. + * + * The path can either be empty or absolute (starting with a slash) or + * rootless (not starting with a slash). Implementations MUST support all + * three syntaxes. + * + * If the path is intended to be domain-relative rather than path relative then + * it must begin with a slash ("/"). Paths not starting with a slash ("/") + * are assumed to be relative to some base path known to the application or + * consumer. + * + * Users can provide both encoded and decoded path characters. + * Implementations ensure the correct encoding as outlined in getPath(). + * + * @param string $path The path to use with the new instance. + * @return static A new instance with the specified path. + * @throws \InvalidArgumentException for invalid paths. + */ + public function withPath(string $path): UriInterface; + + /** + * Return an instance with the specified query string. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified query string. + * + * Users can provide both encoded and decoded query characters. + * Implementations ensure the correct encoding as outlined in getQuery(). + * + * An empty query string value is equivalent to removing the query string. + * + * @param string $query The query string to use with the new instance. + * @return static A new instance with the specified query string. + * @throws \InvalidArgumentException for invalid query strings. + */ + public function withQuery(string $query): UriInterface; + + /** + * Return an instance with the specified URI fragment. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified URI fragment. + * + * Users can provide both encoded and decoded fragment characters. + * Implementations ensure the correct encoding as outlined in getFragment(). + * + * An empty fragment value is equivalent to removing the fragment. + * + * @param string $fragment The fragment to use with the new instance. + * @return static A new instance with the specified fragment. + */ + public function withFragment(string $fragment): UriInterface; + + /** + * Return the string representation as a URI reference. + * + * Depending on which components of the URI are present, the resulting + * string is either a full URI or relative reference according to RFC 3986, + * Section 4.1. The method concatenates the various components of the URI, + * using the appropriate delimiters: + * + * - If a scheme is present, it MUST be suffixed by ":". + * - If an authority is present, it MUST be prefixed by "//". + * - The path can be concatenated without delimiters. But there are two + * cases where the path has to be adjusted to make the URI reference + * valid as PHP does not allow to throw an exception in __toString(): + * - If the path is rootless and an authority is present, the path MUST + * be prefixed by "/". + * - If the path is starting with more than one "/" and no authority is + * present, the starting slashes MUST be reduced to one. + * - If a query is present, it MUST be prefixed by "?". + * - If a fragment is present, it MUST be prefixed by "#". + * + * @see http://tools.ietf.org/html/rfc3986#section-4.1 + * @return string + */ + public function __toString(): string; +} diff --git a/vendor/psr/log/LICENSE b/vendor/psr/log/LICENSE new file mode 100644 index 0000000..474c952 --- /dev/null +++ b/vendor/psr/log/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2012 PHP Framework Interoperability Group + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/psr/log/Psr/Log/AbstractLogger.php b/vendor/psr/log/Psr/Log/AbstractLogger.php new file mode 100644 index 0000000..e02f9da --- /dev/null +++ b/vendor/psr/log/Psr/Log/AbstractLogger.php @@ -0,0 +1,128 @@ +log(LogLevel::EMERGENCY, $message, $context); + } + + /** + * Action must be taken immediately. + * + * Example: Entire website down, database unavailable, etc. This should + * trigger the SMS alerts and wake you up. + * + * @param string $message + * @param mixed[] $context + * + * @return void + */ + public function alert($message, array $context = array()) + { + $this->log(LogLevel::ALERT, $message, $context); + } + + /** + * Critical conditions. + * + * Example: Application component unavailable, unexpected exception. + * + * @param string $message + * @param mixed[] $context + * + * @return void + */ + public function critical($message, array $context = array()) + { + $this->log(LogLevel::CRITICAL, $message, $context); + } + + /** + * Runtime errors that do not require immediate action but should typically + * be logged and monitored. + * + * @param string $message + * @param mixed[] $context + * + * @return void + */ + public function error($message, array $context = array()) + { + $this->log(LogLevel::ERROR, $message, $context); + } + + /** + * Exceptional occurrences that are not errors. + * + * Example: Use of deprecated APIs, poor use of an API, undesirable things + * that are not necessarily wrong. + * + * @param string $message + * @param mixed[] $context + * + * @return void + */ + public function warning($message, array $context = array()) + { + $this->log(LogLevel::WARNING, $message, $context); + } + + /** + * Normal but significant events. + * + * @param string $message + * @param mixed[] $context + * + * @return void + */ + public function notice($message, array $context = array()) + { + $this->log(LogLevel::NOTICE, $message, $context); + } + + /** + * Interesting events. + * + * Example: User logs in, SQL logs. + * + * @param string $message + * @param mixed[] $context + * + * @return void + */ + public function info($message, array $context = array()) + { + $this->log(LogLevel::INFO, $message, $context); + } + + /** + * Detailed debug information. + * + * @param string $message + * @param mixed[] $context + * + * @return void + */ + public function debug($message, array $context = array()) + { + $this->log(LogLevel::DEBUG, $message, $context); + } +} diff --git a/vendor/psr/log/Psr/Log/InvalidArgumentException.php b/vendor/psr/log/Psr/Log/InvalidArgumentException.php new file mode 100644 index 0000000..67f852d --- /dev/null +++ b/vendor/psr/log/Psr/Log/InvalidArgumentException.php @@ -0,0 +1,7 @@ +logger = $logger; + } +} diff --git a/vendor/psr/log/Psr/Log/LoggerInterface.php b/vendor/psr/log/Psr/Log/LoggerInterface.php new file mode 100644 index 0000000..2206cfd --- /dev/null +++ b/vendor/psr/log/Psr/Log/LoggerInterface.php @@ -0,0 +1,125 @@ +log(LogLevel::EMERGENCY, $message, $context); + } + + /** + * Action must be taken immediately. + * + * Example: Entire website down, database unavailable, etc. This should + * trigger the SMS alerts and wake you up. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function alert($message, array $context = array()) + { + $this->log(LogLevel::ALERT, $message, $context); + } + + /** + * Critical conditions. + * + * Example: Application component unavailable, unexpected exception. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function critical($message, array $context = array()) + { + $this->log(LogLevel::CRITICAL, $message, $context); + } + + /** + * Runtime errors that do not require immediate action but should typically + * be logged and monitored. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function error($message, array $context = array()) + { + $this->log(LogLevel::ERROR, $message, $context); + } + + /** + * Exceptional occurrences that are not errors. + * + * Example: Use of deprecated APIs, poor use of an API, undesirable things + * that are not necessarily wrong. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function warning($message, array $context = array()) + { + $this->log(LogLevel::WARNING, $message, $context); + } + + /** + * Normal but significant events. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function notice($message, array $context = array()) + { + $this->log(LogLevel::NOTICE, $message, $context); + } + + /** + * Interesting events. + * + * Example: User logs in, SQL logs. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function info($message, array $context = array()) + { + $this->log(LogLevel::INFO, $message, $context); + } + + /** + * Detailed debug information. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function debug($message, array $context = array()) + { + $this->log(LogLevel::DEBUG, $message, $context); + } + + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param string $message + * @param array $context + * + * @return void + * + * @throws \Psr\Log\InvalidArgumentException + */ + abstract public function log($level, $message, array $context = array()); +} diff --git a/vendor/psr/log/Psr/Log/NullLogger.php b/vendor/psr/log/Psr/Log/NullLogger.php new file mode 100644 index 0000000..c8f7293 --- /dev/null +++ b/vendor/psr/log/Psr/Log/NullLogger.php @@ -0,0 +1,30 @@ +logger) { }` + * blocks. + */ +class NullLogger extends AbstractLogger +{ + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param string $message + * @param array $context + * + * @return void + * + * @throws \Psr\Log\InvalidArgumentException + */ + public function log($level, $message, array $context = array()) + { + // noop + } +} diff --git a/vendor/psr/log/Psr/Log/Test/DummyTest.php b/vendor/psr/log/Psr/Log/Test/DummyTest.php new file mode 100644 index 0000000..9638c11 --- /dev/null +++ b/vendor/psr/log/Psr/Log/Test/DummyTest.php @@ -0,0 +1,18 @@ + ". + * + * Example ->error('Foo') would yield "error Foo". + * + * @return string[] + */ + abstract public function getLogs(); + + public function testImplements() + { + $this->assertInstanceOf('Psr\Log\LoggerInterface', $this->getLogger()); + } + + /** + * @dataProvider provideLevelsAndMessages + */ + public function testLogsAtAllLevels($level, $message) + { + $logger = $this->getLogger(); + $logger->{$level}($message, array('user' => 'Bob')); + $logger->log($level, $message, array('user' => 'Bob')); + + $expected = array( + $level.' message of level '.$level.' with context: Bob', + $level.' message of level '.$level.' with context: Bob', + ); + $this->assertEquals($expected, $this->getLogs()); + } + + public function provideLevelsAndMessages() + { + return array( + LogLevel::EMERGENCY => array(LogLevel::EMERGENCY, 'message of level emergency with context: {user}'), + LogLevel::ALERT => array(LogLevel::ALERT, 'message of level alert with context: {user}'), + LogLevel::CRITICAL => array(LogLevel::CRITICAL, 'message of level critical with context: {user}'), + LogLevel::ERROR => array(LogLevel::ERROR, 'message of level error with context: {user}'), + LogLevel::WARNING => array(LogLevel::WARNING, 'message of level warning with context: {user}'), + LogLevel::NOTICE => array(LogLevel::NOTICE, 'message of level notice with context: {user}'), + LogLevel::INFO => array(LogLevel::INFO, 'message of level info with context: {user}'), + LogLevel::DEBUG => array(LogLevel::DEBUG, 'message of level debug with context: {user}'), + ); + } + + /** + * @expectedException \Psr\Log\InvalidArgumentException + */ + public function testThrowsOnInvalidLevel() + { + $logger = $this->getLogger(); + $logger->log('invalid level', 'Foo'); + } + + public function testContextReplacement() + { + $logger = $this->getLogger(); + $logger->info('{Message {nothing} {user} {foo.bar} a}', array('user' => 'Bob', 'foo.bar' => 'Bar')); + + $expected = array('info {Message {nothing} Bob Bar a}'); + $this->assertEquals($expected, $this->getLogs()); + } + + public function testObjectCastToString() + { + if (method_exists($this, 'createPartialMock')) { + $dummy = $this->createPartialMock('Psr\Log\Test\DummyTest', array('__toString')); + } else { + $dummy = $this->getMock('Psr\Log\Test\DummyTest', array('__toString')); + } + $dummy->expects($this->once()) + ->method('__toString') + ->will($this->returnValue('DUMMY')); + + $this->getLogger()->warning($dummy); + + $expected = array('warning DUMMY'); + $this->assertEquals($expected, $this->getLogs()); + } + + public function testContextCanContainAnything() + { + $closed = fopen('php://memory', 'r'); + fclose($closed); + + $context = array( + 'bool' => true, + 'null' => null, + 'string' => 'Foo', + 'int' => 0, + 'float' => 0.5, + 'nested' => array('with object' => new DummyTest), + 'object' => new \DateTime, + 'resource' => fopen('php://memory', 'r'), + 'closed' => $closed, + ); + + $this->getLogger()->warning('Crazy context data', $context); + + $expected = array('warning Crazy context data'); + $this->assertEquals($expected, $this->getLogs()); + } + + public function testContextExceptionKeyCanBeExceptionOrOtherValues() + { + $logger = $this->getLogger(); + $logger->warning('Random message', array('exception' => 'oops')); + $logger->critical('Uncaught Exception!', array('exception' => new \LogicException('Fail'))); + + $expected = array( + 'warning Random message', + 'critical Uncaught Exception!' + ); + $this->assertEquals($expected, $this->getLogs()); + } +} diff --git a/vendor/psr/log/Psr/Log/Test/TestLogger.php b/vendor/psr/log/Psr/Log/Test/TestLogger.php new file mode 100644 index 0000000..1be3230 --- /dev/null +++ b/vendor/psr/log/Psr/Log/Test/TestLogger.php @@ -0,0 +1,147 @@ + $level, + 'message' => $message, + 'context' => $context, + ]; + + $this->recordsByLevel[$record['level']][] = $record; + $this->records[] = $record; + } + + public function hasRecords($level) + { + return isset($this->recordsByLevel[$level]); + } + + public function hasRecord($record, $level) + { + if (is_string($record)) { + $record = ['message' => $record]; + } + return $this->hasRecordThatPasses(function ($rec) use ($record) { + if ($rec['message'] !== $record['message']) { + return false; + } + if (isset($record['context']) && $rec['context'] !== $record['context']) { + return false; + } + return true; + }, $level); + } + + public function hasRecordThatContains($message, $level) + { + return $this->hasRecordThatPasses(function ($rec) use ($message) { + return strpos($rec['message'], $message) !== false; + }, $level); + } + + public function hasRecordThatMatches($regex, $level) + { + return $this->hasRecordThatPasses(function ($rec) use ($regex) { + return preg_match($regex, $rec['message']) > 0; + }, $level); + } + + public function hasRecordThatPasses(callable $predicate, $level) + { + if (!isset($this->recordsByLevel[$level])) { + return false; + } + foreach ($this->recordsByLevel[$level] as $i => $rec) { + if (call_user_func($predicate, $rec, $i)) { + return true; + } + } + return false; + } + + public function __call($method, $args) + { + if (preg_match('/(.*)(Debug|Info|Notice|Warning|Error|Critical|Alert|Emergency)(.*)/', $method, $matches) > 0) { + $genericMethod = $matches[1] . ('Records' !== $matches[3] ? 'Record' : '') . $matches[3]; + $level = strtolower($matches[2]); + if (method_exists($this, $genericMethod)) { + $args[] = $level; + return call_user_func_array([$this, $genericMethod], $args); + } + } + throw new \BadMethodCallException('Call to undefined method ' . get_class($this) . '::' . $method . '()'); + } + + public function reset() + { + $this->records = []; + $this->recordsByLevel = []; + } +} diff --git a/vendor/psr/log/README.md b/vendor/psr/log/README.md new file mode 100644 index 0000000..a9f20c4 --- /dev/null +++ b/vendor/psr/log/README.md @@ -0,0 +1,58 @@ +PSR Log +======= + +This repository holds all interfaces/classes/traits related to +[PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md). + +Note that this is not a logger of its own. It is merely an interface that +describes a logger. See the specification for more details. + +Installation +------------ + +```bash +composer require psr/log +``` + +Usage +----- + +If you need a logger, you can use the interface like this: + +```php +logger = $logger; + } + + public function doSomething() + { + if ($this->logger) { + $this->logger->info('Doing work'); + } + + try { + $this->doSomethingElse(); + } catch (Exception $exception) { + $this->logger->error('Oh no!', array('exception' => $exception)); + } + + // do something useful + } +} +``` + +You can then pick one of the implementations of the interface to get a logger. + +If you want to implement the interface, you can require this package and +implement `Psr\Log\LoggerInterface` in your code. Please read the +[specification text](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md) +for details. diff --git a/vendor/psr/simple-cache/.editorconfig b/vendor/psr/simple-cache/.editorconfig new file mode 100644 index 0000000..48542cb --- /dev/null +++ b/vendor/psr/simple-cache/.editorconfig @@ -0,0 +1,12 @@ +; This file is for unifying the coding style for different editors and IDEs. +; More information at http://editorconfig.org + +root = true + +[*] +charset = utf-8 +indent_size = 4 +indent_style = space +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/vendor/psr/simple-cache/LICENSE.md b/vendor/psr/simple-cache/LICENSE.md new file mode 100644 index 0000000..e49a7c8 --- /dev/null +++ b/vendor/psr/simple-cache/LICENSE.md @@ -0,0 +1,21 @@ +# The MIT License (MIT) + +Copyright (c) 2016 PHP Framework Interoperability Group + +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. diff --git a/vendor/psr/simple-cache/README.md b/vendor/psr/simple-cache/README.md new file mode 100644 index 0000000..43641d1 --- /dev/null +++ b/vendor/psr/simple-cache/README.md @@ -0,0 +1,8 @@ +PHP FIG Simple Cache PSR +======================== + +This repository holds all interfaces related to PSR-16. + +Note that this is not a cache implementation of its own. It is merely an interface that describes a cache implementation. See [the specification](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-16-simple-cache.md) for more details. + +You can find implementations of the specification by looking for packages providing the [psr/simple-cache-implementation](https://packagist.org/providers/psr/simple-cache-implementation) virtual package. diff --git a/vendor/psr/simple-cache/src/CacheException.php b/vendor/psr/simple-cache/src/CacheException.php new file mode 100644 index 0000000..eba5381 --- /dev/null +++ b/vendor/psr/simple-cache/src/CacheException.php @@ -0,0 +1,10 @@ + value pairs. Cache keys that do not exist or are stale will have $default as value. + * + * @throws \Psr\SimpleCache\InvalidArgumentException + * MUST be thrown if $keys is neither an array nor a Traversable, + * or if any of the $keys are not a legal value. + */ + public function getMultiple($keys, $default = null); + + /** + * Persists a set of key => value pairs in the cache, with an optional TTL. + * + * @param iterable $values A list of key => value pairs for a multiple-set operation. + * @param null|int|\DateInterval $ttl Optional. The TTL value of this item. If no value is sent and + * the driver supports TTL then the library may set a default value + * for it or let the driver take care of that. + * + * @return bool True on success and false on failure. + * + * @throws \Psr\SimpleCache\InvalidArgumentException + * MUST be thrown if $values is neither an array nor a Traversable, + * or if any of the $values are not a legal value. + */ + public function setMultiple($values, $ttl = null); + + /** + * Deletes multiple cache items in a single operation. + * + * @param iterable $keys A list of string-based keys to be deleted. + * + * @return bool True if the items were successfully removed. False if there was an error. + * + * @throws \Psr\SimpleCache\InvalidArgumentException + * MUST be thrown if $keys is neither an array nor a Traversable, + * or if any of the $keys are not a legal value. + */ + public function deleteMultiple($keys); + + /** + * Determines whether an item is present in the cache. + * + * NOTE: It is recommended that has() is only to be used for cache warming type purposes + * and not to be used within your live applications operations for get/set, as this method + * is subject to a race condition where your has() will return true and immediately after, + * another script can remove it making the state of your app out of date. + * + * @param string $key The cache item key. + * + * @return bool + * + * @throws \Psr\SimpleCache\InvalidArgumentException + * MUST be thrown if the $key string is not a legal value. + */ + public function has($key); +} diff --git a/vendor/psr/simple-cache/src/InvalidArgumentException.php b/vendor/psr/simple-cache/src/InvalidArgumentException.php new file mode 100644 index 0000000..6a9524a --- /dev/null +++ b/vendor/psr/simple-cache/src/InvalidArgumentException.php @@ -0,0 +1,13 @@ += 5.3. + +[![Build Status](https://travis-ci.org/ralouphie/getallheaders.svg?branch=master)](https://travis-ci.org/ralouphie/getallheaders) +[![Coverage Status](https://coveralls.io/repos/ralouphie/getallheaders/badge.png?branch=master)](https://coveralls.io/r/ralouphie/getallheaders?branch=master) +[![Latest Stable Version](https://poser.pugx.org/ralouphie/getallheaders/v/stable.png)](https://packagist.org/packages/ralouphie/getallheaders) +[![Latest Unstable Version](https://poser.pugx.org/ralouphie/getallheaders/v/unstable.png)](https://packagist.org/packages/ralouphie/getallheaders) +[![License](https://poser.pugx.org/ralouphie/getallheaders/license.png)](https://packagist.org/packages/ralouphie/getallheaders) + + +This is a simple polyfill for [`getallheaders()`](http://www.php.net/manual/en/function.getallheaders.php). + +## Install + +For PHP version **`>= 5.6`**: + +``` +composer require ralouphie/getallheaders +``` + +For PHP version **`< 5.6`**: + +``` +composer require ralouphie/getallheaders "^2" +``` diff --git a/vendor/ralouphie/getallheaders/src/getallheaders.php b/vendor/ralouphie/getallheaders/src/getallheaders.php new file mode 100644 index 0000000..c7285a5 --- /dev/null +++ b/vendor/ralouphie/getallheaders/src/getallheaders.php @@ -0,0 +1,46 @@ + 'Content-Type', + 'CONTENT_LENGTH' => 'Content-Length', + 'CONTENT_MD5' => 'Content-Md5', + ); + + foreach ($_SERVER as $key => $value) { + if (substr($key, 0, 5) === 'HTTP_') { + $key = substr($key, 5); + if (!isset($copy_server[$key]) || !isset($_SERVER[$key])) { + $key = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', $key)))); + $headers[$key] = $value; + } + } elseif (isset($copy_server[$key])) { + $headers[$copy_server[$key]] = $value; + } + } + + if (!isset($headers['Authorization'])) { + if (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) { + $headers['Authorization'] = $_SERVER['REDIRECT_HTTP_AUTHORIZATION']; + } elseif (isset($_SERVER['PHP_AUTH_USER'])) { + $basic_pass = isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : ''; + $headers['Authorization'] = 'Basic ' . base64_encode($_SERVER['PHP_AUTH_USER'] . ':' . $basic_pass); + } elseif (isset($_SERVER['PHP_AUTH_DIGEST'])) { + $headers['Authorization'] = $_SERVER['PHP_AUTH_DIGEST']; + } + } + + return $headers; + } + +} diff --git a/vendor/symfony/console/Application.php b/vendor/symfony/console/Application.php new file mode 100644 index 0000000..29951e9 --- /dev/null +++ b/vendor/symfony/console/Application.php @@ -0,0 +1,1301 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Command\CompleteCommand; +use Symfony\Component\Console\Command\DumpCompletionCommand; +use Symfony\Component\Console\Command\HelpCommand; +use Symfony\Component\Console\Command\LazyCommand; +use Symfony\Component\Console\Command\ListCommand; +use Symfony\Component\Console\Command\SignalableCommandInterface; +use Symfony\Component\Console\CommandLoader\CommandLoaderInterface; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Event\ConsoleCommandEvent; +use Symfony\Component\Console\Event\ConsoleErrorEvent; +use Symfony\Component\Console\Event\ConsoleSignalEvent; +use Symfony\Component\Console\Event\ConsoleTerminateEvent; +use Symfony\Component\Console\Exception\CommandNotFoundException; +use Symfony\Component\Console\Exception\ExceptionInterface; +use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Console\Exception\NamespaceNotFoundException; +use Symfony\Component\Console\Exception\RuntimeException; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Helper\DebugFormatterHelper; +use Symfony\Component\Console\Helper\FormatterHelper; +use Symfony\Component\Console\Helper\Helper; +use Symfony\Component\Console\Helper\HelperSet; +use Symfony\Component\Console\Helper\ProcessHelper; +use Symfony\Component\Console\Helper\QuestionHelper; +use Symfony\Component\Console\Input\ArgvInput; +use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputAwareInterface; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\ConsoleOutput; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\SignalRegistry\SignalRegistry; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\ErrorHandler\ErrorHandler; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * An Application is the container for a collection of commands. + * + * It is the main entry point of a Console application. + * + * This class is optimized for a standard CLI environment. + * + * Usage: + * + * $app = new Application('myapp', '1.0 (stable)'); + * $app->add(new SimpleCommand()); + * $app->run(); + * + * @author Fabien Potencier + */ +class Application implements ResetInterface +{ + private $commands = []; + private $wantHelps = false; + private $runningCommand; + private $name; + private $version; + private $commandLoader; + private $catchExceptions = true; + private $autoExit = true; + private $definition; + private $helperSet; + private $dispatcher; + private $terminal; + private $defaultCommand; + private $singleCommand = false; + private $initialized; + private $signalRegistry; + private $signalsToDispatchEvent = []; + + public function __construct(string $name = 'UNKNOWN', string $version = 'UNKNOWN') + { + $this->name = $name; + $this->version = $version; + $this->terminal = new Terminal(); + $this->defaultCommand = 'list'; + if (\defined('SIGINT') && SignalRegistry::isSupported()) { + $this->signalRegistry = new SignalRegistry(); + $this->signalsToDispatchEvent = [\SIGINT, \SIGTERM, \SIGUSR1, \SIGUSR2]; + } + } + + /** + * @final + */ + public function setDispatcher(EventDispatcherInterface $dispatcher) + { + $this->dispatcher = $dispatcher; + } + + public function setCommandLoader(CommandLoaderInterface $commandLoader) + { + $this->commandLoader = $commandLoader; + } + + public function getSignalRegistry(): SignalRegistry + { + if (!$this->signalRegistry) { + throw new RuntimeException('Signals are not supported. Make sure that the `pcntl` extension is installed and that "pcntl_*" functions are not disabled by your php.ini\'s "disable_functions" directive.'); + } + + return $this->signalRegistry; + } + + public function setSignalsToDispatchEvent(int ...$signalsToDispatchEvent) + { + $this->signalsToDispatchEvent = $signalsToDispatchEvent; + } + + /** + * Runs the current application. + * + * @return int 0 if everything went fine, or an error code + * + * @throws \Exception When running fails. Bypass this when {@link setCatchExceptions()}. + */ + public function run(InputInterface $input = null, OutputInterface $output = null) + { + if (\function_exists('putenv')) { + @putenv('LINES='.$this->terminal->getHeight()); + @putenv('COLUMNS='.$this->terminal->getWidth()); + } + + if (null === $input) { + $input = new ArgvInput(); + } + + if (null === $output) { + $output = new ConsoleOutput(); + } + + $renderException = function (\Throwable $e) use ($output) { + if ($output instanceof ConsoleOutputInterface) { + $this->renderThrowable($e, $output->getErrorOutput()); + } else { + $this->renderThrowable($e, $output); + } + }; + if ($phpHandler = set_exception_handler($renderException)) { + restore_exception_handler(); + if (!\is_array($phpHandler) || !$phpHandler[0] instanceof ErrorHandler) { + $errorHandler = true; + } elseif ($errorHandler = $phpHandler[0]->setExceptionHandler($renderException)) { + $phpHandler[0]->setExceptionHandler($errorHandler); + } + } + + $this->configureIO($input, $output); + + try { + $exitCode = $this->doRun($input, $output); + } catch (\Exception $e) { + if (!$this->catchExceptions) { + throw $e; + } + + $renderException($e); + + $exitCode = $e->getCode(); + if (is_numeric($exitCode)) { + $exitCode = (int) $exitCode; + if ($exitCode <= 0) { + $exitCode = 1; + } + } else { + $exitCode = 1; + } + } finally { + // if the exception handler changed, keep it + // otherwise, unregister $renderException + if (!$phpHandler) { + if (set_exception_handler($renderException) === $renderException) { + restore_exception_handler(); + } + restore_exception_handler(); + } elseif (!$errorHandler) { + $finalHandler = $phpHandler[0]->setExceptionHandler(null); + if ($finalHandler !== $renderException) { + $phpHandler[0]->setExceptionHandler($finalHandler); + } + } + } + + if ($this->autoExit) { + if ($exitCode > 255) { + $exitCode = 255; + } + + exit($exitCode); + } + + return $exitCode; + } + + /** + * Runs the current application. + * + * @return int 0 if everything went fine, or an error code + */ + public function doRun(InputInterface $input, OutputInterface $output) + { + if (true === $input->hasParameterOption(['--version', '-V'], true)) { + $output->writeln($this->getLongVersion()); + + return 0; + } + + try { + // Makes ArgvInput::getFirstArgument() able to distinguish an option from an argument. + $input->bind($this->getDefinition()); + } catch (ExceptionInterface $e) { + // Errors must be ignored, full binding/validation happens later when the command is known. + } + + $name = $this->getCommandName($input); + if (true === $input->hasParameterOption(['--help', '-h'], true)) { + if (!$name) { + $name = 'help'; + $input = new ArrayInput(['command_name' => $this->defaultCommand]); + } else { + $this->wantHelps = true; + } + } + + if (!$name) { + $name = $this->defaultCommand; + $definition = $this->getDefinition(); + $definition->setArguments(array_merge( + $definition->getArguments(), + [ + 'command' => new InputArgument('command', InputArgument::OPTIONAL, $definition->getArgument('command')->getDescription(), $name), + ] + )); + } + + try { + $this->runningCommand = null; + // the command name MUST be the first element of the input + $command = $this->find($name); + } catch (\Throwable $e) { + if (!($e instanceof CommandNotFoundException && !$e instanceof NamespaceNotFoundException) || 1 !== \count($alternatives = $e->getAlternatives()) || !$input->isInteractive()) { + if (null !== $this->dispatcher) { + $event = new ConsoleErrorEvent($input, $output, $e); + $this->dispatcher->dispatch($event, ConsoleEvents::ERROR); + + if (0 === $event->getExitCode()) { + return 0; + } + + $e = $event->getError(); + } + + throw $e; + } + + $alternative = $alternatives[0]; + + $style = new SymfonyStyle($input, $output); + $output->writeln(''); + $formattedBlock = (new FormatterHelper())->formatBlock(sprintf('Command "%s" is not defined.', $name), 'error', true); + $output->writeln($formattedBlock); + if (!$style->confirm(sprintf('Do you want to run "%s" instead? ', $alternative), false)) { + if (null !== $this->dispatcher) { + $event = new ConsoleErrorEvent($input, $output, $e); + $this->dispatcher->dispatch($event, ConsoleEvents::ERROR); + + return $event->getExitCode(); + } + + return 1; + } + + $command = $this->find($alternative); + } + + if ($command instanceof LazyCommand) { + $command = $command->getCommand(); + } + + $this->runningCommand = $command; + $exitCode = $this->doRunCommand($command, $input, $output); + $this->runningCommand = null; + + return $exitCode; + } + + /** + * {@inheritdoc} + */ + public function reset() + { + } + + public function setHelperSet(HelperSet $helperSet) + { + $this->helperSet = $helperSet; + } + + /** + * Get the helper set associated with the command. + * + * @return HelperSet + */ + public function getHelperSet() + { + if (!$this->helperSet) { + $this->helperSet = $this->getDefaultHelperSet(); + } + + return $this->helperSet; + } + + public function setDefinition(InputDefinition $definition) + { + $this->definition = $definition; + } + + /** + * Gets the InputDefinition related to this Application. + * + * @return InputDefinition + */ + public function getDefinition() + { + if (!$this->definition) { + $this->definition = $this->getDefaultInputDefinition(); + } + + if ($this->singleCommand) { + $inputDefinition = $this->definition; + $inputDefinition->setArguments(); + + return $inputDefinition; + } + + return $this->definition; + } + + /** + * Adds suggestions to $suggestions for the current completion input (e.g. option or argument). + */ + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ( + CompletionInput::TYPE_ARGUMENT_VALUE === $input->getCompletionType() + && 'command' === $input->getCompletionName() + ) { + $commandNames = []; + foreach ($this->all() as $name => $command) { + // skip hidden commands and aliased commands as they already get added below + if ($command->isHidden() || $command->getName() !== $name) { + continue; + } + $commandNames[] = $command->getName(); + foreach ($command->getAliases() as $name) { + $commandNames[] = $name; + } + } + $suggestions->suggestValues(array_filter($commandNames)); + + return; + } + + if (CompletionInput::TYPE_OPTION_NAME === $input->getCompletionType()) { + $suggestions->suggestOptions($this->getDefinition()->getOptions()); + + return; + } + } + + /** + * Gets the help message. + * + * @return string + */ + public function getHelp() + { + return $this->getLongVersion(); + } + + /** + * Gets whether to catch exceptions or not during commands execution. + * + * @return bool + */ + public function areExceptionsCaught() + { + return $this->catchExceptions; + } + + /** + * Sets whether to catch exceptions or not during commands execution. + */ + public function setCatchExceptions(bool $boolean) + { + $this->catchExceptions = $boolean; + } + + /** + * Gets whether to automatically exit after a command execution or not. + * + * @return bool + */ + public function isAutoExitEnabled() + { + return $this->autoExit; + } + + /** + * Sets whether to automatically exit after a command execution or not. + */ + public function setAutoExit(bool $boolean) + { + $this->autoExit = $boolean; + } + + /** + * Gets the name of the application. + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Sets the application name. + **/ + public function setName(string $name) + { + $this->name = $name; + } + + /** + * Gets the application version. + * + * @return string + */ + public function getVersion() + { + return $this->version; + } + + /** + * Sets the application version. + */ + public function setVersion(string $version) + { + $this->version = $version; + } + + /** + * Returns the long version of the application. + * + * @return string + */ + public function getLongVersion() + { + if ('UNKNOWN' !== $this->getName()) { + if ('UNKNOWN' !== $this->getVersion()) { + return sprintf('%s %s', $this->getName(), $this->getVersion()); + } + + return $this->getName(); + } + + return 'Console Tool'; + } + + /** + * Registers a new command. + * + * @return Command + */ + public function register(string $name) + { + return $this->add(new Command($name)); + } + + /** + * Adds an array of command objects. + * + * If a Command is not enabled it will not be added. + * + * @param Command[] $commands An array of commands + */ + public function addCommands(array $commands) + { + foreach ($commands as $command) { + $this->add($command); + } + } + + /** + * Adds a command object. + * + * If a command with the same name already exists, it will be overridden. + * If the command is not enabled it will not be added. + * + * @return Command|null + */ + public function add(Command $command) + { + $this->init(); + + $command->setApplication($this); + + if (!$command->isEnabled()) { + $command->setApplication(null); + + return null; + } + + if (!$command instanceof LazyCommand) { + // Will throw if the command is not correctly initialized. + $command->getDefinition(); + } + + if (!$command->getName()) { + throw new LogicException(sprintf('The command defined in "%s" cannot have an empty name.', get_debug_type($command))); + } + + $this->commands[$command->getName()] = $command; + + foreach ($command->getAliases() as $alias) { + $this->commands[$alias] = $command; + } + + return $command; + } + + /** + * Returns a registered command by name or alias. + * + * @return Command + * + * @throws CommandNotFoundException When given command name does not exist + */ + public function get(string $name) + { + $this->init(); + + if (!$this->has($name)) { + throw new CommandNotFoundException(sprintf('The command "%s" does not exist.', $name)); + } + + // When the command has a different name than the one used at the command loader level + if (!isset($this->commands[$name])) { + throw new CommandNotFoundException(sprintf('The "%s" command cannot be found because it is registered under multiple names. Make sure you don\'t set a different name via constructor or "setName()".', $name)); + } + + $command = $this->commands[$name]; + + if ($this->wantHelps) { + $this->wantHelps = false; + + $helpCommand = $this->get('help'); + $helpCommand->setCommand($command); + + return $helpCommand; + } + + return $command; + } + + /** + * Returns true if the command exists, false otherwise. + * + * @return bool + */ + public function has(string $name) + { + $this->init(); + + return isset($this->commands[$name]) || ($this->commandLoader && $this->commandLoader->has($name) && $this->add($this->commandLoader->get($name))); + } + + /** + * Returns an array of all unique namespaces used by currently registered commands. + * + * It does not return the global namespace which always exists. + * + * @return string[] + */ + public function getNamespaces() + { + $namespaces = []; + foreach ($this->all() as $command) { + if ($command->isHidden()) { + continue; + } + + $namespaces[] = $this->extractAllNamespaces($command->getName()); + + foreach ($command->getAliases() as $alias) { + $namespaces[] = $this->extractAllNamespaces($alias); + } + } + + return array_values(array_unique(array_filter(array_merge([], ...$namespaces)))); + } + + /** + * Finds a registered namespace by a name or an abbreviation. + * + * @return string + * + * @throws NamespaceNotFoundException When namespace is incorrect or ambiguous + */ + public function findNamespace(string $namespace) + { + $allNamespaces = $this->getNamespaces(); + $expr = implode('[^:]*:', array_map('preg_quote', explode(':', $namespace))).'[^:]*'; + $namespaces = preg_grep('{^'.$expr.'}', $allNamespaces); + + if (empty($namespaces)) { + $message = sprintf('There are no commands defined in the "%s" namespace.', $namespace); + + if ($alternatives = $this->findAlternatives($namespace, $allNamespaces)) { + if (1 == \count($alternatives)) { + $message .= "\n\nDid you mean this?\n "; + } else { + $message .= "\n\nDid you mean one of these?\n "; + } + + $message .= implode("\n ", $alternatives); + } + + throw new NamespaceNotFoundException($message, $alternatives); + } + + $exact = \in_array($namespace, $namespaces, true); + if (\count($namespaces) > 1 && !$exact) { + throw new NamespaceNotFoundException(sprintf("The namespace \"%s\" is ambiguous.\nDid you mean one of these?\n%s.", $namespace, $this->getAbbreviationSuggestions(array_values($namespaces))), array_values($namespaces)); + } + + return $exact ? $namespace : reset($namespaces); + } + + /** + * Finds a command by name or alias. + * + * Contrary to get, this command tries to find the best + * match if you give it an abbreviation of a name or alias. + * + * @return Command + * + * @throws CommandNotFoundException When command name is incorrect or ambiguous + */ + public function find(string $name) + { + $this->init(); + + $aliases = []; + + foreach ($this->commands as $command) { + foreach ($command->getAliases() as $alias) { + if (!$this->has($alias)) { + $this->commands[$alias] = $command; + } + } + } + + if ($this->has($name)) { + return $this->get($name); + } + + $allCommands = $this->commandLoader ? array_merge($this->commandLoader->getNames(), array_keys($this->commands)) : array_keys($this->commands); + $expr = implode('[^:]*:', array_map('preg_quote', explode(':', $name))).'[^:]*'; + $commands = preg_grep('{^'.$expr.'}', $allCommands); + + if (empty($commands)) { + $commands = preg_grep('{^'.$expr.'}i', $allCommands); + } + + // if no commands matched or we just matched namespaces + if (empty($commands) || \count(preg_grep('{^'.$expr.'$}i', $commands)) < 1) { + if (false !== $pos = strrpos($name, ':')) { + // check if a namespace exists and contains commands + $this->findNamespace(substr($name, 0, $pos)); + } + + $message = sprintf('Command "%s" is not defined.', $name); + + if ($alternatives = $this->findAlternatives($name, $allCommands)) { + // remove hidden commands + $alternatives = array_filter($alternatives, function ($name) { + return !$this->get($name)->isHidden(); + }); + + if (1 == \count($alternatives)) { + $message .= "\n\nDid you mean this?\n "; + } else { + $message .= "\n\nDid you mean one of these?\n "; + } + $message .= implode("\n ", $alternatives); + } + + throw new CommandNotFoundException($message, array_values($alternatives)); + } + + // filter out aliases for commands which are already on the list + if (\count($commands) > 1) { + $commandList = $this->commandLoader ? array_merge(array_flip($this->commandLoader->getNames()), $this->commands) : $this->commands; + $commands = array_unique(array_filter($commands, function ($nameOrAlias) use (&$commandList, $commands, &$aliases) { + if (!$commandList[$nameOrAlias] instanceof Command) { + $commandList[$nameOrAlias] = $this->commandLoader->get($nameOrAlias); + } + + $commandName = $commandList[$nameOrAlias]->getName(); + + $aliases[$nameOrAlias] = $commandName; + + return $commandName === $nameOrAlias || !\in_array($commandName, $commands); + })); + } + + if (\count($commands) > 1) { + $usableWidth = $this->terminal->getWidth() - 10; + $abbrevs = array_values($commands); + $maxLen = 0; + foreach ($abbrevs as $abbrev) { + $maxLen = max(Helper::width($abbrev), $maxLen); + } + $abbrevs = array_map(function ($cmd) use ($commandList, $usableWidth, $maxLen, &$commands) { + if ($commandList[$cmd]->isHidden()) { + unset($commands[array_search($cmd, $commands)]); + + return false; + } + + $abbrev = str_pad($cmd, $maxLen, ' ').' '.$commandList[$cmd]->getDescription(); + + return Helper::width($abbrev) > $usableWidth ? Helper::substr($abbrev, 0, $usableWidth - 3).'...' : $abbrev; + }, array_values($commands)); + + if (\count($commands) > 1) { + $suggestions = $this->getAbbreviationSuggestions(array_filter($abbrevs)); + + throw new CommandNotFoundException(sprintf("Command \"%s\" is ambiguous.\nDid you mean one of these?\n%s.", $name, $suggestions), array_values($commands)); + } + } + + $command = $this->get(reset($commands)); + + if ($command->isHidden()) { + throw new CommandNotFoundException(sprintf('The command "%s" does not exist.', $name)); + } + + return $command; + } + + /** + * Gets the commands (registered in the given namespace if provided). + * + * The array keys are the full names and the values the command instances. + * + * @return Command[] + */ + public function all(string $namespace = null) + { + $this->init(); + + if (null === $namespace) { + if (!$this->commandLoader) { + return $this->commands; + } + + $commands = $this->commands; + foreach ($this->commandLoader->getNames() as $name) { + if (!isset($commands[$name]) && $this->has($name)) { + $commands[$name] = $this->get($name); + } + } + + return $commands; + } + + $commands = []; + foreach ($this->commands as $name => $command) { + if ($namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1)) { + $commands[$name] = $command; + } + } + + if ($this->commandLoader) { + foreach ($this->commandLoader->getNames() as $name) { + if (!isset($commands[$name]) && $namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1) && $this->has($name)) { + $commands[$name] = $this->get($name); + } + } + } + + return $commands; + } + + /** + * Returns an array of possible abbreviations given a set of names. + * + * @return string[][] + */ + public static function getAbbreviations(array $names) + { + $abbrevs = []; + foreach ($names as $name) { + for ($len = \strlen($name); $len > 0; --$len) { + $abbrev = substr($name, 0, $len); + $abbrevs[$abbrev][] = $name; + } + } + + return $abbrevs; + } + + public function renderThrowable(\Throwable $e, OutputInterface $output): void + { + $output->writeln('', OutputInterface::VERBOSITY_QUIET); + + $this->doRenderThrowable($e, $output); + + if (null !== $this->runningCommand) { + $output->writeln(sprintf('%s', OutputFormatter::escape(sprintf($this->runningCommand->getSynopsis(), $this->getName()))), OutputInterface::VERBOSITY_QUIET); + $output->writeln('', OutputInterface::VERBOSITY_QUIET); + } + } + + protected function doRenderThrowable(\Throwable $e, OutputInterface $output): void + { + do { + $message = trim($e->getMessage()); + if ('' === $message || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { + $class = get_debug_type($e); + $title = sprintf(' [%s%s] ', $class, 0 !== ($code = $e->getCode()) ? ' ('.$code.')' : ''); + $len = Helper::width($title); + } else { + $len = 0; + } + + if (str_contains($message, "@anonymous\0")) { + $message = preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', function ($m) { + return class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0]; + }, $message); + } + + $width = $this->terminal->getWidth() ? $this->terminal->getWidth() - 1 : \PHP_INT_MAX; + $lines = []; + foreach ('' !== $message ? preg_split('/\r?\n/', $message) : [] as $line) { + foreach ($this->splitStringByWidth($line, $width - 4) as $line) { + // pre-format lines to get the right string length + $lineLength = Helper::width($line) + 4; + $lines[] = [$line, $lineLength]; + + $len = max($lineLength, $len); + } + } + + $messages = []; + if (!$e instanceof ExceptionInterface || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { + $messages[] = sprintf('%s', OutputFormatter::escape(sprintf('In %s line %s:', basename($e->getFile()) ?: 'n/a', $e->getLine() ?: 'n/a'))); + } + $messages[] = $emptyLine = sprintf('%s', str_repeat(' ', $len)); + if ('' === $message || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { + $messages[] = sprintf('%s%s', $title, str_repeat(' ', max(0, $len - Helper::width($title)))); + } + foreach ($lines as $line) { + $messages[] = sprintf(' %s %s', OutputFormatter::escape($line[0]), str_repeat(' ', $len - $line[1])); + } + $messages[] = $emptyLine; + $messages[] = ''; + + $output->writeln($messages, OutputInterface::VERBOSITY_QUIET); + + if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { + $output->writeln('Exception trace:', OutputInterface::VERBOSITY_QUIET); + + // exception related properties + $trace = $e->getTrace(); + + array_unshift($trace, [ + 'function' => '', + 'file' => $e->getFile() ?: 'n/a', + 'line' => $e->getLine() ?: 'n/a', + 'args' => [], + ]); + + for ($i = 0, $count = \count($trace); $i < $count; ++$i) { + $class = $trace[$i]['class'] ?? ''; + $type = $trace[$i]['type'] ?? ''; + $function = $trace[$i]['function'] ?? ''; + $file = $trace[$i]['file'] ?? 'n/a'; + $line = $trace[$i]['line'] ?? 'n/a'; + + $output->writeln(sprintf(' %s%s at %s:%s', $class, $function ? $type.$function.'()' : '', $file, $line), OutputInterface::VERBOSITY_QUIET); + } + + $output->writeln('', OutputInterface::VERBOSITY_QUIET); + } + } while ($e = $e->getPrevious()); + } + + /** + * Configures the input and output instances based on the user arguments and options. + */ + protected function configureIO(InputInterface $input, OutputInterface $output) + { + if (true === $input->hasParameterOption(['--ansi'], true)) { + $output->setDecorated(true); + } elseif (true === $input->hasParameterOption(['--no-ansi'], true)) { + $output->setDecorated(false); + } + + if (true === $input->hasParameterOption(['--no-interaction', '-n'], true)) { + $input->setInteractive(false); + } + + switch ($shellVerbosity = (int) getenv('SHELL_VERBOSITY')) { + case -1: + $output->setVerbosity(OutputInterface::VERBOSITY_QUIET); + break; + case 1: + $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE); + break; + case 2: + $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE); + break; + case 3: + $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG); + break; + default: + $shellVerbosity = 0; + break; + } + + if (true === $input->hasParameterOption(['--quiet', '-q'], true)) { + $output->setVerbosity(OutputInterface::VERBOSITY_QUIET); + $shellVerbosity = -1; + } else { + if ($input->hasParameterOption('-vvv', true) || $input->hasParameterOption('--verbose=3', true) || 3 === $input->getParameterOption('--verbose', false, true)) { + $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG); + $shellVerbosity = 3; + } elseif ($input->hasParameterOption('-vv', true) || $input->hasParameterOption('--verbose=2', true) || 2 === $input->getParameterOption('--verbose', false, true)) { + $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE); + $shellVerbosity = 2; + } elseif ($input->hasParameterOption('-v', true) || $input->hasParameterOption('--verbose=1', true) || $input->hasParameterOption('--verbose', true) || $input->getParameterOption('--verbose', false, true)) { + $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE); + $shellVerbosity = 1; + } + } + + if (-1 === $shellVerbosity) { + $input->setInteractive(false); + } + + if (\function_exists('putenv')) { + @putenv('SHELL_VERBOSITY='.$shellVerbosity); + } + $_ENV['SHELL_VERBOSITY'] = $shellVerbosity; + $_SERVER['SHELL_VERBOSITY'] = $shellVerbosity; + } + + /** + * Runs the current command. + * + * If an event dispatcher has been attached to the application, + * events are also dispatched during the life-cycle of the command. + * + * @return int 0 if everything went fine, or an error code + */ + protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output) + { + foreach ($command->getHelperSet() as $helper) { + if ($helper instanceof InputAwareInterface) { + $helper->setInput($input); + } + } + + if ($this->signalsToDispatchEvent) { + $commandSignals = $command instanceof SignalableCommandInterface ? $command->getSubscribedSignals() : []; + + if ($commandSignals || null !== $this->dispatcher) { + if (!$this->signalRegistry) { + throw new RuntimeException('Unable to subscribe to signal events. Make sure that the `pcntl` extension is installed and that "pcntl_*" functions are not disabled by your php.ini\'s "disable_functions" directive.'); + } + + if (Terminal::hasSttyAvailable()) { + $sttyMode = shell_exec('stty -g'); + + foreach ([\SIGINT, \SIGTERM] as $signal) { + $this->signalRegistry->register($signal, static function () use ($sttyMode) { + shell_exec('stty '.$sttyMode); + }); + } + } + } + + if (null !== $this->dispatcher) { + foreach ($this->signalsToDispatchEvent as $signal) { + $event = new ConsoleSignalEvent($command, $input, $output, $signal); + + $this->signalRegistry->register($signal, function ($signal, $hasNext) use ($event) { + $this->dispatcher->dispatch($event, ConsoleEvents::SIGNAL); + + // No more handlers, we try to simulate PHP default behavior + if (!$hasNext) { + if (!\in_array($signal, [\SIGUSR1, \SIGUSR2], true)) { + exit(0); + } + } + }); + } + } + + foreach ($commandSignals as $signal) { + $this->signalRegistry->register($signal, [$command, 'handleSignal']); + } + } + + if (null === $this->dispatcher) { + return $command->run($input, $output); + } + + // bind before the console.command event, so the listeners have access to input options/arguments + try { + $command->mergeApplicationDefinition(); + $input->bind($command->getDefinition()); + } catch (ExceptionInterface $e) { + // ignore invalid options/arguments for now, to allow the event listeners to customize the InputDefinition + } + + $event = new ConsoleCommandEvent($command, $input, $output); + $e = null; + + try { + $this->dispatcher->dispatch($event, ConsoleEvents::COMMAND); + + if ($event->commandShouldRun()) { + $exitCode = $command->run($input, $output); + } else { + $exitCode = ConsoleCommandEvent::RETURN_CODE_DISABLED; + } + } catch (\Throwable $e) { + $event = new ConsoleErrorEvent($input, $output, $e, $command); + $this->dispatcher->dispatch($event, ConsoleEvents::ERROR); + $e = $event->getError(); + + if (0 === $exitCode = $event->getExitCode()) { + $e = null; + } + } + + $event = new ConsoleTerminateEvent($command, $input, $output, $exitCode); + $this->dispatcher->dispatch($event, ConsoleEvents::TERMINATE); + + if (null !== $e) { + throw $e; + } + + return $event->getExitCode(); + } + + /** + * Gets the name of the command based on input. + * + * @return string|null + */ + protected function getCommandName(InputInterface $input) + { + return $this->singleCommand ? $this->defaultCommand : $input->getFirstArgument(); + } + + /** + * Gets the default input definition. + * + * @return InputDefinition + */ + protected function getDefaultInputDefinition() + { + return new InputDefinition([ + new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'), + new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display help for the given command. When no command is given display help for the '.$this->defaultCommand.' command'), + new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message'), + new InputOption('--verbose', '-v|vv|vvv', InputOption::VALUE_NONE, 'Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug'), + new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this application version'), + new InputOption('--ansi', '', InputOption::VALUE_NEGATABLE, 'Force (or disable --no-ansi) ANSI output', null), + new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question'), + ]); + } + + /** + * Gets the default commands that should always be available. + * + * @return Command[] + */ + protected function getDefaultCommands() + { + return [new HelpCommand(), new ListCommand(), new CompleteCommand(), new DumpCompletionCommand()]; + } + + /** + * Gets the default helper set with the helpers that should always be available. + * + * @return HelperSet + */ + protected function getDefaultHelperSet() + { + return new HelperSet([ + new FormatterHelper(), + new DebugFormatterHelper(), + new ProcessHelper(), + new QuestionHelper(), + ]); + } + + /** + * Returns abbreviated suggestions in string format. + */ + private function getAbbreviationSuggestions(array $abbrevs): string + { + return ' '.implode("\n ", $abbrevs); + } + + /** + * Returns the namespace part of the command name. + * + * This method is not part of public API and should not be used directly. + * + * @return string + */ + public function extractNamespace(string $name, int $limit = null) + { + $parts = explode(':', $name, -1); + + return implode(':', null === $limit ? $parts : \array_slice($parts, 0, $limit)); + } + + /** + * Finds alternative of $name among $collection, + * if nothing is found in $collection, try in $abbrevs. + * + * @return string[] + */ + private function findAlternatives(string $name, iterable $collection): array + { + $threshold = 1e3; + $alternatives = []; + + $collectionParts = []; + foreach ($collection as $item) { + $collectionParts[$item] = explode(':', $item); + } + + foreach (explode(':', $name) as $i => $subname) { + foreach ($collectionParts as $collectionName => $parts) { + $exists = isset($alternatives[$collectionName]); + if (!isset($parts[$i]) && $exists) { + $alternatives[$collectionName] += $threshold; + continue; + } elseif (!isset($parts[$i])) { + continue; + } + + $lev = levenshtein($subname, $parts[$i]); + if ($lev <= \strlen($subname) / 3 || '' !== $subname && str_contains($parts[$i], $subname)) { + $alternatives[$collectionName] = $exists ? $alternatives[$collectionName] + $lev : $lev; + } elseif ($exists) { + $alternatives[$collectionName] += $threshold; + } + } + } + + foreach ($collection as $item) { + $lev = levenshtein($name, $item); + if ($lev <= \strlen($name) / 3 || str_contains($item, $name)) { + $alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev; + } + } + + $alternatives = array_filter($alternatives, function ($lev) use ($threshold) { return $lev < 2 * $threshold; }); + ksort($alternatives, \SORT_NATURAL | \SORT_FLAG_CASE); + + return array_keys($alternatives); + } + + /** + * Sets the default Command name. + * + * @return $this + */ + public function setDefaultCommand(string $commandName, bool $isSingleCommand = false) + { + $this->defaultCommand = explode('|', ltrim($commandName, '|'))[0]; + + if ($isSingleCommand) { + // Ensure the command exist + $this->find($commandName); + + $this->singleCommand = true; + } + + return $this; + } + + /** + * @internal + */ + public function isSingleCommand(): bool + { + return $this->singleCommand; + } + + private function splitStringByWidth(string $string, int $width): array + { + // str_split is not suitable for multi-byte characters, we should use preg_split to get char array properly. + // additionally, array_slice() is not enough as some character has doubled width. + // we need a function to split string not by character count but by string width + if (false === $encoding = mb_detect_encoding($string, null, true)) { + return str_split($string, $width); + } + + $utf8String = mb_convert_encoding($string, 'utf8', $encoding); + $lines = []; + $line = ''; + + $offset = 0; + while (preg_match('/.{1,10000}/u', $utf8String, $m, 0, $offset)) { + $offset += \strlen($m[0]); + + foreach (preg_split('//u', $m[0]) as $char) { + // test if $char could be appended to current line + if (mb_strwidth($line.$char, 'utf8') <= $width) { + $line .= $char; + continue; + } + // if not, push current line to array and make new line + $lines[] = str_pad($line, $width); + $line = $char; + } + } + + $lines[] = \count($lines) ? str_pad($line, $width) : $line; + + mb_convert_variables($encoding, 'utf8', $lines); + + return $lines; + } + + /** + * Returns all namespaces of the command name. + * + * @return string[] + */ + private function extractAllNamespaces(string $name): array + { + // -1 as third argument is needed to skip the command short name when exploding + $parts = explode(':', $name, -1); + $namespaces = []; + + foreach ($parts as $part) { + if (\count($namespaces)) { + $namespaces[] = end($namespaces).':'.$part; + } else { + $namespaces[] = $part; + } + } + + return $namespaces; + } + + private function init() + { + if ($this->initialized) { + return; + } + $this->initialized = true; + + foreach ($this->getDefaultCommands() as $command) { + $this->add($command); + } + } +} diff --git a/vendor/symfony/console/Attribute/AsCommand.php b/vendor/symfony/console/Attribute/AsCommand.php new file mode 100644 index 0000000..b337f54 --- /dev/null +++ b/vendor/symfony/console/Attribute/AsCommand.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Attribute; + +/** + * Service tag to autoconfigure commands. + */ +#[\Attribute(\Attribute::TARGET_CLASS)] +class AsCommand +{ + public function __construct( + public string $name, + public ?string $description = null, + array $aliases = [], + bool $hidden = false, + ) { + if (!$hidden && !$aliases) { + return; + } + + $name = explode('|', $name); + $name = array_merge($name, $aliases); + + if ($hidden && '' !== $name[0]) { + array_unshift($name, ''); + } + + $this->name = implode('|', $name); + } +} diff --git a/vendor/symfony/console/CHANGELOG.md b/vendor/symfony/console/CHANGELOG.md new file mode 100644 index 0000000..6662dd1 --- /dev/null +++ b/vendor/symfony/console/CHANGELOG.md @@ -0,0 +1,217 @@ +CHANGELOG +========= + +5.4 +--- + + * Add `TesterTrait::assertCommandIsSuccessful()` to test command + * Deprecate `HelperSet::setCommand()` and `getCommand()` without replacement + +5.3 +--- + + * Add `GithubActionReporter` to render annotations in a Github Action + * Add `InputOption::VALUE_NEGATABLE` flag to handle `--foo`/`--no-foo` options + * Add the `Command::$defaultDescription` static property and the `description` attribute + on the `console.command` tag to allow the `list` command to instantiate commands lazily + * Add option `--short` to the `list` command + * Add support for bright colors + * Add `#[AsCommand]` attribute for declaring commands on PHP 8 + * Add `Helper::width()` and `Helper::length()` + * The `--ansi` and `--no-ansi` options now default to `null`. + +5.2.0 +----- + + * Added `SingleCommandApplication::setAutoExit()` to allow testing via `CommandTester` + * added support for multiline responses to questions through `Question::setMultiline()` + and `Question::isMultiline()` + * Added `SignalRegistry` class to stack signals handlers + * Added support for signals: + * Added `Application::getSignalRegistry()` and `Application::setSignalsToDispatchEvent()` methods + * Added `SignalableCommandInterface` interface + * Added `TableCellStyle` class to customize table cell + * Removed `php ` prefix invocation from help messages. + +5.1.0 +----- + + * `Command::setHidden()` is final since Symfony 5.1 + * Add `SingleCommandApplication` + * Add `Cursor` class + +5.0.0 +----- + + * removed support for finding hidden commands using an abbreviation, use the full name instead + * removed `TableStyle::setCrossingChar()` method in favor of `TableStyle::setDefaultCrossingChar()` + * removed `TableStyle::setHorizontalBorderChar()` method in favor of `TableStyle::setDefaultCrossingChars()` + * removed `TableStyle::getHorizontalBorderChar()` method in favor of `TableStyle::getBorderChars()` + * removed `TableStyle::setVerticalBorderChar()` method in favor of `TableStyle::setVerticalBorderChars()` + * removed `TableStyle::getVerticalBorderChar()` method in favor of `TableStyle::getBorderChars()` + * removed support for returning `null` from `Command::execute()`, return `0` instead + * `ProcessHelper::run()` accepts only `array|Symfony\Component\Process\Process` for its `command` argument + * `Application::setDispatcher` accepts only `Symfony\Contracts\EventDispatcher\EventDispatcherInterface` + for its `dispatcher` argument + * renamed `Application::renderException()` and `Application::doRenderException()` + to `renderThrowable()` and `doRenderThrowable()` respectively. + +4.4.0 +----- + + * deprecated finding hidden commands using an abbreviation, use the full name instead + * added `Question::setTrimmable` default to true to allow the answer to be trimmed + * added method `minSecondsBetweenRedraws()` and `maxSecondsBetweenRedraws()` on `ProgressBar` + * `Application` implements `ResetInterface` + * marked all dispatched event classes as `@final` + * added support for displaying table horizontally + * deprecated returning `null` from `Command::execute()`, return `0` instead + * Deprecated the `Application::renderException()` and `Application::doRenderException()` methods, + use `renderThrowable()` and `doRenderThrowable()` instead. + * added support for the `NO_COLOR` env var (https://no-color.org/) + +4.3.0 +----- + + * added support for hyperlinks + * added `ProgressBar::iterate()` method that simplify updating the progress bar when iterating + * added `Question::setAutocompleterCallback()` to provide a callback function + that dynamically generates suggestions as the user types + +4.2.0 +----- + + * allowed passing commands as `[$process, 'ENV_VAR' => 'value']` to + `ProcessHelper::run()` to pass environment variables + * deprecated passing a command as a string to `ProcessHelper::run()`, + pass it the command as an array of its arguments instead + * made the `ProcessHelper` class final + * added `WrappableOutputFormatterInterface::formatAndWrap()` (implemented in `OutputFormatter`) + * added `capture_stderr_separately` option to `CommandTester::execute()` + +4.1.0 +----- + + * added option to run suggested command if command is not found and only 1 alternative is available + * added option to modify console output and print multiple modifiable sections + * added support for iterable messages in output `write` and `writeln` methods + +4.0.0 +----- + + * `OutputFormatter` throws an exception when unknown options are used + * removed `QuestionHelper::setInputStream()/getInputStream()` + * removed `Application::getTerminalWidth()/getTerminalHeight()` and + `Application::setTerminalDimensions()/getTerminalDimensions()` + * removed `ConsoleExceptionEvent` + * removed `ConsoleEvents::EXCEPTION` + +3.4.0 +----- + + * added `SHELL_VERBOSITY` env var to control verbosity + * added `CommandLoaderInterface`, `FactoryCommandLoader` and PSR-11 + `ContainerCommandLoader` for commands lazy-loading + * added a case-insensitive command name matching fallback + * added static `Command::$defaultName/getDefaultName()`, allowing for + commands to be registered at compile time in the application command loader. + Setting the `$defaultName` property avoids the need for filling the `command` + attribute on the `console.command` tag when using `AddConsoleCommandPass`. + +3.3.0 +----- + + * added `ExceptionListener` + * added `AddConsoleCommandPass` (originally in FrameworkBundle) + * [BC BREAK] `Input::getOption()` no longer returns the default value for options + with value optional explicitly passed empty + * added console.error event to catch exceptions thrown by other listeners + * deprecated console.exception event in favor of console.error + * added ability to handle `CommandNotFoundException` through the + `console.error` event + * deprecated default validation in `SymfonyQuestionHelper::ask` + +3.2.0 +------ + + * added `setInputs()` method to CommandTester for ease testing of commands expecting inputs + * added `setStream()` and `getStream()` methods to Input (implement StreamableInputInterface) + * added StreamableInputInterface + * added LockableTrait + +3.1.0 +----- + + * added truncate method to FormatterHelper + * added setColumnWidth(s) method to Table + +2.8.3 +----- + + * remove readline support from the question helper as it caused issues + +2.8.0 +----- + + * use readline for user input in the question helper when available to allow + the use of arrow keys + +2.6.0 +----- + + * added a Process helper + * added a DebugFormatter helper + +2.5.0 +----- + + * deprecated the dialog helper (use the question helper instead) + * deprecated TableHelper in favor of Table + * deprecated ProgressHelper in favor of ProgressBar + * added ConsoleLogger + * added a question helper + * added a way to set the process name of a command + * added a way to set a default command instead of `ListCommand` + +2.4.0 +----- + + * added a way to force terminal dimensions + * added a convenient method to detect verbosity level + * [BC BREAK] made descriptors use output instead of returning a string + +2.3.0 +----- + + * added multiselect support to the select dialog helper + * added Table Helper for tabular data rendering + * added support for events in `Application` + * added a way to normalize EOLs in `ApplicationTester::getDisplay()` and `CommandTester::getDisplay()` + * added a way to set the progress bar progress via the `setCurrent` method + * added support for multiple InputOption shortcuts, written as `'-a|-b|-c'` + * added two additional verbosity levels, VERBOSITY_VERY_VERBOSE and VERBOSITY_DEBUG + +2.2.0 +----- + + * added support for colorization on Windows via ConEmu + * add a method to Dialog Helper to ask for a question and hide the response + * added support for interactive selections in console (DialogHelper::select()) + * added support for autocompletion as you type in Dialog Helper + +2.1.0 +----- + + * added ConsoleOutputInterface + * added the possibility to disable a command (Command::isEnabled()) + * added suggestions when a command does not exist + * added a --raw option to the list command + * added support for STDERR in the console output class (errors are now sent + to STDERR) + * made the defaults (helper set, commands, input definition) in Application + more easily customizable + * added support for the shell even if readline is not available + * added support for process isolation in Symfony shell via + `--process-isolation` switch + * added support for `--`, which disables options parsing after that point + (tokens will be parsed as arguments) diff --git a/vendor/symfony/console/CI/GithubActionReporter.php b/vendor/symfony/console/CI/GithubActionReporter.php new file mode 100644 index 0000000..a15c1ff --- /dev/null +++ b/vendor/symfony/console/CI/GithubActionReporter.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\CI; + +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Utility class for Github actions. + * + * @author Maxime Steinhausser + */ +class GithubActionReporter +{ + private $output; + + /** + * @see https://github.com/actions/toolkit/blob/5e5e1b7aacba68a53836a34db4a288c3c1c1585b/packages/core/src/command.ts#L80-L85 + */ + private const ESCAPED_DATA = [ + '%' => '%25', + "\r" => '%0D', + "\n" => '%0A', + ]; + + /** + * @see https://github.com/actions/toolkit/blob/5e5e1b7aacba68a53836a34db4a288c3c1c1585b/packages/core/src/command.ts#L87-L94 + */ + private const ESCAPED_PROPERTIES = [ + '%' => '%25', + "\r" => '%0D', + "\n" => '%0A', + ':' => '%3A', + ',' => '%2C', + ]; + + public function __construct(OutputInterface $output) + { + $this->output = $output; + } + + public static function isGithubActionEnvironment(): bool + { + return false !== getenv('GITHUB_ACTIONS'); + } + + /** + * Output an error using the Github annotations format. + * + * @see https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-an-error-message + */ + public function error(string $message, string $file = null, int $line = null, int $col = null): void + { + $this->log('error', $message, $file, $line, $col); + } + + /** + * Output a warning using the Github annotations format. + * + * @see https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-a-warning-message + */ + public function warning(string $message, string $file = null, int $line = null, int $col = null): void + { + $this->log('warning', $message, $file, $line, $col); + } + + /** + * Output a debug log using the Github annotations format. + * + * @see https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-a-debug-message + */ + public function debug(string $message, string $file = null, int $line = null, int $col = null): void + { + $this->log('debug', $message, $file, $line, $col); + } + + private function log(string $type, string $message, string $file = null, int $line = null, int $col = null): void + { + // Some values must be encoded. + $message = strtr($message, self::ESCAPED_DATA); + + if (!$file) { + // No file provided, output the message solely: + $this->output->writeln(sprintf('::%s::%s', $type, $message)); + + return; + } + + $this->output->writeln(sprintf('::%s file=%s,line=%s,col=%s::%s', $type, strtr($file, self::ESCAPED_PROPERTIES), strtr($line ?? 1, self::ESCAPED_PROPERTIES), strtr($col ?? 0, self::ESCAPED_PROPERTIES), $message)); + } +} diff --git a/vendor/symfony/console/Color.php b/vendor/symfony/console/Color.php new file mode 100644 index 0000000..22a4ce9 --- /dev/null +++ b/vendor/symfony/console/Color.php @@ -0,0 +1,180 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console; + +use Symfony\Component\Console\Exception\InvalidArgumentException; + +/** + * @author Fabien Potencier + */ +final class Color +{ + private const COLORS = [ + 'black' => 0, + 'red' => 1, + 'green' => 2, + 'yellow' => 3, + 'blue' => 4, + 'magenta' => 5, + 'cyan' => 6, + 'white' => 7, + 'default' => 9, + ]; + + private const BRIGHT_COLORS = [ + 'gray' => 0, + 'bright-red' => 1, + 'bright-green' => 2, + 'bright-yellow' => 3, + 'bright-blue' => 4, + 'bright-magenta' => 5, + 'bright-cyan' => 6, + 'bright-white' => 7, + ]; + + private const AVAILABLE_OPTIONS = [ + 'bold' => ['set' => 1, 'unset' => 22], + 'underscore' => ['set' => 4, 'unset' => 24], + 'blink' => ['set' => 5, 'unset' => 25], + 'reverse' => ['set' => 7, 'unset' => 27], + 'conceal' => ['set' => 8, 'unset' => 28], + ]; + + private $foreground; + private $background; + private $options = []; + + public function __construct(string $foreground = '', string $background = '', array $options = []) + { + $this->foreground = $this->parseColor($foreground); + $this->background = $this->parseColor($background, true); + + foreach ($options as $option) { + if (!isset(self::AVAILABLE_OPTIONS[$option])) { + throw new InvalidArgumentException(sprintf('Invalid option specified: "%s". Expected one of (%s).', $option, implode(', ', array_keys(self::AVAILABLE_OPTIONS)))); + } + + $this->options[$option] = self::AVAILABLE_OPTIONS[$option]; + } + } + + public function apply(string $text): string + { + return $this->set().$text.$this->unset(); + } + + public function set(): string + { + $setCodes = []; + if ('' !== $this->foreground) { + $setCodes[] = $this->foreground; + } + if ('' !== $this->background) { + $setCodes[] = $this->background; + } + foreach ($this->options as $option) { + $setCodes[] = $option['set']; + } + if (0 === \count($setCodes)) { + return ''; + } + + return sprintf("\033[%sm", implode(';', $setCodes)); + } + + public function unset(): string + { + $unsetCodes = []; + if ('' !== $this->foreground) { + $unsetCodes[] = 39; + } + if ('' !== $this->background) { + $unsetCodes[] = 49; + } + foreach ($this->options as $option) { + $unsetCodes[] = $option['unset']; + } + if (0 === \count($unsetCodes)) { + return ''; + } + + return sprintf("\033[%sm", implode(';', $unsetCodes)); + } + + private function parseColor(string $color, bool $background = false): string + { + if ('' === $color) { + return ''; + } + + if ('#' === $color[0]) { + $color = substr($color, 1); + + if (3 === \strlen($color)) { + $color = $color[0].$color[0].$color[1].$color[1].$color[2].$color[2]; + } + + if (6 !== \strlen($color)) { + throw new InvalidArgumentException(sprintf('Invalid "%s" color.', $color)); + } + + return ($background ? '4' : '3').$this->convertHexColorToAnsi(hexdec($color)); + } + + if (isset(self::COLORS[$color])) { + return ($background ? '4' : '3').self::COLORS[$color]; + } + + if (isset(self::BRIGHT_COLORS[$color])) { + return ($background ? '10' : '9').self::BRIGHT_COLORS[$color]; + } + + throw new InvalidArgumentException(sprintf('Invalid "%s" color; expected one of (%s).', $color, implode(', ', array_merge(array_keys(self::COLORS), array_keys(self::BRIGHT_COLORS))))); + } + + private function convertHexColorToAnsi(int $color): string + { + $r = ($color >> 16) & 255; + $g = ($color >> 8) & 255; + $b = $color & 255; + + // see https://github.com/termstandard/colors/ for more information about true color support + if ('truecolor' !== getenv('COLORTERM')) { + return (string) $this->degradeHexColorToAnsi($r, $g, $b); + } + + return sprintf('8;2;%d;%d;%d', $r, $g, $b); + } + + private function degradeHexColorToAnsi(int $r, int $g, int $b): int + { + if (0 === round($this->getSaturation($r, $g, $b) / 50)) { + return 0; + } + + return (round($b / 255) << 2) | (round($g / 255) << 1) | round($r / 255); + } + + private function getSaturation(int $r, int $g, int $b): int + { + $r = $r / 255; + $g = $g / 255; + $b = $b / 255; + $v = max($r, $g, $b); + + if (0 === $diff = $v - min($r, $g, $b)) { + return 0; + } + + return (int) $diff * 100 / $v; + } +} diff --git a/vendor/symfony/console/Command/Command.php b/vendor/symfony/console/Command/Command.php new file mode 100644 index 0000000..cfa1836 --- /dev/null +++ b/vendor/symfony/console/Command/Command.php @@ -0,0 +1,710 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Command; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Exception\ExceptionInterface; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Console\Helper\HelperSet; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Base class for all commands. + * + * @author Fabien Potencier + */ +class Command +{ + // see https://tldp.org/LDP/abs/html/exitcodes.html + public const SUCCESS = 0; + public const FAILURE = 1; + public const INVALID = 2; + + /** + * @var string|null The default command name + */ + protected static $defaultName; + + /** + * @var string|null The default command description + */ + protected static $defaultDescription; + + private $application; + private $name; + private $processTitle; + private $aliases = []; + private $definition; + private $hidden = false; + private $help = ''; + private $description = ''; + private $fullDefinition; + private $ignoreValidationErrors = false; + private $code; + private $synopsis = []; + private $usages = []; + private $helperSet; + + /** + * @return string|null + */ + public static function getDefaultName() + { + $class = static::class; + + if (\PHP_VERSION_ID >= 80000 && $attribute = (new \ReflectionClass($class))->getAttributes(AsCommand::class)) { + return $attribute[0]->newInstance()->name; + } + + $r = new \ReflectionProperty($class, 'defaultName'); + + return $class === $r->class ? static::$defaultName : null; + } + + public static function getDefaultDescription(): ?string + { + $class = static::class; + + if (\PHP_VERSION_ID >= 80000 && $attribute = (new \ReflectionClass($class))->getAttributes(AsCommand::class)) { + return $attribute[0]->newInstance()->description; + } + + $r = new \ReflectionProperty($class, 'defaultDescription'); + + return $class === $r->class ? static::$defaultDescription : null; + } + + /** + * @param string|null $name The name of the command; passing null means it must be set in configure() + * + * @throws LogicException When the command name is empty + */ + public function __construct(string $name = null) + { + $this->definition = new InputDefinition(); + + if (null === $name && null !== $name = static::getDefaultName()) { + $aliases = explode('|', $name); + + if ('' === $name = array_shift($aliases)) { + $this->setHidden(true); + $name = array_shift($aliases); + } + + $this->setAliases($aliases); + } + + if (null !== $name) { + $this->setName($name); + } + + if ('' === $this->description) { + $this->setDescription(static::getDefaultDescription() ?? ''); + } + + $this->configure(); + } + + /** + * Ignores validation errors. + * + * This is mainly useful for the help command. + */ + public function ignoreValidationErrors() + { + $this->ignoreValidationErrors = true; + } + + public function setApplication(Application $application = null) + { + $this->application = $application; + if ($application) { + $this->setHelperSet($application->getHelperSet()); + } else { + $this->helperSet = null; + } + + $this->fullDefinition = null; + } + + public function setHelperSet(HelperSet $helperSet) + { + $this->helperSet = $helperSet; + } + + /** + * Gets the helper set. + * + * @return HelperSet|null + */ + public function getHelperSet() + { + return $this->helperSet; + } + + /** + * Gets the application instance for this command. + * + * @return Application|null + */ + public function getApplication() + { + return $this->application; + } + + /** + * Checks whether the command is enabled or not in the current environment. + * + * Override this to check for x or y and return false if the command cannot + * run properly under the current conditions. + * + * @return bool + */ + public function isEnabled() + { + return true; + } + + /** + * Configures the current command. + */ + protected function configure() + { + } + + /** + * Executes the current command. + * + * This method is not abstract because you can use this class + * as a concrete class. In this case, instead of defining the + * execute() method, you set the code to execute by passing + * a Closure to the setCode() method. + * + * @return int 0 if everything went fine, or an exit code + * + * @throws LogicException When this abstract method is not implemented + * + * @see setCode() + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + throw new LogicException('You must override the execute() method in the concrete command class.'); + } + + /** + * Interacts with the user. + * + * This method is executed before the InputDefinition is validated. + * This means that this is the only place where the command can + * interactively ask for values of missing required arguments. + */ + protected function interact(InputInterface $input, OutputInterface $output) + { + } + + /** + * Initializes the command after the input has been bound and before the input + * is validated. + * + * This is mainly useful when a lot of commands extends one main command + * where some things need to be initialized based on the input arguments and options. + * + * @see InputInterface::bind() + * @see InputInterface::validate() + */ + protected function initialize(InputInterface $input, OutputInterface $output) + { + } + + /** + * Runs the command. + * + * The code to execute is either defined directly with the + * setCode() method or by overriding the execute() method + * in a sub-class. + * + * @return int The command exit code + * + * @throws ExceptionInterface When input binding fails. Bypass this by calling {@link ignoreValidationErrors()}. + * + * @see setCode() + * @see execute() + */ + public function run(InputInterface $input, OutputInterface $output) + { + // add the application arguments and options + $this->mergeApplicationDefinition(); + + // bind the input against the command specific arguments/options + try { + $input->bind($this->getDefinition()); + } catch (ExceptionInterface $e) { + if (!$this->ignoreValidationErrors) { + throw $e; + } + } + + $this->initialize($input, $output); + + if (null !== $this->processTitle) { + if (\function_exists('cli_set_process_title')) { + if (!@cli_set_process_title($this->processTitle)) { + if ('Darwin' === \PHP_OS) { + $output->writeln('Running "cli_set_process_title" as an unprivileged user is not supported on MacOS.', OutputInterface::VERBOSITY_VERY_VERBOSE); + } else { + cli_set_process_title($this->processTitle); + } + } + } elseif (\function_exists('setproctitle')) { + setproctitle($this->processTitle); + } elseif (OutputInterface::VERBOSITY_VERY_VERBOSE === $output->getVerbosity()) { + $output->writeln('Install the proctitle PECL to be able to change the process title.'); + } + } + + if ($input->isInteractive()) { + $this->interact($input, $output); + } + + // The command name argument is often omitted when a command is executed directly with its run() method. + // It would fail the validation if we didn't make sure the command argument is present, + // since it's required by the application. + if ($input->hasArgument('command') && null === $input->getArgument('command')) { + $input->setArgument('command', $this->getName()); + } + + $input->validate(); + + if ($this->code) { + $statusCode = ($this->code)($input, $output); + } else { + $statusCode = $this->execute($input, $output); + + if (!\is_int($statusCode)) { + throw new \TypeError(sprintf('Return value of "%s::execute()" must be of the type int, "%s" returned.', static::class, get_debug_type($statusCode))); + } + } + + return is_numeric($statusCode) ? (int) $statusCode : 0; + } + + /** + * Adds suggestions to $suggestions for the current completion input (e.g. option or argument). + */ + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + } + + /** + * Sets the code to execute when running this command. + * + * If this method is used, it overrides the code defined + * in the execute() method. + * + * @param callable $code A callable(InputInterface $input, OutputInterface $output) + * + * @return $this + * + * @throws InvalidArgumentException + * + * @see execute() + */ + public function setCode(callable $code) + { + if ($code instanceof \Closure) { + $r = new \ReflectionFunction($code); + if (null === $r->getClosureThis()) { + set_error_handler(static function () {}); + try { + if ($c = \Closure::bind($code, $this)) { + $code = $c; + } + } finally { + restore_error_handler(); + } + } + } + + $this->code = $code; + + return $this; + } + + /** + * Merges the application definition with the command definition. + * + * This method is not part of public API and should not be used directly. + * + * @param bool $mergeArgs Whether to merge or not the Application definition arguments to Command definition arguments + * + * @internal + */ + public function mergeApplicationDefinition(bool $mergeArgs = true) + { + if (null === $this->application) { + return; + } + + $this->fullDefinition = new InputDefinition(); + $this->fullDefinition->setOptions($this->definition->getOptions()); + $this->fullDefinition->addOptions($this->application->getDefinition()->getOptions()); + + if ($mergeArgs) { + $this->fullDefinition->setArguments($this->application->getDefinition()->getArguments()); + $this->fullDefinition->addArguments($this->definition->getArguments()); + } else { + $this->fullDefinition->setArguments($this->definition->getArguments()); + } + } + + /** + * Sets an array of argument and option instances. + * + * @param array|InputDefinition $definition An array of argument and option instances or a definition instance + * + * @return $this + */ + public function setDefinition($definition) + { + if ($definition instanceof InputDefinition) { + $this->definition = $definition; + } else { + $this->definition->setDefinition($definition); + } + + $this->fullDefinition = null; + + return $this; + } + + /** + * Gets the InputDefinition attached to this Command. + * + * @return InputDefinition + */ + public function getDefinition() + { + return $this->fullDefinition ?? $this->getNativeDefinition(); + } + + /** + * Gets the InputDefinition to be used to create representations of this Command. + * + * Can be overridden to provide the original command representation when it would otherwise + * be changed by merging with the application InputDefinition. + * + * This method is not part of public API and should not be used directly. + * + * @return InputDefinition + */ + public function getNativeDefinition() + { + if (null === $this->definition) { + throw new LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', static::class)); + } + + return $this->definition; + } + + /** + * Adds an argument. + * + * @param int|null $mode The argument mode: InputArgument::REQUIRED or InputArgument::OPTIONAL + * @param mixed $default The default value (for InputArgument::OPTIONAL mode only) + * + * @return $this + * + * @throws InvalidArgumentException When argument mode is not valid + */ + public function addArgument(string $name, int $mode = null, string $description = '', $default = null) + { + $this->definition->addArgument(new InputArgument($name, $mode, $description, $default)); + if (null !== $this->fullDefinition) { + $this->fullDefinition->addArgument(new InputArgument($name, $mode, $description, $default)); + } + + return $this; + } + + /** + * Adds an option. + * + * @param string|array|null $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts + * @param int|null $mode The option mode: One of the InputOption::VALUE_* constants + * @param mixed $default The default value (must be null for InputOption::VALUE_NONE) + * + * @return $this + * + * @throws InvalidArgumentException If option mode is invalid or incompatible + */ + public function addOption(string $name, $shortcut = null, int $mode = null, string $description = '', $default = null) + { + $this->definition->addOption(new InputOption($name, $shortcut, $mode, $description, $default)); + if (null !== $this->fullDefinition) { + $this->fullDefinition->addOption(new InputOption($name, $shortcut, $mode, $description, $default)); + } + + return $this; + } + + /** + * Sets the name of the command. + * + * This method can set both the namespace and the name if + * you separate them by a colon (:) + * + * $command->setName('foo:bar'); + * + * @return $this + * + * @throws InvalidArgumentException When the name is invalid + */ + public function setName(string $name) + { + $this->validateName($name); + + $this->name = $name; + + return $this; + } + + /** + * Sets the process title of the command. + * + * This feature should be used only when creating a long process command, + * like a daemon. + * + * @return $this + */ + public function setProcessTitle(string $title) + { + $this->processTitle = $title; + + return $this; + } + + /** + * Returns the command name. + * + * @return string|null + */ + public function getName() + { + return $this->name; + } + + /** + * @param bool $hidden Whether or not the command should be hidden from the list of commands + * The default value will be true in Symfony 6.0 + * + * @return $this + * + * @final since Symfony 5.1 + */ + public function setHidden(bool $hidden /* = true */) + { + $this->hidden = $hidden; + + return $this; + } + + /** + * @return bool whether the command should be publicly shown or not + */ + public function isHidden() + { + return $this->hidden; + } + + /** + * Sets the description for the command. + * + * @return $this + */ + public function setDescription(string $description) + { + $this->description = $description; + + return $this; + } + + /** + * Returns the description for the command. + * + * @return string + */ + public function getDescription() + { + return $this->description; + } + + /** + * Sets the help for the command. + * + * @return $this + */ + public function setHelp(string $help) + { + $this->help = $help; + + return $this; + } + + /** + * Returns the help for the command. + * + * @return string + */ + public function getHelp() + { + return $this->help; + } + + /** + * Returns the processed help for the command replacing the %command.name% and + * %command.full_name% patterns with the real values dynamically. + * + * @return string + */ + public function getProcessedHelp() + { + $name = $this->name; + $isSingleCommand = $this->application && $this->application->isSingleCommand(); + + $placeholders = [ + '%command.name%', + '%command.full_name%', + ]; + $replacements = [ + $name, + $isSingleCommand ? $_SERVER['PHP_SELF'] : $_SERVER['PHP_SELF'].' '.$name, + ]; + + return str_replace($placeholders, $replacements, $this->getHelp() ?: $this->getDescription()); + } + + /** + * Sets the aliases for the command. + * + * @param string[] $aliases An array of aliases for the command + * + * @return $this + * + * @throws InvalidArgumentException When an alias is invalid + */ + public function setAliases(iterable $aliases) + { + $list = []; + + foreach ($aliases as $alias) { + $this->validateName($alias); + $list[] = $alias; + } + + $this->aliases = \is_array($aliases) ? $aliases : $list; + + return $this; + } + + /** + * Returns the aliases for the command. + * + * @return array + */ + public function getAliases() + { + return $this->aliases; + } + + /** + * Returns the synopsis for the command. + * + * @param bool $short Whether to show the short version of the synopsis (with options folded) or not + * + * @return string + */ + public function getSynopsis(bool $short = false) + { + $key = $short ? 'short' : 'long'; + + if (!isset($this->synopsis[$key])) { + $this->synopsis[$key] = trim(sprintf('%s %s', $this->name, $this->definition->getSynopsis($short))); + } + + return $this->synopsis[$key]; + } + + /** + * Add a command usage example, it'll be prefixed with the command name. + * + * @return $this + */ + public function addUsage(string $usage) + { + if (!str_starts_with($usage, $this->name)) { + $usage = sprintf('%s %s', $this->name, $usage); + } + + $this->usages[] = $usage; + + return $this; + } + + /** + * Returns alternative usages of the command. + * + * @return array + */ + public function getUsages() + { + return $this->usages; + } + + /** + * Gets a helper instance by name. + * + * @return mixed + * + * @throws LogicException if no HelperSet is defined + * @throws InvalidArgumentException if the helper is not defined + */ + public function getHelper(string $name) + { + if (null === $this->helperSet) { + throw new LogicException(sprintf('Cannot retrieve helper "%s" because there is no HelperSet defined. Did you forget to add your command to the application or to set the application on the command using the setApplication() method? You can also set the HelperSet directly using the setHelperSet() method.', $name)); + } + + return $this->helperSet->get($name); + } + + /** + * Validates a command name. + * + * It must be non-empty and parts can optionally be separated by ":". + * + * @throws InvalidArgumentException When the name is invalid + */ + private function validateName(string $name) + { + if (!preg_match('/^[^\:]++(\:[^\:]++)*$/', $name)) { + throw new InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name)); + } + } +} diff --git a/vendor/symfony/console/Command/CompleteCommand.php b/vendor/symfony/console/Command/CompleteCommand.php new file mode 100644 index 0000000..0e35143 --- /dev/null +++ b/vendor/symfony/console/Command/CompleteCommand.php @@ -0,0 +1,205 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Command; + +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Completion\Output\BashCompletionOutput; +use Symfony\Component\Console\Completion\Output\CompletionOutputInterface; +use Symfony\Component\Console\Exception\CommandNotFoundException; +use Symfony\Component\Console\Exception\ExceptionInterface; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Responsible for providing the values to the shell completion. + * + * @author Wouter de Jong + */ +final class CompleteCommand extends Command +{ + protected static $defaultName = '|_complete'; + protected static $defaultDescription = 'Internal command to provide shell completion suggestions'; + + private $completionOutputs; + + private $isDebug = false; + + /** + * @param array> $completionOutputs A list of additional completion outputs, with shell name as key and FQCN as value + */ + public function __construct(array $completionOutputs = []) + { + // must be set before the parent constructor, as the property value is used in configure() + $this->completionOutputs = $completionOutputs + ['bash' => BashCompletionOutput::class]; + + parent::__construct(); + } + + protected function configure(): void + { + $this + ->addOption('shell', 's', InputOption::VALUE_REQUIRED, 'The shell type ("'.implode('", "', array_keys($this->completionOutputs)).'")') + ->addOption('input', 'i', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'An array of input tokens (e.g. COMP_WORDS or argv)') + ->addOption('current', 'c', InputOption::VALUE_REQUIRED, 'The index of the "input" array that the cursor is in (e.g. COMP_CWORD)') + ->addOption('symfony', 'S', InputOption::VALUE_REQUIRED, 'The version of the completion script') + ; + } + + protected function initialize(InputInterface $input, OutputInterface $output) + { + $this->isDebug = filter_var(getenv('SYMFONY_COMPLETION_DEBUG'), \FILTER_VALIDATE_BOOLEAN); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + try { + // uncomment when a bugfix or BC break has been introduced in the shell completion scripts + // $version = $input->getOption('symfony'); + // if ($version && version_compare($version, 'x.y', '>=')) { + // $message = sprintf('Completion script version is not supported ("%s" given, ">=x.y" required).', $version); + // $this->log($message); + + // $output->writeln($message.' Install the Symfony completion script again by using the "completion" command.'); + + // return 126; + // } + + $shell = $input->getOption('shell'); + if (!$shell) { + throw new \RuntimeException('The "--shell" option must be set.'); + } + + if (!$completionOutput = $this->completionOutputs[$shell] ?? false) { + throw new \RuntimeException(sprintf('Shell completion is not supported for your shell: "%s" (supported: "%s").', $shell, implode('", "', array_keys($this->completionOutputs)))); + } + + $completionInput = $this->createCompletionInput($input); + $suggestions = new CompletionSuggestions(); + + $this->log([ + '', + ''.date('Y-m-d H:i:s').'', + 'Input: ("|" indicates the cursor position)', + ' '.(string) $completionInput, + 'Command:', + ' '.(string) implode(' ', $_SERVER['argv']), + 'Messages:', + ]); + + $command = $this->findCommand($completionInput, $output); + if (null === $command) { + $this->log(' No command found, completing using the Application class.'); + + $this->getApplication()->complete($completionInput, $suggestions); + } elseif ( + $completionInput->mustSuggestArgumentValuesFor('command') + && $command->getName() !== $completionInput->getCompletionValue() + && !\in_array($completionInput->getCompletionValue(), $command->getAliases(), true) + ) { + $this->log(' No command found, completing using the Application class.'); + + // expand shortcut names ("cache:cl") into their full name ("cache:clear") + $suggestions->suggestValues(array_filter(array_merge([$command->getName()], $command->getAliases()))); + } else { + $command->mergeApplicationDefinition(); + $completionInput->bind($command->getDefinition()); + + if (CompletionInput::TYPE_OPTION_NAME === $completionInput->getCompletionType()) { + $this->log(' Completing option names for the '.\get_class($command instanceof LazyCommand ? $command->getCommand() : $command).' command.'); + + $suggestions->suggestOptions($command->getDefinition()->getOptions()); + } else { + $this->log([ + ' Completing using the '.\get_class($command instanceof LazyCommand ? $command->getCommand() : $command).' class.', + ' Completing '.$completionInput->getCompletionType().' for '.$completionInput->getCompletionName().'', + ]); + if (null !== $compval = $completionInput->getCompletionValue()) { + $this->log(' Current value: '.$compval.''); + } + + $command->complete($completionInput, $suggestions); + } + } + + /** @var CompletionOutputInterface $completionOutput */ + $completionOutput = new $completionOutput(); + + $this->log('Suggestions:'); + if ($options = $suggestions->getOptionSuggestions()) { + $this->log(' --'.implode(' --', array_map(function ($o) { return $o->getName(); }, $options))); + } elseif ($values = $suggestions->getValueSuggestions()) { + $this->log(' '.implode(' ', $values)); + } else { + $this->log(' No suggestions were provided'); + } + + $completionOutput->write($suggestions, $output); + } catch (\Throwable $e) { + $this->log([ + 'Error!', + (string) $e, + ]); + + if ($output->isDebug()) { + throw $e; + } + + return 2; + } + + return 0; + } + + private function createCompletionInput(InputInterface $input): CompletionInput + { + $currentIndex = $input->getOption('current'); + if (!$currentIndex || !ctype_digit($currentIndex)) { + throw new \RuntimeException('The "--current" option must be set and it must be an integer.'); + } + + $completionInput = CompletionInput::fromTokens($input->getOption('input'), (int) $currentIndex); + + try { + $completionInput->bind($this->getApplication()->getDefinition()); + } catch (ExceptionInterface $e) { + } + + return $completionInput; + } + + private function findCommand(CompletionInput $completionInput, OutputInterface $output): ?Command + { + try { + $inputName = $completionInput->getFirstArgument(); + if (null === $inputName) { + return null; + } + + return $this->getApplication()->find($inputName); + } catch (CommandNotFoundException $e) { + } + + return null; + } + + private function log($messages): void + { + if (!$this->isDebug) { + return; + } + + $commandName = basename($_SERVER['argv'][0]); + file_put_contents(sys_get_temp_dir().'/sf_'.$commandName.'.log', implode(\PHP_EOL, (array) $messages).\PHP_EOL, \FILE_APPEND); + } +} diff --git a/vendor/symfony/console/Command/DumpCompletionCommand.php b/vendor/symfony/console/Command/DumpCompletionCommand.php new file mode 100644 index 0000000..eaf22be --- /dev/null +++ b/vendor/symfony/console/Command/DumpCompletionCommand.php @@ -0,0 +1,145 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Command; + +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Process\Process; + +/** + * Dumps the completion script for the current shell. + * + * @author Wouter de Jong + */ +final class DumpCompletionCommand extends Command +{ + protected static $defaultName = 'completion'; + protected static $defaultDescription = 'Dump the shell completion script'; + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ($input->mustSuggestArgumentValuesFor('shell')) { + $suggestions->suggestValues($this->getSupportedShells()); + } + } + + protected function configure() + { + $fullCommand = $_SERVER['PHP_SELF']; + $commandName = basename($fullCommand); + $fullCommand = @realpath($fullCommand) ?: $fullCommand; + + $this + ->setHelp(<<%command.name% command dumps the shell completion script required +to use shell autocompletion (currently only bash completion is supported). + +Static installation +------------------- + +Dump the script to a global completion file and restart your shell: + + %command.full_name% bash | sudo tee /etc/bash_completion.d/{$commandName} + +Or dump the script to a local file and source it: + + %command.full_name% bash > completion.sh + + # source the file whenever you use the project + source completion.sh + + # or add this line at the end of your "~/.bashrc" file: + source /path/to/completion.sh + +Dynamic installation +-------------------- + +Add this to the end of your shell configuration file (e.g. "~/.bashrc"): + + eval "$({$fullCommand} completion bash)" +EOH + ) + ->addArgument('shell', InputArgument::OPTIONAL, 'The shell type (e.g. "bash"), the value of the "$SHELL" env var will be used if this is not given') + ->addOption('debug', null, InputOption::VALUE_NONE, 'Tail the completion debug log') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $commandName = basename($_SERVER['argv'][0]); + + if ($input->getOption('debug')) { + $this->tailDebugLog($commandName, $output); + + return 0; + } + + $shell = $input->getArgument('shell') ?? self::guessShell(); + $completionFile = __DIR__.'/../Resources/completion.'.$shell; + if (!file_exists($completionFile)) { + $supportedShells = $this->getSupportedShells(); + + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + if ($shell) { + $output->writeln(sprintf('Detected shell "%s", which is not supported by Symfony shell completion (supported shells: "%s").', $shell, implode('", "', $supportedShells))); + } else { + $output->writeln(sprintf('Shell not detected, Symfony shell completion only supports "%s").', implode('", "', $supportedShells))); + } + + return 2; + } + + $output->write(str_replace(['{{ COMMAND_NAME }}', '{{ VERSION }}'], [$commandName, $this->getApplication()->getVersion()], file_get_contents($completionFile))); + + return 0; + } + + private static function guessShell(): string + { + return basename($_SERVER['SHELL'] ?? ''); + } + + private function tailDebugLog(string $commandName, OutputInterface $output): void + { + $debugFile = sys_get_temp_dir().'/sf_'.$commandName.'.log'; + if (!file_exists($debugFile)) { + touch($debugFile); + } + $process = new Process(['tail', '-f', $debugFile], null, null, null, 0); + $process->run(function (string $type, string $line) use ($output): void { + $output->write($line); + }); + } + + /** + * @return string[] + */ + private function getSupportedShells(): array + { + $shells = []; + + foreach (new \DirectoryIterator(__DIR__.'/../Resources/') as $file) { + if (str_starts_with($file->getBasename(), 'completion.') && $file->isFile()) { + $shells[] = $file->getExtension(); + } + } + + return $shells; + } +} diff --git a/vendor/symfony/console/Command/HelpCommand.php b/vendor/symfony/console/Command/HelpCommand.php new file mode 100644 index 0000000..c66ef46 --- /dev/null +++ b/vendor/symfony/console/Command/HelpCommand.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Command; + +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Descriptor\ApplicationDescription; +use Symfony\Component\Console\Helper\DescriptorHelper; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * HelpCommand displays the help for a given command. + * + * @author Fabien Potencier + */ +class HelpCommand extends Command +{ + private $command; + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->ignoreValidationErrors(); + + $this + ->setName('help') + ->setDefinition([ + new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help'), + new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'), + new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command help'), + ]) + ->setDescription('Display help for a command') + ->setHelp(<<<'EOF' +The %command.name% command displays help for a given command: + + %command.full_name% list + +You can also output the help in other formats by using the --format option: + + %command.full_name% --format=xml list + +To display the list of available commands, please use the list command. +EOF + ) + ; + } + + public function setCommand(Command $command) + { + $this->command = $command; + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + if (null === $this->command) { + $this->command = $this->getApplication()->find($input->getArgument('command_name')); + } + + $helper = new DescriptorHelper(); + $helper->describe($output, $this->command, [ + 'format' => $input->getOption('format'), + 'raw_text' => $input->getOption('raw'), + ]); + + $this->command = null; + + return 0; + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ($input->mustSuggestArgumentValuesFor('command_name')) { + $descriptor = new ApplicationDescription($this->getApplication()); + $suggestions->suggestValues(array_keys($descriptor->getCommands())); + + return; + } + + if ($input->mustSuggestOptionValuesFor('format')) { + $helper = new DescriptorHelper(); + $suggestions->suggestValues($helper->getFormats()); + } + } +} diff --git a/vendor/symfony/console/Command/LazyCommand.php b/vendor/symfony/console/Command/LazyCommand.php new file mode 100644 index 0000000..e576ad0 --- /dev/null +++ b/vendor/symfony/console/Command/LazyCommand.php @@ -0,0 +1,218 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Command; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Helper\HelperSet; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Nicolas Grekas + */ +final class LazyCommand extends Command +{ + private $command; + private $isEnabled; + + public function __construct(string $name, array $aliases, string $description, bool $isHidden, \Closure $commandFactory, ?bool $isEnabled = true) + { + $this->setName($name) + ->setAliases($aliases) + ->setHidden($isHidden) + ->setDescription($description); + + $this->command = $commandFactory; + $this->isEnabled = $isEnabled; + } + + public function ignoreValidationErrors(): void + { + $this->getCommand()->ignoreValidationErrors(); + } + + public function setApplication(Application $application = null): void + { + if ($this->command instanceof parent) { + $this->command->setApplication($application); + } + + parent::setApplication($application); + } + + public function setHelperSet(HelperSet $helperSet): void + { + if ($this->command instanceof parent) { + $this->command->setHelperSet($helperSet); + } + + parent::setHelperSet($helperSet); + } + + public function isEnabled(): bool + { + return $this->isEnabled ?? $this->getCommand()->isEnabled(); + } + + public function run(InputInterface $input, OutputInterface $output): int + { + return $this->getCommand()->run($input, $output); + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + $this->getCommand()->complete($input, $suggestions); + } + + /** + * @return $this + */ + public function setCode(callable $code): self + { + $this->getCommand()->setCode($code); + + return $this; + } + + /** + * @internal + */ + public function mergeApplicationDefinition(bool $mergeArgs = true): void + { + $this->getCommand()->mergeApplicationDefinition($mergeArgs); + } + + /** + * @return $this + */ + public function setDefinition($definition): self + { + $this->getCommand()->setDefinition($definition); + + return $this; + } + + public function getDefinition(): InputDefinition + { + return $this->getCommand()->getDefinition(); + } + + public function getNativeDefinition(): InputDefinition + { + return $this->getCommand()->getNativeDefinition(); + } + + /** + * @return $this + */ + public function addArgument(string $name, int $mode = null, string $description = '', $default = null): self + { + $this->getCommand()->addArgument($name, $mode, $description, $default); + + return $this; + } + + /** + * @return $this + */ + public function addOption(string $name, $shortcut = null, int $mode = null, string $description = '', $default = null): self + { + $this->getCommand()->addOption($name, $shortcut, $mode, $description, $default); + + return $this; + } + + /** + * @return $this + */ + public function setProcessTitle(string $title): self + { + $this->getCommand()->setProcessTitle($title); + + return $this; + } + + /** + * @return $this + */ + public function setHelp(string $help): self + { + $this->getCommand()->setHelp($help); + + return $this; + } + + public function getHelp(): string + { + return $this->getCommand()->getHelp(); + } + + public function getProcessedHelp(): string + { + return $this->getCommand()->getProcessedHelp(); + } + + public function getSynopsis(bool $short = false): string + { + return $this->getCommand()->getSynopsis($short); + } + + /** + * @return $this + */ + public function addUsage(string $usage): self + { + $this->getCommand()->addUsage($usage); + + return $this; + } + + public function getUsages(): array + { + return $this->getCommand()->getUsages(); + } + + /** + * @return mixed + */ + public function getHelper(string $name) + { + return $this->getCommand()->getHelper($name); + } + + public function getCommand(): parent + { + if (!$this->command instanceof \Closure) { + return $this->command; + } + + $command = $this->command = ($this->command)(); + $command->setApplication($this->getApplication()); + + if (null !== $this->getHelperSet()) { + $command->setHelperSet($this->getHelperSet()); + } + + $command->setName($this->getName()) + ->setAliases($this->getAliases()) + ->setHidden($this->isHidden()) + ->setDescription($this->getDescription()); + + // Will throw if the command is not correctly initialized. + $command->getDefinition(); + + return $command; + } +} diff --git a/vendor/symfony/console/Command/ListCommand.php b/vendor/symfony/console/Command/ListCommand.php new file mode 100644 index 0000000..f04a4ef --- /dev/null +++ b/vendor/symfony/console/Command/ListCommand.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Command; + +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Descriptor\ApplicationDescription; +use Symfony\Component\Console\Helper\DescriptorHelper; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * ListCommand displays the list of all available commands for the application. + * + * @author Fabien Potencier + */ +class ListCommand extends Command +{ + /** + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setName('list') + ->setDefinition([ + new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name'), + new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command list'), + new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'), + new InputOption('short', null, InputOption::VALUE_NONE, 'To skip describing commands\' arguments'), + ]) + ->setDescription('List commands') + ->setHelp(<<<'EOF' +The %command.name% command lists all commands: + + %command.full_name% + +You can also display the commands for a specific namespace: + + %command.full_name% test + +You can also output the information in other formats by using the --format option: + + %command.full_name% --format=xml + +It's also possible to get raw list of commands (useful for embedding command runner): + + %command.full_name% --raw +EOF + ) + ; + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $helper = new DescriptorHelper(); + $helper->describe($output, $this->getApplication(), [ + 'format' => $input->getOption('format'), + 'raw_text' => $input->getOption('raw'), + 'namespace' => $input->getArgument('namespace'), + 'short' => $input->getOption('short'), + ]); + + return 0; + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ($input->mustSuggestArgumentValuesFor('namespace')) { + $descriptor = new ApplicationDescription($this->getApplication()); + $suggestions->suggestValues(array_keys($descriptor->getNamespaces())); + + return; + } + + if ($input->mustSuggestOptionValuesFor('format')) { + $helper = new DescriptorHelper(); + $suggestions->suggestValues($helper->getFormats()); + } + } +} diff --git a/vendor/symfony/console/Command/LockableTrait.php b/vendor/symfony/console/Command/LockableTrait.php new file mode 100644 index 0000000..b1856dc --- /dev/null +++ b/vendor/symfony/console/Command/LockableTrait.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Command; + +use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Lock\LockFactory; +use Symfony\Component\Lock\LockInterface; +use Symfony\Component\Lock\Store\FlockStore; +use Symfony\Component\Lock\Store\SemaphoreStore; + +/** + * Basic lock feature for commands. + * + * @author Geoffrey Brier + */ +trait LockableTrait +{ + /** @var LockInterface|null */ + private $lock; + + /** + * Locks a command. + */ + private function lock(string $name = null, bool $blocking = false): bool + { + if (!class_exists(SemaphoreStore::class)) { + throw new LogicException('To enable the locking feature you must install the symfony/lock component.'); + } + + if (null !== $this->lock) { + throw new LogicException('A lock is already in place.'); + } + + if (SemaphoreStore::isSupported()) { + $store = new SemaphoreStore(); + } else { + $store = new FlockStore(); + } + + $this->lock = (new LockFactory($store))->createLock($name ?: $this->getName()); + if (!$this->lock->acquire($blocking)) { + $this->lock = null; + + return false; + } + + return true; + } + + /** + * Releases the command lock if there is one. + */ + private function release() + { + if ($this->lock) { + $this->lock->release(); + $this->lock = null; + } + } +} diff --git a/vendor/symfony/console/Command/SignalableCommandInterface.php b/vendor/symfony/console/Command/SignalableCommandInterface.php new file mode 100644 index 0000000..d439728 --- /dev/null +++ b/vendor/symfony/console/Command/SignalableCommandInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Command; + +/** + * Interface for command reacting to signal. + * + * @author Grégoire Pineau + */ +interface SignalableCommandInterface +{ + /** + * Returns the list of signals to subscribe. + */ + public function getSubscribedSignals(): array; + + /** + * The method will be called when the application is signaled. + */ + public function handleSignal(int $signal): void; +} diff --git a/vendor/symfony/console/CommandLoader/CommandLoaderInterface.php b/vendor/symfony/console/CommandLoader/CommandLoaderInterface.php new file mode 100644 index 0000000..0adaf88 --- /dev/null +++ b/vendor/symfony/console/CommandLoader/CommandLoaderInterface.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 Symfony\Component\Console\CommandLoader; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\CommandNotFoundException; + +/** + * @author Robin Chalas + */ +interface CommandLoaderInterface +{ + /** + * Loads a command. + * + * @return Command + * + * @throws CommandNotFoundException + */ + public function get(string $name); + + /** + * Checks if a command exists. + * + * @return bool + */ + public function has(string $name); + + /** + * @return string[] + */ + public function getNames(); +} diff --git a/vendor/symfony/console/CommandLoader/ContainerCommandLoader.php b/vendor/symfony/console/CommandLoader/ContainerCommandLoader.php new file mode 100644 index 0000000..ddccb3d --- /dev/null +++ b/vendor/symfony/console/CommandLoader/ContainerCommandLoader.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\CommandLoader; + +use Psr\Container\ContainerInterface; +use Symfony\Component\Console\Exception\CommandNotFoundException; + +/** + * Loads commands from a PSR-11 container. + * + * @author Robin Chalas + */ +class ContainerCommandLoader implements CommandLoaderInterface +{ + private $container; + private $commandMap; + + /** + * @param array $commandMap An array with command names as keys and service ids as values + */ + public function __construct(ContainerInterface $container, array $commandMap) + { + $this->container = $container; + $this->commandMap = $commandMap; + } + + /** + * {@inheritdoc} + */ + public function get(string $name) + { + if (!$this->has($name)) { + throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name)); + } + + return $this->container->get($this->commandMap[$name]); + } + + /** + * {@inheritdoc} + */ + public function has(string $name) + { + return isset($this->commandMap[$name]) && $this->container->has($this->commandMap[$name]); + } + + /** + * {@inheritdoc} + */ + public function getNames() + { + return array_keys($this->commandMap); + } +} diff --git a/vendor/symfony/console/CommandLoader/FactoryCommandLoader.php b/vendor/symfony/console/CommandLoader/FactoryCommandLoader.php new file mode 100644 index 0000000..7e2db34 --- /dev/null +++ b/vendor/symfony/console/CommandLoader/FactoryCommandLoader.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\CommandLoader; + +use Symfony\Component\Console\Exception\CommandNotFoundException; + +/** + * A simple command loader using factories to instantiate commands lazily. + * + * @author Maxime Steinhausser + */ +class FactoryCommandLoader implements CommandLoaderInterface +{ + private $factories; + + /** + * @param callable[] $factories Indexed by command names + */ + public function __construct(array $factories) + { + $this->factories = $factories; + } + + /** + * {@inheritdoc} + */ + public function has(string $name) + { + return isset($this->factories[$name]); + } + + /** + * {@inheritdoc} + */ + public function get(string $name) + { + if (!isset($this->factories[$name])) { + throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name)); + } + + $factory = $this->factories[$name]; + + return $factory(); + } + + /** + * {@inheritdoc} + */ + public function getNames() + { + return array_keys($this->factories); + } +} diff --git a/vendor/symfony/console/Completion/CompletionInput.php b/vendor/symfony/console/Completion/CompletionInput.php new file mode 100644 index 0000000..368b945 --- /dev/null +++ b/vendor/symfony/console/Completion/CompletionInput.php @@ -0,0 +1,249 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Completion; + +use Symfony\Component\Console\Exception\RuntimeException; +use Symfony\Component\Console\Input\ArgvInput; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; + +/** + * An input specialized for shell completion. + * + * This input allows unfinished option names or values and exposes what kind of + * completion is expected. + * + * @author Wouter de Jong + */ +final class CompletionInput extends ArgvInput +{ + public const TYPE_ARGUMENT_VALUE = 'argument_value'; + public const TYPE_OPTION_VALUE = 'option_value'; + public const TYPE_OPTION_NAME = 'option_name'; + public const TYPE_NONE = 'none'; + + private $tokens; + private $currentIndex; + private $completionType; + private $completionName = null; + private $completionValue = ''; + + /** + * Converts a terminal string into tokens. + * + * This is required for shell completions without COMP_WORDS support. + */ + public static function fromString(string $inputStr, int $currentIndex): self + { + preg_match_all('/(?<=^|\s)([\'"]?)(.+?)(?tokens = $tokens; + $input->currentIndex = $currentIndex; + + return $input; + } + + /** + * {@inheritdoc} + */ + public function bind(InputDefinition $definition): void + { + parent::bind($definition); + + $relevantToken = $this->getRelevantToken(); + if ('-' === $relevantToken[0]) { + // the current token is an input option: complete either option name or option value + [$optionToken, $optionValue] = explode('=', $relevantToken, 2) + ['', '']; + + $option = $this->getOptionFromToken($optionToken); + if (null === $option && !$this->isCursorFree()) { + $this->completionType = self::TYPE_OPTION_NAME; + $this->completionValue = $relevantToken; + + return; + } + + if (null !== $option && $option->acceptValue()) { + $this->completionType = self::TYPE_OPTION_VALUE; + $this->completionName = $option->getName(); + $this->completionValue = $optionValue ?: (!str_starts_with($optionToken, '--') ? substr($optionToken, 2) : ''); + + return; + } + } + + $previousToken = $this->tokens[$this->currentIndex - 1]; + if ('-' === $previousToken[0] && '' !== trim($previousToken, '-')) { + // check if previous option accepted a value + $previousOption = $this->getOptionFromToken($previousToken); + if (null !== $previousOption && $previousOption->acceptValue()) { + $this->completionType = self::TYPE_OPTION_VALUE; + $this->completionName = $previousOption->getName(); + $this->completionValue = $relevantToken; + + return; + } + } + + // complete argument value + $this->completionType = self::TYPE_ARGUMENT_VALUE; + + foreach ($this->definition->getArguments() as $argumentName => $argument) { + if (!isset($this->arguments[$argumentName])) { + break; + } + + $argumentValue = $this->arguments[$argumentName]; + $this->completionName = $argumentName; + if (\is_array($argumentValue)) { + $this->completionValue = $argumentValue ? $argumentValue[array_key_last($argumentValue)] : null; + } else { + $this->completionValue = $argumentValue; + } + } + + if ($this->currentIndex >= \count($this->tokens)) { + if (!isset($this->arguments[$argumentName]) || $this->definition->getArgument($argumentName)->isArray()) { + $this->completionName = $argumentName; + $this->completionValue = ''; + } else { + // we've reached the end + $this->completionType = self::TYPE_NONE; + $this->completionName = null; + $this->completionValue = ''; + } + } + } + + /** + * Returns the type of completion required. + * + * TYPE_ARGUMENT_VALUE when completing the value of an input argument + * TYPE_OPTION_VALUE when completing the value of an input option + * TYPE_OPTION_NAME when completing the name of an input option + * TYPE_NONE when nothing should be completed + * + * @return string One of self::TYPE_* constants. TYPE_OPTION_NAME and TYPE_NONE are already implemented by the Console component + */ + public function getCompletionType(): string + { + return $this->completionType; + } + + /** + * The name of the input option or argument when completing a value. + * + * @return string|null returns null when completing an option name + */ + public function getCompletionName(): ?string + { + return $this->completionName; + } + + /** + * The value already typed by the user (or empty string). + */ + public function getCompletionValue(): string + { + return $this->completionValue; + } + + public function mustSuggestOptionValuesFor(string $optionName): bool + { + return self::TYPE_OPTION_VALUE === $this->getCompletionType() && $optionName === $this->getCompletionName(); + } + + public function mustSuggestArgumentValuesFor(string $argumentName): bool + { + return self::TYPE_ARGUMENT_VALUE === $this->getCompletionType() && $argumentName === $this->getCompletionName(); + } + + protected function parseToken(string $token, bool $parseOptions): bool + { + try { + return parent::parseToken($token, $parseOptions); + } catch (RuntimeException $e) { + // suppress errors, completed input is almost never valid + } + + return $parseOptions; + } + + private function getOptionFromToken(string $optionToken): ?InputOption + { + $optionName = ltrim($optionToken, '-'); + if (!$optionName) { + return null; + } + + if ('-' === ($optionToken[1] ?? ' ')) { + // long option name + return $this->definition->hasOption($optionName) ? $this->definition->getOption($optionName) : null; + } + + // short option name + return $this->definition->hasShortcut($optionName[0]) ? $this->definition->getOptionForShortcut($optionName[0]) : null; + } + + /** + * The token of the cursor, or the last token if the cursor is at the end of the input. + */ + private function getRelevantToken(): string + { + return $this->tokens[$this->isCursorFree() ? $this->currentIndex - 1 : $this->currentIndex]; + } + + /** + * Whether the cursor is "free" (i.e. at the end of the input preceded by a space). + */ + private function isCursorFree(): bool + { + $nrOfTokens = \count($this->tokens); + if ($this->currentIndex > $nrOfTokens) { + throw new \LogicException('Current index is invalid, it must be the number of input tokens or one more.'); + } + + return $this->currentIndex >= $nrOfTokens; + } + + public function __toString() + { + $str = ''; + foreach ($this->tokens as $i => $token) { + $str .= $token; + + if ($this->currentIndex === $i) { + $str .= '|'; + } + + $str .= ' '; + } + + if ($this->currentIndex > $i) { + $str .= '|'; + } + + return rtrim($str); + } +} diff --git a/vendor/symfony/console/Completion/CompletionSuggestions.php b/vendor/symfony/console/Completion/CompletionSuggestions.php new file mode 100644 index 0000000..d8905e5 --- /dev/null +++ b/vendor/symfony/console/Completion/CompletionSuggestions.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Completion; + +use Symfony\Component\Console\Input\InputOption; + +/** + * Stores all completion suggestions for the current input. + * + * @author Wouter de Jong + */ +final class CompletionSuggestions +{ + private $valueSuggestions = []; + private $optionSuggestions = []; + + /** + * Add a suggested value for an input option or argument. + * + * @param string|Suggestion $value + * + * @return $this + */ + public function suggestValue($value): self + { + $this->valueSuggestions[] = !$value instanceof Suggestion ? new Suggestion($value) : $value; + + return $this; + } + + /** + * Add multiple suggested values at once for an input option or argument. + * + * @param list $values + * + * @return $this + */ + public function suggestValues(array $values): self + { + foreach ($values as $value) { + $this->suggestValue($value); + } + + return $this; + } + + /** + * Add a suggestion for an input option name. + * + * @return $this + */ + public function suggestOption(InputOption $option): self + { + $this->optionSuggestions[] = $option; + + return $this; + } + + /** + * Add multiple suggestions for input option names at once. + * + * @param InputOption[] $options + * + * @return $this + */ + public function suggestOptions(array $options): self + { + foreach ($options as $option) { + $this->suggestOption($option); + } + + return $this; + } + + /** + * @return InputOption[] + */ + public function getOptionSuggestions(): array + { + return $this->optionSuggestions; + } + + /** + * @return Suggestion[] + */ + public function getValueSuggestions(): array + { + return $this->valueSuggestions; + } +} diff --git a/vendor/symfony/console/Completion/Output/BashCompletionOutput.php b/vendor/symfony/console/Completion/Output/BashCompletionOutput.php new file mode 100644 index 0000000..c6f76eb --- /dev/null +++ b/vendor/symfony/console/Completion/Output/BashCompletionOutput.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Completion\Output; + +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Wouter de Jong + */ +class BashCompletionOutput implements CompletionOutputInterface +{ + public function write(CompletionSuggestions $suggestions, OutputInterface $output): void + { + $values = $suggestions->getValueSuggestions(); + foreach ($suggestions->getOptionSuggestions() as $option) { + $values[] = '--'.$option->getName(); + if ($option->isNegatable()) { + $values[] = '--no-'.$option->getName(); + } + } + $output->writeln(implode("\n", $values)); + } +} diff --git a/vendor/symfony/console/Completion/Output/CompletionOutputInterface.php b/vendor/symfony/console/Completion/Output/CompletionOutputInterface.php new file mode 100644 index 0000000..659e596 --- /dev/null +++ b/vendor/symfony/console/Completion/Output/CompletionOutputInterface.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 Symfony\Component\Console\Completion\Output; + +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Transforms the {@see CompletionSuggestions} object into output readable by the shell completion. + * + * @author Wouter de Jong + */ +interface CompletionOutputInterface +{ + public function write(CompletionSuggestions $suggestions, OutputInterface $output): void; +} diff --git a/vendor/symfony/console/Completion/Suggestion.php b/vendor/symfony/console/Completion/Suggestion.php new file mode 100644 index 0000000..6c7bc4d --- /dev/null +++ b/vendor/symfony/console/Completion/Suggestion.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Completion; + +/** + * Represents a single suggested value. + * + * @author Wouter de Jong + */ +class Suggestion +{ + private $value; + + public function __construct(string $value) + { + $this->value = $value; + } + + public function getValue(): string + { + return $this->value; + } + + public function __toString(): string + { + return $this->getValue(); + } +} diff --git a/vendor/symfony/console/ConsoleEvents.php b/vendor/symfony/console/ConsoleEvents.php new file mode 100644 index 0000000..6ae8f32 --- /dev/null +++ b/vendor/symfony/console/ConsoleEvents.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console; + +use Symfony\Component\Console\Event\ConsoleCommandEvent; +use Symfony\Component\Console\Event\ConsoleErrorEvent; +use Symfony\Component\Console\Event\ConsoleSignalEvent; +use Symfony\Component\Console\Event\ConsoleTerminateEvent; + +/** + * Contains all events dispatched by an Application. + * + * @author Francesco Levorato + */ +final class ConsoleEvents +{ + /** + * The COMMAND event allows you to attach listeners before any command is + * executed by the console. It also allows you to modify the command, input and output + * before they are handed to the command. + * + * @Event("Symfony\Component\Console\Event\ConsoleCommandEvent") + */ + public const COMMAND = 'console.command'; + + /** + * The SIGNAL event allows you to perform some actions + * after the command execution was interrupted. + * + * @Event("Symfony\Component\Console\Event\ConsoleSignalEvent") + */ + public const SIGNAL = 'console.signal'; + + /** + * The TERMINATE event allows you to attach listeners after a command is + * executed by the console. + * + * @Event("Symfony\Component\Console\Event\ConsoleTerminateEvent") + */ + public const TERMINATE = 'console.terminate'; + + /** + * The ERROR event occurs when an uncaught exception or error appears. + * + * This event allows you to deal with the exception/error or + * to modify the thrown exception. + * + * @Event("Symfony\Component\Console\Event\ConsoleErrorEvent") + */ + public const ERROR = 'console.error'; + + /** + * Event aliases. + * + * These aliases can be consumed by RegisterListenersPass. + */ + public const ALIASES = [ + ConsoleCommandEvent::class => self::COMMAND, + ConsoleErrorEvent::class => self::ERROR, + ConsoleSignalEvent::class => self::SIGNAL, + ConsoleTerminateEvent::class => self::TERMINATE, + ]; +} diff --git a/vendor/symfony/console/Cursor.php b/vendor/symfony/console/Cursor.php new file mode 100644 index 0000000..0c4dafb --- /dev/null +++ b/vendor/symfony/console/Cursor.php @@ -0,0 +1,207 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console; + +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Pierre du Plessis + */ +final class Cursor +{ + private $output; + private $input; + + /** + * @param resource|null $input + */ + public function __construct(OutputInterface $output, $input = null) + { + $this->output = $output; + $this->input = $input ?? (\defined('STDIN') ? \STDIN : fopen('php://input', 'r+')); + } + + /** + * @return $this + */ + public function moveUp(int $lines = 1): self + { + $this->output->write(sprintf("\x1b[%dA", $lines)); + + return $this; + } + + /** + * @return $this + */ + public function moveDown(int $lines = 1): self + { + $this->output->write(sprintf("\x1b[%dB", $lines)); + + return $this; + } + + /** + * @return $this + */ + public function moveRight(int $columns = 1): self + { + $this->output->write(sprintf("\x1b[%dC", $columns)); + + return $this; + } + + /** + * @return $this + */ + public function moveLeft(int $columns = 1): self + { + $this->output->write(sprintf("\x1b[%dD", $columns)); + + return $this; + } + + /** + * @return $this + */ + public function moveToColumn(int $column): self + { + $this->output->write(sprintf("\x1b[%dG", $column)); + + return $this; + } + + /** + * @return $this + */ + public function moveToPosition(int $column, int $row): self + { + $this->output->write(sprintf("\x1b[%d;%dH", $row + 1, $column)); + + return $this; + } + + /** + * @return $this + */ + public function savePosition(): self + { + $this->output->write("\x1b7"); + + return $this; + } + + /** + * @return $this + */ + public function restorePosition(): self + { + $this->output->write("\x1b8"); + + return $this; + } + + /** + * @return $this + */ + public function hide(): self + { + $this->output->write("\x1b[?25l"); + + return $this; + } + + /** + * @return $this + */ + public function show(): self + { + $this->output->write("\x1b[?25h\x1b[?0c"); + + return $this; + } + + /** + * Clears all the output from the current line. + * + * @return $this + */ + public function clearLine(): self + { + $this->output->write("\x1b[2K"); + + return $this; + } + + /** + * Clears all the output from the current line after the current position. + */ + public function clearLineAfter(): self + { + $this->output->write("\x1b[K"); + + return $this; + } + + /** + * Clears all the output from the cursors' current position to the end of the screen. + * + * @return $this + */ + public function clearOutput(): self + { + $this->output->write("\x1b[0J"); + + return $this; + } + + /** + * Clears the entire screen. + * + * @return $this + */ + public function clearScreen(): self + { + $this->output->write("\x1b[2J"); + + return $this; + } + + /** + * Returns the current cursor position as x,y coordinates. + */ + public function getCurrentPosition(): array + { + static $isTtySupported; + + if (null === $isTtySupported && \function_exists('proc_open')) { + $isTtySupported = (bool) @proc_open('echo 1 >/dev/null', [['file', '/dev/tty', 'r'], ['file', '/dev/tty', 'w'], ['file', '/dev/tty', 'w']], $pipes); + } + + if (!$isTtySupported) { + return [1, 1]; + } + + $sttyMode = shell_exec('stty -g'); + shell_exec('stty -icanon -echo'); + + @fwrite($this->input, "\033[6n"); + + $code = trim(fread($this->input, 1024)); + + shell_exec(sprintf('stty %s', $sttyMode)); + + sscanf($code, "\033[%d;%dR", $row, $col); + + return [$col, $row]; + } +} diff --git a/vendor/symfony/console/DependencyInjection/AddConsoleCommandPass.php b/vendor/symfony/console/DependencyInjection/AddConsoleCommandPass.php new file mode 100644 index 0000000..1fbb212 --- /dev/null +++ b/vendor/symfony/console/DependencyInjection/AddConsoleCommandPass.php @@ -0,0 +1,148 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\DependencyInjection; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Command\LazyCommand; +use Symfony\Component\Console\CommandLoader\ContainerCommandLoader; +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\TypedReference; + +/** + * Registers console commands. + * + * @author Grégoire Pineau + */ +class AddConsoleCommandPass implements CompilerPassInterface +{ + private $commandLoaderServiceId; + private $commandTag; + private $noPreloadTag; + private $privateTagName; + + public function __construct(string $commandLoaderServiceId = 'console.command_loader', string $commandTag = 'console.command', string $noPreloadTag = 'container.no_preload', string $privateTagName = 'container.private') + { + if (0 < \func_num_args()) { + trigger_deprecation('symfony/console', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); + } + + $this->commandLoaderServiceId = $commandLoaderServiceId; + $this->commandTag = $commandTag; + $this->noPreloadTag = $noPreloadTag; + $this->privateTagName = $privateTagName; + } + + public function process(ContainerBuilder $container) + { + $commandServices = $container->findTaggedServiceIds($this->commandTag, true); + $lazyCommandMap = []; + $lazyCommandRefs = []; + $serviceIds = []; + + foreach ($commandServices as $id => $tags) { + $definition = $container->getDefinition($id); + $definition->addTag($this->noPreloadTag); + $class = $container->getParameterBag()->resolveValue($definition->getClass()); + + if (isset($tags[0]['command'])) { + $aliases = $tags[0]['command']; + } else { + if (!$r = $container->getReflectionClass($class)) { + throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); + } + if (!$r->isSubclassOf(Command::class)) { + throw new InvalidArgumentException(sprintf('The service "%s" tagged "%s" must be a subclass of "%s".', $id, $this->commandTag, Command::class)); + } + $aliases = str_replace('%', '%%', $class::getDefaultName() ?? ''); + } + + $aliases = explode('|', $aliases ?? ''); + $commandName = array_shift($aliases); + + if ($isHidden = '' === $commandName) { + $commandName = array_shift($aliases); + } + + if (null === $commandName) { + if (!$definition->isPublic() || $definition->isPrivate() || $definition->hasTag($this->privateTagName)) { + $commandId = 'console.command.public_alias.'.$id; + $container->setAlias($commandId, $id)->setPublic(true); + $id = $commandId; + } + $serviceIds[] = $id; + + continue; + } + + $description = $tags[0]['description'] ?? null; + + unset($tags[0]); + $lazyCommandMap[$commandName] = $id; + $lazyCommandRefs[$id] = new TypedReference($id, $class); + + foreach ($aliases as $alias) { + $lazyCommandMap[$alias] = $id; + } + + foreach ($tags as $tag) { + if (isset($tag['command'])) { + $aliases[] = $tag['command']; + $lazyCommandMap[$tag['command']] = $id; + } + + $description = $description ?? $tag['description'] ?? null; + } + + $definition->addMethodCall('setName', [$commandName]); + + if ($aliases) { + $definition->addMethodCall('setAliases', [$aliases]); + } + + if ($isHidden) { + $definition->addMethodCall('setHidden', [true]); + } + + if (!$description) { + if (!$r = $container->getReflectionClass($class)) { + throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); + } + if (!$r->isSubclassOf(Command::class)) { + throw new InvalidArgumentException(sprintf('The service "%s" tagged "%s" must be a subclass of "%s".', $id, $this->commandTag, Command::class)); + } + $description = str_replace('%', '%%', $class::getDefaultDescription() ?? ''); + } + + if ($description) { + $definition->addMethodCall('setDescription', [$description]); + + $container->register('.'.$id.'.lazy', LazyCommand::class) + ->setArguments([$commandName, $aliases, $description, $isHidden, new ServiceClosureArgument($lazyCommandRefs[$id])]); + + $lazyCommandRefs[$id] = new Reference('.'.$id.'.lazy'); + } + } + + $container + ->register($this->commandLoaderServiceId, ContainerCommandLoader::class) + ->setPublic(true) + ->addTag($this->noPreloadTag) + ->setArguments([ServiceLocatorTagPass::register($container, $lazyCommandRefs), $lazyCommandMap]); + + $container->setParameter('console.command.ids', $serviceIds); + } +} diff --git a/vendor/symfony/console/Descriptor/ApplicationDescription.php b/vendor/symfony/console/Descriptor/ApplicationDescription.php new file mode 100644 index 0000000..2a3acc9 --- /dev/null +++ b/vendor/symfony/console/Descriptor/ApplicationDescription.php @@ -0,0 +1,143 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Descriptor; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\CommandNotFoundException; + +/** + * @author Jean-François Simon + * + * @internal + */ +class ApplicationDescription +{ + public const GLOBAL_NAMESPACE = '_global'; + + private $application; + private $namespace; + private $showHidden; + + /** + * @var array + */ + private $namespaces; + + /** + * @var array + */ + private $commands; + + /** + * @var array + */ + private $aliases; + + public function __construct(Application $application, string $namespace = null, bool $showHidden = false) + { + $this->application = $application; + $this->namespace = $namespace; + $this->showHidden = $showHidden; + } + + public function getNamespaces(): array + { + if (null === $this->namespaces) { + $this->inspectApplication(); + } + + return $this->namespaces; + } + + /** + * @return Command[] + */ + public function getCommands(): array + { + if (null === $this->commands) { + $this->inspectApplication(); + } + + return $this->commands; + } + + /** + * @throws CommandNotFoundException + */ + public function getCommand(string $name): Command + { + if (!isset($this->commands[$name]) && !isset($this->aliases[$name])) { + throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name)); + } + + return $this->commands[$name] ?? $this->aliases[$name]; + } + + private function inspectApplication() + { + $this->commands = []; + $this->namespaces = []; + + $all = $this->application->all($this->namespace ? $this->application->findNamespace($this->namespace) : null); + foreach ($this->sortCommands($all) as $namespace => $commands) { + $names = []; + + /** @var Command $command */ + foreach ($commands as $name => $command) { + if (!$command->getName() || (!$this->showHidden && $command->isHidden())) { + continue; + } + + if ($command->getName() === $name) { + $this->commands[$name] = $command; + } else { + $this->aliases[$name] = $command; + } + + $names[] = $name; + } + + $this->namespaces[$namespace] = ['id' => $namespace, 'commands' => $names]; + } + } + + private function sortCommands(array $commands): array + { + $namespacedCommands = []; + $globalCommands = []; + $sortedCommands = []; + foreach ($commands as $name => $command) { + $key = $this->application->extractNamespace($name, 1); + if (\in_array($key, ['', self::GLOBAL_NAMESPACE], true)) { + $globalCommands[$name] = $command; + } else { + $namespacedCommands[$key][$name] = $command; + } + } + + if ($globalCommands) { + ksort($globalCommands); + $sortedCommands[self::GLOBAL_NAMESPACE] = $globalCommands; + } + + if ($namespacedCommands) { + ksort($namespacedCommands, \SORT_STRING); + foreach ($namespacedCommands as $key => $commandsSet) { + ksort($commandsSet); + $sortedCommands[$key] = $commandsSet; + } + } + + return $sortedCommands; + } +} diff --git a/vendor/symfony/console/Descriptor/Descriptor.php b/vendor/symfony/console/Descriptor/Descriptor.php new file mode 100644 index 0000000..a364830 --- /dev/null +++ b/vendor/symfony/console/Descriptor/Descriptor.php @@ -0,0 +1,94 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Descriptor; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Jean-François Simon + * + * @internal + */ +abstract class Descriptor implements DescriptorInterface +{ + /** + * @var OutputInterface + */ + protected $output; + + /** + * {@inheritdoc} + */ + public function describe(OutputInterface $output, object $object, array $options = []) + { + $this->output = $output; + + switch (true) { + case $object instanceof InputArgument: + $this->describeInputArgument($object, $options); + break; + case $object instanceof InputOption: + $this->describeInputOption($object, $options); + break; + case $object instanceof InputDefinition: + $this->describeInputDefinition($object, $options); + break; + case $object instanceof Command: + $this->describeCommand($object, $options); + break; + case $object instanceof Application: + $this->describeApplication($object, $options); + break; + default: + throw new InvalidArgumentException(sprintf('Object of type "%s" is not describable.', get_debug_type($object))); + } + } + + /** + * Writes content to output. + */ + protected function write(string $content, bool $decorated = false) + { + $this->output->write($content, false, $decorated ? OutputInterface::OUTPUT_NORMAL : OutputInterface::OUTPUT_RAW); + } + + /** + * Describes an InputArgument instance. + */ + abstract protected function describeInputArgument(InputArgument $argument, array $options = []); + + /** + * Describes an InputOption instance. + */ + abstract protected function describeInputOption(InputOption $option, array $options = []); + + /** + * Describes an InputDefinition instance. + */ + abstract protected function describeInputDefinition(InputDefinition $definition, array $options = []); + + /** + * Describes a Command instance. + */ + abstract protected function describeCommand(Command $command, array $options = []); + + /** + * Describes an Application instance. + */ + abstract protected function describeApplication(Application $application, array $options = []); +} diff --git a/vendor/symfony/console/Descriptor/DescriptorInterface.php b/vendor/symfony/console/Descriptor/DescriptorInterface.php new file mode 100644 index 0000000..ebea303 --- /dev/null +++ b/vendor/symfony/console/Descriptor/DescriptorInterface.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Descriptor; + +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Descriptor interface. + * + * @author Jean-François Simon + */ +interface DescriptorInterface +{ + public function describe(OutputInterface $output, object $object, array $options = []); +} diff --git a/vendor/symfony/console/Descriptor/JsonDescriptor.php b/vendor/symfony/console/Descriptor/JsonDescriptor.php new file mode 100644 index 0000000..1d28659 --- /dev/null +++ b/vendor/symfony/console/Descriptor/JsonDescriptor.php @@ -0,0 +1,181 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Descriptor; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; + +/** + * JSON descriptor. + * + * @author Jean-François Simon + * + * @internal + */ +class JsonDescriptor extends Descriptor +{ + /** + * {@inheritdoc} + */ + protected function describeInputArgument(InputArgument $argument, array $options = []) + { + $this->writeData($this->getInputArgumentData($argument), $options); + } + + /** + * {@inheritdoc} + */ + protected function describeInputOption(InputOption $option, array $options = []) + { + $this->writeData($this->getInputOptionData($option), $options); + if ($option->isNegatable()) { + $this->writeData($this->getInputOptionData($option, true), $options); + } + } + + /** + * {@inheritdoc} + */ + protected function describeInputDefinition(InputDefinition $definition, array $options = []) + { + $this->writeData($this->getInputDefinitionData($definition), $options); + } + + /** + * {@inheritdoc} + */ + protected function describeCommand(Command $command, array $options = []) + { + $this->writeData($this->getCommandData($command, $options['short'] ?? false), $options); + } + + /** + * {@inheritdoc} + */ + protected function describeApplication(Application $application, array $options = []) + { + $describedNamespace = $options['namespace'] ?? null; + $description = new ApplicationDescription($application, $describedNamespace, true); + $commands = []; + + foreach ($description->getCommands() as $command) { + $commands[] = $this->getCommandData($command, $options['short'] ?? false); + } + + $data = []; + if ('UNKNOWN' !== $application->getName()) { + $data['application']['name'] = $application->getName(); + if ('UNKNOWN' !== $application->getVersion()) { + $data['application']['version'] = $application->getVersion(); + } + } + + $data['commands'] = $commands; + + if ($describedNamespace) { + $data['namespace'] = $describedNamespace; + } else { + $data['namespaces'] = array_values($description->getNamespaces()); + } + + $this->writeData($data, $options); + } + + /** + * Writes data as json. + */ + private function writeData(array $data, array $options) + { + $flags = $options['json_encoding'] ?? 0; + + $this->write(json_encode($data, $flags)); + } + + private function getInputArgumentData(InputArgument $argument): array + { + return [ + 'name' => $argument->getName(), + 'is_required' => $argument->isRequired(), + 'is_array' => $argument->isArray(), + 'description' => preg_replace('/\s*[\r\n]\s*/', ' ', $argument->getDescription()), + 'default' => \INF === $argument->getDefault() ? 'INF' : $argument->getDefault(), + ]; + } + + private function getInputOptionData(InputOption $option, bool $negated = false): array + { + return $negated ? [ + 'name' => '--no-'.$option->getName(), + 'shortcut' => '', + 'accept_value' => false, + 'is_value_required' => false, + 'is_multiple' => false, + 'description' => 'Negate the "--'.$option->getName().'" option', + 'default' => false, + ] : [ + 'name' => '--'.$option->getName(), + 'shortcut' => $option->getShortcut() ? '-'.str_replace('|', '|-', $option->getShortcut()) : '', + 'accept_value' => $option->acceptValue(), + 'is_value_required' => $option->isValueRequired(), + 'is_multiple' => $option->isArray(), + 'description' => preg_replace('/\s*[\r\n]\s*/', ' ', $option->getDescription()), + 'default' => \INF === $option->getDefault() ? 'INF' : $option->getDefault(), + ]; + } + + private function getInputDefinitionData(InputDefinition $definition): array + { + $inputArguments = []; + foreach ($definition->getArguments() as $name => $argument) { + $inputArguments[$name] = $this->getInputArgumentData($argument); + } + + $inputOptions = []; + foreach ($definition->getOptions() as $name => $option) { + $inputOptions[$name] = $this->getInputOptionData($option); + if ($option->isNegatable()) { + $inputOptions['no-'.$name] = $this->getInputOptionData($option, true); + } + } + + return ['arguments' => $inputArguments, 'options' => $inputOptions]; + } + + private function getCommandData(Command $command, bool $short = false): array + { + $data = [ + 'name' => $command->getName(), + 'description' => $command->getDescription(), + ]; + + if ($short) { + $data += [ + 'usage' => $command->getAliases(), + ]; + } else { + $command->mergeApplicationDefinition(false); + + $data += [ + 'usage' => array_merge([$command->getSynopsis()], $command->getUsages(), $command->getAliases()), + 'help' => $command->getProcessedHelp(), + 'definition' => $this->getInputDefinitionData($command->getDefinition()), + ]; + } + + $data['hidden'] = $command->isHidden(); + + return $data; + } +} diff --git a/vendor/symfony/console/Descriptor/MarkdownDescriptor.php b/vendor/symfony/console/Descriptor/MarkdownDescriptor.php new file mode 100644 index 0000000..21ceca6 --- /dev/null +++ b/vendor/symfony/console/Descriptor/MarkdownDescriptor.php @@ -0,0 +1,206 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Descriptor; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Helper\Helper; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Markdown descriptor. + * + * @author Jean-François Simon + * + * @internal + */ +class MarkdownDescriptor extends Descriptor +{ + /** + * {@inheritdoc} + */ + public function describe(OutputInterface $output, object $object, array $options = []) + { + $decorated = $output->isDecorated(); + $output->setDecorated(false); + + parent::describe($output, $object, $options); + + $output->setDecorated($decorated); + } + + /** + * {@inheritdoc} + */ + protected function write(string $content, bool $decorated = true) + { + parent::write($content, $decorated); + } + + /** + * {@inheritdoc} + */ + protected function describeInputArgument(InputArgument $argument, array $options = []) + { + $this->write( + '#### `'.($argument->getName() ?: '')."`\n\n" + .($argument->getDescription() ? preg_replace('/\s*[\r\n]\s*/', "\n", $argument->getDescription())."\n\n" : '') + .'* Is required: '.($argument->isRequired() ? 'yes' : 'no')."\n" + .'* Is array: '.($argument->isArray() ? 'yes' : 'no')."\n" + .'* Default: `'.str_replace("\n", '', var_export($argument->getDefault(), true)).'`' + ); + } + + /** + * {@inheritdoc} + */ + protected function describeInputOption(InputOption $option, array $options = []) + { + $name = '--'.$option->getName(); + if ($option->isNegatable()) { + $name .= '|--no-'.$option->getName(); + } + if ($option->getShortcut()) { + $name .= '|-'.str_replace('|', '|-', $option->getShortcut()).''; + } + + $this->write( + '#### `'.$name.'`'."\n\n" + .($option->getDescription() ? preg_replace('/\s*[\r\n]\s*/', "\n", $option->getDescription())."\n\n" : '') + .'* Accept value: '.($option->acceptValue() ? 'yes' : 'no')."\n" + .'* Is value required: '.($option->isValueRequired() ? 'yes' : 'no')."\n" + .'* Is multiple: '.($option->isArray() ? 'yes' : 'no')."\n" + .'* Is negatable: '.($option->isNegatable() ? 'yes' : 'no')."\n" + .'* Default: `'.str_replace("\n", '', var_export($option->getDefault(), true)).'`' + ); + } + + /** + * {@inheritdoc} + */ + protected function describeInputDefinition(InputDefinition $definition, array $options = []) + { + if ($showArguments = \count($definition->getArguments()) > 0) { + $this->write('### Arguments'); + foreach ($definition->getArguments() as $argument) { + $this->write("\n\n"); + if (null !== $describeInputArgument = $this->describeInputArgument($argument)) { + $this->write($describeInputArgument); + } + } + } + + if (\count($definition->getOptions()) > 0) { + if ($showArguments) { + $this->write("\n\n"); + } + + $this->write('### Options'); + foreach ($definition->getOptions() as $option) { + $this->write("\n\n"); + if (null !== $describeInputOption = $this->describeInputOption($option)) { + $this->write($describeInputOption); + } + } + } + } + + /** + * {@inheritdoc} + */ + protected function describeCommand(Command $command, array $options = []) + { + if ($options['short'] ?? false) { + $this->write( + '`'.$command->getName()."`\n" + .str_repeat('-', Helper::width($command->getName()) + 2)."\n\n" + .($command->getDescription() ? $command->getDescription()."\n\n" : '') + .'### Usage'."\n\n" + .array_reduce($command->getAliases(), function ($carry, $usage) { + return $carry.'* `'.$usage.'`'."\n"; + }) + ); + + return; + } + + $command->mergeApplicationDefinition(false); + + $this->write( + '`'.$command->getName()."`\n" + .str_repeat('-', Helper::width($command->getName()) + 2)."\n\n" + .($command->getDescription() ? $command->getDescription()."\n\n" : '') + .'### Usage'."\n\n" + .array_reduce(array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()), function ($carry, $usage) { + return $carry.'* `'.$usage.'`'."\n"; + }) + ); + + if ($help = $command->getProcessedHelp()) { + $this->write("\n"); + $this->write($help); + } + + $definition = $command->getDefinition(); + if ($definition->getOptions() || $definition->getArguments()) { + $this->write("\n\n"); + $this->describeInputDefinition($definition); + } + } + + /** + * {@inheritdoc} + */ + protected function describeApplication(Application $application, array $options = []) + { + $describedNamespace = $options['namespace'] ?? null; + $description = new ApplicationDescription($application, $describedNamespace); + $title = $this->getApplicationTitle($application); + + $this->write($title."\n".str_repeat('=', Helper::width($title))); + + foreach ($description->getNamespaces() as $namespace) { + if (ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) { + $this->write("\n\n"); + $this->write('**'.$namespace['id'].':**'); + } + + $this->write("\n\n"); + $this->write(implode("\n", array_map(function ($commandName) use ($description) { + return sprintf('* [`%s`](#%s)', $commandName, str_replace(':', '', $description->getCommand($commandName)->getName())); + }, $namespace['commands']))); + } + + foreach ($description->getCommands() as $command) { + $this->write("\n\n"); + if (null !== $describeCommand = $this->describeCommand($command, $options)) { + $this->write($describeCommand); + } + } + } + + private function getApplicationTitle(Application $application): string + { + if ('UNKNOWN' !== $application->getName()) { + if ('UNKNOWN' !== $application->getVersion()) { + return sprintf('%s %s', $application->getName(), $application->getVersion()); + } + + return $application->getName(); + } + + return 'Console Tool'; + } +} diff --git a/vendor/symfony/console/Descriptor/TextDescriptor.php b/vendor/symfony/console/Descriptor/TextDescriptor.php new file mode 100644 index 0000000..fbb140a --- /dev/null +++ b/vendor/symfony/console/Descriptor/TextDescriptor.php @@ -0,0 +1,341 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Descriptor; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Helper\Helper; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; + +/** + * Text descriptor. + * + * @author Jean-François Simon + * + * @internal + */ +class TextDescriptor extends Descriptor +{ + /** + * {@inheritdoc} + */ + protected function describeInputArgument(InputArgument $argument, array $options = []) + { + if (null !== $argument->getDefault() && (!\is_array($argument->getDefault()) || \count($argument->getDefault()))) { + $default = sprintf(' [default: %s]', $this->formatDefaultValue($argument->getDefault())); + } else { + $default = ''; + } + + $totalWidth = $options['total_width'] ?? Helper::width($argument->getName()); + $spacingWidth = $totalWidth - \strlen($argument->getName()); + + $this->writeText(sprintf(' %s %s%s%s', + $argument->getName(), + str_repeat(' ', $spacingWidth), + // + 4 = 2 spaces before , 2 spaces after + preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 4), $argument->getDescription()), + $default + ), $options); + } + + /** + * {@inheritdoc} + */ + protected function describeInputOption(InputOption $option, array $options = []) + { + if ($option->acceptValue() && null !== $option->getDefault() && (!\is_array($option->getDefault()) || \count($option->getDefault()))) { + $default = sprintf(' [default: %s]', $this->formatDefaultValue($option->getDefault())); + } else { + $default = ''; + } + + $value = ''; + if ($option->acceptValue()) { + $value = '='.strtoupper($option->getName()); + + if ($option->isValueOptional()) { + $value = '['.$value.']'; + } + } + + $totalWidth = $options['total_width'] ?? $this->calculateTotalWidthForOptions([$option]); + $synopsis = sprintf('%s%s', + $option->getShortcut() ? sprintf('-%s, ', $option->getShortcut()) : ' ', + sprintf($option->isNegatable() ? '--%1$s|--no-%1$s' : '--%1$s%2$s', $option->getName(), $value) + ); + + $spacingWidth = $totalWidth - Helper::width($synopsis); + + $this->writeText(sprintf(' %s %s%s%s%s', + $synopsis, + str_repeat(' ', $spacingWidth), + // + 4 = 2 spaces before , 2 spaces after + preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 4), $option->getDescription()), + $default, + $option->isArray() ? ' (multiple values allowed)' : '' + ), $options); + } + + /** + * {@inheritdoc} + */ + protected function describeInputDefinition(InputDefinition $definition, array $options = []) + { + $totalWidth = $this->calculateTotalWidthForOptions($definition->getOptions()); + foreach ($definition->getArguments() as $argument) { + $totalWidth = max($totalWidth, Helper::width($argument->getName())); + } + + if ($definition->getArguments()) { + $this->writeText('Arguments:', $options); + $this->writeText("\n"); + foreach ($definition->getArguments() as $argument) { + $this->describeInputArgument($argument, array_merge($options, ['total_width' => $totalWidth])); + $this->writeText("\n"); + } + } + + if ($definition->getArguments() && $definition->getOptions()) { + $this->writeText("\n"); + } + + if ($definition->getOptions()) { + $laterOptions = []; + + $this->writeText('Options:', $options); + foreach ($definition->getOptions() as $option) { + if (\strlen($option->getShortcut() ?? '') > 1) { + $laterOptions[] = $option; + continue; + } + $this->writeText("\n"); + $this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth])); + } + foreach ($laterOptions as $option) { + $this->writeText("\n"); + $this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth])); + } + } + } + + /** + * {@inheritdoc} + */ + protected function describeCommand(Command $command, array $options = []) + { + $command->mergeApplicationDefinition(false); + + if ($description = $command->getDescription()) { + $this->writeText('Description:', $options); + $this->writeText("\n"); + $this->writeText(' '.$description); + $this->writeText("\n\n"); + } + + $this->writeText('Usage:', $options); + foreach (array_merge([$command->getSynopsis(true)], $command->getAliases(), $command->getUsages()) as $usage) { + $this->writeText("\n"); + $this->writeText(' '.OutputFormatter::escape($usage), $options); + } + $this->writeText("\n"); + + $definition = $command->getDefinition(); + if ($definition->getOptions() || $definition->getArguments()) { + $this->writeText("\n"); + $this->describeInputDefinition($definition, $options); + $this->writeText("\n"); + } + + $help = $command->getProcessedHelp(); + if ($help && $help !== $description) { + $this->writeText("\n"); + $this->writeText('Help:', $options); + $this->writeText("\n"); + $this->writeText(' '.str_replace("\n", "\n ", $help), $options); + $this->writeText("\n"); + } + } + + /** + * {@inheritdoc} + */ + protected function describeApplication(Application $application, array $options = []) + { + $describedNamespace = $options['namespace'] ?? null; + $description = new ApplicationDescription($application, $describedNamespace); + + if (isset($options['raw_text']) && $options['raw_text']) { + $width = $this->getColumnWidth($description->getCommands()); + + foreach ($description->getCommands() as $command) { + $this->writeText(sprintf("%-{$width}s %s", $command->getName(), $command->getDescription()), $options); + $this->writeText("\n"); + } + } else { + if ('' != $help = $application->getHelp()) { + $this->writeText("$help\n\n", $options); + } + + $this->writeText("Usage:\n", $options); + $this->writeText(" command [options] [arguments]\n\n", $options); + + $this->describeInputDefinition(new InputDefinition($application->getDefinition()->getOptions()), $options); + + $this->writeText("\n"); + $this->writeText("\n"); + + $commands = $description->getCommands(); + $namespaces = $description->getNamespaces(); + if ($describedNamespace && $namespaces) { + // make sure all alias commands are included when describing a specific namespace + $describedNamespaceInfo = reset($namespaces); + foreach ($describedNamespaceInfo['commands'] as $name) { + $commands[$name] = $description->getCommand($name); + } + } + + // calculate max. width based on available commands per namespace + $width = $this->getColumnWidth(array_merge(...array_values(array_map(function ($namespace) use ($commands) { + return array_intersect($namespace['commands'], array_keys($commands)); + }, array_values($namespaces))))); + + if ($describedNamespace) { + $this->writeText(sprintf('Available commands for the "%s" namespace:', $describedNamespace), $options); + } else { + $this->writeText('Available commands:', $options); + } + + foreach ($namespaces as $namespace) { + $namespace['commands'] = array_filter($namespace['commands'], function ($name) use ($commands) { + return isset($commands[$name]); + }); + + if (!$namespace['commands']) { + continue; + } + + if (!$describedNamespace && ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) { + $this->writeText("\n"); + $this->writeText(' '.$namespace['id'].'', $options); + } + + foreach ($namespace['commands'] as $name) { + $this->writeText("\n"); + $spacingWidth = $width - Helper::width($name); + $command = $commands[$name]; + $commandAliases = $name === $command->getName() ? $this->getCommandAliasesText($command) : ''; + $this->writeText(sprintf(' %s%s%s', $name, str_repeat(' ', $spacingWidth), $commandAliases.$command->getDescription()), $options); + } + } + + $this->writeText("\n"); + } + } + + /** + * {@inheritdoc} + */ + private function writeText(string $content, array $options = []) + { + $this->write( + isset($options['raw_text']) && $options['raw_text'] ? strip_tags($content) : $content, + isset($options['raw_output']) ? !$options['raw_output'] : true + ); + } + + /** + * Formats command aliases to show them in the command description. + */ + private function getCommandAliasesText(Command $command): string + { + $text = ''; + $aliases = $command->getAliases(); + + if ($aliases) { + $text = '['.implode('|', $aliases).'] '; + } + + return $text; + } + + /** + * Formats input option/argument default value. + * + * @param mixed $default + */ + private function formatDefaultValue($default): string + { + if (\INF === $default) { + return 'INF'; + } + + if (\is_string($default)) { + $default = OutputFormatter::escape($default); + } elseif (\is_array($default)) { + foreach ($default as $key => $value) { + if (\is_string($value)) { + $default[$key] = OutputFormatter::escape($value); + } + } + } + + return str_replace('\\\\', '\\', json_encode($default, \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE)); + } + + /** + * @param array $commands + */ + private function getColumnWidth(array $commands): int + { + $widths = []; + + foreach ($commands as $command) { + if ($command instanceof Command) { + $widths[] = Helper::width($command->getName()); + foreach ($command->getAliases() as $alias) { + $widths[] = Helper::width($alias); + } + } else { + $widths[] = Helper::width($command); + } + } + + return $widths ? max($widths) + 2 : 0; + } + + /** + * @param InputOption[] $options + */ + private function calculateTotalWidthForOptions(array $options): int + { + $totalWidth = 0; + foreach ($options as $option) { + // "-" + shortcut + ", --" + name + $nameLength = 1 + max(Helper::width($option->getShortcut()), 1) + 4 + Helper::width($option->getName()); + if ($option->isNegatable()) { + $nameLength += 6 + Helper::width($option->getName()); // |--no- + name + } elseif ($option->acceptValue()) { + $valueLength = 1 + Helper::width($option->getName()); // = + value + $valueLength += $option->isValueOptional() ? 2 : 0; // [ + ] + + $nameLength += $valueLength; + } + $totalWidth = max($totalWidth, $nameLength); + } + + return $totalWidth; + } +} diff --git a/vendor/symfony/console/Descriptor/XmlDescriptor.php b/vendor/symfony/console/Descriptor/XmlDescriptor.php new file mode 100644 index 0000000..4f7cd8b --- /dev/null +++ b/vendor/symfony/console/Descriptor/XmlDescriptor.php @@ -0,0 +1,247 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Descriptor; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; + +/** + * XML descriptor. + * + * @author Jean-François Simon + * + * @internal + */ +class XmlDescriptor extends Descriptor +{ + public function getInputDefinitionDocument(InputDefinition $definition): \DOMDocument + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + $dom->appendChild($definitionXML = $dom->createElement('definition')); + + $definitionXML->appendChild($argumentsXML = $dom->createElement('arguments')); + foreach ($definition->getArguments() as $argument) { + $this->appendDocument($argumentsXML, $this->getInputArgumentDocument($argument)); + } + + $definitionXML->appendChild($optionsXML = $dom->createElement('options')); + foreach ($definition->getOptions() as $option) { + $this->appendDocument($optionsXML, $this->getInputOptionDocument($option)); + } + + return $dom; + } + + public function getCommandDocument(Command $command, bool $short = false): \DOMDocument + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + $dom->appendChild($commandXML = $dom->createElement('command')); + + $commandXML->setAttribute('id', $command->getName()); + $commandXML->setAttribute('name', $command->getName()); + $commandXML->setAttribute('hidden', $command->isHidden() ? 1 : 0); + + $commandXML->appendChild($usagesXML = $dom->createElement('usages')); + + $commandXML->appendChild($descriptionXML = $dom->createElement('description')); + $descriptionXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getDescription()))); + + if ($short) { + foreach ($command->getAliases() as $usage) { + $usagesXML->appendChild($dom->createElement('usage', $usage)); + } + } else { + $command->mergeApplicationDefinition(false); + + foreach (array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()) as $usage) { + $usagesXML->appendChild($dom->createElement('usage', $usage)); + } + + $commandXML->appendChild($helpXML = $dom->createElement('help')); + $helpXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getProcessedHelp()))); + + $definitionXML = $this->getInputDefinitionDocument($command->getDefinition()); + $this->appendDocument($commandXML, $definitionXML->getElementsByTagName('definition')->item(0)); + } + + return $dom; + } + + public function getApplicationDocument(Application $application, string $namespace = null, bool $short = false): \DOMDocument + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + $dom->appendChild($rootXml = $dom->createElement('symfony')); + + if ('UNKNOWN' !== $application->getName()) { + $rootXml->setAttribute('name', $application->getName()); + if ('UNKNOWN' !== $application->getVersion()) { + $rootXml->setAttribute('version', $application->getVersion()); + } + } + + $rootXml->appendChild($commandsXML = $dom->createElement('commands')); + + $description = new ApplicationDescription($application, $namespace, true); + + if ($namespace) { + $commandsXML->setAttribute('namespace', $namespace); + } + + foreach ($description->getCommands() as $command) { + $this->appendDocument($commandsXML, $this->getCommandDocument($command, $short)); + } + + if (!$namespace) { + $rootXml->appendChild($namespacesXML = $dom->createElement('namespaces')); + + foreach ($description->getNamespaces() as $namespaceDescription) { + $namespacesXML->appendChild($namespaceArrayXML = $dom->createElement('namespace')); + $namespaceArrayXML->setAttribute('id', $namespaceDescription['id']); + + foreach ($namespaceDescription['commands'] as $name) { + $namespaceArrayXML->appendChild($commandXML = $dom->createElement('command')); + $commandXML->appendChild($dom->createTextNode($name)); + } + } + } + + return $dom; + } + + /** + * {@inheritdoc} + */ + protected function describeInputArgument(InputArgument $argument, array $options = []) + { + $this->writeDocument($this->getInputArgumentDocument($argument)); + } + + /** + * {@inheritdoc} + */ + protected function describeInputOption(InputOption $option, array $options = []) + { + $this->writeDocument($this->getInputOptionDocument($option)); + } + + /** + * {@inheritdoc} + */ + protected function describeInputDefinition(InputDefinition $definition, array $options = []) + { + $this->writeDocument($this->getInputDefinitionDocument($definition)); + } + + /** + * {@inheritdoc} + */ + protected function describeCommand(Command $command, array $options = []) + { + $this->writeDocument($this->getCommandDocument($command, $options['short'] ?? false)); + } + + /** + * {@inheritdoc} + */ + protected function describeApplication(Application $application, array $options = []) + { + $this->writeDocument($this->getApplicationDocument($application, $options['namespace'] ?? null, $options['short'] ?? false)); + } + + /** + * Appends document children to parent node. + */ + private function appendDocument(\DOMNode $parentNode, \DOMNode $importedParent) + { + foreach ($importedParent->childNodes as $childNode) { + $parentNode->appendChild($parentNode->ownerDocument->importNode($childNode, true)); + } + } + + /** + * Writes DOM document. + */ + private function writeDocument(\DOMDocument $dom) + { + $dom->formatOutput = true; + $this->write($dom->saveXML()); + } + + private function getInputArgumentDocument(InputArgument $argument): \DOMDocument + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + + $dom->appendChild($objectXML = $dom->createElement('argument')); + $objectXML->setAttribute('name', $argument->getName()); + $objectXML->setAttribute('is_required', $argument->isRequired() ? 1 : 0); + $objectXML->setAttribute('is_array', $argument->isArray() ? 1 : 0); + $objectXML->appendChild($descriptionXML = $dom->createElement('description')); + $descriptionXML->appendChild($dom->createTextNode($argument->getDescription())); + + $objectXML->appendChild($defaultsXML = $dom->createElement('defaults')); + $defaults = \is_array($argument->getDefault()) ? $argument->getDefault() : (\is_bool($argument->getDefault()) ? [var_export($argument->getDefault(), true)] : ($argument->getDefault() ? [$argument->getDefault()] : [])); + foreach ($defaults as $default) { + $defaultsXML->appendChild($defaultXML = $dom->createElement('default')); + $defaultXML->appendChild($dom->createTextNode($default)); + } + + return $dom; + } + + private function getInputOptionDocument(InputOption $option): \DOMDocument + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + + $dom->appendChild($objectXML = $dom->createElement('option')); + $objectXML->setAttribute('name', '--'.$option->getName()); + $pos = strpos($option->getShortcut() ?? '', '|'); + if (false !== $pos) { + $objectXML->setAttribute('shortcut', '-'.substr($option->getShortcut(), 0, $pos)); + $objectXML->setAttribute('shortcuts', '-'.str_replace('|', '|-', $option->getShortcut())); + } else { + $objectXML->setAttribute('shortcut', $option->getShortcut() ? '-'.$option->getShortcut() : ''); + } + $objectXML->setAttribute('accept_value', $option->acceptValue() ? 1 : 0); + $objectXML->setAttribute('is_value_required', $option->isValueRequired() ? 1 : 0); + $objectXML->setAttribute('is_multiple', $option->isArray() ? 1 : 0); + $objectXML->appendChild($descriptionXML = $dom->createElement('description')); + $descriptionXML->appendChild($dom->createTextNode($option->getDescription())); + + if ($option->acceptValue()) { + $defaults = \is_array($option->getDefault()) ? $option->getDefault() : (\is_bool($option->getDefault()) ? [var_export($option->getDefault(), true)] : ($option->getDefault() ? [$option->getDefault()] : [])); + $objectXML->appendChild($defaultsXML = $dom->createElement('defaults')); + + if (!empty($defaults)) { + foreach ($defaults as $default) { + $defaultsXML->appendChild($defaultXML = $dom->createElement('default')); + $defaultXML->appendChild($dom->createTextNode($default)); + } + } + } + + if ($option->isNegatable()) { + $dom->appendChild($objectXML = $dom->createElement('option')); + $objectXML->setAttribute('name', '--no-'.$option->getName()); + $objectXML->setAttribute('shortcut', ''); + $objectXML->setAttribute('accept_value', 0); + $objectXML->setAttribute('is_value_required', 0); + $objectXML->setAttribute('is_multiple', 0); + $objectXML->appendChild($descriptionXML = $dom->createElement('description')); + $descriptionXML->appendChild($dom->createTextNode('Negate the "--'.$option->getName().'" option')); + } + + return $dom; + } +} diff --git a/vendor/symfony/console/Event/ConsoleCommandEvent.php b/vendor/symfony/console/Event/ConsoleCommandEvent.php new file mode 100644 index 0000000..08bd18f --- /dev/null +++ b/vendor/symfony/console/Event/ConsoleCommandEvent.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Event; + +/** + * Allows to do things before the command is executed, like skipping the command or changing the input. + * + * @author Fabien Potencier + */ +final class ConsoleCommandEvent extends ConsoleEvent +{ + /** + * The return code for skipped commands, this will also be passed into the terminate event. + */ + public const RETURN_CODE_DISABLED = 113; + + /** + * Indicates if the command should be run or skipped. + */ + private $commandShouldRun = true; + + /** + * Disables the command, so it won't be run. + */ + public function disableCommand(): bool + { + return $this->commandShouldRun = false; + } + + public function enableCommand(): bool + { + return $this->commandShouldRun = true; + } + + /** + * Returns true if the command is runnable, false otherwise. + */ + public function commandShouldRun(): bool + { + return $this->commandShouldRun; + } +} diff --git a/vendor/symfony/console/Event/ConsoleErrorEvent.php b/vendor/symfony/console/Event/ConsoleErrorEvent.php new file mode 100644 index 0000000..57d9b38 --- /dev/null +++ b/vendor/symfony/console/Event/ConsoleErrorEvent.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Event; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Allows to handle throwables thrown while running a command. + * + * @author Wouter de Jong + */ +final class ConsoleErrorEvent extends ConsoleEvent +{ + private $error; + private $exitCode; + + public function __construct(InputInterface $input, OutputInterface $output, \Throwable $error, Command $command = null) + { + parent::__construct($command, $input, $output); + + $this->error = $error; + } + + public function getError(): \Throwable + { + return $this->error; + } + + public function setError(\Throwable $error): void + { + $this->error = $error; + } + + public function setExitCode(int $exitCode): void + { + $this->exitCode = $exitCode; + + $r = new \ReflectionProperty($this->error, 'code'); + $r->setAccessible(true); + $r->setValue($this->error, $this->exitCode); + } + + public function getExitCode(): int + { + return $this->exitCode ?? (\is_int($this->error->getCode()) && 0 !== $this->error->getCode() ? $this->error->getCode() : 1); + } +} diff --git a/vendor/symfony/console/Event/ConsoleEvent.php b/vendor/symfony/console/Event/ConsoleEvent.php new file mode 100644 index 0000000..be7937d --- /dev/null +++ b/vendor/symfony/console/Event/ConsoleEvent.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 Symfony\Component\Console\Event; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Contracts\EventDispatcher\Event; + +/** + * Allows to inspect input and output of a command. + * + * @author Francesco Levorato + */ +class ConsoleEvent extends Event +{ + protected $command; + + private $input; + private $output; + + public function __construct(?Command $command, InputInterface $input, OutputInterface $output) + { + $this->command = $command; + $this->input = $input; + $this->output = $output; + } + + /** + * Gets the command that is executed. + * + * @return Command|null + */ + public function getCommand() + { + return $this->command; + } + + /** + * Gets the input instance. + * + * @return InputInterface + */ + public function getInput() + { + return $this->input; + } + + /** + * Gets the output instance. + * + * @return OutputInterface + */ + public function getOutput() + { + return $this->output; + } +} diff --git a/vendor/symfony/console/Event/ConsoleSignalEvent.php b/vendor/symfony/console/Event/ConsoleSignalEvent.php new file mode 100644 index 0000000..ef13ed2 --- /dev/null +++ b/vendor/symfony/console/Event/ConsoleSignalEvent.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Event; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author marie + */ +final class ConsoleSignalEvent extends ConsoleEvent +{ + private $handlingSignal; + + public function __construct(Command $command, InputInterface $input, OutputInterface $output, int $handlingSignal) + { + parent::__construct($command, $input, $output); + $this->handlingSignal = $handlingSignal; + } + + public function getHandlingSignal(): int + { + return $this->handlingSignal; + } +} diff --git a/vendor/symfony/console/Event/ConsoleTerminateEvent.php b/vendor/symfony/console/Event/ConsoleTerminateEvent.php new file mode 100644 index 0000000..190038d --- /dev/null +++ b/vendor/symfony/console/Event/ConsoleTerminateEvent.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Event; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Allows to manipulate the exit code of a command after its execution. + * + * @author Francesco Levorato + */ +final class ConsoleTerminateEvent extends ConsoleEvent +{ + private $exitCode; + + public function __construct(Command $command, InputInterface $input, OutputInterface $output, int $exitCode) + { + parent::__construct($command, $input, $output); + + $this->setExitCode($exitCode); + } + + public function setExitCode(int $exitCode): void + { + $this->exitCode = $exitCode; + } + + public function getExitCode(): int + { + return $this->exitCode; + } +} diff --git a/vendor/symfony/console/EventListener/ErrorListener.php b/vendor/symfony/console/EventListener/ErrorListener.php new file mode 100644 index 0000000..897d985 --- /dev/null +++ b/vendor/symfony/console/EventListener/ErrorListener.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\EventListener; + +use Psr\Log\LoggerInterface; +use Symfony\Component\Console\ConsoleEvents; +use Symfony\Component\Console\Event\ConsoleErrorEvent; +use Symfony\Component\Console\Event\ConsoleEvent; +use Symfony\Component\Console\Event\ConsoleTerminateEvent; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * @author James Halsall + * @author Robin Chalas + */ +class ErrorListener implements EventSubscriberInterface +{ + private $logger; + + public function __construct(LoggerInterface $logger = null) + { + $this->logger = $logger; + } + + public function onConsoleError(ConsoleErrorEvent $event) + { + if (null === $this->logger) { + return; + } + + $error = $event->getError(); + + if (!$inputString = $this->getInputString($event)) { + $this->logger->critical('An error occurred while using the console. Message: "{message}"', ['exception' => $error, 'message' => $error->getMessage()]); + + return; + } + + $this->logger->critical('Error thrown while running command "{command}". Message: "{message}"', ['exception' => $error, 'command' => $inputString, 'message' => $error->getMessage()]); + } + + public function onConsoleTerminate(ConsoleTerminateEvent $event) + { + if (null === $this->logger) { + return; + } + + $exitCode = $event->getExitCode(); + + if (0 === $exitCode) { + return; + } + + if (!$inputString = $this->getInputString($event)) { + $this->logger->debug('The console exited with code "{code}"', ['code' => $exitCode]); + + return; + } + + $this->logger->debug('Command "{command}" exited with code "{code}"', ['command' => $inputString, 'code' => $exitCode]); + } + + public static function getSubscribedEvents() + { + return [ + ConsoleEvents::ERROR => ['onConsoleError', -128], + ConsoleEvents::TERMINATE => ['onConsoleTerminate', -128], + ]; + } + + private static function getInputString(ConsoleEvent $event): ?string + { + $commandName = $event->getCommand() ? $event->getCommand()->getName() : null; + $input = $event->getInput(); + + if (method_exists($input, '__toString')) { + if ($commandName) { + return str_replace(["'$commandName'", "\"$commandName\""], $commandName, (string) $input); + } + + return (string) $input; + } + + return $commandName; + } +} diff --git a/vendor/symfony/console/Exception/CommandNotFoundException.php b/vendor/symfony/console/Exception/CommandNotFoundException.php new file mode 100644 index 0000000..910ae19 --- /dev/null +++ b/vendor/symfony/console/Exception/CommandNotFoundException.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Exception; + +/** + * Represents an incorrect command name typed in the console. + * + * @author Jérôme Tamarelle + */ +class CommandNotFoundException extends \InvalidArgumentException implements ExceptionInterface +{ + private $alternatives; + + /** + * @param string $message Exception message to throw + * @param string[] $alternatives List of similar defined names + * @param int $code Exception code + * @param \Throwable|null $previous Previous exception used for the exception chaining + */ + public function __construct(string $message, array $alternatives = [], int $code = 0, \Throwable $previous = null) + { + parent::__construct($message, $code, $previous); + + $this->alternatives = $alternatives; + } + + /** + * @return string[] + */ + public function getAlternatives() + { + return $this->alternatives; + } +} diff --git a/vendor/symfony/console/Exception/ExceptionInterface.php b/vendor/symfony/console/Exception/ExceptionInterface.php new file mode 100644 index 0000000..1624e13 --- /dev/null +++ b/vendor/symfony/console/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Exception; + +/** + * ExceptionInterface. + * + * @author Jérôme Tamarelle + */ +interface ExceptionInterface extends \Throwable +{ +} diff --git a/vendor/symfony/console/Exception/InvalidArgumentException.php b/vendor/symfony/console/Exception/InvalidArgumentException.php new file mode 100644 index 0000000..07cc0b6 --- /dev/null +++ b/vendor/symfony/console/Exception/InvalidArgumentException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Exception; + +/** + * @author Jérôme Tamarelle + */ +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/console/Exception/InvalidOptionException.php b/vendor/symfony/console/Exception/InvalidOptionException.php new file mode 100644 index 0000000..5cf6279 --- /dev/null +++ b/vendor/symfony/console/Exception/InvalidOptionException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Exception; + +/** + * Represents an incorrect option name or value typed in the console. + * + * @author Jérôme Tamarelle + */ +class InvalidOptionException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/console/Exception/LogicException.php b/vendor/symfony/console/Exception/LogicException.php new file mode 100644 index 0000000..fc37b8d --- /dev/null +++ b/vendor/symfony/console/Exception/LogicException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Exception; + +/** + * @author Jérôme Tamarelle + */ +class LogicException extends \LogicException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/console/Exception/MissingInputException.php b/vendor/symfony/console/Exception/MissingInputException.php new file mode 100644 index 0000000..04f02ad --- /dev/null +++ b/vendor/symfony/console/Exception/MissingInputException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Exception; + +/** + * Represents failure to read input from stdin. + * + * @author Gabriel Ostrolucký + */ +class MissingInputException extends RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/console/Exception/NamespaceNotFoundException.php b/vendor/symfony/console/Exception/NamespaceNotFoundException.php new file mode 100644 index 0000000..dd16e45 --- /dev/null +++ b/vendor/symfony/console/Exception/NamespaceNotFoundException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Exception; + +/** + * Represents an incorrect namespace typed in the console. + * + * @author Pierre du Plessis + */ +class NamespaceNotFoundException extends CommandNotFoundException +{ +} diff --git a/vendor/symfony/console/Exception/RuntimeException.php b/vendor/symfony/console/Exception/RuntimeException.php new file mode 100644 index 0000000..51d7d80 --- /dev/null +++ b/vendor/symfony/console/Exception/RuntimeException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Exception; + +/** + * @author Jérôme Tamarelle + */ +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/console/Formatter/NullOutputFormatter.php b/vendor/symfony/console/Formatter/NullOutputFormatter.php new file mode 100644 index 0000000..d770e14 --- /dev/null +++ b/vendor/symfony/console/Formatter/NullOutputFormatter.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Formatter; + +/** + * @author Tien Xuan Vo + */ +final class NullOutputFormatter implements OutputFormatterInterface +{ + private $style; + + /** + * {@inheritdoc} + */ + public function format(?string $message): ?string + { + return null; + } + + /** + * {@inheritdoc} + */ + public function getStyle(string $name): OutputFormatterStyleInterface + { + // to comply with the interface we must return a OutputFormatterStyleInterface + return $this->style ?? $this->style = new NullOutputFormatterStyle(); + } + + /** + * {@inheritdoc} + */ + public function hasStyle(string $name): bool + { + return false; + } + + /** + * {@inheritdoc} + */ + public function isDecorated(): bool + { + return false; + } + + /** + * {@inheritdoc} + */ + public function setDecorated(bool $decorated): void + { + // do nothing + } + + /** + * {@inheritdoc} + */ + public function setStyle(string $name, OutputFormatterStyleInterface $style): void + { + // do nothing + } +} diff --git a/vendor/symfony/console/Formatter/NullOutputFormatterStyle.php b/vendor/symfony/console/Formatter/NullOutputFormatterStyle.php new file mode 100644 index 0000000..9232510 --- /dev/null +++ b/vendor/symfony/console/Formatter/NullOutputFormatterStyle.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 Symfony\Component\Console\Formatter; + +/** + * @author Tien Xuan Vo + */ +final class NullOutputFormatterStyle implements OutputFormatterStyleInterface +{ + /** + * {@inheritdoc} + */ + public function apply(string $text): string + { + return $text; + } + + /** + * {@inheritdoc} + */ + public function setBackground(string $color = null): void + { + // do nothing + } + + /** + * {@inheritdoc} + */ + public function setForeground(string $color = null): void + { + // do nothing + } + + /** + * {@inheritdoc} + */ + public function setOption(string $option): void + { + // do nothing + } + + /** + * {@inheritdoc} + */ + public function setOptions(array $options): void + { + // do nothing + } + + /** + * {@inheritdoc} + */ + public function unsetOption(string $option): void + { + // do nothing + } +} diff --git a/vendor/symfony/console/Formatter/OutputFormatter.php b/vendor/symfony/console/Formatter/OutputFormatter.php new file mode 100644 index 0000000..603e5dc --- /dev/null +++ b/vendor/symfony/console/Formatter/OutputFormatter.php @@ -0,0 +1,285 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Formatter; + +use Symfony\Component\Console\Exception\InvalidArgumentException; + +/** + * Formatter class for console output. + * + * @author Konstantin Kudryashov + * @author Roland Franssen + */ +class OutputFormatter implements WrappableOutputFormatterInterface +{ + private $decorated; + private $styles = []; + private $styleStack; + + public function __clone() + { + $this->styleStack = clone $this->styleStack; + foreach ($this->styles as $key => $value) { + $this->styles[$key] = clone $value; + } + } + + /** + * Escapes "<" and ">" special chars in given text. + * + * @return string + */ + public static function escape(string $text) + { + $text = preg_replace('/([^\\\\]|^)([<>])/', '$1\\\\$2', $text); + + return self::escapeTrailingBackslash($text); + } + + /** + * Escapes trailing "\" in given text. + * + * @internal + */ + public static function escapeTrailingBackslash(string $text): string + { + if (str_ends_with($text, '\\')) { + $len = \strlen($text); + $text = rtrim($text, '\\'); + $text = str_replace("\0", '', $text); + $text .= str_repeat("\0", $len - \strlen($text)); + } + + return $text; + } + + /** + * Initializes console output formatter. + * + * @param OutputFormatterStyleInterface[] $styles Array of "name => FormatterStyle" instances + */ + public function __construct(bool $decorated = false, array $styles = []) + { + $this->decorated = $decorated; + + $this->setStyle('error', new OutputFormatterStyle('white', 'red')); + $this->setStyle('info', new OutputFormatterStyle('green')); + $this->setStyle('comment', new OutputFormatterStyle('yellow')); + $this->setStyle('question', new OutputFormatterStyle('black', 'cyan')); + + foreach ($styles as $name => $style) { + $this->setStyle($name, $style); + } + + $this->styleStack = new OutputFormatterStyleStack(); + } + + /** + * {@inheritdoc} + */ + public function setDecorated(bool $decorated) + { + $this->decorated = $decorated; + } + + /** + * {@inheritdoc} + */ + public function isDecorated() + { + return $this->decorated; + } + + /** + * {@inheritdoc} + */ + public function setStyle(string $name, OutputFormatterStyleInterface $style) + { + $this->styles[strtolower($name)] = $style; + } + + /** + * {@inheritdoc} + */ + public function hasStyle(string $name) + { + return isset($this->styles[strtolower($name)]); + } + + /** + * {@inheritdoc} + */ + public function getStyle(string $name) + { + if (!$this->hasStyle($name)) { + throw new InvalidArgumentException(sprintf('Undefined style: "%s".', $name)); + } + + return $this->styles[strtolower($name)]; + } + + /** + * {@inheritdoc} + */ + public function format(?string $message) + { + return $this->formatAndWrap($message, 0); + } + + /** + * {@inheritdoc} + */ + public function formatAndWrap(?string $message, int $width) + { + if (null === $message) { + return ''; + } + + $offset = 0; + $output = ''; + $openTagRegex = '[a-z](?:[^\\\\<>]*+ | \\\\.)*'; + $closeTagRegex = '[a-z][^<>]*+'; + $currentLineLength = 0; + preg_match_all("#<(($openTagRegex) | /($closeTagRegex)?)>#ix", $message, $matches, \PREG_OFFSET_CAPTURE); + foreach ($matches[0] as $i => $match) { + $pos = $match[1]; + $text = $match[0]; + + if (0 != $pos && '\\' == $message[$pos - 1]) { + continue; + } + + // add the text up to the next tag + $output .= $this->applyCurrentStyle(substr($message, $offset, $pos - $offset), $output, $width, $currentLineLength); + $offset = $pos + \strlen($text); + + // opening tag? + if ($open = '/' != $text[1]) { + $tag = $matches[1][$i][0]; + } else { + $tag = $matches[3][$i][0] ?? ''; + } + + if (!$open && !$tag) { + // + $this->styleStack->pop(); + } elseif (null === $style = $this->createStyleFromString($tag)) { + $output .= $this->applyCurrentStyle($text, $output, $width, $currentLineLength); + } elseif ($open) { + $this->styleStack->push($style); + } else { + $this->styleStack->pop($style); + } + } + + $output .= $this->applyCurrentStyle(substr($message, $offset), $output, $width, $currentLineLength); + + return strtr($output, ["\0" => '\\', '\\<' => '<', '\\>' => '>']); + } + + /** + * @return OutputFormatterStyleStack + */ + public function getStyleStack() + { + return $this->styleStack; + } + + /** + * Tries to create new style instance from string. + */ + private function createStyleFromString(string $string): ?OutputFormatterStyleInterface + { + if (isset($this->styles[$string])) { + return $this->styles[$string]; + } + + if (!preg_match_all('/([^=]+)=([^;]+)(;|$)/', $string, $matches, \PREG_SET_ORDER)) { + return null; + } + + $style = new OutputFormatterStyle(); + foreach ($matches as $match) { + array_shift($match); + $match[0] = strtolower($match[0]); + + if ('fg' == $match[0]) { + $style->setForeground(strtolower($match[1])); + } elseif ('bg' == $match[0]) { + $style->setBackground(strtolower($match[1])); + } elseif ('href' === $match[0]) { + $url = preg_replace('{\\\\([<>])}', '$1', $match[1]); + $style->setHref($url); + } elseif ('options' === $match[0]) { + preg_match_all('([^,;]+)', strtolower($match[1]), $options); + $options = array_shift($options); + foreach ($options as $option) { + $style->setOption($option); + } + } else { + return null; + } + } + + return $style; + } + + /** + * Applies current style from stack to text, if must be applied. + */ + private function applyCurrentStyle(string $text, string $current, int $width, int &$currentLineLength): string + { + if ('' === $text) { + return ''; + } + + if (!$width) { + return $this->isDecorated() ? $this->styleStack->getCurrent()->apply($text) : $text; + } + + if (!$currentLineLength && '' !== $current) { + $text = ltrim($text); + } + + if ($currentLineLength) { + $prefix = substr($text, 0, $i = $width - $currentLineLength)."\n"; + $text = substr($text, $i); + } else { + $prefix = ''; + } + + preg_match('~(\\n)$~', $text, $matches); + $text = $prefix.preg_replace('~([^\\n]{'.$width.'})\\ *~', "\$1\n", $text); + $text = rtrim($text, "\n").($matches[1] ?? ''); + + if (!$currentLineLength && '' !== $current && "\n" !== substr($current, -1)) { + $text = "\n".$text; + } + + $lines = explode("\n", $text); + + foreach ($lines as $line) { + $currentLineLength += \strlen($line); + if ($width <= $currentLineLength) { + $currentLineLength = 0; + } + } + + if ($this->isDecorated()) { + foreach ($lines as $i => $line) { + $lines[$i] = $this->styleStack->getCurrent()->apply($line); + } + } + + return implode("\n", $lines); + } +} diff --git a/vendor/symfony/console/Formatter/OutputFormatterInterface.php b/vendor/symfony/console/Formatter/OutputFormatterInterface.php new file mode 100644 index 0000000..0b5f839 --- /dev/null +++ b/vendor/symfony/console/Formatter/OutputFormatterInterface.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Formatter; + +/** + * Formatter interface for console output. + * + * @author Konstantin Kudryashov + */ +interface OutputFormatterInterface +{ + /** + * Sets the decorated flag. + */ + public function setDecorated(bool $decorated); + + /** + * Whether the output will decorate messages. + * + * @return bool + */ + public function isDecorated(); + + /** + * Sets a new style. + */ + public function setStyle(string $name, OutputFormatterStyleInterface $style); + + /** + * Checks if output formatter has style with specified name. + * + * @return bool + */ + public function hasStyle(string $name); + + /** + * Gets style options from style with specified name. + * + * @return OutputFormatterStyleInterface + * + * @throws \InvalidArgumentException When style isn't defined + */ + public function getStyle(string $name); + + /** + * Formats a message according to the given styles. + * + * @return string|null + */ + public function format(?string $message); +} diff --git a/vendor/symfony/console/Formatter/OutputFormatterStyle.php b/vendor/symfony/console/Formatter/OutputFormatterStyle.php new file mode 100644 index 0000000..8370ba0 --- /dev/null +++ b/vendor/symfony/console/Formatter/OutputFormatterStyle.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Formatter; + +use Symfony\Component\Console\Color; + +/** + * Formatter style class for defining styles. + * + * @author Konstantin Kudryashov + */ +class OutputFormatterStyle implements OutputFormatterStyleInterface +{ + private $color; + private $foreground; + private $background; + private $options; + private $href; + private $handlesHrefGracefully; + + /** + * Initializes output formatter style. + * + * @param string|null $foreground The style foreground color name + * @param string|null $background The style background color name + */ + public function __construct(string $foreground = null, string $background = null, array $options = []) + { + $this->color = new Color($this->foreground = $foreground ?: '', $this->background = $background ?: '', $this->options = $options); + } + + /** + * {@inheritdoc} + */ + public function setForeground(string $color = null) + { + $this->color = new Color($this->foreground = $color ?: '', $this->background, $this->options); + } + + /** + * {@inheritdoc} + */ + public function setBackground(string $color = null) + { + $this->color = new Color($this->foreground, $this->background = $color ?: '', $this->options); + } + + public function setHref(string $url): void + { + $this->href = $url; + } + + /** + * {@inheritdoc} + */ + public function setOption(string $option) + { + $this->options[] = $option; + $this->color = new Color($this->foreground, $this->background, $this->options); + } + + /** + * {@inheritdoc} + */ + public function unsetOption(string $option) + { + $pos = array_search($option, $this->options); + if (false !== $pos) { + unset($this->options[$pos]); + } + + $this->color = new Color($this->foreground, $this->background, $this->options); + } + + /** + * {@inheritdoc} + */ + public function setOptions(array $options) + { + $this->color = new Color($this->foreground, $this->background, $this->options = $options); + } + + /** + * {@inheritdoc} + */ + public function apply(string $text) + { + if (null === $this->handlesHrefGracefully) { + $this->handlesHrefGracefully = 'JetBrains-JediTerm' !== getenv('TERMINAL_EMULATOR') + && (!getenv('KONSOLE_VERSION') || (int) getenv('KONSOLE_VERSION') > 201100) + && !isset($_SERVER['IDEA_INITIAL_DIRECTORY']); + } + + if (null !== $this->href && $this->handlesHrefGracefully) { + $text = "\033]8;;$this->href\033\\$text\033]8;;\033\\"; + } + + return $this->color->apply($text); + } +} diff --git a/vendor/symfony/console/Formatter/OutputFormatterStyleInterface.php b/vendor/symfony/console/Formatter/OutputFormatterStyleInterface.php new file mode 100644 index 0000000..b30560d --- /dev/null +++ b/vendor/symfony/console/Formatter/OutputFormatterStyleInterface.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 Symfony\Component\Console\Formatter; + +/** + * Formatter style interface for defining styles. + * + * @author Konstantin Kudryashov + */ +interface OutputFormatterStyleInterface +{ + /** + * Sets style foreground color. + */ + public function setForeground(string $color = null); + + /** + * Sets style background color. + */ + public function setBackground(string $color = null); + + /** + * Sets some specific style option. + */ + public function setOption(string $option); + + /** + * Unsets some specific style option. + */ + public function unsetOption(string $option); + + /** + * Sets multiple style options at once. + */ + public function setOptions(array $options); + + /** + * Applies the style to a given text. + * + * @return string + */ + public function apply(string $text); +} diff --git a/vendor/symfony/console/Formatter/OutputFormatterStyleStack.php b/vendor/symfony/console/Formatter/OutputFormatterStyleStack.php new file mode 100644 index 0000000..fc48dc0 --- /dev/null +++ b/vendor/symfony/console/Formatter/OutputFormatterStyleStack.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 Symfony\Component\Console\Formatter; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Contracts\Service\ResetInterface; + +/** + * @author Jean-François Simon + */ +class OutputFormatterStyleStack implements ResetInterface +{ + /** + * @var OutputFormatterStyleInterface[] + */ + private $styles; + + private $emptyStyle; + + public function __construct(OutputFormatterStyleInterface $emptyStyle = null) + { + $this->emptyStyle = $emptyStyle ?? new OutputFormatterStyle(); + $this->reset(); + } + + /** + * Resets stack (ie. empty internal arrays). + */ + public function reset() + { + $this->styles = []; + } + + /** + * Pushes a style in the stack. + */ + public function push(OutputFormatterStyleInterface $style) + { + $this->styles[] = $style; + } + + /** + * Pops a style from the stack. + * + * @return OutputFormatterStyleInterface + * + * @throws InvalidArgumentException When style tags incorrectly nested + */ + public function pop(OutputFormatterStyleInterface $style = null) + { + if (empty($this->styles)) { + return $this->emptyStyle; + } + + if (null === $style) { + return array_pop($this->styles); + } + + foreach (array_reverse($this->styles, true) as $index => $stackedStyle) { + if ($style->apply('') === $stackedStyle->apply('')) { + $this->styles = \array_slice($this->styles, 0, $index); + + return $stackedStyle; + } + } + + throw new InvalidArgumentException('Incorrectly nested style tag found.'); + } + + /** + * Computes current style with stacks top codes. + * + * @return OutputFormatterStyle + */ + public function getCurrent() + { + if (empty($this->styles)) { + return $this->emptyStyle; + } + + return $this->styles[\count($this->styles) - 1]; + } + + /** + * @return $this + */ + public function setEmptyStyle(OutputFormatterStyleInterface $emptyStyle) + { + $this->emptyStyle = $emptyStyle; + + return $this; + } + + /** + * @return OutputFormatterStyleInterface + */ + public function getEmptyStyle() + { + return $this->emptyStyle; + } +} diff --git a/vendor/symfony/console/Formatter/WrappableOutputFormatterInterface.php b/vendor/symfony/console/Formatter/WrappableOutputFormatterInterface.php new file mode 100644 index 0000000..42319ee --- /dev/null +++ b/vendor/symfony/console/Formatter/WrappableOutputFormatterInterface.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 Symfony\Component\Console\Formatter; + +/** + * Formatter interface for console output that supports word wrapping. + * + * @author Roland Franssen + */ +interface WrappableOutputFormatterInterface extends OutputFormatterInterface +{ + /** + * Formats a message according to the given styles, wrapping at `$width` (0 means no wrapping). + */ + public function formatAndWrap(?string $message, int $width); +} diff --git a/vendor/symfony/console/Helper/DebugFormatterHelper.php b/vendor/symfony/console/Helper/DebugFormatterHelper.php new file mode 100644 index 0000000..e258ba0 --- /dev/null +++ b/vendor/symfony/console/Helper/DebugFormatterHelper.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 Symfony\Component\Console\Helper; + +/** + * Helps outputting debug information when running an external program from a command. + * + * An external program can be a Process, an HTTP request, or anything else. + * + * @author Fabien Potencier + */ +class DebugFormatterHelper extends Helper +{ + private const COLORS = ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white', 'default']; + private $started = []; + private $count = -1; + + /** + * Starts a debug formatting session. + * + * @return string + */ + public function start(string $id, string $message, string $prefix = 'RUN') + { + $this->started[$id] = ['border' => ++$this->count % \count(self::COLORS)]; + + return sprintf("%s %s %s\n", $this->getBorder($id), $prefix, $message); + } + + /** + * Adds progress to a formatting session. + * + * @return string + */ + public function progress(string $id, string $buffer, bool $error = false, string $prefix = 'OUT', string $errorPrefix = 'ERR') + { + $message = ''; + + if ($error) { + if (isset($this->started[$id]['out'])) { + $message .= "\n"; + unset($this->started[$id]['out']); + } + if (!isset($this->started[$id]['err'])) { + $message .= sprintf('%s %s ', $this->getBorder($id), $errorPrefix); + $this->started[$id]['err'] = true; + } + + $message .= str_replace("\n", sprintf("\n%s %s ", $this->getBorder($id), $errorPrefix), $buffer); + } else { + if (isset($this->started[$id]['err'])) { + $message .= "\n"; + unset($this->started[$id]['err']); + } + if (!isset($this->started[$id]['out'])) { + $message .= sprintf('%s %s ', $this->getBorder($id), $prefix); + $this->started[$id]['out'] = true; + } + + $message .= str_replace("\n", sprintf("\n%s %s ", $this->getBorder($id), $prefix), $buffer); + } + + return $message; + } + + /** + * Stops a formatting session. + * + * @return string + */ + public function stop(string $id, string $message, bool $successful, string $prefix = 'RES') + { + $trailingEOL = isset($this->started[$id]['out']) || isset($this->started[$id]['err']) ? "\n" : ''; + + if ($successful) { + return sprintf("%s%s %s %s\n", $trailingEOL, $this->getBorder($id), $prefix, $message); + } + + $message = sprintf("%s%s %s %s\n", $trailingEOL, $this->getBorder($id), $prefix, $message); + + unset($this->started[$id]['out'], $this->started[$id]['err']); + + return $message; + } + + private function getBorder(string $id): string + { + return sprintf(' ', self::COLORS[$this->started[$id]['border']]); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'debug_formatter'; + } +} diff --git a/vendor/symfony/console/Helper/DescriptorHelper.php b/vendor/symfony/console/Helper/DescriptorHelper.php new file mode 100644 index 0000000..af85e9c --- /dev/null +++ b/vendor/symfony/console/Helper/DescriptorHelper.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Descriptor\DescriptorInterface; +use Symfony\Component\Console\Descriptor\JsonDescriptor; +use Symfony\Component\Console\Descriptor\MarkdownDescriptor; +use Symfony\Component\Console\Descriptor\TextDescriptor; +use Symfony\Component\Console\Descriptor\XmlDescriptor; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * This class adds helper method to describe objects in various formats. + * + * @author Jean-François Simon + */ +class DescriptorHelper extends Helper +{ + /** + * @var DescriptorInterface[] + */ + private $descriptors = []; + + public function __construct() + { + $this + ->register('txt', new TextDescriptor()) + ->register('xml', new XmlDescriptor()) + ->register('json', new JsonDescriptor()) + ->register('md', new MarkdownDescriptor()) + ; + } + + /** + * Describes an object if supported. + * + * Available options are: + * * format: string, the output format name + * * raw_text: boolean, sets output type as raw + * + * @throws InvalidArgumentException when the given format is not supported + */ + public function describe(OutputInterface $output, ?object $object, array $options = []) + { + $options = array_merge([ + 'raw_text' => false, + 'format' => 'txt', + ], $options); + + if (!isset($this->descriptors[$options['format']])) { + throw new InvalidArgumentException(sprintf('Unsupported format "%s".', $options['format'])); + } + + $descriptor = $this->descriptors[$options['format']]; + $descriptor->describe($output, $object, $options); + } + + /** + * Registers a descriptor. + * + * @return $this + */ + public function register(string $format, DescriptorInterface $descriptor) + { + $this->descriptors[$format] = $descriptor; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'descriptor'; + } + + public function getFormats(): array + { + return array_keys($this->descriptors); + } +} diff --git a/vendor/symfony/console/Helper/Dumper.php b/vendor/symfony/console/Helper/Dumper.php new file mode 100644 index 0000000..b013b6c --- /dev/null +++ b/vendor/symfony/console/Helper/Dumper.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 Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\VarDumper\Cloner\ClonerInterface; +use Symfony\Component\VarDumper\Cloner\VarCloner; +use Symfony\Component\VarDumper\Dumper\CliDumper; + +/** + * @author Roland Franssen + */ +final class Dumper +{ + private $output; + private $dumper; + private $cloner; + private $handler; + + public function __construct(OutputInterface $output, CliDumper $dumper = null, ClonerInterface $cloner = null) + { + $this->output = $output; + $this->dumper = $dumper; + $this->cloner = $cloner; + + if (class_exists(CliDumper::class)) { + $this->handler = function ($var): string { + $dumper = $this->dumper ?? $this->dumper = new CliDumper(null, null, CliDumper::DUMP_LIGHT_ARRAY | CliDumper::DUMP_COMMA_SEPARATOR); + $dumper->setColors($this->output->isDecorated()); + + return rtrim($dumper->dump(($this->cloner ?? $this->cloner = new VarCloner())->cloneVar($var)->withRefHandles(false), true)); + }; + } else { + $this->handler = function ($var): string { + switch (true) { + case null === $var: + return 'null'; + case true === $var: + return 'true'; + case false === $var: + return 'false'; + case \is_string($var): + return '"'.$var.'"'; + default: + return rtrim(print_r($var, true)); + } + }; + } + } + + public function __invoke($var): string + { + return ($this->handler)($var); + } +} diff --git a/vendor/symfony/console/Helper/FormatterHelper.php b/vendor/symfony/console/Helper/FormatterHelper.php new file mode 100644 index 0000000..92d8dc7 --- /dev/null +++ b/vendor/symfony/console/Helper/FormatterHelper.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Formatter\OutputFormatter; + +/** + * The Formatter class provides helpers to format messages. + * + * @author Fabien Potencier + */ +class FormatterHelper extends Helper +{ + /** + * Formats a message within a section. + * + * @return string + */ + public function formatSection(string $section, string $message, string $style = 'info') + { + return sprintf('<%s>[%s] %s', $style, $section, $style, $message); + } + + /** + * Formats a message as a block of text. + * + * @param string|array $messages The message to write in the block + * + * @return string + */ + public function formatBlock($messages, string $style, bool $large = false) + { + if (!\is_array($messages)) { + $messages = [$messages]; + } + + $len = 0; + $lines = []; + foreach ($messages as $message) { + $message = OutputFormatter::escape($message); + $lines[] = sprintf($large ? ' %s ' : ' %s ', $message); + $len = max(self::width($message) + ($large ? 4 : 2), $len); + } + + $messages = $large ? [str_repeat(' ', $len)] : []; + for ($i = 0; isset($lines[$i]); ++$i) { + $messages[] = $lines[$i].str_repeat(' ', $len - self::width($lines[$i])); + } + if ($large) { + $messages[] = str_repeat(' ', $len); + } + + for ($i = 0; isset($messages[$i]); ++$i) { + $messages[$i] = sprintf('<%s>%s', $style, $messages[$i], $style); + } + + return implode("\n", $messages); + } + + /** + * Truncates a message to the given length. + * + * @return string + */ + public function truncate(string $message, int $length, string $suffix = '...') + { + $computedLength = $length - self::width($suffix); + + if ($computedLength > self::width($message)) { + return $message; + } + + return self::substr($message, 0, $length).$suffix; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'formatter'; + } +} diff --git a/vendor/symfony/console/Helper/Helper.php b/vendor/symfony/console/Helper/Helper.php new file mode 100644 index 0000000..c7d3e25 --- /dev/null +++ b/vendor/symfony/console/Helper/Helper.php @@ -0,0 +1,180 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Formatter\OutputFormatterInterface; +use Symfony\Component\String\UnicodeString; + +/** + * Helper is the base class for all helper classes. + * + * @author Fabien Potencier + */ +abstract class Helper implements HelperInterface +{ + protected $helperSet = null; + + /** + * {@inheritdoc} + */ + public function setHelperSet(HelperSet $helperSet = null) + { + $this->helperSet = $helperSet; + } + + /** + * {@inheritdoc} + */ + public function getHelperSet() + { + return $this->helperSet; + } + + /** + * Returns the length of a string, using mb_strwidth if it is available. + * + * @deprecated since Symfony 5.3 + * + * @return int + */ + public static function strlen(?string $string) + { + trigger_deprecation('symfony/console', '5.3', 'Method "%s()" is deprecated and will be removed in Symfony 6.0. Use Helper::width() or Helper::length() instead.', __METHOD__); + + return self::width($string); + } + + /** + * Returns the width of a string, using mb_strwidth if it is available. + * The width is how many characters positions the string will use. + */ + public static function width(?string $string): int + { + $string ?? $string = ''; + + if (preg_match('//u', $string)) { + return (new UnicodeString($string))->width(false); + } + + if (false === $encoding = mb_detect_encoding($string, null, true)) { + return \strlen($string); + } + + return mb_strwidth($string, $encoding); + } + + /** + * Returns the length of a string, using mb_strlen if it is available. + * The length is related to how many bytes the string will use. + */ + public static function length(?string $string): int + { + $string ?? $string = ''; + + if (preg_match('//u', $string)) { + return (new UnicodeString($string))->length(); + } + + if (false === $encoding = mb_detect_encoding($string, null, true)) { + return \strlen($string); + } + + return mb_strlen($string, $encoding); + } + + /** + * Returns the subset of a string, using mb_substr if it is available. + * + * @return string + */ + public static function substr(?string $string, int $from, int $length = null) + { + $string ?? $string = ''; + + if (false === $encoding = mb_detect_encoding($string, null, true)) { + return substr($string, $from, $length); + } + + return mb_substr($string, $from, $length, $encoding); + } + + public static function formatTime($secs) + { + static $timeFormats = [ + [0, '< 1 sec'], + [1, '1 sec'], + [2, 'secs', 1], + [60, '1 min'], + [120, 'mins', 60], + [3600, '1 hr'], + [7200, 'hrs', 3600], + [86400, '1 day'], + [172800, 'days', 86400], + ]; + + foreach ($timeFormats as $index => $format) { + if ($secs >= $format[0]) { + if ((isset($timeFormats[$index + 1]) && $secs < $timeFormats[$index + 1][0]) + || $index == \count($timeFormats) - 1 + ) { + if (2 == \count($format)) { + return $format[1]; + } + + return floor($secs / $format[2]).' '.$format[1]; + } + } + } + } + + public static function formatMemory(int $memory) + { + if ($memory >= 1024 * 1024 * 1024) { + return sprintf('%.1f GiB', $memory / 1024 / 1024 / 1024); + } + + if ($memory >= 1024 * 1024) { + return sprintf('%.1f MiB', $memory / 1024 / 1024); + } + + if ($memory >= 1024) { + return sprintf('%d KiB', $memory / 1024); + } + + return sprintf('%d B', $memory); + } + + /** + * @deprecated since Symfony 5.3 + */ + public static function strlenWithoutDecoration(OutputFormatterInterface $formatter, ?string $string) + { + trigger_deprecation('symfony/console', '5.3', 'Method "%s()" is deprecated and will be removed in Symfony 6.0. Use Helper::removeDecoration() instead.', __METHOD__); + + return self::width(self::removeDecoration($formatter, $string)); + } + + public static function removeDecoration(OutputFormatterInterface $formatter, ?string $string) + { + $isDecorated = $formatter->isDecorated(); + $formatter->setDecorated(false); + // remove <...> formatting + $string = $formatter->format($string ?? ''); + // remove already formatted characters + $string = preg_replace("/\033\[[^m]*m/", '', $string ?? ''); + // remove terminal hyperlinks + $string = preg_replace('/\\033]8;[^;]*;[^\\033]*\\033\\\\/', '', $string ?? ''); + $formatter->setDecorated($isDecorated); + + return $string; + } +} diff --git a/vendor/symfony/console/Helper/HelperInterface.php b/vendor/symfony/console/Helper/HelperInterface.php new file mode 100644 index 0000000..fc952b4 --- /dev/null +++ b/vendor/symfony/console/Helper/HelperInterface.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +/** + * HelperInterface is the interface all helpers must implement. + * + * @author Fabien Potencier + */ +interface HelperInterface +{ + /** + * Sets the helper set associated with this helper. + */ + public function setHelperSet(HelperSet $helperSet = null); + + /** + * Gets the helper set associated with this helper. + * + * @return HelperSet|null + */ + public function getHelperSet(); + + /** + * Returns the canonical name of this helper. + * + * @return string + */ + public function getName(); +} diff --git a/vendor/symfony/console/Helper/HelperSet.php b/vendor/symfony/console/Helper/HelperSet.php new file mode 100644 index 0000000..719762d --- /dev/null +++ b/vendor/symfony/console/Helper/HelperSet.php @@ -0,0 +1,108 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\InvalidArgumentException; + +/** + * HelperSet represents a set of helpers to be used with a command. + * + * @author Fabien Potencier + * + * @implements \IteratorAggregate + */ +class HelperSet implements \IteratorAggregate +{ + /** @var array */ + private $helpers = []; + private $command; + + /** + * @param Helper[] $helpers An array of helper + */ + public function __construct(array $helpers = []) + { + foreach ($helpers as $alias => $helper) { + $this->set($helper, \is_int($alias) ? null : $alias); + } + } + + public function set(HelperInterface $helper, string $alias = null) + { + $this->helpers[$helper->getName()] = $helper; + if (null !== $alias) { + $this->helpers[$alias] = $helper; + } + + $helper->setHelperSet($this); + } + + /** + * Returns true if the helper if defined. + * + * @return bool + */ + public function has(string $name) + { + return isset($this->helpers[$name]); + } + + /** + * Gets a helper value. + * + * @return HelperInterface + * + * @throws InvalidArgumentException if the helper is not defined + */ + public function get(string $name) + { + if (!$this->has($name)) { + throw new InvalidArgumentException(sprintf('The helper "%s" is not defined.', $name)); + } + + return $this->helpers[$name]; + } + + /** + * @deprecated since Symfony 5.4 + */ + public function setCommand(Command $command = null) + { + trigger_deprecation('symfony/console', '5.4', 'Method "%s()" is deprecated.', __METHOD__); + + $this->command = $command; + } + + /** + * Gets the command associated with this helper set. + * + * @return Command + * + * @deprecated since Symfony 5.4 + */ + public function getCommand() + { + trigger_deprecation('symfony/console', '5.4', 'Method "%s()" is deprecated.', __METHOD__); + + return $this->command; + } + + /** + * @return \Traversable + */ + #[\ReturnTypeWillChange] + public function getIterator() + { + return new \ArrayIterator($this->helpers); + } +} diff --git a/vendor/symfony/console/Helper/InputAwareHelper.php b/vendor/symfony/console/Helper/InputAwareHelper.php new file mode 100644 index 0000000..0d0dba2 --- /dev/null +++ b/vendor/symfony/console/Helper/InputAwareHelper.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Input\InputAwareInterface; +use Symfony\Component\Console\Input\InputInterface; + +/** + * An implementation of InputAwareInterface for Helpers. + * + * @author Wouter J + */ +abstract class InputAwareHelper extends Helper implements InputAwareInterface +{ + protected $input; + + /** + * {@inheritdoc} + */ + public function setInput(InputInterface $input) + { + $this->input = $input; + } +} diff --git a/vendor/symfony/console/Helper/ProcessHelper.php b/vendor/symfony/console/Helper/ProcessHelper.php new file mode 100644 index 0000000..4ea3d72 --- /dev/null +++ b/vendor/symfony/console/Helper/ProcessHelper.php @@ -0,0 +1,144 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Process\Exception\ProcessFailedException; +use Symfony\Component\Process\Process; + +/** + * The ProcessHelper class provides helpers to run external processes. + * + * @author Fabien Potencier + * + * @final + */ +class ProcessHelper extends Helper +{ + /** + * Runs an external process. + * + * @param array|Process $cmd An instance of Process or an array of the command and arguments + * @param callable|null $callback A PHP callback to run whenever there is some + * output available on STDOUT or STDERR + */ + public function run(OutputInterface $output, $cmd, string $error = null, callable $callback = null, int $verbosity = OutputInterface::VERBOSITY_VERY_VERBOSE): Process + { + if (!class_exists(Process::class)) { + throw new \LogicException('The ProcessHelper cannot be run as the Process component is not installed. Try running "compose require symfony/process".'); + } + + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + + $formatter = $this->getHelperSet()->get('debug_formatter'); + + if ($cmd instanceof Process) { + $cmd = [$cmd]; + } + + if (!\is_array($cmd)) { + throw new \TypeError(sprintf('The "command" argument of "%s()" must be an array or a "%s" instance, "%s" given.', __METHOD__, Process::class, get_debug_type($cmd))); + } + + if (\is_string($cmd[0] ?? null)) { + $process = new Process($cmd); + $cmd = []; + } elseif (($cmd[0] ?? null) instanceof Process) { + $process = $cmd[0]; + unset($cmd[0]); + } else { + throw new \InvalidArgumentException(sprintf('Invalid command provided to "%s()": the command should be an array whose first element is either the path to the binary to run or a "Process" object.', __METHOD__)); + } + + if ($verbosity <= $output->getVerbosity()) { + $output->write($formatter->start(spl_object_hash($process), $this->escapeString($process->getCommandLine()))); + } + + if ($output->isDebug()) { + $callback = $this->wrapCallback($output, $process, $callback); + } + + $process->run($callback, $cmd); + + if ($verbosity <= $output->getVerbosity()) { + $message = $process->isSuccessful() ? 'Command ran successfully' : sprintf('%s Command did not run successfully', $process->getExitCode()); + $output->write($formatter->stop(spl_object_hash($process), $message, $process->isSuccessful())); + } + + if (!$process->isSuccessful() && null !== $error) { + $output->writeln(sprintf('%s', $this->escapeString($error))); + } + + return $process; + } + + /** + * Runs the process. + * + * This is identical to run() except that an exception is thrown if the process + * exits with a non-zero exit code. + * + * @param array|Process $cmd An instance of Process or a command to run + * @param callable|null $callback A PHP callback to run whenever there is some + * output available on STDOUT or STDERR + * + * @throws ProcessFailedException + * + * @see run() + */ + public function mustRun(OutputInterface $output, $cmd, string $error = null, callable $callback = null): Process + { + $process = $this->run($output, $cmd, $error, $callback); + + if (!$process->isSuccessful()) { + throw new ProcessFailedException($process); + } + + return $process; + } + + /** + * Wraps a Process callback to add debugging output. + */ + public function wrapCallback(OutputInterface $output, Process $process, callable $callback = null): callable + { + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + + $formatter = $this->getHelperSet()->get('debug_formatter'); + + return function ($type, $buffer) use ($output, $process, $callback, $formatter) { + $output->write($formatter->progress(spl_object_hash($process), $this->escapeString($buffer), Process::ERR === $type)); + + if (null !== $callback) { + $callback($type, $buffer); + } + }; + } + + private function escapeString(string $str): string + { + return str_replace('<', '\\<', $str); + } + + /** + * {@inheritdoc} + */ + public function getName(): string + { + return 'process'; + } +} diff --git a/vendor/symfony/console/Helper/ProgressBar.php b/vendor/symfony/console/Helper/ProgressBar.php new file mode 100644 index 0000000..eb6aacb --- /dev/null +++ b/vendor/symfony/console/Helper/ProgressBar.php @@ -0,0 +1,609 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Cursor; +use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\ConsoleSectionOutput; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Terminal; + +/** + * The ProgressBar provides helpers to display progress output. + * + * @author Fabien Potencier + * @author Chris Jones + */ +final class ProgressBar +{ + public const FORMAT_VERBOSE = 'verbose'; + public const FORMAT_VERY_VERBOSE = 'very_verbose'; + public const FORMAT_DEBUG = 'debug'; + public const FORMAT_NORMAL = 'normal'; + + private const FORMAT_VERBOSE_NOMAX = 'verbose_nomax'; + private const FORMAT_VERY_VERBOSE_NOMAX = 'very_verbose_nomax'; + private const FORMAT_DEBUG_NOMAX = 'debug_nomax'; + private const FORMAT_NORMAL_NOMAX = 'normal_nomax'; + + private $barWidth = 28; + private $barChar; + private $emptyBarChar = '-'; + private $progressChar = '>'; + private $format; + private $internalFormat; + private $redrawFreq = 1; + private $writeCount; + private $lastWriteTime; + private $minSecondsBetweenRedraws = 0; + private $maxSecondsBetweenRedraws = 1; + private $output; + private $step = 0; + private $max; + private $startTime; + private $stepWidth; + private $percent = 0.0; + private $messages = []; + private $overwrite = true; + private $terminal; + private $previousMessage; + private $cursor; + + private static $formatters; + private static $formats; + + /** + * @param int $max Maximum steps (0 if unknown) + */ + public function __construct(OutputInterface $output, int $max = 0, float $minSecondsBetweenRedraws = 1 / 25) + { + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + + $this->output = $output; + $this->setMaxSteps($max); + $this->terminal = new Terminal(); + + if (0 < $minSecondsBetweenRedraws) { + $this->redrawFreq = null; + $this->minSecondsBetweenRedraws = $minSecondsBetweenRedraws; + } + + if (!$this->output->isDecorated()) { + // disable overwrite when output does not support ANSI codes. + $this->overwrite = false; + + // set a reasonable redraw frequency so output isn't flooded + $this->redrawFreq = null; + } + + $this->startTime = time(); + $this->cursor = new Cursor($output); + } + + /** + * Sets a placeholder formatter for a given name. + * + * This method also allow you to override an existing placeholder. + * + * @param string $name The placeholder name (including the delimiter char like %) + * @param callable $callable A PHP callable + */ + public static function setPlaceholderFormatterDefinition(string $name, callable $callable): void + { + if (!self::$formatters) { + self::$formatters = self::initPlaceholderFormatters(); + } + + self::$formatters[$name] = $callable; + } + + /** + * Gets the placeholder formatter for a given name. + * + * @param string $name The placeholder name (including the delimiter char like %) + */ + public static function getPlaceholderFormatterDefinition(string $name): ?callable + { + if (!self::$formatters) { + self::$formatters = self::initPlaceholderFormatters(); + } + + return self::$formatters[$name] ?? null; + } + + /** + * Sets a format for a given name. + * + * This method also allow you to override an existing format. + * + * @param string $name The format name + * @param string $format A format string + */ + public static function setFormatDefinition(string $name, string $format): void + { + if (!self::$formats) { + self::$formats = self::initFormats(); + } + + self::$formats[$name] = $format; + } + + /** + * Gets the format for a given name. + * + * @param string $name The format name + */ + public static function getFormatDefinition(string $name): ?string + { + if (!self::$formats) { + self::$formats = self::initFormats(); + } + + return self::$formats[$name] ?? null; + } + + /** + * Associates a text with a named placeholder. + * + * The text is displayed when the progress bar is rendered but only + * when the corresponding placeholder is part of the custom format line + * (by wrapping the name with %). + * + * @param string $message The text to associate with the placeholder + * @param string $name The name of the placeholder + */ + public function setMessage(string $message, string $name = 'message') + { + $this->messages[$name] = $message; + } + + public function getMessage(string $name = 'message') + { + return $this->messages[$name]; + } + + public function getStartTime(): int + { + return $this->startTime; + } + + public function getMaxSteps(): int + { + return $this->max; + } + + public function getProgress(): int + { + return $this->step; + } + + private function getStepWidth(): int + { + return $this->stepWidth; + } + + public function getProgressPercent(): float + { + return $this->percent; + } + + public function getBarOffset(): float + { + return floor($this->max ? $this->percent * $this->barWidth : (null === $this->redrawFreq ? (int) (min(5, $this->barWidth / 15) * $this->writeCount) : $this->step) % $this->barWidth); + } + + public function getEstimated(): float + { + if (!$this->step) { + return 0; + } + + return round((time() - $this->startTime) / $this->step * $this->max); + } + + public function getRemaining(): float + { + if (!$this->step) { + return 0; + } + + return round((time() - $this->startTime) / $this->step * ($this->max - $this->step)); + } + + public function setBarWidth(int $size) + { + $this->barWidth = max(1, $size); + } + + public function getBarWidth(): int + { + return $this->barWidth; + } + + public function setBarCharacter(string $char) + { + $this->barChar = $char; + } + + public function getBarCharacter(): string + { + return $this->barChar ?? ($this->max ? '=' : $this->emptyBarChar); + } + + public function setEmptyBarCharacter(string $char) + { + $this->emptyBarChar = $char; + } + + public function getEmptyBarCharacter(): string + { + return $this->emptyBarChar; + } + + public function setProgressCharacter(string $char) + { + $this->progressChar = $char; + } + + public function getProgressCharacter(): string + { + return $this->progressChar; + } + + public function setFormat(string $format) + { + $this->format = null; + $this->internalFormat = $format; + } + + /** + * Sets the redraw frequency. + * + * @param int|null $freq The frequency in steps + */ + public function setRedrawFrequency(?int $freq) + { + $this->redrawFreq = null !== $freq ? max(1, $freq) : null; + } + + public function minSecondsBetweenRedraws(float $seconds): void + { + $this->minSecondsBetweenRedraws = $seconds; + } + + public function maxSecondsBetweenRedraws(float $seconds): void + { + $this->maxSecondsBetweenRedraws = $seconds; + } + + /** + * Returns an iterator that will automatically update the progress bar when iterated. + * + * @param int|null $max Number of steps to complete the bar (0 if indeterminate), if null it will be inferred from $iterable + */ + public function iterate(iterable $iterable, int $max = null): iterable + { + $this->start($max ?? (is_countable($iterable) ? \count($iterable) : 0)); + + foreach ($iterable as $key => $value) { + yield $key => $value; + + $this->advance(); + } + + $this->finish(); + } + + /** + * Starts the progress output. + * + * @param int|null $max Number of steps to complete the bar (0 if indeterminate), null to leave unchanged + */ + public function start(int $max = null) + { + $this->startTime = time(); + $this->step = 0; + $this->percent = 0.0; + + if (null !== $max) { + $this->setMaxSteps($max); + } + + $this->display(); + } + + /** + * Advances the progress output X steps. + * + * @param int $step Number of steps to advance + */ + public function advance(int $step = 1) + { + $this->setProgress($this->step + $step); + } + + /** + * Sets whether to overwrite the progressbar, false for new line. + */ + public function setOverwrite(bool $overwrite) + { + $this->overwrite = $overwrite; + } + + public function setProgress(int $step) + { + if ($this->max && $step > $this->max) { + $this->max = $step; + } elseif ($step < 0) { + $step = 0; + } + + $redrawFreq = $this->redrawFreq ?? (($this->max ?: 10) / 10); + $prevPeriod = (int) ($this->step / $redrawFreq); + $currPeriod = (int) ($step / $redrawFreq); + $this->step = $step; + $this->percent = $this->max ? (float) $this->step / $this->max : 0; + $timeInterval = microtime(true) - $this->lastWriteTime; + + // Draw regardless of other limits + if ($this->max === $step) { + $this->display(); + + return; + } + + // Throttling + if ($timeInterval < $this->minSecondsBetweenRedraws) { + return; + } + + // Draw each step period, but not too late + if ($prevPeriod !== $currPeriod || $timeInterval >= $this->maxSecondsBetweenRedraws) { + $this->display(); + } + } + + public function setMaxSteps(int $max) + { + $this->format = null; + $this->max = max(0, $max); + $this->stepWidth = $this->max ? Helper::width((string) $this->max) : 4; + } + + /** + * Finishes the progress output. + */ + public function finish(): void + { + if (!$this->max) { + $this->max = $this->step; + } + + if ($this->step === $this->max && !$this->overwrite) { + // prevent double 100% output + return; + } + + $this->setProgress($this->max); + } + + /** + * Outputs the current progress string. + */ + public function display(): void + { + if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) { + return; + } + + if (null === $this->format) { + $this->setRealFormat($this->internalFormat ?: $this->determineBestFormat()); + } + + $this->overwrite($this->buildLine()); + } + + /** + * Removes the progress bar from the current line. + * + * This is useful if you wish to write some output + * while a progress bar is running. + * Call display() to show the progress bar again. + */ + public function clear(): void + { + if (!$this->overwrite) { + return; + } + + if (null === $this->format) { + $this->setRealFormat($this->internalFormat ?: $this->determineBestFormat()); + } + + $this->overwrite(''); + } + + private function setRealFormat(string $format) + { + // try to use the _nomax variant if available + if (!$this->max && null !== self::getFormatDefinition($format.'_nomax')) { + $this->format = self::getFormatDefinition($format.'_nomax'); + } elseif (null !== self::getFormatDefinition($format)) { + $this->format = self::getFormatDefinition($format); + } else { + $this->format = $format; + } + } + + /** + * Overwrites a previous message to the output. + */ + private function overwrite(string $message): void + { + if ($this->previousMessage === $message) { + return; + } + + $originalMessage = $message; + + if ($this->overwrite) { + if (null !== $this->previousMessage) { + if ($this->output instanceof ConsoleSectionOutput) { + $messageLines = explode("\n", $this->previousMessage); + $lineCount = \count($messageLines); + foreach ($messageLines as $messageLine) { + $messageLineLength = Helper::width(Helper::removeDecoration($this->output->getFormatter(), $messageLine)); + if ($messageLineLength > $this->terminal->getWidth()) { + $lineCount += floor($messageLineLength / $this->terminal->getWidth()); + } + } + $this->output->clear($lineCount); + } else { + $lineCount = substr_count($this->previousMessage, "\n"); + for ($i = 0; $i < $lineCount; ++$i) { + $this->cursor->moveToColumn(1); + $this->cursor->clearLine(); + $this->cursor->moveUp(); + } + + $this->cursor->moveToColumn(1); + $this->cursor->clearLine(); + } + } + } elseif ($this->step > 0) { + $message = \PHP_EOL.$message; + } + + $this->previousMessage = $originalMessage; + $this->lastWriteTime = microtime(true); + + $this->output->write($message); + ++$this->writeCount; + } + + private function determineBestFormat(): string + { + switch ($this->output->getVerbosity()) { + // OutputInterface::VERBOSITY_QUIET: display is disabled anyway + case OutputInterface::VERBOSITY_VERBOSE: + return $this->max ? self::FORMAT_VERBOSE : self::FORMAT_VERBOSE_NOMAX; + case OutputInterface::VERBOSITY_VERY_VERBOSE: + return $this->max ? self::FORMAT_VERY_VERBOSE : self::FORMAT_VERY_VERBOSE_NOMAX; + case OutputInterface::VERBOSITY_DEBUG: + return $this->max ? self::FORMAT_DEBUG : self::FORMAT_DEBUG_NOMAX; + default: + return $this->max ? self::FORMAT_NORMAL : self::FORMAT_NORMAL_NOMAX; + } + } + + private static function initPlaceholderFormatters(): array + { + return [ + 'bar' => function (self $bar, OutputInterface $output) { + $completeBars = $bar->getBarOffset(); + $display = str_repeat($bar->getBarCharacter(), $completeBars); + if ($completeBars < $bar->getBarWidth()) { + $emptyBars = $bar->getBarWidth() - $completeBars - Helper::length(Helper::removeDecoration($output->getFormatter(), $bar->getProgressCharacter())); + $display .= $bar->getProgressCharacter().str_repeat($bar->getEmptyBarCharacter(), $emptyBars); + } + + return $display; + }, + 'elapsed' => function (self $bar) { + return Helper::formatTime(time() - $bar->getStartTime()); + }, + 'remaining' => function (self $bar) { + if (!$bar->getMaxSteps()) { + throw new LogicException('Unable to display the remaining time if the maximum number of steps is not set.'); + } + + return Helper::formatTime($bar->getRemaining()); + }, + 'estimated' => function (self $bar) { + if (!$bar->getMaxSteps()) { + throw new LogicException('Unable to display the estimated time if the maximum number of steps is not set.'); + } + + return Helper::formatTime($bar->getEstimated()); + }, + 'memory' => function (self $bar) { + return Helper::formatMemory(memory_get_usage(true)); + }, + 'current' => function (self $bar) { + return str_pad($bar->getProgress(), $bar->getStepWidth(), ' ', \STR_PAD_LEFT); + }, + 'max' => function (self $bar) { + return $bar->getMaxSteps(); + }, + 'percent' => function (self $bar) { + return floor($bar->getProgressPercent() * 100); + }, + ]; + } + + private static function initFormats(): array + { + return [ + self::FORMAT_NORMAL => ' %current%/%max% [%bar%] %percent:3s%%', + self::FORMAT_NORMAL_NOMAX => ' %current% [%bar%]', + + self::FORMAT_VERBOSE => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%', + self::FORMAT_VERBOSE_NOMAX => ' %current% [%bar%] %elapsed:6s%', + + self::FORMAT_VERY_VERBOSE => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s%', + self::FORMAT_VERY_VERBOSE_NOMAX => ' %current% [%bar%] %elapsed:6s%', + + self::FORMAT_DEBUG => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%', + self::FORMAT_DEBUG_NOMAX => ' %current% [%bar%] %elapsed:6s% %memory:6s%', + ]; + } + + private function buildLine(): string + { + $regex = "{%([a-z\-_]+)(?:\:([^%]+))?%}i"; + $callback = function ($matches) { + if ($formatter = $this::getPlaceholderFormatterDefinition($matches[1])) { + $text = $formatter($this, $this->output); + } elseif (isset($this->messages[$matches[1]])) { + $text = $this->messages[$matches[1]]; + } else { + return $matches[0]; + } + + if (isset($matches[2])) { + $text = sprintf('%'.$matches[2], $text); + } + + return $text; + }; + $line = preg_replace_callback($regex, $callback, $this->format); + + // gets string length for each sub line with multiline format + $linesLength = array_map(function ($subLine) { + return Helper::width(Helper::removeDecoration($this->output->getFormatter(), rtrim($subLine, "\r"))); + }, explode("\n", $line)); + + $linesWidth = max($linesLength); + + $terminalWidth = $this->terminal->getWidth(); + if ($linesWidth <= $terminalWidth) { + return $line; + } + + $this->setBarWidth($this->barWidth - $linesWidth + $terminalWidth); + + return preg_replace_callback($regex, $callback, $this->format); + } +} diff --git a/vendor/symfony/console/Helper/ProgressIndicator.php b/vendor/symfony/console/Helper/ProgressIndicator.php new file mode 100644 index 0000000..3482343 --- /dev/null +++ b/vendor/symfony/console/Helper/ProgressIndicator.php @@ -0,0 +1,249 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Kevin Bond + */ +class ProgressIndicator +{ + private const FORMATS = [ + 'normal' => ' %indicator% %message%', + 'normal_no_ansi' => ' %message%', + + 'verbose' => ' %indicator% %message% (%elapsed:6s%)', + 'verbose_no_ansi' => ' %message% (%elapsed:6s%)', + + 'very_verbose' => ' %indicator% %message% (%elapsed:6s%, %memory:6s%)', + 'very_verbose_no_ansi' => ' %message% (%elapsed:6s%, %memory:6s%)', + ]; + + private $output; + private $startTime; + private $format; + private $message; + private $indicatorValues; + private $indicatorCurrent; + private $indicatorChangeInterval; + private $indicatorUpdateTime; + private $started = false; + + /** + * @var array + */ + private static $formatters; + + /** + * @param int $indicatorChangeInterval Change interval in milliseconds + * @param array|null $indicatorValues Animated indicator characters + */ + public function __construct(OutputInterface $output, string $format = null, int $indicatorChangeInterval = 100, array $indicatorValues = null) + { + $this->output = $output; + + if (null === $format) { + $format = $this->determineBestFormat(); + } + + if (null === $indicatorValues) { + $indicatorValues = ['-', '\\', '|', '/']; + } + + $indicatorValues = array_values($indicatorValues); + + if (2 > \count($indicatorValues)) { + throw new InvalidArgumentException('Must have at least 2 indicator value characters.'); + } + + $this->format = self::getFormatDefinition($format); + $this->indicatorChangeInterval = $indicatorChangeInterval; + $this->indicatorValues = $indicatorValues; + $this->startTime = time(); + } + + /** + * Sets the current indicator message. + */ + public function setMessage(?string $message) + { + $this->message = $message; + + $this->display(); + } + + /** + * Starts the indicator output. + */ + public function start(string $message) + { + if ($this->started) { + throw new LogicException('Progress indicator already started.'); + } + + $this->message = $message; + $this->started = true; + $this->startTime = time(); + $this->indicatorUpdateTime = $this->getCurrentTimeInMilliseconds() + $this->indicatorChangeInterval; + $this->indicatorCurrent = 0; + + $this->display(); + } + + /** + * Advances the indicator. + */ + public function advance() + { + if (!$this->started) { + throw new LogicException('Progress indicator has not yet been started.'); + } + + if (!$this->output->isDecorated()) { + return; + } + + $currentTime = $this->getCurrentTimeInMilliseconds(); + + if ($currentTime < $this->indicatorUpdateTime) { + return; + } + + $this->indicatorUpdateTime = $currentTime + $this->indicatorChangeInterval; + ++$this->indicatorCurrent; + + $this->display(); + } + + /** + * Finish the indicator with message. + * + * @param $message + */ + public function finish(string $message) + { + if (!$this->started) { + throw new LogicException('Progress indicator has not yet been started.'); + } + + $this->message = $message; + $this->display(); + $this->output->writeln(''); + $this->started = false; + } + + /** + * Gets the format for a given name. + * + * @return string|null + */ + public static function getFormatDefinition(string $name) + { + return self::FORMATS[$name] ?? null; + } + + /** + * Sets a placeholder formatter for a given name. + * + * This method also allow you to override an existing placeholder. + */ + public static function setPlaceholderFormatterDefinition(string $name, callable $callable) + { + if (!self::$formatters) { + self::$formatters = self::initPlaceholderFormatters(); + } + + self::$formatters[$name] = $callable; + } + + /** + * Gets the placeholder formatter for a given name (including the delimiter char like %). + * + * @return callable|null + */ + public static function getPlaceholderFormatterDefinition(string $name) + { + if (!self::$formatters) { + self::$formatters = self::initPlaceholderFormatters(); + } + + return self::$formatters[$name] ?? null; + } + + private function display() + { + if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) { + return; + } + + $this->overwrite(preg_replace_callback("{%([a-z\-_]+)(?:\:([^%]+))?%}i", function ($matches) { + if ($formatter = self::getPlaceholderFormatterDefinition($matches[1])) { + return $formatter($this); + } + + return $matches[0]; + }, $this->format ?? '')); + } + + private function determineBestFormat(): string + { + switch ($this->output->getVerbosity()) { + // OutputInterface::VERBOSITY_QUIET: display is disabled anyway + case OutputInterface::VERBOSITY_VERBOSE: + return $this->output->isDecorated() ? 'verbose' : 'verbose_no_ansi'; + case OutputInterface::VERBOSITY_VERY_VERBOSE: + case OutputInterface::VERBOSITY_DEBUG: + return $this->output->isDecorated() ? 'very_verbose' : 'very_verbose_no_ansi'; + default: + return $this->output->isDecorated() ? 'normal' : 'normal_no_ansi'; + } + } + + /** + * Overwrites a previous message to the output. + */ + private function overwrite(string $message) + { + if ($this->output->isDecorated()) { + $this->output->write("\x0D\x1B[2K"); + $this->output->write($message); + } else { + $this->output->writeln($message); + } + } + + private function getCurrentTimeInMilliseconds(): float + { + return round(microtime(true) * 1000); + } + + private static function initPlaceholderFormatters(): array + { + return [ + 'indicator' => function (self $indicator) { + return $indicator->indicatorValues[$indicator->indicatorCurrent % \count($indicator->indicatorValues)]; + }, + 'message' => function (self $indicator) { + return $indicator->message; + }, + 'elapsed' => function (self $indicator) { + return Helper::formatTime(time() - $indicator->startTime); + }, + 'memory' => function () { + return Helper::formatMemory(memory_get_usage(true)); + }, + ]; + } +} diff --git a/vendor/symfony/console/Helper/QuestionHelper.php b/vendor/symfony/console/Helper/QuestionHelper.php new file mode 100644 index 0000000..e236be9 --- /dev/null +++ b/vendor/symfony/console/Helper/QuestionHelper.php @@ -0,0 +1,625 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Cursor; +use Symfony\Component\Console\Exception\MissingInputException; +use Symfony\Component\Console\Exception\RuntimeException; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Formatter\OutputFormatterStyle; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\StreamableInputInterface; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\ConsoleSectionOutput; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Question\ChoiceQuestion; +use Symfony\Component\Console\Question\Question; +use Symfony\Component\Console\Terminal; + +use function Symfony\Component\String\s; + +/** + * The QuestionHelper class provides helpers to interact with the user. + * + * @author Fabien Potencier + */ +class QuestionHelper extends Helper +{ + /** + * @var resource|null + */ + private $inputStream; + + private static $stty = true; + private static $stdinIsInteractive; + + /** + * Asks a question to the user. + * + * @return mixed The user answer + * + * @throws RuntimeException If there is no data to read in the input stream + */ + public function ask(InputInterface $input, OutputInterface $output, Question $question) + { + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + + if (!$input->isInteractive()) { + return $this->getDefaultAnswer($question); + } + + if ($input instanceof StreamableInputInterface && $stream = $input->getStream()) { + $this->inputStream = $stream; + } + + try { + if (!$question->getValidator()) { + return $this->doAsk($output, $question); + } + + $interviewer = function () use ($output, $question) { + return $this->doAsk($output, $question); + }; + + return $this->validateAttempts($interviewer, $output, $question); + } catch (MissingInputException $exception) { + $input->setInteractive(false); + + if (null === $fallbackOutput = $this->getDefaultAnswer($question)) { + throw $exception; + } + + return $fallbackOutput; + } + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'question'; + } + + /** + * Prevents usage of stty. + */ + public static function disableStty() + { + self::$stty = false; + } + + /** + * Asks the question to the user. + * + * @return mixed + * + * @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden + */ + private function doAsk(OutputInterface $output, Question $question) + { + $this->writePrompt($output, $question); + + $inputStream = $this->inputStream ?: \STDIN; + $autocomplete = $question->getAutocompleterCallback(); + + if (null === $autocomplete || !self::$stty || !Terminal::hasSttyAvailable()) { + $ret = false; + if ($question->isHidden()) { + try { + $hiddenResponse = $this->getHiddenResponse($output, $inputStream, $question->isTrimmable()); + $ret = $question->isTrimmable() ? trim($hiddenResponse) : $hiddenResponse; + } catch (RuntimeException $e) { + if (!$question->isHiddenFallback()) { + throw $e; + } + } + } + + if (false === $ret) { + $isBlocked = stream_get_meta_data($inputStream)['blocked'] ?? true; + + if (!$isBlocked) { + stream_set_blocking($inputStream, true); + } + + $ret = $this->readInput($inputStream, $question); + + if (!$isBlocked) { + stream_set_blocking($inputStream, false); + } + + if (false === $ret) { + throw new MissingInputException('Aborted.'); + } + if ($question->isTrimmable()) { + $ret = trim($ret); + } + } + } else { + $autocomplete = $this->autocomplete($output, $question, $inputStream, $autocomplete); + $ret = $question->isTrimmable() ? trim($autocomplete) : $autocomplete; + } + + if ($output instanceof ConsoleSectionOutput) { + $output->addContent($ret); + } + + $ret = \strlen($ret) > 0 ? $ret : $question->getDefault(); + + if ($normalizer = $question->getNormalizer()) { + return $normalizer($ret); + } + + return $ret; + } + + /** + * @return mixed + */ + private function getDefaultAnswer(Question $question) + { + $default = $question->getDefault(); + + if (null === $default) { + return $default; + } + + if ($validator = $question->getValidator()) { + return \call_user_func($question->getValidator(), $default); + } elseif ($question instanceof ChoiceQuestion) { + $choices = $question->getChoices(); + + if (!$question->isMultiselect()) { + return $choices[$default] ?? $default; + } + + $default = explode(',', $default); + foreach ($default as $k => $v) { + $v = $question->isTrimmable() ? trim($v) : $v; + $default[$k] = $choices[$v] ?? $v; + } + } + + return $default; + } + + /** + * Outputs the question prompt. + */ + protected function writePrompt(OutputInterface $output, Question $question) + { + $message = $question->getQuestion(); + + if ($question instanceof ChoiceQuestion) { + $output->writeln(array_merge([ + $question->getQuestion(), + ], $this->formatChoiceQuestionChoices($question, 'info'))); + + $message = $question->getPrompt(); + } + + $output->write($message); + } + + /** + * @return string[] + */ + protected function formatChoiceQuestionChoices(ChoiceQuestion $question, string $tag) + { + $messages = []; + + $maxWidth = max(array_map([__CLASS__, 'width'], array_keys($choices = $question->getChoices()))); + + foreach ($choices as $key => $value) { + $padding = str_repeat(' ', $maxWidth - self::width($key)); + + $messages[] = sprintf(" [<$tag>%s$padding] %s", $key, $value); + } + + return $messages; + } + + /** + * Outputs an error message. + */ + protected function writeError(OutputInterface $output, \Exception $error) + { + if (null !== $this->getHelperSet() && $this->getHelperSet()->has('formatter')) { + $message = $this->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error'); + } else { + $message = ''.$error->getMessage().''; + } + + $output->writeln($message); + } + + /** + * Autocompletes a question. + * + * @param resource $inputStream + */ + private function autocomplete(OutputInterface $output, Question $question, $inputStream, callable $autocomplete): string + { + $cursor = new Cursor($output, $inputStream); + + $fullChoice = ''; + $ret = ''; + + $i = 0; + $ofs = -1; + $matches = $autocomplete($ret); + $numMatches = \count($matches); + + $sttyMode = shell_exec('stty -g'); + $isStdin = 'php://stdin' === (stream_get_meta_data($inputStream)['uri'] ?? null); + $r = [$inputStream]; + $w = []; + + // Disable icanon (so we can fread each keypress) and echo (we'll do echoing here instead) + shell_exec('stty -icanon -echo'); + + // Add highlighted text style + $output->getFormatter()->setStyle('hl', new OutputFormatterStyle('black', 'white')); + + // Read a keypress + while (!feof($inputStream)) { + while ($isStdin && 0 === @stream_select($r, $w, $w, 0, 100)) { + // Give signal handlers a chance to run + $r = [$inputStream]; + } + $c = fread($inputStream, 1); + + // as opposed to fgets(), fread() returns an empty string when the stream content is empty, not false. + if (false === $c || ('' === $ret && '' === $c && null === $question->getDefault())) { + shell_exec('stty '.$sttyMode); + throw new MissingInputException('Aborted.'); + } elseif ("\177" === $c) { // Backspace Character + if (0 === $numMatches && 0 !== $i) { + --$i; + $cursor->moveLeft(s($fullChoice)->slice(-1)->width(false)); + + $fullChoice = self::substr($fullChoice, 0, $i); + } + + if (0 === $i) { + $ofs = -1; + $matches = $autocomplete($ret); + $numMatches = \count($matches); + } else { + $numMatches = 0; + } + + // Pop the last character off the end of our string + $ret = self::substr($ret, 0, $i); + } elseif ("\033" === $c) { + // Did we read an escape sequence? + $c .= fread($inputStream, 2); + + // A = Up Arrow. B = Down Arrow + if (isset($c[2]) && ('A' === $c[2] || 'B' === $c[2])) { + if ('A' === $c[2] && -1 === $ofs) { + $ofs = 0; + } + + if (0 === $numMatches) { + continue; + } + + $ofs += ('A' === $c[2]) ? -1 : 1; + $ofs = ($numMatches + $ofs) % $numMatches; + } + } elseif (\ord($c) < 32) { + if ("\t" === $c || "\n" === $c) { + if ($numMatches > 0 && -1 !== $ofs) { + $ret = (string) $matches[$ofs]; + // Echo out remaining chars for current match + $remainingCharacters = substr($ret, \strlen(trim($this->mostRecentlyEnteredValue($fullChoice)))); + $output->write($remainingCharacters); + $fullChoice .= $remainingCharacters; + $i = (false === $encoding = mb_detect_encoding($fullChoice, null, true)) ? \strlen($fullChoice) : mb_strlen($fullChoice, $encoding); + + $matches = array_filter( + $autocomplete($ret), + function ($match) use ($ret) { + return '' === $ret || str_starts_with($match, $ret); + } + ); + $numMatches = \count($matches); + $ofs = -1; + } + + if ("\n" === $c) { + $output->write($c); + break; + } + + $numMatches = 0; + } + + continue; + } else { + if ("\x80" <= $c) { + $c .= fread($inputStream, ["\xC0" => 1, "\xD0" => 1, "\xE0" => 2, "\xF0" => 3][$c & "\xF0"]); + } + + $output->write($c); + $ret .= $c; + $fullChoice .= $c; + ++$i; + + $tempRet = $ret; + + if ($question instanceof ChoiceQuestion && $question->isMultiselect()) { + $tempRet = $this->mostRecentlyEnteredValue($fullChoice); + } + + $numMatches = 0; + $ofs = 0; + + foreach ($autocomplete($ret) as $value) { + // If typed characters match the beginning chunk of value (e.g. [AcmeDe]moBundle) + if (str_starts_with($value, $tempRet)) { + $matches[$numMatches++] = $value; + } + } + } + + $cursor->clearLineAfter(); + + if ($numMatches > 0 && -1 !== $ofs) { + $cursor->savePosition(); + // Write highlighted text, complete the partially entered response + $charactersEntered = \strlen(trim($this->mostRecentlyEnteredValue($fullChoice))); + $output->write(''.OutputFormatter::escapeTrailingBackslash(substr($matches[$ofs], $charactersEntered)).''); + $cursor->restorePosition(); + } + } + + // Reset stty so it behaves normally again + shell_exec('stty '.$sttyMode); + + return $fullChoice; + } + + private function mostRecentlyEnteredValue(string $entered): string + { + // Determine the most recent value that the user entered + if (!str_contains($entered, ',')) { + return $entered; + } + + $choices = explode(',', $entered); + if ('' !== $lastChoice = trim($choices[\count($choices) - 1])) { + return $lastChoice; + } + + return $entered; + } + + /** + * Gets a hidden response from user. + * + * @param resource $inputStream The handler resource + * @param bool $trimmable Is the answer trimmable + * + * @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden + */ + private function getHiddenResponse(OutputInterface $output, $inputStream, bool $trimmable = true): string + { + if ('\\' === \DIRECTORY_SEPARATOR) { + $exe = __DIR__.'/../Resources/bin/hiddeninput.exe'; + + // handle code running from a phar + if ('phar:' === substr(__FILE__, 0, 5)) { + $tmpExe = sys_get_temp_dir().'/hiddeninput.exe'; + copy($exe, $tmpExe); + $exe = $tmpExe; + } + + $sExec = shell_exec('"'.$exe.'"'); + $value = $trimmable ? rtrim($sExec) : $sExec; + $output->writeln(''); + + if (isset($tmpExe)) { + unlink($tmpExe); + } + + return $value; + } + + if (self::$stty && Terminal::hasSttyAvailable()) { + $sttyMode = shell_exec('stty -g'); + shell_exec('stty -echo'); + } elseif ($this->isInteractiveInput($inputStream)) { + throw new RuntimeException('Unable to hide the response.'); + } + + $value = fgets($inputStream, 4096); + + if (self::$stty && Terminal::hasSttyAvailable()) { + shell_exec('stty '.$sttyMode); + } + + if (false === $value) { + throw new MissingInputException('Aborted.'); + } + if ($trimmable) { + $value = trim($value); + } + $output->writeln(''); + + return $value; + } + + /** + * Validates an attempt. + * + * @param callable $interviewer A callable that will ask for a question and return the result + * + * @return mixed The validated response + * + * @throws \Exception In case the max number of attempts has been reached and no valid response has been given + */ + private function validateAttempts(callable $interviewer, OutputInterface $output, Question $question) + { + $error = null; + $attempts = $question->getMaxAttempts(); + + while (null === $attempts || $attempts--) { + if (null !== $error) { + $this->writeError($output, $error); + } + + try { + return $question->getValidator()($interviewer()); + } catch (RuntimeException $e) { + throw $e; + } catch (\Exception $error) { + } + } + + throw $error; + } + + private function isInteractiveInput($inputStream): bool + { + if ('php://stdin' !== (stream_get_meta_data($inputStream)['uri'] ?? null)) { + return false; + } + + if (null !== self::$stdinIsInteractive) { + return self::$stdinIsInteractive; + } + + if (\function_exists('stream_isatty')) { + return self::$stdinIsInteractive = @stream_isatty(fopen('php://stdin', 'r')); + } + + if (\function_exists('posix_isatty')) { + return self::$stdinIsInteractive = @posix_isatty(fopen('php://stdin', 'r')); + } + + if (!\function_exists('shell_exec')) { + return self::$stdinIsInteractive = true; + } + + return self::$stdinIsInteractive = (bool) shell_exec('stty 2> '.('\\' === \DIRECTORY_SEPARATOR ? 'NUL' : '/dev/null')); + } + + /** + * Reads one or more lines of input and returns what is read. + * + * @param resource $inputStream The handler resource + * @param Question $question The question being asked + * + * @return string|false The input received, false in case input could not be read + */ + private function readInput($inputStream, Question $question) + { + if (!$question->isMultiline()) { + $cp = $this->setIOCodepage(); + $ret = fgets($inputStream, 4096); + + return $this->resetIOCodepage($cp, $ret); + } + + $multiLineStreamReader = $this->cloneInputStream($inputStream); + if (null === $multiLineStreamReader) { + return false; + } + + $ret = ''; + $cp = $this->setIOCodepage(); + while (false !== ($char = fgetc($multiLineStreamReader))) { + if (\PHP_EOL === "{$ret}{$char}") { + break; + } + $ret .= $char; + } + + return $this->resetIOCodepage($cp, $ret); + } + + /** + * Sets console I/O to the host code page. + * + * @return int Previous code page in IBM/EBCDIC format + */ + private function setIOCodepage(): int + { + if (\function_exists('sapi_windows_cp_set')) { + $cp = sapi_windows_cp_get(); + sapi_windows_cp_set(sapi_windows_cp_get('oem')); + + return $cp; + } + + return 0; + } + + /** + * Sets console I/O to the specified code page and converts the user input. + * + * @param string|false $input + * + * @return string|false + */ + private function resetIOCodepage(int $cp, $input) + { + if (0 !== $cp) { + sapi_windows_cp_set($cp); + + if (false !== $input && '' !== $input) { + $input = sapi_windows_cp_conv(sapi_windows_cp_get('oem'), $cp, $input); + } + } + + return $input; + } + + /** + * Clones an input stream in order to act on one instance of the same + * stream without affecting the other instance. + * + * @param resource $inputStream The handler resource + * + * @return resource|null The cloned resource, null in case it could not be cloned + */ + private function cloneInputStream($inputStream) + { + $streamMetaData = stream_get_meta_data($inputStream); + $seekable = $streamMetaData['seekable'] ?? false; + $mode = $streamMetaData['mode'] ?? 'rb'; + $uri = $streamMetaData['uri'] ?? null; + + if (null === $uri) { + return null; + } + + $cloneStream = fopen($uri, $mode); + + // For seekable and writable streams, add all the same data to the + // cloned stream and then seek to the same offset. + if (true === $seekable && !\in_array($mode, ['r', 'rb', 'rt'])) { + $offset = ftell($inputStream); + rewind($inputStream); + stream_copy_to_stream($inputStream, $cloneStream); + fseek($inputStream, $offset); + fseek($cloneStream, $offset); + } + + return $cloneStream; + } +} diff --git a/vendor/symfony/console/Helper/SymfonyQuestionHelper.php b/vendor/symfony/console/Helper/SymfonyQuestionHelper.php new file mode 100644 index 0000000..01f94ab --- /dev/null +++ b/vendor/symfony/console/Helper/SymfonyQuestionHelper.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Question\ChoiceQuestion; +use Symfony\Component\Console\Question\ConfirmationQuestion; +use Symfony\Component\Console\Question\Question; +use Symfony\Component\Console\Style\SymfonyStyle; + +/** + * Symfony Style Guide compliant question helper. + * + * @author Kevin Bond + */ +class SymfonyQuestionHelper extends QuestionHelper +{ + /** + * {@inheritdoc} + */ + protected function writePrompt(OutputInterface $output, Question $question) + { + $text = OutputFormatter::escapeTrailingBackslash($question->getQuestion()); + $default = $question->getDefault(); + + if ($question->isMultiline()) { + $text .= sprintf(' (press %s to continue)', $this->getEofShortcut()); + } + + switch (true) { + case null === $default: + $text = sprintf(' %s:', $text); + + break; + + case $question instanceof ConfirmationQuestion: + $text = sprintf(' %s (yes/no) [%s]:', $text, $default ? 'yes' : 'no'); + + break; + + case $question instanceof ChoiceQuestion && $question->isMultiselect(): + $choices = $question->getChoices(); + $default = explode(',', $default); + + foreach ($default as $key => $value) { + $default[$key] = $choices[trim($value)]; + } + + $text = sprintf(' %s [%s]:', $text, OutputFormatter::escape(implode(', ', $default))); + + break; + + case $question instanceof ChoiceQuestion: + $choices = $question->getChoices(); + $text = sprintf(' %s [%s]:', $text, OutputFormatter::escape($choices[$default] ?? $default)); + + break; + + default: + $text = sprintf(' %s [%s]:', $text, OutputFormatter::escape($default)); + } + + $output->writeln($text); + + $prompt = ' > '; + + if ($question instanceof ChoiceQuestion) { + $output->writeln($this->formatChoiceQuestionChoices($question, 'comment')); + + $prompt = $question->getPrompt(); + } + + $output->write($prompt); + } + + /** + * {@inheritdoc} + */ + protected function writeError(OutputInterface $output, \Exception $error) + { + if ($output instanceof SymfonyStyle) { + $output->newLine(); + $output->error($error->getMessage()); + + return; + } + + parent::writeError($output, $error); + } + + private function getEofShortcut(): string + { + if ('Windows' === \PHP_OS_FAMILY) { + return 'Ctrl+Z then Enter'; + } + + return 'Ctrl+D'; + } +} diff --git a/vendor/symfony/console/Helper/Table.php b/vendor/symfony/console/Helper/Table.php new file mode 100644 index 0000000..5c3447a --- /dev/null +++ b/vendor/symfony/console/Helper/Table.php @@ -0,0 +1,915 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\RuntimeException; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Formatter\WrappableOutputFormatterInterface; +use Symfony\Component\Console\Output\ConsoleSectionOutput; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Provides helpers to display a table. + * + * @author Fabien Potencier + * @author Саша Стаменковић + * @author Abdellatif Ait boudad + * @author Max Grigorian + * @author Dany Maillard + */ +class Table +{ + private const SEPARATOR_TOP = 0; + private const SEPARATOR_TOP_BOTTOM = 1; + private const SEPARATOR_MID = 2; + private const SEPARATOR_BOTTOM = 3; + private const BORDER_OUTSIDE = 0; + private const BORDER_INSIDE = 1; + + private $headerTitle; + private $footerTitle; + + /** + * Table headers. + */ + private $headers = []; + + /** + * Table rows. + */ + private $rows = []; + private $horizontal = false; + + /** + * Column widths cache. + */ + private $effectiveColumnWidths = []; + + /** + * Number of columns cache. + * + * @var int + */ + private $numberOfColumns; + + /** + * @var OutputInterface + */ + private $output; + + /** + * @var TableStyle + */ + private $style; + + /** + * @var array + */ + private $columnStyles = []; + + /** + * User set column widths. + * + * @var array + */ + private $columnWidths = []; + private $columnMaxWidths = []; + + /** + * @var array|null + */ + private static $styles; + + private $rendered = false; + + public function __construct(OutputInterface $output) + { + $this->output = $output; + + if (!self::$styles) { + self::$styles = self::initStyles(); + } + + $this->setStyle('default'); + } + + /** + * Sets a style definition. + */ + public static function setStyleDefinition(string $name, TableStyle $style) + { + if (!self::$styles) { + self::$styles = self::initStyles(); + } + + self::$styles[$name] = $style; + } + + /** + * Gets a style definition by name. + * + * @return TableStyle + */ + public static function getStyleDefinition(string $name) + { + if (!self::$styles) { + self::$styles = self::initStyles(); + } + + if (isset(self::$styles[$name])) { + return self::$styles[$name]; + } + + throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name)); + } + + /** + * Sets table style. + * + * @param TableStyle|string $name The style name or a TableStyle instance + * + * @return $this + */ + public function setStyle($name) + { + $this->style = $this->resolveStyle($name); + + return $this; + } + + /** + * Gets the current table style. + * + * @return TableStyle + */ + public function getStyle() + { + return $this->style; + } + + /** + * Sets table column style. + * + * @param TableStyle|string $name The style name or a TableStyle instance + * + * @return $this + */ + public function setColumnStyle(int $columnIndex, $name) + { + $this->columnStyles[$columnIndex] = $this->resolveStyle($name); + + return $this; + } + + /** + * Gets the current style for a column. + * + * If style was not set, it returns the global table style. + * + * @return TableStyle + */ + public function getColumnStyle(int $columnIndex) + { + return $this->columnStyles[$columnIndex] ?? $this->getStyle(); + } + + /** + * Sets the minimum width of a column. + * + * @return $this + */ + public function setColumnWidth(int $columnIndex, int $width) + { + $this->columnWidths[$columnIndex] = $width; + + return $this; + } + + /** + * Sets the minimum width of all columns. + * + * @return $this + */ + public function setColumnWidths(array $widths) + { + $this->columnWidths = []; + foreach ($widths as $index => $width) { + $this->setColumnWidth($index, $width); + } + + return $this; + } + + /** + * Sets the maximum width of a column. + * + * Any cell within this column which contents exceeds the specified width will be wrapped into multiple lines, while + * formatted strings are preserved. + * + * @return $this + */ + public function setColumnMaxWidth(int $columnIndex, int $width): self + { + if (!$this->output->getFormatter() instanceof WrappableOutputFormatterInterface) { + throw new \LogicException(sprintf('Setting a maximum column width is only supported when using a "%s" formatter, got "%s".', WrappableOutputFormatterInterface::class, get_debug_type($this->output->getFormatter()))); + } + + $this->columnMaxWidths[$columnIndex] = $width; + + return $this; + } + + /** + * @return $this + */ + public function setHeaders(array $headers) + { + $headers = array_values($headers); + if (!empty($headers) && !\is_array($headers[0])) { + $headers = [$headers]; + } + + $this->headers = $headers; + + return $this; + } + + public function setRows(array $rows) + { + $this->rows = []; + + return $this->addRows($rows); + } + + /** + * @return $this + */ + public function addRows(array $rows) + { + foreach ($rows as $row) { + $this->addRow($row); + } + + return $this; + } + + /** + * @return $this + */ + public function addRow($row) + { + if ($row instanceof TableSeparator) { + $this->rows[] = $row; + + return $this; + } + + if (!\is_array($row)) { + throw new InvalidArgumentException('A row must be an array or a TableSeparator instance.'); + } + + $this->rows[] = array_values($row); + + return $this; + } + + /** + * Adds a row to the table, and re-renders the table. + * + * @return $this + */ + public function appendRow($row): self + { + if (!$this->output instanceof ConsoleSectionOutput) { + throw new RuntimeException(sprintf('Output should be an instance of "%s" when calling "%s".', ConsoleSectionOutput::class, __METHOD__)); + } + + if ($this->rendered) { + $this->output->clear($this->calculateRowCount()); + } + + $this->addRow($row); + $this->render(); + + return $this; + } + + /** + * @return $this + */ + public function setRow($column, array $row) + { + $this->rows[$column] = $row; + + return $this; + } + + /** + * @return $this + */ + public function setHeaderTitle(?string $title): self + { + $this->headerTitle = $title; + + return $this; + } + + /** + * @return $this + */ + public function setFooterTitle(?string $title): self + { + $this->footerTitle = $title; + + return $this; + } + + /** + * @return $this + */ + public function setHorizontal(bool $horizontal = true): self + { + $this->horizontal = $horizontal; + + return $this; + } + + /** + * Renders table to output. + * + * Example: + * + * +---------------+-----------------------+------------------+ + * | ISBN | Title | Author | + * +---------------+-----------------------+------------------+ + * | 99921-58-10-7 | Divine Comedy | Dante Alighieri | + * | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | + * | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien | + * +---------------+-----------------------+------------------+ + */ + public function render() + { + $divider = new TableSeparator(); + if ($this->horizontal) { + $rows = []; + foreach ($this->headers[0] ?? [] as $i => $header) { + $rows[$i] = [$header]; + foreach ($this->rows as $row) { + if ($row instanceof TableSeparator) { + continue; + } + if (isset($row[$i])) { + $rows[$i][] = $row[$i]; + } elseif ($rows[$i][0] instanceof TableCell && $rows[$i][0]->getColspan() >= 2) { + // Noop, there is a "title" + } else { + $rows[$i][] = null; + } + } + } + } else { + $rows = array_merge($this->headers, [$divider], $this->rows); + } + + $this->calculateNumberOfColumns($rows); + + $rowGroups = $this->buildTableRows($rows); + $this->calculateColumnsWidth($rowGroups); + + $isHeader = !$this->horizontal; + $isFirstRow = $this->horizontal; + $hasTitle = (bool) $this->headerTitle; + + foreach ($rowGroups as $rowGroup) { + $isHeaderSeparatorRendered = false; + + foreach ($rowGroup as $row) { + if ($divider === $row) { + $isHeader = false; + $isFirstRow = true; + + continue; + } + + if ($row instanceof TableSeparator) { + $this->renderRowSeparator(); + + continue; + } + + if (!$row) { + continue; + } + + if ($isHeader && !$isHeaderSeparatorRendered) { + $this->renderRowSeparator( + $isHeader ? self::SEPARATOR_TOP : self::SEPARATOR_TOP_BOTTOM, + $hasTitle ? $this->headerTitle : null, + $hasTitle ? $this->style->getHeaderTitleFormat() : null + ); + $hasTitle = false; + $isHeaderSeparatorRendered = true; + } + + if ($isFirstRow) { + $this->renderRowSeparator( + $isHeader ? self::SEPARATOR_TOP : self::SEPARATOR_TOP_BOTTOM, + $hasTitle ? $this->headerTitle : null, + $hasTitle ? $this->style->getHeaderTitleFormat() : null + ); + $isFirstRow = false; + $hasTitle = false; + } + + if ($this->horizontal) { + $this->renderRow($row, $this->style->getCellRowFormat(), $this->style->getCellHeaderFormat()); + } else { + $this->renderRow($row, $isHeader ? $this->style->getCellHeaderFormat() : $this->style->getCellRowFormat()); + } + } + } + $this->renderRowSeparator(self::SEPARATOR_BOTTOM, $this->footerTitle, $this->style->getFooterTitleFormat()); + + $this->cleanup(); + $this->rendered = true; + } + + /** + * Renders horizontal header separator. + * + * Example: + * + * +-----+-----------+-------+ + */ + private function renderRowSeparator(int $type = self::SEPARATOR_MID, string $title = null, string $titleFormat = null) + { + if (0 === $count = $this->numberOfColumns) { + return; + } + + $borders = $this->style->getBorderChars(); + if (!$borders[0] && !$borders[2] && !$this->style->getCrossingChar()) { + return; + } + + $crossings = $this->style->getCrossingChars(); + if (self::SEPARATOR_MID === $type) { + [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[2], $crossings[8], $crossings[0], $crossings[4]]; + } elseif (self::SEPARATOR_TOP === $type) { + [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[1], $crossings[2], $crossings[3]]; + } elseif (self::SEPARATOR_TOP_BOTTOM === $type) { + [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[9], $crossings[10], $crossings[11]]; + } else { + [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[7], $crossings[6], $crossings[5]]; + } + + $markup = $leftChar; + for ($column = 0; $column < $count; ++$column) { + $markup .= str_repeat($horizontal, $this->effectiveColumnWidths[$column]); + $markup .= $column === $count - 1 ? $rightChar : $midChar; + } + + if (null !== $title) { + $titleLength = Helper::width(Helper::removeDecoration($formatter = $this->output->getFormatter(), $formattedTitle = sprintf($titleFormat, $title))); + $markupLength = Helper::width($markup); + if ($titleLength > $limit = $markupLength - 4) { + $titleLength = $limit; + $formatLength = Helper::width(Helper::removeDecoration($formatter, sprintf($titleFormat, ''))); + $formattedTitle = sprintf($titleFormat, Helper::substr($title, 0, $limit - $formatLength - 3).'...'); + } + + $titleStart = intdiv($markupLength - $titleLength, 2); + if (false === mb_detect_encoding($markup, null, true)) { + $markup = substr_replace($markup, $formattedTitle, $titleStart, $titleLength); + } else { + $markup = mb_substr($markup, 0, $titleStart).$formattedTitle.mb_substr($markup, $titleStart + $titleLength); + } + } + + $this->output->writeln(sprintf($this->style->getBorderFormat(), $markup)); + } + + /** + * Renders vertical column separator. + */ + private function renderColumnSeparator(int $type = self::BORDER_OUTSIDE): string + { + $borders = $this->style->getBorderChars(); + + return sprintf($this->style->getBorderFormat(), self::BORDER_OUTSIDE === $type ? $borders[1] : $borders[3]); + } + + /** + * Renders table row. + * + * Example: + * + * | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | + */ + private function renderRow(array $row, string $cellFormat, string $firstCellFormat = null) + { + $rowContent = $this->renderColumnSeparator(self::BORDER_OUTSIDE); + $columns = $this->getRowColumns($row); + $last = \count($columns) - 1; + foreach ($columns as $i => $column) { + if ($firstCellFormat && 0 === $i) { + $rowContent .= $this->renderCell($row, $column, $firstCellFormat); + } else { + $rowContent .= $this->renderCell($row, $column, $cellFormat); + } + $rowContent .= $this->renderColumnSeparator($last === $i ? self::BORDER_OUTSIDE : self::BORDER_INSIDE); + } + $this->output->writeln($rowContent); + } + + /** + * Renders table cell with padding. + */ + private function renderCell(array $row, int $column, string $cellFormat): string + { + $cell = $row[$column] ?? ''; + $width = $this->effectiveColumnWidths[$column]; + if ($cell instanceof TableCell && $cell->getColspan() > 1) { + // add the width of the following columns(numbers of colspan). + foreach (range($column + 1, $column + $cell->getColspan() - 1) as $nextColumn) { + $width += $this->getColumnSeparatorWidth() + $this->effectiveColumnWidths[$nextColumn]; + } + } + + // str_pad won't work properly with multi-byte strings, we need to fix the padding + if (false !== $encoding = mb_detect_encoding($cell, null, true)) { + $width += \strlen($cell) - mb_strwidth($cell, $encoding); + } + + $style = $this->getColumnStyle($column); + + if ($cell instanceof TableSeparator) { + return sprintf($style->getBorderFormat(), str_repeat($style->getBorderChars()[2], $width)); + } + + $width += Helper::length($cell) - Helper::length(Helper::removeDecoration($this->output->getFormatter(), $cell)); + $content = sprintf($style->getCellRowContentFormat(), $cell); + + $padType = $style->getPadType(); + if ($cell instanceof TableCell && $cell->getStyle() instanceof TableCellStyle) { + $isNotStyledByTag = !preg_match('/^<(\w+|(\w+=[\w,]+;?)*)>.+<\/(\w+|(\w+=\w+;?)*)?>$/', $cell); + if ($isNotStyledByTag) { + $cellFormat = $cell->getStyle()->getCellFormat(); + if (!\is_string($cellFormat)) { + $tag = http_build_query($cell->getStyle()->getTagOptions(), '', ';'); + $cellFormat = '<'.$tag.'>%s'; + } + + if (strstr($content, '')) { + $content = str_replace('', '', $content); + $width -= 3; + } + if (strstr($content, '')) { + $content = str_replace('', '', $content); + $width -= \strlen(''); + } + } + + $padType = $cell->getStyle()->getPadByAlign(); + } + + return sprintf($cellFormat, str_pad($content, $width, $style->getPaddingChar(), $padType)); + } + + /** + * Calculate number of columns for this table. + */ + private function calculateNumberOfColumns(array $rows) + { + $columns = [0]; + foreach ($rows as $row) { + if ($row instanceof TableSeparator) { + continue; + } + + $columns[] = $this->getNumberOfColumns($row); + } + + $this->numberOfColumns = max($columns); + } + + private function buildTableRows(array $rows): TableRows + { + /** @var WrappableOutputFormatterInterface $formatter */ + $formatter = $this->output->getFormatter(); + $unmergedRows = []; + for ($rowKey = 0; $rowKey < \count($rows); ++$rowKey) { + $rows = $this->fillNextRows($rows, $rowKey); + + // Remove any new line breaks and replace it with a new line + foreach ($rows[$rowKey] as $column => $cell) { + $colspan = $cell instanceof TableCell ? $cell->getColspan() : 1; + + if (isset($this->columnMaxWidths[$column]) && Helper::width(Helper::removeDecoration($formatter, $cell)) > $this->columnMaxWidths[$column]) { + $cell = $formatter->formatAndWrap($cell, $this->columnMaxWidths[$column] * $colspan); + } + if (!strstr($cell ?? '', "\n")) { + continue; + } + $escaped = implode("\n", array_map([OutputFormatter::class, 'escapeTrailingBackslash'], explode("\n", $cell))); + $cell = $cell instanceof TableCell ? new TableCell($escaped, ['colspan' => $cell->getColspan()]) : $escaped; + $lines = explode("\n", str_replace("\n", "\n", $cell)); + foreach ($lines as $lineKey => $line) { + if ($colspan > 1) { + $line = new TableCell($line, ['colspan' => $colspan]); + } + if (0 === $lineKey) { + $rows[$rowKey][$column] = $line; + } else { + if (!\array_key_exists($rowKey, $unmergedRows) || !\array_key_exists($lineKey, $unmergedRows[$rowKey])) { + $unmergedRows[$rowKey][$lineKey] = $this->copyRow($rows, $rowKey); + } + $unmergedRows[$rowKey][$lineKey][$column] = $line; + } + } + } + } + + return new TableRows(function () use ($rows, $unmergedRows): \Traversable { + foreach ($rows as $rowKey => $row) { + $rowGroup = [$row instanceof TableSeparator ? $row : $this->fillCells($row)]; + + if (isset($unmergedRows[$rowKey])) { + foreach ($unmergedRows[$rowKey] as $row) { + $rowGroup[] = $row instanceof TableSeparator ? $row : $this->fillCells($row); + } + } + yield $rowGroup; + } + }); + } + + private function calculateRowCount(): int + { + $numberOfRows = \count(iterator_to_array($this->buildTableRows(array_merge($this->headers, [new TableSeparator()], $this->rows)))); + + if ($this->headers) { + ++$numberOfRows; // Add row for header separator + } + + if (\count($this->rows) > 0) { + ++$numberOfRows; // Add row for footer separator + } + + return $numberOfRows; + } + + /** + * fill rows that contains rowspan > 1. + * + * @throws InvalidArgumentException + */ + private function fillNextRows(array $rows, int $line): array + { + $unmergedRows = []; + foreach ($rows[$line] as $column => $cell) { + if (null !== $cell && !$cell instanceof TableCell && !\is_scalar($cell) && !(\is_object($cell) && method_exists($cell, '__toString'))) { + throw new InvalidArgumentException(sprintf('A cell must be a TableCell, a scalar or an object implementing "__toString()", "%s" given.', get_debug_type($cell))); + } + if ($cell instanceof TableCell && $cell->getRowspan() > 1) { + $nbLines = $cell->getRowspan() - 1; + $lines = [$cell]; + if (strstr($cell, "\n")) { + $lines = explode("\n", str_replace("\n", "\n", $cell)); + $nbLines = \count($lines) > $nbLines ? substr_count($cell, "\n") : $nbLines; + + $rows[$line][$column] = new TableCell($lines[0], ['colspan' => $cell->getColspan(), 'style' => $cell->getStyle()]); + unset($lines[0]); + } + + // create a two dimensional array (rowspan x colspan) + $unmergedRows = array_replace_recursive(array_fill($line + 1, $nbLines, []), $unmergedRows); + foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) { + $value = $lines[$unmergedRowKey - $line] ?? ''; + $unmergedRows[$unmergedRowKey][$column] = new TableCell($value, ['colspan' => $cell->getColspan(), 'style' => $cell->getStyle()]); + if ($nbLines === $unmergedRowKey - $line) { + break; + } + } + } + } + + foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) { + // we need to know if $unmergedRow will be merged or inserted into $rows + if (isset($rows[$unmergedRowKey]) && \is_array($rows[$unmergedRowKey]) && ($this->getNumberOfColumns($rows[$unmergedRowKey]) + $this->getNumberOfColumns($unmergedRows[$unmergedRowKey]) <= $this->numberOfColumns)) { + foreach ($unmergedRow as $cellKey => $cell) { + // insert cell into row at cellKey position + array_splice($rows[$unmergedRowKey], $cellKey, 0, [$cell]); + } + } else { + $row = $this->copyRow($rows, $unmergedRowKey - 1); + foreach ($unmergedRow as $column => $cell) { + if (!empty($cell)) { + $row[$column] = $unmergedRow[$column]; + } + } + array_splice($rows, $unmergedRowKey, 0, [$row]); + } + } + + return $rows; + } + + /** + * fill cells for a row that contains colspan > 1. + */ + private function fillCells(iterable $row) + { + $newRow = []; + + foreach ($row as $column => $cell) { + $newRow[] = $cell; + if ($cell instanceof TableCell && $cell->getColspan() > 1) { + foreach (range($column + 1, $column + $cell->getColspan() - 1) as $position) { + // insert empty value at column position + $newRow[] = ''; + } + } + } + + return $newRow ?: $row; + } + + private function copyRow(array $rows, int $line): array + { + $row = $rows[$line]; + foreach ($row as $cellKey => $cellValue) { + $row[$cellKey] = ''; + if ($cellValue instanceof TableCell) { + $row[$cellKey] = new TableCell('', ['colspan' => $cellValue->getColspan()]); + } + } + + return $row; + } + + /** + * Gets number of columns by row. + */ + private function getNumberOfColumns(array $row): int + { + $columns = \count($row); + foreach ($row as $column) { + $columns += $column instanceof TableCell ? ($column->getColspan() - 1) : 0; + } + + return $columns; + } + + /** + * Gets list of columns for the given row. + */ + private function getRowColumns(array $row): array + { + $columns = range(0, $this->numberOfColumns - 1); + foreach ($row as $cellKey => $cell) { + if ($cell instanceof TableCell && $cell->getColspan() > 1) { + // exclude grouped columns. + $columns = array_diff($columns, range($cellKey + 1, $cellKey + $cell->getColspan() - 1)); + } + } + + return $columns; + } + + /** + * Calculates columns widths. + */ + private function calculateColumnsWidth(iterable $groups) + { + for ($column = 0; $column < $this->numberOfColumns; ++$column) { + $lengths = []; + foreach ($groups as $group) { + foreach ($group as $row) { + if ($row instanceof TableSeparator) { + continue; + } + + foreach ($row as $i => $cell) { + if ($cell instanceof TableCell) { + $textContent = Helper::removeDecoration($this->output->getFormatter(), $cell); + $textLength = Helper::width($textContent); + if ($textLength > 0) { + $contentColumns = mb_str_split($textContent, ceil($textLength / $cell->getColspan())); + foreach ($contentColumns as $position => $content) { + $row[$i + $position] = $content; + } + } + } + } + + $lengths[] = $this->getCellWidth($row, $column); + } + } + + $this->effectiveColumnWidths[$column] = max($lengths) + Helper::width($this->style->getCellRowContentFormat()) - 2; + } + } + + private function getColumnSeparatorWidth(): int + { + return Helper::width(sprintf($this->style->getBorderFormat(), $this->style->getBorderChars()[3])); + } + + private function getCellWidth(array $row, int $column): int + { + $cellWidth = 0; + + if (isset($row[$column])) { + $cell = $row[$column]; + $cellWidth = Helper::width(Helper::removeDecoration($this->output->getFormatter(), $cell)); + } + + $columnWidth = $this->columnWidths[$column] ?? 0; + $cellWidth = max($cellWidth, $columnWidth); + + return isset($this->columnMaxWidths[$column]) ? min($this->columnMaxWidths[$column], $cellWidth) : $cellWidth; + } + + /** + * Called after rendering to cleanup cache data. + */ + private function cleanup() + { + $this->effectiveColumnWidths = []; + $this->numberOfColumns = null; + } + + /** + * @return array + */ + private static function initStyles(): array + { + $borderless = new TableStyle(); + $borderless + ->setHorizontalBorderChars('=') + ->setVerticalBorderChars(' ') + ->setDefaultCrossingChar(' ') + ; + + $compact = new TableStyle(); + $compact + ->setHorizontalBorderChars('') + ->setVerticalBorderChars('') + ->setDefaultCrossingChar('') + ->setCellRowContentFormat('%s ') + ; + + $styleGuide = new TableStyle(); + $styleGuide + ->setHorizontalBorderChars('-') + ->setVerticalBorderChars(' ') + ->setDefaultCrossingChar(' ') + ->setCellHeaderFormat('%s') + ; + + $box = (new TableStyle()) + ->setHorizontalBorderChars('─') + ->setVerticalBorderChars('│') + ->setCrossingChars('┼', '┌', '┬', '┐', '┤', '┘', '┴', '└', '├') + ; + + $boxDouble = (new TableStyle()) + ->setHorizontalBorderChars('═', '─') + ->setVerticalBorderChars('║', '│') + ->setCrossingChars('┼', '╔', '╤', '╗', '╢', '╝', '╧', '╚', '╟', '╠', '╪', '╣') + ; + + return [ + 'default' => new TableStyle(), + 'borderless' => $borderless, + 'compact' => $compact, + 'symfony-style-guide' => $styleGuide, + 'box' => $box, + 'box-double' => $boxDouble, + ]; + } + + private function resolveStyle($name): TableStyle + { + if ($name instanceof TableStyle) { + return $name; + } + + if (isset(self::$styles[$name])) { + return self::$styles[$name]; + } + + throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name)); + } +} diff --git a/vendor/symfony/console/Helper/TableCell.php b/vendor/symfony/console/Helper/TableCell.php new file mode 100644 index 0000000..1a7bc6e --- /dev/null +++ b/vendor/symfony/console/Helper/TableCell.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Exception\InvalidArgumentException; + +/** + * @author Abdellatif Ait boudad + */ +class TableCell +{ + private $value; + private $options = [ + 'rowspan' => 1, + 'colspan' => 1, + 'style' => null, + ]; + + public function __construct(string $value = '', array $options = []) + { + $this->value = $value; + + // check option names + if ($diff = array_diff(array_keys($options), array_keys($this->options))) { + throw new InvalidArgumentException(sprintf('The TableCell does not support the following options: \'%s\'.', implode('\', \'', $diff))); + } + + if (isset($options['style']) && !$options['style'] instanceof TableCellStyle) { + throw new InvalidArgumentException('The style option must be an instance of "TableCellStyle".'); + } + + $this->options = array_merge($this->options, $options); + } + + /** + * Returns the cell value. + * + * @return string + */ + public function __toString() + { + return $this->value; + } + + /** + * Gets number of colspan. + * + * @return int + */ + public function getColspan() + { + return (int) $this->options['colspan']; + } + + /** + * Gets number of rowspan. + * + * @return int + */ + public function getRowspan() + { + return (int) $this->options['rowspan']; + } + + public function getStyle(): ?TableCellStyle + { + return $this->options['style']; + } +} diff --git a/vendor/symfony/console/Helper/TableCellStyle.php b/vendor/symfony/console/Helper/TableCellStyle.php new file mode 100644 index 0000000..19cd0ff --- /dev/null +++ b/vendor/symfony/console/Helper/TableCellStyle.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Exception\InvalidArgumentException; + +/** + * @author Yewhen Khoptynskyi + */ +class TableCellStyle +{ + public const DEFAULT_ALIGN = 'left'; + + private const TAG_OPTIONS = [ + 'fg', + 'bg', + 'options', + ]; + + private const ALIGN_MAP = [ + 'left' => \STR_PAD_RIGHT, + 'center' => \STR_PAD_BOTH, + 'right' => \STR_PAD_LEFT, + ]; + + private $options = [ + 'fg' => 'default', + 'bg' => 'default', + 'options' => null, + 'align' => self::DEFAULT_ALIGN, + 'cellFormat' => null, + ]; + + public function __construct(array $options = []) + { + if ($diff = array_diff(array_keys($options), array_keys($this->options))) { + throw new InvalidArgumentException(sprintf('The TableCellStyle does not support the following options: \'%s\'.', implode('\', \'', $diff))); + } + + if (isset($options['align']) && !\array_key_exists($options['align'], self::ALIGN_MAP)) { + throw new InvalidArgumentException(sprintf('Wrong align value. Value must be following: \'%s\'.', implode('\', \'', array_keys(self::ALIGN_MAP)))); + } + + $this->options = array_merge($this->options, $options); + } + + public function getOptions(): array + { + return $this->options; + } + + /** + * Gets options we need for tag for example fg, bg. + * + * @return string[] + */ + public function getTagOptions() + { + return array_filter( + $this->getOptions(), + function ($key) { + return \in_array($key, self::TAG_OPTIONS) && isset($this->options[$key]); + }, + \ARRAY_FILTER_USE_KEY + ); + } + + /** + * @return int + */ + public function getPadByAlign() + { + return self::ALIGN_MAP[$this->getOptions()['align']]; + } + + public function getCellFormat(): ?string + { + return $this->getOptions()['cellFormat']; + } +} diff --git a/vendor/symfony/console/Helper/TableRows.php b/vendor/symfony/console/Helper/TableRows.php new file mode 100644 index 0000000..cbc07d2 --- /dev/null +++ b/vendor/symfony/console/Helper/TableRows.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +/** + * @internal + */ +class TableRows implements \IteratorAggregate +{ + private $generator; + + public function __construct(\Closure $generator) + { + $this->generator = $generator; + } + + public function getIterator(): \Traversable + { + return ($this->generator)(); + } +} diff --git a/vendor/symfony/console/Helper/TableSeparator.php b/vendor/symfony/console/Helper/TableSeparator.php new file mode 100644 index 0000000..e541c53 --- /dev/null +++ b/vendor/symfony/console/Helper/TableSeparator.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 Symfony\Component\Console\Helper; + +/** + * Marks a row as being a separator. + * + * @author Fabien Potencier + */ +class TableSeparator extends TableCell +{ + public function __construct(array $options = []) + { + parent::__construct('', $options); + } +} diff --git a/vendor/symfony/console/Helper/TableStyle.php b/vendor/symfony/console/Helper/TableStyle.php new file mode 100644 index 0000000..dfc41e6 --- /dev/null +++ b/vendor/symfony/console/Helper/TableStyle.php @@ -0,0 +1,376 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\LogicException; + +/** + * Defines the styles for a Table. + * + * @author Fabien Potencier + * @author Саша Стаменковић + * @author Dany Maillard + */ +class TableStyle +{ + private $paddingChar = ' '; + private $horizontalOutsideBorderChar = '-'; + private $horizontalInsideBorderChar = '-'; + private $verticalOutsideBorderChar = '|'; + private $verticalInsideBorderChar = '|'; + private $crossingChar = '+'; + private $crossingTopRightChar = '+'; + private $crossingTopMidChar = '+'; + private $crossingTopLeftChar = '+'; + private $crossingMidRightChar = '+'; + private $crossingBottomRightChar = '+'; + private $crossingBottomMidChar = '+'; + private $crossingBottomLeftChar = '+'; + private $crossingMidLeftChar = '+'; + private $crossingTopLeftBottomChar = '+'; + private $crossingTopMidBottomChar = '+'; + private $crossingTopRightBottomChar = '+'; + private $headerTitleFormat = ' %s '; + private $footerTitleFormat = ' %s '; + private $cellHeaderFormat = '%s'; + private $cellRowFormat = '%s'; + private $cellRowContentFormat = ' %s '; + private $borderFormat = '%s'; + private $padType = \STR_PAD_RIGHT; + + /** + * Sets padding character, used for cell padding. + * + * @return $this + */ + public function setPaddingChar(string $paddingChar) + { + if (!$paddingChar) { + throw new LogicException('The padding char must not be empty.'); + } + + $this->paddingChar = $paddingChar; + + return $this; + } + + /** + * Gets padding character, used for cell padding. + * + * @return string + */ + public function getPaddingChar() + { + return $this->paddingChar; + } + + /** + * Sets horizontal border characters. + * + * + * ╔═══════════════╤══════════════════════════╤══════════════════╗ + * 1 ISBN 2 Title │ Author ║ + * ╠═══════════════╪══════════════════════════╪══════════════════╣ + * ║ 99921-58-10-7 │ Divine Comedy │ Dante Alighieri ║ + * ║ 9971-5-0210-0 │ A Tale of Two Cities │ Charles Dickens ║ + * ║ 960-425-059-0 │ The Lord of the Rings │ J. R. R. Tolkien ║ + * ║ 80-902734-1-6 │ And Then There Were None │ Agatha Christie ║ + * ╚═══════════════╧══════════════════════════╧══════════════════╝ + * + * + * @return $this + */ + public function setHorizontalBorderChars(string $outside, string $inside = null): self + { + $this->horizontalOutsideBorderChar = $outside; + $this->horizontalInsideBorderChar = $inside ?? $outside; + + return $this; + } + + /** + * Sets vertical border characters. + * + * + * ╔═══════════════╤══════════════════════════╤══════════════════╗ + * ║ ISBN │ Title │ Author ║ + * ╠═══════1═══════╪══════════════════════════╪══════════════════╣ + * ║ 99921-58-10-7 │ Divine Comedy │ Dante Alighieri ║ + * ║ 9971-5-0210-0 │ A Tale of Two Cities │ Charles Dickens ║ + * ╟───────2───────┼──────────────────────────┼──────────────────╢ + * ║ 960-425-059-0 │ The Lord of the Rings │ J. R. R. Tolkien ║ + * ║ 80-902734-1-6 │ And Then There Were None │ Agatha Christie ║ + * ╚═══════════════╧══════════════════════════╧══════════════════╝ + * + * + * @return $this + */ + public function setVerticalBorderChars(string $outside, string $inside = null): self + { + $this->verticalOutsideBorderChar = $outside; + $this->verticalInsideBorderChar = $inside ?? $outside; + + return $this; + } + + /** + * Gets border characters. + * + * @internal + */ + public function getBorderChars(): array + { + return [ + $this->horizontalOutsideBorderChar, + $this->verticalOutsideBorderChar, + $this->horizontalInsideBorderChar, + $this->verticalInsideBorderChar, + ]; + } + + /** + * Sets crossing characters. + * + * Example: + * + * 1═══════════════2══════════════════════════2══════════════════3 + * ║ ISBN │ Title │ Author ║ + * 8'══════════════0'═════════════════════════0'═════════════════4' + * ║ 99921-58-10-7 │ Divine Comedy │ Dante Alighieri ║ + * ║ 9971-5-0210-0 │ A Tale of Two Cities │ Charles Dickens ║ + * 8───────────────0──────────────────────────0──────────────────4 + * ║ 960-425-059-0 │ The Lord of the Rings │ J. R. R. Tolkien ║ + * ║ 80-902734-1-6 │ And Then There Were None │ Agatha Christie ║ + * 7═══════════════6══════════════════════════6══════════════════5 + * + * + * @param string $cross Crossing char (see #0 of example) + * @param string $topLeft Top left char (see #1 of example) + * @param string $topMid Top mid char (see #2 of example) + * @param string $topRight Top right char (see #3 of example) + * @param string $midRight Mid right char (see #4 of example) + * @param string $bottomRight Bottom right char (see #5 of example) + * @param string $bottomMid Bottom mid char (see #6 of example) + * @param string $bottomLeft Bottom left char (see #7 of example) + * @param string $midLeft Mid left char (see #8 of example) + * @param string|null $topLeftBottom Top left bottom char (see #8' of example), equals to $midLeft if null + * @param string|null $topMidBottom Top mid bottom char (see #0' of example), equals to $cross if null + * @param string|null $topRightBottom Top right bottom char (see #4' of example), equals to $midRight if null + * + * @return $this + */ + public function setCrossingChars(string $cross, string $topLeft, string $topMid, string $topRight, string $midRight, string $bottomRight, string $bottomMid, string $bottomLeft, string $midLeft, string $topLeftBottom = null, string $topMidBottom = null, string $topRightBottom = null): self + { + $this->crossingChar = $cross; + $this->crossingTopLeftChar = $topLeft; + $this->crossingTopMidChar = $topMid; + $this->crossingTopRightChar = $topRight; + $this->crossingMidRightChar = $midRight; + $this->crossingBottomRightChar = $bottomRight; + $this->crossingBottomMidChar = $bottomMid; + $this->crossingBottomLeftChar = $bottomLeft; + $this->crossingMidLeftChar = $midLeft; + $this->crossingTopLeftBottomChar = $topLeftBottom ?? $midLeft; + $this->crossingTopMidBottomChar = $topMidBottom ?? $cross; + $this->crossingTopRightBottomChar = $topRightBottom ?? $midRight; + + return $this; + } + + /** + * Sets default crossing character used for each cross. + * + * @see {@link setCrossingChars()} for setting each crossing individually. + */ + public function setDefaultCrossingChar(string $char): self + { + return $this->setCrossingChars($char, $char, $char, $char, $char, $char, $char, $char, $char); + } + + /** + * Gets crossing character. + * + * @return string + */ + public function getCrossingChar() + { + return $this->crossingChar; + } + + /** + * Gets crossing characters. + * + * @internal + */ + public function getCrossingChars(): array + { + return [ + $this->crossingChar, + $this->crossingTopLeftChar, + $this->crossingTopMidChar, + $this->crossingTopRightChar, + $this->crossingMidRightChar, + $this->crossingBottomRightChar, + $this->crossingBottomMidChar, + $this->crossingBottomLeftChar, + $this->crossingMidLeftChar, + $this->crossingTopLeftBottomChar, + $this->crossingTopMidBottomChar, + $this->crossingTopRightBottomChar, + ]; + } + + /** + * Sets header cell format. + * + * @return $this + */ + public function setCellHeaderFormat(string $cellHeaderFormat) + { + $this->cellHeaderFormat = $cellHeaderFormat; + + return $this; + } + + /** + * Gets header cell format. + * + * @return string + */ + public function getCellHeaderFormat() + { + return $this->cellHeaderFormat; + } + + /** + * Sets row cell format. + * + * @return $this + */ + public function setCellRowFormat(string $cellRowFormat) + { + $this->cellRowFormat = $cellRowFormat; + + return $this; + } + + /** + * Gets row cell format. + * + * @return string + */ + public function getCellRowFormat() + { + return $this->cellRowFormat; + } + + /** + * Sets row cell content format. + * + * @return $this + */ + public function setCellRowContentFormat(string $cellRowContentFormat) + { + $this->cellRowContentFormat = $cellRowContentFormat; + + return $this; + } + + /** + * Gets row cell content format. + * + * @return string + */ + public function getCellRowContentFormat() + { + return $this->cellRowContentFormat; + } + + /** + * Sets table border format. + * + * @return $this + */ + public function setBorderFormat(string $borderFormat) + { + $this->borderFormat = $borderFormat; + + return $this; + } + + /** + * Gets table border format. + * + * @return string + */ + public function getBorderFormat() + { + return $this->borderFormat; + } + + /** + * Sets cell padding type. + * + * @return $this + */ + public function setPadType(int $padType) + { + if (!\in_array($padType, [\STR_PAD_LEFT, \STR_PAD_RIGHT, \STR_PAD_BOTH], true)) { + throw new InvalidArgumentException('Invalid padding type. Expected one of (STR_PAD_LEFT, STR_PAD_RIGHT, STR_PAD_BOTH).'); + } + + $this->padType = $padType; + + return $this; + } + + /** + * Gets cell padding type. + * + * @return int + */ + public function getPadType() + { + return $this->padType; + } + + public function getHeaderTitleFormat(): string + { + return $this->headerTitleFormat; + } + + /** + * @return $this + */ + public function setHeaderTitleFormat(string $format): self + { + $this->headerTitleFormat = $format; + + return $this; + } + + public function getFooterTitleFormat(): string + { + return $this->footerTitleFormat; + } + + /** + * @return $this + */ + public function setFooterTitleFormat(string $format): self + { + $this->footerTitleFormat = $format; + + return $this; + } +} diff --git a/vendor/symfony/console/Input/ArgvInput.php b/vendor/symfony/console/Input/ArgvInput.php new file mode 100644 index 0000000..675b9ef --- /dev/null +++ b/vendor/symfony/console/Input/ArgvInput.php @@ -0,0 +1,378 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +use Symfony\Component\Console\Exception\RuntimeException; + +/** + * ArgvInput represents an input coming from the CLI arguments. + * + * Usage: + * + * $input = new ArgvInput(); + * + * By default, the `$_SERVER['argv']` array is used for the input values. + * + * This can be overridden by explicitly passing the input values in the constructor: + * + * $input = new ArgvInput($_SERVER['argv']); + * + * If you pass it yourself, don't forget that the first element of the array + * is the name of the running application. + * + * When passing an argument to the constructor, be sure that it respects + * the same rules as the argv one. It's almost always better to use the + * `StringInput` when you want to provide your own input. + * + * @author Fabien Potencier + * + * @see http://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html + * @see http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap12.html#tag_12_02 + */ +class ArgvInput extends Input +{ + private $tokens; + private $parsed; + + public function __construct(array $argv = null, InputDefinition $definition = null) + { + $argv = $argv ?? $_SERVER['argv'] ?? []; + + // strip the application name + array_shift($argv); + + $this->tokens = $argv; + + parent::__construct($definition); + } + + protected function setTokens(array $tokens) + { + $this->tokens = $tokens; + } + + /** + * {@inheritdoc} + */ + protected function parse() + { + $parseOptions = true; + $this->parsed = $this->tokens; + while (null !== $token = array_shift($this->parsed)) { + $parseOptions = $this->parseToken($token, $parseOptions); + } + } + + protected function parseToken(string $token, bool $parseOptions): bool + { + if ($parseOptions && '' == $token) { + $this->parseArgument($token); + } elseif ($parseOptions && '--' == $token) { + return false; + } elseif ($parseOptions && str_starts_with($token, '--')) { + $this->parseLongOption($token); + } elseif ($parseOptions && '-' === $token[0] && '-' !== $token) { + $this->parseShortOption($token); + } else { + $this->parseArgument($token); + } + + return $parseOptions; + } + + /** + * Parses a short option. + */ + private function parseShortOption(string $token) + { + $name = substr($token, 1); + + if (\strlen($name) > 1) { + if ($this->definition->hasShortcut($name[0]) && $this->definition->getOptionForShortcut($name[0])->acceptValue()) { + // an option with a value (with no space) + $this->addShortOption($name[0], substr($name, 1)); + } else { + $this->parseShortOptionSet($name); + } + } else { + $this->addShortOption($name, null); + } + } + + /** + * Parses a short option set. + * + * @throws RuntimeException When option given doesn't exist + */ + private function parseShortOptionSet(string $name) + { + $len = \strlen($name); + for ($i = 0; $i < $len; ++$i) { + if (!$this->definition->hasShortcut($name[$i])) { + $encoding = mb_detect_encoding($name, null, true); + throw new RuntimeException(sprintf('The "-%s" option does not exist.', false === $encoding ? $name[$i] : mb_substr($name, $i, 1, $encoding))); + } + + $option = $this->definition->getOptionForShortcut($name[$i]); + if ($option->acceptValue()) { + $this->addLongOption($option->getName(), $i === $len - 1 ? null : substr($name, $i + 1)); + + break; + } else { + $this->addLongOption($option->getName(), null); + } + } + } + + /** + * Parses a long option. + */ + private function parseLongOption(string $token) + { + $name = substr($token, 2); + + if (false !== $pos = strpos($name, '=')) { + if ('' === $value = substr($name, $pos + 1)) { + array_unshift($this->parsed, $value); + } + $this->addLongOption(substr($name, 0, $pos), $value); + } else { + $this->addLongOption($name, null); + } + } + + /** + * Parses an argument. + * + * @throws RuntimeException When too many arguments are given + */ + private function parseArgument(string $token) + { + $c = \count($this->arguments); + + // if input is expecting another argument, add it + if ($this->definition->hasArgument($c)) { + $arg = $this->definition->getArgument($c); + $this->arguments[$arg->getName()] = $arg->isArray() ? [$token] : $token; + + // if last argument isArray(), append token to last argument + } elseif ($this->definition->hasArgument($c - 1) && $this->definition->getArgument($c - 1)->isArray()) { + $arg = $this->definition->getArgument($c - 1); + $this->arguments[$arg->getName()][] = $token; + + // unexpected argument + } else { + $all = $this->definition->getArguments(); + $symfonyCommandName = null; + if (($inputArgument = $all[$key = array_key_first($all)] ?? null) && 'command' === $inputArgument->getName()) { + $symfonyCommandName = $this->arguments['command'] ?? null; + unset($all[$key]); + } + + if (\count($all)) { + if ($symfonyCommandName) { + $message = sprintf('Too many arguments to "%s" command, expected arguments "%s".', $symfonyCommandName, implode('" "', array_keys($all))); + } else { + $message = sprintf('Too many arguments, expected arguments "%s".', implode('" "', array_keys($all))); + } + } elseif ($symfonyCommandName) { + $message = sprintf('No arguments expected for "%s" command, got "%s".', $symfonyCommandName, $token); + } else { + $message = sprintf('No arguments expected, got "%s".', $token); + } + + throw new RuntimeException($message); + } + } + + /** + * Adds a short option value. + * + * @throws RuntimeException When option given doesn't exist + */ + private function addShortOption(string $shortcut, $value) + { + if (!$this->definition->hasShortcut($shortcut)) { + throw new RuntimeException(sprintf('The "-%s" option does not exist.', $shortcut)); + } + + $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value); + } + + /** + * Adds a long option value. + * + * @throws RuntimeException When option given doesn't exist + */ + private function addLongOption(string $name, $value) + { + if (!$this->definition->hasOption($name)) { + if (!$this->definition->hasNegation($name)) { + throw new RuntimeException(sprintf('The "--%s" option does not exist.', $name)); + } + + $optionName = $this->definition->negationToName($name); + if (null !== $value) { + throw new RuntimeException(sprintf('The "--%s" option does not accept a value.', $name)); + } + $this->options[$optionName] = false; + + return; + } + + $option = $this->definition->getOption($name); + + if (null !== $value && !$option->acceptValue()) { + throw new RuntimeException(sprintf('The "--%s" option does not accept a value.', $name)); + } + + if (\in_array($value, ['', null], true) && $option->acceptValue() && \count($this->parsed)) { + // if option accepts an optional or mandatory argument + // let's see if there is one provided + $next = array_shift($this->parsed); + if ((isset($next[0]) && '-' !== $next[0]) || \in_array($next, ['', null], true)) { + $value = $next; + } else { + array_unshift($this->parsed, $next); + } + } + + if (null === $value) { + if ($option->isValueRequired()) { + throw new RuntimeException(sprintf('The "--%s" option requires a value.', $name)); + } + + if (!$option->isArray() && !$option->isValueOptional()) { + $value = true; + } + } + + if ($option->isArray()) { + $this->options[$name][] = $value; + } else { + $this->options[$name] = $value; + } + } + + /** + * {@inheritdoc} + */ + public function getFirstArgument() + { + $isOption = false; + foreach ($this->tokens as $i => $token) { + if ($token && '-' === $token[0]) { + if (str_contains($token, '=') || !isset($this->tokens[$i + 1])) { + continue; + } + + // If it's a long option, consider that everything after "--" is the option name. + // Otherwise, use the last char (if it's a short option set, only the last one can take a value with space separator) + $name = '-' === $token[1] ? substr($token, 2) : substr($token, -1); + if (!isset($this->options[$name]) && !$this->definition->hasShortcut($name)) { + // noop + } elseif ((isset($this->options[$name]) || isset($this->options[$name = $this->definition->shortcutToName($name)])) && $this->tokens[$i + 1] === $this->options[$name]) { + $isOption = true; + } + + continue; + } + + if ($isOption) { + $isOption = false; + continue; + } + + return $token; + } + + return null; + } + + /** + * {@inheritdoc} + */ + public function hasParameterOption($values, bool $onlyParams = false) + { + $values = (array) $values; + + foreach ($this->tokens as $token) { + if ($onlyParams && '--' === $token) { + return false; + } + foreach ($values as $value) { + // Options with values: + // For long options, test for '--option=' at beginning + // For short options, test for '-o' at beginning + $leading = str_starts_with($value, '--') ? $value.'=' : $value; + if ($token === $value || '' !== $leading && str_starts_with($token, $leading)) { + return true; + } + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function getParameterOption($values, $default = false, bool $onlyParams = false) + { + $values = (array) $values; + $tokens = $this->tokens; + + while (0 < \count($tokens)) { + $token = array_shift($tokens); + if ($onlyParams && '--' === $token) { + return $default; + } + + foreach ($values as $value) { + if ($token === $value) { + return array_shift($tokens); + } + // Options with values: + // For long options, test for '--option=' at beginning + // For short options, test for '-o' at beginning + $leading = str_starts_with($value, '--') ? $value.'=' : $value; + if ('' !== $leading && str_starts_with($token, $leading)) { + return substr($token, \strlen($leading)); + } + } + } + + return $default; + } + + /** + * Returns a stringified representation of the args passed to the command. + * + * @return string + */ + public function __toString() + { + $tokens = array_map(function ($token) { + if (preg_match('{^(-[^=]+=)(.+)}', $token, $match)) { + return $match[1].$this->escapeToken($match[2]); + } + + if ($token && '-' !== $token[0]) { + return $this->escapeToken($token); + } + + return $token; + }, $this->tokens); + + return implode(' ', $tokens); + } +} diff --git a/vendor/symfony/console/Input/ArrayInput.php b/vendor/symfony/console/Input/ArrayInput.php new file mode 100644 index 0000000..c651614 --- /dev/null +++ b/vendor/symfony/console/Input/ArrayInput.php @@ -0,0 +1,210 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\InvalidOptionException; + +/** + * ArrayInput represents an input provided as an array. + * + * Usage: + * + * $input = new ArrayInput(['command' => 'foo:bar', 'foo' => 'bar', '--bar' => 'foobar']); + * + * @author Fabien Potencier + */ +class ArrayInput extends Input +{ + private $parameters; + + public function __construct(array $parameters, InputDefinition $definition = null) + { + $this->parameters = $parameters; + + parent::__construct($definition); + } + + /** + * {@inheritdoc} + */ + public function getFirstArgument() + { + foreach ($this->parameters as $param => $value) { + if ($param && \is_string($param) && '-' === $param[0]) { + continue; + } + + return $value; + } + + return null; + } + + /** + * {@inheritdoc} + */ + public function hasParameterOption($values, bool $onlyParams = false) + { + $values = (array) $values; + + foreach ($this->parameters as $k => $v) { + if (!\is_int($k)) { + $v = $k; + } + + if ($onlyParams && '--' === $v) { + return false; + } + + if (\in_array($v, $values)) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function getParameterOption($values, $default = false, bool $onlyParams = false) + { + $values = (array) $values; + + foreach ($this->parameters as $k => $v) { + if ($onlyParams && ('--' === $k || (\is_int($k) && '--' === $v))) { + return $default; + } + + if (\is_int($k)) { + if (\in_array($v, $values)) { + return true; + } + } elseif (\in_array($k, $values)) { + return $v; + } + } + + return $default; + } + + /** + * Returns a stringified representation of the args passed to the command. + * + * @return string + */ + public function __toString() + { + $params = []; + foreach ($this->parameters as $param => $val) { + if ($param && \is_string($param) && '-' === $param[0]) { + $glue = ('-' === $param[1]) ? '=' : ' '; + if (\is_array($val)) { + foreach ($val as $v) { + $params[] = $param.('' != $v ? $glue.$this->escapeToken($v) : ''); + } + } else { + $params[] = $param.('' != $val ? $glue.$this->escapeToken($val) : ''); + } + } else { + $params[] = \is_array($val) ? implode(' ', array_map([$this, 'escapeToken'], $val)) : $this->escapeToken($val); + } + } + + return implode(' ', $params); + } + + /** + * {@inheritdoc} + */ + protected function parse() + { + foreach ($this->parameters as $key => $value) { + if ('--' === $key) { + return; + } + if (str_starts_with($key, '--')) { + $this->addLongOption(substr($key, 2), $value); + } elseif (str_starts_with($key, '-')) { + $this->addShortOption(substr($key, 1), $value); + } else { + $this->addArgument($key, $value); + } + } + } + + /** + * Adds a short option value. + * + * @throws InvalidOptionException When option given doesn't exist + */ + private function addShortOption(string $shortcut, $value) + { + if (!$this->definition->hasShortcut($shortcut)) { + throw new InvalidOptionException(sprintf('The "-%s" option does not exist.', $shortcut)); + } + + $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value); + } + + /** + * Adds a long option value. + * + * @throws InvalidOptionException When option given doesn't exist + * @throws InvalidOptionException When a required value is missing + */ + private function addLongOption(string $name, $value) + { + if (!$this->definition->hasOption($name)) { + if (!$this->definition->hasNegation($name)) { + throw new InvalidOptionException(sprintf('The "--%s" option does not exist.', $name)); + } + + $optionName = $this->definition->negationToName($name); + $this->options[$optionName] = false; + + return; + } + + $option = $this->definition->getOption($name); + + if (null === $value) { + if ($option->isValueRequired()) { + throw new InvalidOptionException(sprintf('The "--%s" option requires a value.', $name)); + } + + if (!$option->isValueOptional()) { + $value = true; + } + } + + $this->options[$name] = $value; + } + + /** + * Adds an argument value. + * + * @param string|int $name The argument name + * @param mixed $value The value for the argument + * + * @throws InvalidArgumentException When argument given doesn't exist + */ + private function addArgument($name, $value) + { + if (!$this->definition->hasArgument($name)) { + throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); + } + + $this->arguments[$name] = $value; + } +} diff --git a/vendor/symfony/console/Input/Input.php b/vendor/symfony/console/Input/Input.php new file mode 100644 index 0000000..d37460e --- /dev/null +++ b/vendor/symfony/console/Input/Input.php @@ -0,0 +1,213 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\RuntimeException; + +/** + * Input is the base class for all concrete Input classes. + * + * Three concrete classes are provided by default: + * + * * `ArgvInput`: The input comes from the CLI arguments (argv) + * * `StringInput`: The input is provided as a string + * * `ArrayInput`: The input is provided as an array + * + * @author Fabien Potencier + */ +abstract class Input implements InputInterface, StreamableInputInterface +{ + protected $definition; + protected $stream; + protected $options = []; + protected $arguments = []; + protected $interactive = true; + + public function __construct(InputDefinition $definition = null) + { + if (null === $definition) { + $this->definition = new InputDefinition(); + } else { + $this->bind($definition); + $this->validate(); + } + } + + /** + * {@inheritdoc} + */ + public function bind(InputDefinition $definition) + { + $this->arguments = []; + $this->options = []; + $this->definition = $definition; + + $this->parse(); + } + + /** + * Processes command line arguments. + */ + abstract protected function parse(); + + /** + * {@inheritdoc} + */ + public function validate() + { + $definition = $this->definition; + $givenArguments = $this->arguments; + + $missingArguments = array_filter(array_keys($definition->getArguments()), function ($argument) use ($definition, $givenArguments) { + return !\array_key_exists($argument, $givenArguments) && $definition->getArgument($argument)->isRequired(); + }); + + if (\count($missingArguments) > 0) { + throw new RuntimeException(sprintf('Not enough arguments (missing: "%s").', implode(', ', $missingArguments))); + } + } + + /** + * {@inheritdoc} + */ + public function isInteractive() + { + return $this->interactive; + } + + /** + * {@inheritdoc} + */ + public function setInteractive(bool $interactive) + { + $this->interactive = $interactive; + } + + /** + * {@inheritdoc} + */ + public function getArguments() + { + return array_merge($this->definition->getArgumentDefaults(), $this->arguments); + } + + /** + * {@inheritdoc} + */ + public function getArgument(string $name) + { + if (!$this->definition->hasArgument($name)) { + throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); + } + + return $this->arguments[$name] ?? $this->definition->getArgument($name)->getDefault(); + } + + /** + * {@inheritdoc} + */ + public function setArgument(string $name, $value) + { + if (!$this->definition->hasArgument($name)) { + throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); + } + + $this->arguments[$name] = $value; + } + + /** + * {@inheritdoc} + */ + public function hasArgument(string $name) + { + return $this->definition->hasArgument($name); + } + + /** + * {@inheritdoc} + */ + public function getOptions() + { + return array_merge($this->definition->getOptionDefaults(), $this->options); + } + + /** + * {@inheritdoc} + */ + public function getOption(string $name) + { + if ($this->definition->hasNegation($name)) { + if (null === $value = $this->getOption($this->definition->negationToName($name))) { + return $value; + } + + return !$value; + } + + if (!$this->definition->hasOption($name)) { + throw new InvalidArgumentException(sprintf('The "%s" option does not exist.', $name)); + } + + return \array_key_exists($name, $this->options) ? $this->options[$name] : $this->definition->getOption($name)->getDefault(); + } + + /** + * {@inheritdoc} + */ + public function setOption(string $name, $value) + { + if ($this->definition->hasNegation($name)) { + $this->options[$this->definition->negationToName($name)] = !$value; + + return; + } elseif (!$this->definition->hasOption($name)) { + throw new InvalidArgumentException(sprintf('The "%s" option does not exist.', $name)); + } + + $this->options[$name] = $value; + } + + /** + * {@inheritdoc} + */ + public function hasOption(string $name) + { + return $this->definition->hasOption($name) || $this->definition->hasNegation($name); + } + + /** + * Escapes a token through escapeshellarg if it contains unsafe chars. + * + * @return string + */ + public function escapeToken(string $token) + { + return preg_match('{^[\w-]+$}', $token) ? $token : escapeshellarg($token); + } + + /** + * {@inheritdoc} + */ + public function setStream($stream) + { + $this->stream = $stream; + } + + /** + * {@inheritdoc} + */ + public function getStream() + { + return $this->stream; + } +} diff --git a/vendor/symfony/console/Input/InputArgument.php b/vendor/symfony/console/Input/InputArgument.php new file mode 100644 index 0000000..8a64f7a --- /dev/null +++ b/vendor/symfony/console/Input/InputArgument.php @@ -0,0 +1,129 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\LogicException; + +/** + * Represents a command line argument. + * + * @author Fabien Potencier + */ +class InputArgument +{ + public const REQUIRED = 1; + public const OPTIONAL = 2; + public const IS_ARRAY = 4; + + private $name; + private $mode; + private $default; + private $description; + + /** + * @param string $name The argument name + * @param int|null $mode The argument mode: a bit mask of self::REQUIRED, self::OPTIONAL and self::IS_ARRAY + * @param string $description A description text + * @param string|bool|int|float|array|null $default The default value (for self::OPTIONAL mode only) + * + * @throws InvalidArgumentException When argument mode is not valid + */ + public function __construct(string $name, int $mode = null, string $description = '', $default = null) + { + if (null === $mode) { + $mode = self::OPTIONAL; + } elseif ($mode > 7 || $mode < 1) { + throw new InvalidArgumentException(sprintf('Argument mode "%s" is not valid.', $mode)); + } + + $this->name = $name; + $this->mode = $mode; + $this->description = $description; + + $this->setDefault($default); + } + + /** + * Returns the argument name. + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Returns true if the argument is required. + * + * @return bool true if parameter mode is self::REQUIRED, false otherwise + */ + public function isRequired() + { + return self::REQUIRED === (self::REQUIRED & $this->mode); + } + + /** + * Returns true if the argument can take multiple values. + * + * @return bool true if mode is self::IS_ARRAY, false otherwise + */ + public function isArray() + { + return self::IS_ARRAY === (self::IS_ARRAY & $this->mode); + } + + /** + * Sets the default value. + * + * @param string|bool|int|float|array|null $default + * + * @throws LogicException When incorrect default value is given + */ + public function setDefault($default = null) + { + if ($this->isRequired() && null !== $default) { + throw new LogicException('Cannot set a default value except for InputArgument::OPTIONAL mode.'); + } + + if ($this->isArray()) { + if (null === $default) { + $default = []; + } elseif (!\is_array($default)) { + throw new LogicException('A default value for an array argument must be an array.'); + } + } + + $this->default = $default; + } + + /** + * Returns the default value. + * + * @return string|bool|int|float|array|null + */ + public function getDefault() + { + return $this->default; + } + + /** + * Returns the description text. + * + * @return string + */ + public function getDescription() + { + return $this->description; + } +} diff --git a/vendor/symfony/console/Input/InputAwareInterface.php b/vendor/symfony/console/Input/InputAwareInterface.php new file mode 100644 index 0000000..5a288de --- /dev/null +++ b/vendor/symfony/console/Input/InputAwareInterface.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +/** + * InputAwareInterface should be implemented by classes that depends on the + * Console Input. + * + * @author Wouter J + */ +interface InputAwareInterface +{ + /** + * Sets the Console Input. + */ + public function setInput(InputInterface $input); +} diff --git a/vendor/symfony/console/Input/InputDefinition.php b/vendor/symfony/console/Input/InputDefinition.php new file mode 100644 index 0000000..11f704f --- /dev/null +++ b/vendor/symfony/console/Input/InputDefinition.php @@ -0,0 +1,424 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\LogicException; + +/** + * A InputDefinition represents a set of valid command line arguments and options. + * + * Usage: + * + * $definition = new InputDefinition([ + * new InputArgument('name', InputArgument::REQUIRED), + * new InputOption('foo', 'f', InputOption::VALUE_REQUIRED), + * ]); + * + * @author Fabien Potencier + */ +class InputDefinition +{ + private $arguments; + private $requiredCount; + private $lastArrayArgument; + private $lastOptionalArgument; + private $options; + private $negations; + private $shortcuts; + + /** + * @param array $definition An array of InputArgument and InputOption instance + */ + public function __construct(array $definition = []) + { + $this->setDefinition($definition); + } + + /** + * Sets the definition of the input. + */ + public function setDefinition(array $definition) + { + $arguments = []; + $options = []; + foreach ($definition as $item) { + if ($item instanceof InputOption) { + $options[] = $item; + } else { + $arguments[] = $item; + } + } + + $this->setArguments($arguments); + $this->setOptions($options); + } + + /** + * Sets the InputArgument objects. + * + * @param InputArgument[] $arguments An array of InputArgument objects + */ + public function setArguments(array $arguments = []) + { + $this->arguments = []; + $this->requiredCount = 0; + $this->lastOptionalArgument = null; + $this->lastArrayArgument = null; + $this->addArguments($arguments); + } + + /** + * Adds an array of InputArgument objects. + * + * @param InputArgument[] $arguments An array of InputArgument objects + */ + public function addArguments(?array $arguments = []) + { + if (null !== $arguments) { + foreach ($arguments as $argument) { + $this->addArgument($argument); + } + } + } + + /** + * @throws LogicException When incorrect argument is given + */ + public function addArgument(InputArgument $argument) + { + if (isset($this->arguments[$argument->getName()])) { + throw new LogicException(sprintf('An argument with name "%s" already exists.', $argument->getName())); + } + + if (null !== $this->lastArrayArgument) { + throw new LogicException(sprintf('Cannot add a required argument "%s" after an array argument "%s".', $argument->getName(), $this->lastArrayArgument->getName())); + } + + if ($argument->isRequired() && null !== $this->lastOptionalArgument) { + throw new LogicException(sprintf('Cannot add a required argument "%s" after an optional one "%s".', $argument->getName(), $this->lastOptionalArgument->getName())); + } + + if ($argument->isArray()) { + $this->lastArrayArgument = $argument; + } + + if ($argument->isRequired()) { + ++$this->requiredCount; + } else { + $this->lastOptionalArgument = $argument; + } + + $this->arguments[$argument->getName()] = $argument; + } + + /** + * Returns an InputArgument by name or by position. + * + * @param string|int $name The InputArgument name or position + * + * @return InputArgument + * + * @throws InvalidArgumentException When argument given doesn't exist + */ + public function getArgument($name) + { + if (!$this->hasArgument($name)) { + throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); + } + + $arguments = \is_int($name) ? array_values($this->arguments) : $this->arguments; + + return $arguments[$name]; + } + + /** + * Returns true if an InputArgument object exists by name or position. + * + * @param string|int $name The InputArgument name or position + * + * @return bool + */ + public function hasArgument($name) + { + $arguments = \is_int($name) ? array_values($this->arguments) : $this->arguments; + + return isset($arguments[$name]); + } + + /** + * Gets the array of InputArgument objects. + * + * @return InputArgument[] + */ + public function getArguments() + { + return $this->arguments; + } + + /** + * Returns the number of InputArguments. + * + * @return int + */ + public function getArgumentCount() + { + return null !== $this->lastArrayArgument ? \PHP_INT_MAX : \count($this->arguments); + } + + /** + * Returns the number of required InputArguments. + * + * @return int + */ + public function getArgumentRequiredCount() + { + return $this->requiredCount; + } + + /** + * @return array + */ + public function getArgumentDefaults() + { + $values = []; + foreach ($this->arguments as $argument) { + $values[$argument->getName()] = $argument->getDefault(); + } + + return $values; + } + + /** + * Sets the InputOption objects. + * + * @param InputOption[] $options An array of InputOption objects + */ + public function setOptions(array $options = []) + { + $this->options = []; + $this->shortcuts = []; + $this->negations = []; + $this->addOptions($options); + } + + /** + * Adds an array of InputOption objects. + * + * @param InputOption[] $options An array of InputOption objects + */ + public function addOptions(array $options = []) + { + foreach ($options as $option) { + $this->addOption($option); + } + } + + /** + * @throws LogicException When option given already exist + */ + public function addOption(InputOption $option) + { + if (isset($this->options[$option->getName()]) && !$option->equals($this->options[$option->getName()])) { + throw new LogicException(sprintf('An option named "%s" already exists.', $option->getName())); + } + if (isset($this->negations[$option->getName()])) { + throw new LogicException(sprintf('An option named "%s" already exists.', $option->getName())); + } + + if ($option->getShortcut()) { + foreach (explode('|', $option->getShortcut()) as $shortcut) { + if (isset($this->shortcuts[$shortcut]) && !$option->equals($this->options[$this->shortcuts[$shortcut]])) { + throw new LogicException(sprintf('An option with shortcut "%s" already exists.', $shortcut)); + } + } + } + + $this->options[$option->getName()] = $option; + if ($option->getShortcut()) { + foreach (explode('|', $option->getShortcut()) as $shortcut) { + $this->shortcuts[$shortcut] = $option->getName(); + } + } + + if ($option->isNegatable()) { + $negatedName = 'no-'.$option->getName(); + if (isset($this->options[$negatedName])) { + throw new LogicException(sprintf('An option named "%s" already exists.', $negatedName)); + } + $this->negations[$negatedName] = $option->getName(); + } + } + + /** + * Returns an InputOption by name. + * + * @return InputOption + * + * @throws InvalidArgumentException When option given doesn't exist + */ + public function getOption(string $name) + { + if (!$this->hasOption($name)) { + throw new InvalidArgumentException(sprintf('The "--%s" option does not exist.', $name)); + } + + return $this->options[$name]; + } + + /** + * Returns true if an InputOption object exists by name. + * + * This method can't be used to check if the user included the option when + * executing the command (use getOption() instead). + * + * @return bool + */ + public function hasOption(string $name) + { + return isset($this->options[$name]); + } + + /** + * Gets the array of InputOption objects. + * + * @return InputOption[] + */ + public function getOptions() + { + return $this->options; + } + + /** + * Returns true if an InputOption object exists by shortcut. + * + * @return bool + */ + public function hasShortcut(string $name) + { + return isset($this->shortcuts[$name]); + } + + /** + * Returns true if an InputOption object exists by negated name. + */ + public function hasNegation(string $name): bool + { + return isset($this->negations[$name]); + } + + /** + * Gets an InputOption by shortcut. + * + * @return InputOption + */ + public function getOptionForShortcut(string $shortcut) + { + return $this->getOption($this->shortcutToName($shortcut)); + } + + /** + * @return array + */ + public function getOptionDefaults() + { + $values = []; + foreach ($this->options as $option) { + $values[$option->getName()] = $option->getDefault(); + } + + return $values; + } + + /** + * Returns the InputOption name given a shortcut. + * + * @throws InvalidArgumentException When option given does not exist + * + * @internal + */ + public function shortcutToName(string $shortcut): string + { + if (!isset($this->shortcuts[$shortcut])) { + throw new InvalidArgumentException(sprintf('The "-%s" option does not exist.', $shortcut)); + } + + return $this->shortcuts[$shortcut]; + } + + /** + * Returns the InputOption name given a negation. + * + * @throws InvalidArgumentException When option given does not exist + * + * @internal + */ + public function negationToName(string $negation): string + { + if (!isset($this->negations[$negation])) { + throw new InvalidArgumentException(sprintf('The "--%s" option does not exist.', $negation)); + } + + return $this->negations[$negation]; + } + + /** + * Gets the synopsis. + * + * @return string + */ + public function getSynopsis(bool $short = false) + { + $elements = []; + + if ($short && $this->getOptions()) { + $elements[] = '[options]'; + } elseif (!$short) { + foreach ($this->getOptions() as $option) { + $value = ''; + if ($option->acceptValue()) { + $value = sprintf( + ' %s%s%s', + $option->isValueOptional() ? '[' : '', + strtoupper($option->getName()), + $option->isValueOptional() ? ']' : '' + ); + } + + $shortcut = $option->getShortcut() ? sprintf('-%s|', $option->getShortcut()) : ''; + $negation = $option->isNegatable() ? sprintf('|--no-%s', $option->getName()) : ''; + $elements[] = sprintf('[%s--%s%s%s]', $shortcut, $option->getName(), $value, $negation); + } + } + + if (\count($elements) && $this->getArguments()) { + $elements[] = '[--]'; + } + + $tail = ''; + foreach ($this->getArguments() as $argument) { + $element = '<'.$argument->getName().'>'; + if ($argument->isArray()) { + $element .= '...'; + } + + if (!$argument->isRequired()) { + $element = '['.$element; + $tail .= ']'; + } + + $elements[] = $element; + } + + return implode(' ', $elements).$tail; + } +} diff --git a/vendor/symfony/console/Input/InputInterface.php b/vendor/symfony/console/Input/InputInterface.php new file mode 100644 index 0000000..628b603 --- /dev/null +++ b/vendor/symfony/console/Input/InputInterface.php @@ -0,0 +1,151 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\RuntimeException; + +/** + * InputInterface is the interface implemented by all input classes. + * + * @author Fabien Potencier + */ +interface InputInterface +{ + /** + * Returns the first argument from the raw parameters (not parsed). + * + * @return string|null + */ + public function getFirstArgument(); + + /** + * Returns true if the raw parameters (not parsed) contain a value. + * + * This method is to be used to introspect the input parameters + * before they have been validated. It must be used carefully. + * Does not necessarily return the correct result for short options + * when multiple flags are combined in the same option. + * + * @param string|array $values The values to look for in the raw parameters (can be an array) + * @param bool $onlyParams Only check real parameters, skip those following an end of options (--) signal + * + * @return bool + */ + public function hasParameterOption($values, bool $onlyParams = false); + + /** + * Returns the value of a raw option (not parsed). + * + * This method is to be used to introspect the input parameters + * before they have been validated. It must be used carefully. + * Does not necessarily return the correct result for short options + * when multiple flags are combined in the same option. + * + * @param string|array $values The value(s) to look for in the raw parameters (can be an array) + * @param string|bool|int|float|array|null $default The default value to return if no result is found + * @param bool $onlyParams Only check real parameters, skip those following an end of options (--) signal + * + * @return mixed + */ + public function getParameterOption($values, $default = false, bool $onlyParams = false); + + /** + * Binds the current Input instance with the given arguments and options. + * + * @throws RuntimeException + */ + public function bind(InputDefinition $definition); + + /** + * Validates the input. + * + * @throws RuntimeException When not enough arguments are given + */ + public function validate(); + + /** + * Returns all the given arguments merged with the default values. + * + * @return array + */ + public function getArguments(); + + /** + * Returns the argument value for a given argument name. + * + * @return mixed + * + * @throws InvalidArgumentException When argument given doesn't exist + */ + public function getArgument(string $name); + + /** + * Sets an argument value by name. + * + * @param mixed $value The argument value + * + * @throws InvalidArgumentException When argument given doesn't exist + */ + public function setArgument(string $name, $value); + + /** + * Returns true if an InputArgument object exists by name or position. + * + * @return bool + */ + public function hasArgument(string $name); + + /** + * Returns all the given options merged with the default values. + * + * @return array + */ + public function getOptions(); + + /** + * Returns the option value for a given option name. + * + * @return mixed + * + * @throws InvalidArgumentException When option given doesn't exist + */ + public function getOption(string $name); + + /** + * Sets an option value by name. + * + * @param mixed $value The option value + * + * @throws InvalidArgumentException When option given doesn't exist + */ + public function setOption(string $name, $value); + + /** + * Returns true if an InputOption object exists by name. + * + * @return bool + */ + public function hasOption(string $name); + + /** + * Is this input means interactive? + * + * @return bool + */ + public function isInteractive(); + + /** + * Sets the input interactivity. + */ + public function setInteractive(bool $interactive); +} diff --git a/vendor/symfony/console/Input/InputOption.php b/vendor/symfony/console/Input/InputOption.php new file mode 100644 index 0000000..2bec34f --- /dev/null +++ b/vendor/symfony/console/Input/InputOption.php @@ -0,0 +1,231 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\LogicException; + +/** + * Represents a command line option. + * + * @author Fabien Potencier + */ +class InputOption +{ + /** + * Do not accept input for the option (e.g. --yell). This is the default behavior of options. + */ + public const VALUE_NONE = 1; + + /** + * A value must be passed when the option is used (e.g. --iterations=5 or -i5). + */ + public const VALUE_REQUIRED = 2; + + /** + * The option may or may not have a value (e.g. --yell or --yell=loud). + */ + public const VALUE_OPTIONAL = 4; + + /** + * The option accepts multiple values (e.g. --dir=/foo --dir=/bar). + */ + public const VALUE_IS_ARRAY = 8; + + /** + * The option may have either positive or negative value (e.g. --ansi or --no-ansi). + */ + public const VALUE_NEGATABLE = 16; + + private $name; + private $shortcut; + private $mode; + private $default; + private $description; + + /** + * @param string|array|null $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts + * @param int|null $mode The option mode: One of the VALUE_* constants + * @param string|bool|int|float|array|null $default The default value (must be null for self::VALUE_NONE) + * + * @throws InvalidArgumentException If option mode is invalid or incompatible + */ + public function __construct(string $name, $shortcut = null, int $mode = null, string $description = '', $default = null) + { + if (str_starts_with($name, '--')) { + $name = substr($name, 2); + } + + if (empty($name)) { + throw new InvalidArgumentException('An option name cannot be empty.'); + } + + if (empty($shortcut)) { + $shortcut = null; + } + + if (null !== $shortcut) { + if (\is_array($shortcut)) { + $shortcut = implode('|', $shortcut); + } + $shortcuts = preg_split('{(\|)-?}', ltrim($shortcut, '-')); + $shortcuts = array_filter($shortcuts); + $shortcut = implode('|', $shortcuts); + + if (empty($shortcut)) { + throw new InvalidArgumentException('An option shortcut cannot be empty.'); + } + } + + if (null === $mode) { + $mode = self::VALUE_NONE; + } elseif ($mode >= (self::VALUE_NEGATABLE << 1) || $mode < 1) { + throw new InvalidArgumentException(sprintf('Option mode "%s" is not valid.', $mode)); + } + + $this->name = $name; + $this->shortcut = $shortcut; + $this->mode = $mode; + $this->description = $description; + + if ($this->isArray() && !$this->acceptValue()) { + throw new InvalidArgumentException('Impossible to have an option mode VALUE_IS_ARRAY if the option does not accept a value.'); + } + if ($this->isNegatable() && $this->acceptValue()) { + throw new InvalidArgumentException('Impossible to have an option mode VALUE_NEGATABLE if the option also accepts a value.'); + } + + $this->setDefault($default); + } + + /** + * Returns the option shortcut. + * + * @return string|null + */ + public function getShortcut() + { + return $this->shortcut; + } + + /** + * Returns the option name. + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Returns true if the option accepts a value. + * + * @return bool true if value mode is not self::VALUE_NONE, false otherwise + */ + public function acceptValue() + { + return $this->isValueRequired() || $this->isValueOptional(); + } + + /** + * Returns true if the option requires a value. + * + * @return bool true if value mode is self::VALUE_REQUIRED, false otherwise + */ + public function isValueRequired() + { + return self::VALUE_REQUIRED === (self::VALUE_REQUIRED & $this->mode); + } + + /** + * Returns true if the option takes an optional value. + * + * @return bool true if value mode is self::VALUE_OPTIONAL, false otherwise + */ + public function isValueOptional() + { + return self::VALUE_OPTIONAL === (self::VALUE_OPTIONAL & $this->mode); + } + + /** + * Returns true if the option can take multiple values. + * + * @return bool true if mode is self::VALUE_IS_ARRAY, false otherwise + */ + public function isArray() + { + return self::VALUE_IS_ARRAY === (self::VALUE_IS_ARRAY & $this->mode); + } + + public function isNegatable(): bool + { + return self::VALUE_NEGATABLE === (self::VALUE_NEGATABLE & $this->mode); + } + + /** + * @param string|bool|int|float|array|null $default + */ + public function setDefault($default = null) + { + if (self::VALUE_NONE === (self::VALUE_NONE & $this->mode) && null !== $default) { + throw new LogicException('Cannot set a default value when using InputOption::VALUE_NONE mode.'); + } + + if ($this->isArray()) { + if (null === $default) { + $default = []; + } elseif (!\is_array($default)) { + throw new LogicException('A default value for an array option must be an array.'); + } + } + + $this->default = $this->acceptValue() || $this->isNegatable() ? $default : false; + } + + /** + * Returns the default value. + * + * @return string|bool|int|float|array|null + */ + public function getDefault() + { + return $this->default; + } + + /** + * Returns the description text. + * + * @return string + */ + public function getDescription() + { + return $this->description; + } + + /** + * Checks whether the given option equals this one. + * + * @return bool + */ + public function equals(self $option) + { + return $option->getName() === $this->getName() + && $option->getShortcut() === $this->getShortcut() + && $option->getDefault() === $this->getDefault() + && $option->isNegatable() === $this->isNegatable() + && $option->isArray() === $this->isArray() + && $option->isValueRequired() === $this->isValueRequired() + && $option->isValueOptional() === $this->isValueOptional() + ; + } +} diff --git a/vendor/symfony/console/Input/StreamableInputInterface.php b/vendor/symfony/console/Input/StreamableInputInterface.php new file mode 100644 index 0000000..d7e462f --- /dev/null +++ b/vendor/symfony/console/Input/StreamableInputInterface.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +/** + * StreamableInputInterface is the interface implemented by all input classes + * that have an input stream. + * + * @author Robin Chalas + */ +interface StreamableInputInterface extends InputInterface +{ + /** + * Sets the input stream to read from when interacting with the user. + * + * This is mainly useful for testing purpose. + * + * @param resource $stream The input stream + */ + public function setStream($stream); + + /** + * Returns the input stream. + * + * @return resource|null + */ + public function getStream(); +} diff --git a/vendor/symfony/console/Input/StringInput.php b/vendor/symfony/console/Input/StringInput.php new file mode 100644 index 0000000..56bb66c --- /dev/null +++ b/vendor/symfony/console/Input/StringInput.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 Symfony\Component\Console\Input; + +use Symfony\Component\Console\Exception\InvalidArgumentException; + +/** + * StringInput represents an input provided as a string. + * + * Usage: + * + * $input = new StringInput('foo --bar="foobar"'); + * + * @author Fabien Potencier + */ +class StringInput extends ArgvInput +{ + public const REGEX_STRING = '([^\s]+?)(?:\s|(?setTokens($this->tokenize($input)); + } + + /** + * Tokenizes a string. + * + * @throws InvalidArgumentException When unable to parse input (should never happen) + */ + private function tokenize(string $input): array + { + $tokens = []; + $length = \strlen($input); + $cursor = 0; + $token = null; + while ($cursor < $length) { + if ('\\' === $input[$cursor]) { + $token .= $input[++$cursor] ?? ''; + ++$cursor; + continue; + } + + if (preg_match('/\s+/A', $input, $match, 0, $cursor)) { + if (null !== $token) { + $tokens[] = $token; + $token = null; + } + } elseif (preg_match('/([^="\'\s]+?)(=?)('.self::REGEX_QUOTED_STRING.'+)/A', $input, $match, 0, $cursor)) { + $token .= $match[1].$match[2].stripcslashes(str_replace(['"\'', '\'"', '\'\'', '""'], '', substr($match[3], 1, -1))); + } elseif (preg_match('/'.self::REGEX_QUOTED_STRING.'/A', $input, $match, 0, $cursor)) { + $token .= stripcslashes(substr($match[0], 1, -1)); + } elseif (preg_match('/'.self::REGEX_UNQUOTED_STRING.'/A', $input, $match, 0, $cursor)) { + $token .= $match[1]; + } else { + // should never happen + throw new InvalidArgumentException(sprintf('Unable to parse input near "... %s ...".', substr($input, $cursor, 10))); + } + + $cursor += \strlen($match[0]); + } + + if (null !== $token) { + $tokens[] = $token; + } + + return $tokens; + } +} diff --git a/vendor/symfony/console/LICENSE b/vendor/symfony/console/LICENSE new file mode 100644 index 0000000..0138f8f --- /dev/null +++ b/vendor/symfony/console/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-present Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/console/Logger/ConsoleLogger.php b/vendor/symfony/console/Logger/ConsoleLogger.php new file mode 100644 index 0000000..4a10fa1 --- /dev/null +++ b/vendor/symfony/console/Logger/ConsoleLogger.php @@ -0,0 +1,126 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Logger; + +use Psr\Log\AbstractLogger; +use Psr\Log\InvalidArgumentException; +use Psr\Log\LogLevel; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * PSR-3 compliant console logger. + * + * @author Kévin Dunglas + * + * @see https://www.php-fig.org/psr/psr-3/ + */ +class ConsoleLogger extends AbstractLogger +{ + public const INFO = 'info'; + public const ERROR = 'error'; + + private $output; + private $verbosityLevelMap = [ + LogLevel::EMERGENCY => OutputInterface::VERBOSITY_NORMAL, + LogLevel::ALERT => OutputInterface::VERBOSITY_NORMAL, + LogLevel::CRITICAL => OutputInterface::VERBOSITY_NORMAL, + LogLevel::ERROR => OutputInterface::VERBOSITY_NORMAL, + LogLevel::WARNING => OutputInterface::VERBOSITY_NORMAL, + LogLevel::NOTICE => OutputInterface::VERBOSITY_VERBOSE, + LogLevel::INFO => OutputInterface::VERBOSITY_VERY_VERBOSE, + LogLevel::DEBUG => OutputInterface::VERBOSITY_DEBUG, + ]; + private $formatLevelMap = [ + LogLevel::EMERGENCY => self::ERROR, + LogLevel::ALERT => self::ERROR, + LogLevel::CRITICAL => self::ERROR, + LogLevel::ERROR => self::ERROR, + LogLevel::WARNING => self::INFO, + LogLevel::NOTICE => self::INFO, + LogLevel::INFO => self::INFO, + LogLevel::DEBUG => self::INFO, + ]; + private $errored = false; + + public function __construct(OutputInterface $output, array $verbosityLevelMap = [], array $formatLevelMap = []) + { + $this->output = $output; + $this->verbosityLevelMap = $verbosityLevelMap + $this->verbosityLevelMap; + $this->formatLevelMap = $formatLevelMap + $this->formatLevelMap; + } + + /** + * {@inheritdoc} + * + * @return void + */ + public function log($level, $message, array $context = []) + { + if (!isset($this->verbosityLevelMap[$level])) { + throw new InvalidArgumentException(sprintf('The log level "%s" does not exist.', $level)); + } + + $output = $this->output; + + // Write to the error output if necessary and available + if (self::ERROR === $this->formatLevelMap[$level]) { + if ($this->output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + $this->errored = true; + } + + // the if condition check isn't necessary -- it's the same one that $output will do internally anyway. + // We only do it for efficiency here as the message formatting is relatively expensive. + if ($output->getVerbosity() >= $this->verbosityLevelMap[$level]) { + $output->writeln(sprintf('<%1$s>[%2$s] %3$s', $this->formatLevelMap[$level], $level, $this->interpolate($message, $context)), $this->verbosityLevelMap[$level]); + } + } + + /** + * Returns true when any messages have been logged at error levels. + * + * @return bool + */ + public function hasErrored() + { + return $this->errored; + } + + /** + * Interpolates context values into the message placeholders. + * + * @author PHP Framework Interoperability Group + */ + private function interpolate(string $message, array $context): string + { + if (!str_contains($message, '{')) { + return $message; + } + + $replacements = []; + foreach ($context as $key => $val) { + if (null === $val || \is_scalar($val) || (\is_object($val) && method_exists($val, '__toString'))) { + $replacements["{{$key}}"] = $val; + } elseif ($val instanceof \DateTimeInterface) { + $replacements["{{$key}}"] = $val->format(\DateTime::RFC3339); + } elseif (\is_object($val)) { + $replacements["{{$key}}"] = '[object '.\get_class($val).']'; + } else { + $replacements["{{$key}}"] = '['.\gettype($val).']'; + } + } + + return strtr($message, $replacements); + } +} diff --git a/vendor/symfony/console/Output/BufferedOutput.php b/vendor/symfony/console/Output/BufferedOutput.php new file mode 100644 index 0000000..d37c6e3 --- /dev/null +++ b/vendor/symfony/console/Output/BufferedOutput.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +/** + * @author Jean-François Simon + */ +class BufferedOutput extends Output +{ + private $buffer = ''; + + /** + * Empties buffer and returns its content. + * + * @return string + */ + public function fetch() + { + $content = $this->buffer; + $this->buffer = ''; + + return $content; + } + + /** + * {@inheritdoc} + */ + protected function doWrite(string $message, bool $newline) + { + $this->buffer .= $message; + + if ($newline) { + $this->buffer .= \PHP_EOL; + } + } +} diff --git a/vendor/symfony/console/Output/ConsoleOutput.php b/vendor/symfony/console/Output/ConsoleOutput.php new file mode 100644 index 0000000..f19f9eb --- /dev/null +++ b/vendor/symfony/console/Output/ConsoleOutput.php @@ -0,0 +1,172 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +use Symfony\Component\Console\Formatter\OutputFormatterInterface; + +/** + * ConsoleOutput is the default class for all CLI output. It uses STDOUT and STDERR. + * + * This class is a convenient wrapper around `StreamOutput` for both STDOUT and STDERR. + * + * $output = new ConsoleOutput(); + * + * This is equivalent to: + * + * $output = new StreamOutput(fopen('php://stdout', 'w')); + * $stdErr = new StreamOutput(fopen('php://stderr', 'w')); + * + * @author Fabien Potencier + */ +class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface +{ + private $stderr; + private $consoleSectionOutputs = []; + + /** + * @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface) + * @param bool|null $decorated Whether to decorate messages (null for auto-guessing) + * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter) + */ + public function __construct(int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = null, OutputFormatterInterface $formatter = null) + { + parent::__construct($this->openOutputStream(), $verbosity, $decorated, $formatter); + + if (null === $formatter) { + // for BC reasons, stdErr has it own Formatter only when user don't inject a specific formatter. + $this->stderr = new StreamOutput($this->openErrorStream(), $verbosity, $decorated); + + return; + } + + $actualDecorated = $this->isDecorated(); + $this->stderr = new StreamOutput($this->openErrorStream(), $verbosity, $decorated, $this->getFormatter()); + + if (null === $decorated) { + $this->setDecorated($actualDecorated && $this->stderr->isDecorated()); + } + } + + /** + * Creates a new output section. + */ + public function section(): ConsoleSectionOutput + { + return new ConsoleSectionOutput($this->getStream(), $this->consoleSectionOutputs, $this->getVerbosity(), $this->isDecorated(), $this->getFormatter()); + } + + /** + * {@inheritdoc} + */ + public function setDecorated(bool $decorated) + { + parent::setDecorated($decorated); + $this->stderr->setDecorated($decorated); + } + + /** + * {@inheritdoc} + */ + public function setFormatter(OutputFormatterInterface $formatter) + { + parent::setFormatter($formatter); + $this->stderr->setFormatter($formatter); + } + + /** + * {@inheritdoc} + */ + public function setVerbosity(int $level) + { + parent::setVerbosity($level); + $this->stderr->setVerbosity($level); + } + + /** + * {@inheritdoc} + */ + public function getErrorOutput() + { + return $this->stderr; + } + + /** + * {@inheritdoc} + */ + public function setErrorOutput(OutputInterface $error) + { + $this->stderr = $error; + } + + /** + * Returns true if current environment supports writing console output to + * STDOUT. + * + * @return bool + */ + protected function hasStdoutSupport() + { + return false === $this->isRunningOS400(); + } + + /** + * Returns true if current environment supports writing console output to + * STDERR. + * + * @return bool + */ + protected function hasStderrSupport() + { + return false === $this->isRunningOS400(); + } + + /** + * Checks if current executing environment is IBM iSeries (OS400), which + * doesn't properly convert character-encodings between ASCII to EBCDIC. + */ + private function isRunningOS400(): bool + { + $checks = [ + \function_exists('php_uname') ? php_uname('s') : '', + getenv('OSTYPE'), + \PHP_OS, + ]; + + return false !== stripos(implode(';', $checks), 'OS400'); + } + + /** + * @return resource + */ + private function openOutputStream() + { + if (!$this->hasStdoutSupport()) { + return fopen('php://output', 'w'); + } + + // Use STDOUT when possible to prevent from opening too many file descriptors + return \defined('STDOUT') ? \STDOUT : (@fopen('php://stdout', 'w') ?: fopen('php://output', 'w')); + } + + /** + * @return resource + */ + private function openErrorStream() + { + if (!$this->hasStderrSupport()) { + return fopen('php://output', 'w'); + } + + // Use STDERR when possible to prevent from opening too many file descriptors + return \defined('STDERR') ? \STDERR : (@fopen('php://stderr', 'w') ?: fopen('php://output', 'w')); + } +} diff --git a/vendor/symfony/console/Output/ConsoleOutputInterface.php b/vendor/symfony/console/Output/ConsoleOutputInterface.php new file mode 100644 index 0000000..6b6635f --- /dev/null +++ b/vendor/symfony/console/Output/ConsoleOutputInterface.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +/** + * ConsoleOutputInterface is the interface implemented by ConsoleOutput class. + * This adds information about stderr and section output stream. + * + * @author Dariusz Górecki + */ +interface ConsoleOutputInterface extends OutputInterface +{ + /** + * Gets the OutputInterface for errors. + * + * @return OutputInterface + */ + public function getErrorOutput(); + + public function setErrorOutput(OutputInterface $error); + + public function section(): ConsoleSectionOutput; +} diff --git a/vendor/symfony/console/Output/ConsoleSectionOutput.php b/vendor/symfony/console/Output/ConsoleSectionOutput.php new file mode 100644 index 0000000..8f16497 --- /dev/null +++ b/vendor/symfony/console/Output/ConsoleSectionOutput.php @@ -0,0 +1,143 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +use Symfony\Component\Console\Formatter\OutputFormatterInterface; +use Symfony\Component\Console\Helper\Helper; +use Symfony\Component\Console\Terminal; + +/** + * @author Pierre du Plessis + * @author Gabriel Ostrolucký + */ +class ConsoleSectionOutput extends StreamOutput +{ + private $content = []; + private $lines = 0; + private $sections; + private $terminal; + + /** + * @param resource $stream + * @param ConsoleSectionOutput[] $sections + */ + public function __construct($stream, array &$sections, int $verbosity, bool $decorated, OutputFormatterInterface $formatter) + { + parent::__construct($stream, $verbosity, $decorated, $formatter); + array_unshift($sections, $this); + $this->sections = &$sections; + $this->terminal = new Terminal(); + } + + /** + * Clears previous output for this section. + * + * @param int $lines Number of lines to clear. If null, then the entire output of this section is cleared + */ + public function clear(int $lines = null) + { + if (empty($this->content) || !$this->isDecorated()) { + return; + } + + if ($lines) { + array_splice($this->content, -($lines * 2)); // Multiply lines by 2 to cater for each new line added between content + } else { + $lines = $this->lines; + $this->content = []; + } + + $this->lines -= $lines; + + parent::doWrite($this->popStreamContentUntilCurrentSection($lines), false); + } + + /** + * Overwrites the previous output with a new message. + * + * @param array|string $message + */ + public function overwrite($message) + { + $this->clear(); + $this->writeln($message); + } + + public function getContent(): string + { + return implode('', $this->content); + } + + /** + * @internal + */ + public function addContent(string $input) + { + foreach (explode(\PHP_EOL, $input) as $lineContent) { + $this->lines += ceil($this->getDisplayLength($lineContent) / $this->terminal->getWidth()) ?: 1; + $this->content[] = $lineContent; + $this->content[] = \PHP_EOL; + } + } + + /** + * {@inheritdoc} + */ + protected function doWrite(string $message, bool $newline) + { + if (!$this->isDecorated()) { + parent::doWrite($message, $newline); + + return; + } + + $erasedContent = $this->popStreamContentUntilCurrentSection(); + + $this->addContent($message); + + parent::doWrite($message, true); + parent::doWrite($erasedContent, false); + } + + /** + * At initial stage, cursor is at the end of stream output. This method makes cursor crawl upwards until it hits + * current section. Then it erases content it crawled through. Optionally, it erases part of current section too. + */ + private function popStreamContentUntilCurrentSection(int $numberOfLinesToClearFromCurrentSection = 0): string + { + $numberOfLinesToClear = $numberOfLinesToClearFromCurrentSection; + $erasedContent = []; + + foreach ($this->sections as $section) { + if ($section === $this) { + break; + } + + $numberOfLinesToClear += $section->lines; + $erasedContent[] = $section->getContent(); + } + + if ($numberOfLinesToClear > 0) { + // move cursor up n lines + parent::doWrite(sprintf("\x1b[%dA", $numberOfLinesToClear), false); + // erase to end of screen + parent::doWrite("\x1b[0J", false); + } + + return implode('', array_reverse($erasedContent)); + } + + private function getDisplayLength(string $text): int + { + return Helper::width(Helper::removeDecoration($this->getFormatter(), str_replace("\t", ' ', $text))); + } +} diff --git a/vendor/symfony/console/Output/NullOutput.php b/vendor/symfony/console/Output/NullOutput.php new file mode 100644 index 0000000..3bbe63e --- /dev/null +++ b/vendor/symfony/console/Output/NullOutput.php @@ -0,0 +1,128 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +use Symfony\Component\Console\Formatter\NullOutputFormatter; +use Symfony\Component\Console\Formatter\OutputFormatterInterface; + +/** + * NullOutput suppresses all output. + * + * $output = new NullOutput(); + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +class NullOutput implements OutputInterface +{ + private $formatter; + + /** + * {@inheritdoc} + */ + public function setFormatter(OutputFormatterInterface $formatter) + { + // do nothing + } + + /** + * {@inheritdoc} + */ + public function getFormatter() + { + if ($this->formatter) { + return $this->formatter; + } + // to comply with the interface we must return a OutputFormatterInterface + return $this->formatter = new NullOutputFormatter(); + } + + /** + * {@inheritdoc} + */ + public function setDecorated(bool $decorated) + { + // do nothing + } + + /** + * {@inheritdoc} + */ + public function isDecorated() + { + return false; + } + + /** + * {@inheritdoc} + */ + public function setVerbosity(int $level) + { + // do nothing + } + + /** + * {@inheritdoc} + */ + public function getVerbosity() + { + return self::VERBOSITY_QUIET; + } + + /** + * {@inheritdoc} + */ + public function isQuiet() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function isVerbose() + { + return false; + } + + /** + * {@inheritdoc} + */ + public function isVeryVerbose() + { + return false; + } + + /** + * {@inheritdoc} + */ + public function isDebug() + { + return false; + } + + /** + * {@inheritdoc} + */ + public function writeln($messages, int $options = self::OUTPUT_NORMAL) + { + // do nothing + } + + /** + * {@inheritdoc} + */ + public function write($messages, bool $newline = false, int $options = self::OUTPUT_NORMAL) + { + // do nothing + } +} diff --git a/vendor/symfony/console/Output/Output.php b/vendor/symfony/console/Output/Output.php new file mode 100644 index 0000000..d7c5fb2 --- /dev/null +++ b/vendor/symfony/console/Output/Output.php @@ -0,0 +1,174 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Formatter\OutputFormatterInterface; + +/** + * Base class for output classes. + * + * There are five levels of verbosity: + * + * * normal: no option passed (normal output) + * * verbose: -v (more output) + * * very verbose: -vv (highly extended output) + * * debug: -vvv (all debug output) + * * quiet: -q (no output) + * + * @author Fabien Potencier + */ +abstract class Output implements OutputInterface +{ + private $verbosity; + private $formatter; + + /** + * @param int|null $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface) + * @param bool $decorated Whether to decorate messages + * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter) + */ + public function __construct(?int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = false, OutputFormatterInterface $formatter = null) + { + $this->verbosity = $verbosity ?? self::VERBOSITY_NORMAL; + $this->formatter = $formatter ?? new OutputFormatter(); + $this->formatter->setDecorated($decorated); + } + + /** + * {@inheritdoc} + */ + public function setFormatter(OutputFormatterInterface $formatter) + { + $this->formatter = $formatter; + } + + /** + * {@inheritdoc} + */ + public function getFormatter() + { + return $this->formatter; + } + + /** + * {@inheritdoc} + */ + public function setDecorated(bool $decorated) + { + $this->formatter->setDecorated($decorated); + } + + /** + * {@inheritdoc} + */ + public function isDecorated() + { + return $this->formatter->isDecorated(); + } + + /** + * {@inheritdoc} + */ + public function setVerbosity(int $level) + { + $this->verbosity = $level; + } + + /** + * {@inheritdoc} + */ + public function getVerbosity() + { + return $this->verbosity; + } + + /** + * {@inheritdoc} + */ + public function isQuiet() + { + return self::VERBOSITY_QUIET === $this->verbosity; + } + + /** + * {@inheritdoc} + */ + public function isVerbose() + { + return self::VERBOSITY_VERBOSE <= $this->verbosity; + } + + /** + * {@inheritdoc} + */ + public function isVeryVerbose() + { + return self::VERBOSITY_VERY_VERBOSE <= $this->verbosity; + } + + /** + * {@inheritdoc} + */ + public function isDebug() + { + return self::VERBOSITY_DEBUG <= $this->verbosity; + } + + /** + * {@inheritdoc} + */ + public function writeln($messages, int $options = self::OUTPUT_NORMAL) + { + $this->write($messages, true, $options); + } + + /** + * {@inheritdoc} + */ + public function write($messages, bool $newline = false, int $options = self::OUTPUT_NORMAL) + { + if (!is_iterable($messages)) { + $messages = [$messages]; + } + + $types = self::OUTPUT_NORMAL | self::OUTPUT_RAW | self::OUTPUT_PLAIN; + $type = $types & $options ?: self::OUTPUT_NORMAL; + + $verbosities = self::VERBOSITY_QUIET | self::VERBOSITY_NORMAL | self::VERBOSITY_VERBOSE | self::VERBOSITY_VERY_VERBOSE | self::VERBOSITY_DEBUG; + $verbosity = $verbosities & $options ?: self::VERBOSITY_NORMAL; + + if ($verbosity > $this->getVerbosity()) { + return; + } + + foreach ($messages as $message) { + switch ($type) { + case OutputInterface::OUTPUT_NORMAL: + $message = $this->formatter->format($message); + break; + case OutputInterface::OUTPUT_RAW: + break; + case OutputInterface::OUTPUT_PLAIN: + $message = strip_tags($this->formatter->format($message)); + break; + } + + $this->doWrite($message ?? '', $newline); + } + } + + /** + * Writes a message to the output. + */ + abstract protected function doWrite(string $message, bool $newline); +} diff --git a/vendor/symfony/console/Output/OutputInterface.php b/vendor/symfony/console/Output/OutputInterface.php new file mode 100644 index 0000000..55caab8 --- /dev/null +++ b/vendor/symfony/console/Output/OutputInterface.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 Symfony\Component\Console\Output; + +use Symfony\Component\Console\Formatter\OutputFormatterInterface; + +/** + * OutputInterface is the interface implemented by all Output classes. + * + * @author Fabien Potencier + */ +interface OutputInterface +{ + public const VERBOSITY_QUIET = 16; + public const VERBOSITY_NORMAL = 32; + public const VERBOSITY_VERBOSE = 64; + public const VERBOSITY_VERY_VERBOSE = 128; + public const VERBOSITY_DEBUG = 256; + + public const OUTPUT_NORMAL = 1; + public const OUTPUT_RAW = 2; + public const OUTPUT_PLAIN = 4; + + /** + * Writes a message to the output. + * + * @param string|iterable $messages The message as an iterable of strings or a single string + * @param bool $newline Whether to add a newline + * @param int $options A bitmask of options (one of the OUTPUT or VERBOSITY constants), 0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL + */ + public function write($messages, bool $newline = false, int $options = 0); + + /** + * Writes a message to the output and adds a newline at the end. + * + * @param string|iterable $messages The message as an iterable of strings or a single string + * @param int $options A bitmask of options (one of the OUTPUT or VERBOSITY constants), 0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL + */ + public function writeln($messages, int $options = 0); + + /** + * Sets the verbosity of the output. + */ + public function setVerbosity(int $level); + + /** + * Gets the current verbosity of the output. + * + * @return int + */ + public function getVerbosity(); + + /** + * Returns whether verbosity is quiet (-q). + * + * @return bool + */ + public function isQuiet(); + + /** + * Returns whether verbosity is verbose (-v). + * + * @return bool + */ + public function isVerbose(); + + /** + * Returns whether verbosity is very verbose (-vv). + * + * @return bool + */ + public function isVeryVerbose(); + + /** + * Returns whether verbosity is debug (-vvv). + * + * @return bool + */ + public function isDebug(); + + /** + * Sets the decorated flag. + */ + public function setDecorated(bool $decorated); + + /** + * Gets the decorated flag. + * + * @return bool + */ + public function isDecorated(); + + public function setFormatter(OutputFormatterInterface $formatter); + + /** + * Returns current output formatter instance. + * + * @return OutputFormatterInterface + */ + public function getFormatter(); +} diff --git a/vendor/symfony/console/Output/StreamOutput.php b/vendor/symfony/console/Output/StreamOutput.php new file mode 100644 index 0000000..7f55518 --- /dev/null +++ b/vendor/symfony/console/Output/StreamOutput.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 Symfony\Component\Console\Output; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Formatter\OutputFormatterInterface; + +/** + * StreamOutput writes the output to a given stream. + * + * Usage: + * + * $output = new StreamOutput(fopen('php://stdout', 'w')); + * + * As `StreamOutput` can use any stream, you can also use a file: + * + * $output = new StreamOutput(fopen('/path/to/output.log', 'a', false)); + * + * @author Fabien Potencier + */ +class StreamOutput extends Output +{ + private $stream; + + /** + * @param resource $stream A stream resource + * @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface) + * @param bool|null $decorated Whether to decorate messages (null for auto-guessing) + * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter) + * + * @throws InvalidArgumentException When first argument is not a real stream + */ + public function __construct($stream, int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = null, OutputFormatterInterface $formatter = null) + { + if (!\is_resource($stream) || 'stream' !== get_resource_type($stream)) { + throw new InvalidArgumentException('The StreamOutput class needs a stream as its first argument.'); + } + + $this->stream = $stream; + + if (null === $decorated) { + $decorated = $this->hasColorSupport(); + } + + parent::__construct($verbosity, $decorated, $formatter); + } + + /** + * Gets the stream attached to this StreamOutput instance. + * + * @return resource + */ + public function getStream() + { + return $this->stream; + } + + /** + * {@inheritdoc} + */ + protected function doWrite(string $message, bool $newline) + { + if ($newline) { + $message .= \PHP_EOL; + } + + @fwrite($this->stream, $message); + + fflush($this->stream); + } + + /** + * Returns true if the stream supports colorization. + * + * Colorization is disabled if not supported by the stream: + * + * This is tricky on Windows, because Cygwin, Msys2 etc emulate pseudo + * terminals via named pipes, so we can only check the environment. + * + * Reference: Composer\XdebugHandler\Process::supportsColor + * https://github.com/composer/xdebug-handler + * + * @return bool true if the stream supports colorization, false otherwise + */ + protected function hasColorSupport() + { + // Follow https://no-color.org/ + if (isset($_SERVER['NO_COLOR']) || false !== getenv('NO_COLOR')) { + return false; + } + + if ('Hyper' === getenv('TERM_PROGRAM')) { + return true; + } + + if (\DIRECTORY_SEPARATOR === '\\') { + return (\function_exists('sapi_windows_vt100_support') + && @sapi_windows_vt100_support($this->stream)) + || false !== getenv('ANSICON') + || 'ON' === getenv('ConEmuANSI') + || 'xterm' === getenv('TERM'); + } + + return stream_isatty($this->stream); + } +} diff --git a/vendor/symfony/console/Output/TrimmedBufferOutput.php b/vendor/symfony/console/Output/TrimmedBufferOutput.php new file mode 100644 index 0000000..3f4d375 --- /dev/null +++ b/vendor/symfony/console/Output/TrimmedBufferOutput.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Formatter\OutputFormatterInterface; + +/** + * A BufferedOutput that keeps only the last N chars. + * + * @author Jérémy Derussé + */ +class TrimmedBufferOutput extends Output +{ + private $maxLength; + private $buffer = ''; + + public function __construct(int $maxLength, ?int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = false, OutputFormatterInterface $formatter = null) + { + if ($maxLength <= 0) { + throw new InvalidArgumentException(sprintf('"%s()" expects a strictly positive maxLength. Got %d.', __METHOD__, $maxLength)); + } + + parent::__construct($verbosity, $decorated, $formatter); + $this->maxLength = $maxLength; + } + + /** + * Empties buffer and returns its content. + * + * @return string + */ + public function fetch() + { + $content = $this->buffer; + $this->buffer = ''; + + return $content; + } + + /** + * {@inheritdoc} + */ + protected function doWrite(string $message, bool $newline) + { + $this->buffer .= $message; + + if ($newline) { + $this->buffer .= \PHP_EOL; + } + + $this->buffer = substr($this->buffer, 0 - $this->maxLength); + } +} diff --git a/vendor/symfony/console/Question/ChoiceQuestion.php b/vendor/symfony/console/Question/ChoiceQuestion.php new file mode 100644 index 0000000..bf1f904 --- /dev/null +++ b/vendor/symfony/console/Question/ChoiceQuestion.php @@ -0,0 +1,183 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Question; + +use Symfony\Component\Console\Exception\InvalidArgumentException; + +/** + * Represents a choice question. + * + * @author Fabien Potencier + */ +class ChoiceQuestion extends Question +{ + private $choices; + private $multiselect = false; + private $prompt = ' > '; + private $errorMessage = 'Value "%s" is invalid'; + + /** + * @param string $question The question to ask to the user + * @param array $choices The list of available choices + * @param mixed $default The default answer to return + */ + public function __construct(string $question, array $choices, $default = null) + { + if (!$choices) { + throw new \LogicException('Choice question must have at least 1 choice available.'); + } + + parent::__construct($question, $default); + + $this->choices = $choices; + $this->setValidator($this->getDefaultValidator()); + $this->setAutocompleterValues($choices); + } + + /** + * Returns available choices. + * + * @return array + */ + public function getChoices() + { + return $this->choices; + } + + /** + * Sets multiselect option. + * + * When multiselect is set to true, multiple choices can be answered. + * + * @return $this + */ + public function setMultiselect(bool $multiselect) + { + $this->multiselect = $multiselect; + $this->setValidator($this->getDefaultValidator()); + + return $this; + } + + /** + * Returns whether the choices are multiselect. + * + * @return bool + */ + public function isMultiselect() + { + return $this->multiselect; + } + + /** + * Gets the prompt for choices. + * + * @return string + */ + public function getPrompt() + { + return $this->prompt; + } + + /** + * Sets the prompt for choices. + * + * @return $this + */ + public function setPrompt(string $prompt) + { + $this->prompt = $prompt; + + return $this; + } + + /** + * Sets the error message for invalid values. + * + * The error message has a string placeholder (%s) for the invalid value. + * + * @return $this + */ + public function setErrorMessage(string $errorMessage) + { + $this->errorMessage = $errorMessage; + $this->setValidator($this->getDefaultValidator()); + + return $this; + } + + private function getDefaultValidator(): callable + { + $choices = $this->choices; + $errorMessage = $this->errorMessage; + $multiselect = $this->multiselect; + $isAssoc = $this->isAssoc($choices); + + return function ($selected) use ($choices, $errorMessage, $multiselect, $isAssoc) { + if ($multiselect) { + // Check for a separated comma values + if (!preg_match('/^[^,]+(?:,[^,]+)*$/', (string) $selected, $matches)) { + throw new InvalidArgumentException(sprintf($errorMessage, $selected)); + } + + $selectedChoices = explode(',', (string) $selected); + } else { + $selectedChoices = [$selected]; + } + + if ($this->isTrimmable()) { + foreach ($selectedChoices as $k => $v) { + $selectedChoices[$k] = trim((string) $v); + } + } + + $multiselectChoices = []; + foreach ($selectedChoices as $value) { + $results = []; + foreach ($choices as $key => $choice) { + if ($choice === $value) { + $results[] = $key; + } + } + + if (\count($results) > 1) { + throw new InvalidArgumentException(sprintf('The provided answer is ambiguous. Value should be one of "%s".', implode('" or "', $results))); + } + + $result = array_search($value, $choices); + + if (!$isAssoc) { + if (false !== $result) { + $result = $choices[$result]; + } elseif (isset($choices[$value])) { + $result = $choices[$value]; + } + } elseif (false === $result && isset($choices[$value])) { + $result = $value; + } + + if (false === $result) { + throw new InvalidArgumentException(sprintf($errorMessage, $value)); + } + + // For associative choices, consistently return the key as string: + $multiselectChoices[] = $isAssoc ? (string) $result : $result; + } + + if ($multiselect) { + return $multiselectChoices; + } + + return current($multiselectChoices); + }; + } +} diff --git a/vendor/symfony/console/Question/ConfirmationQuestion.php b/vendor/symfony/console/Question/ConfirmationQuestion.php new file mode 100644 index 0000000..4228521 --- /dev/null +++ b/vendor/symfony/console/Question/ConfirmationQuestion.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Question; + +/** + * Represents a yes/no question. + * + * @author Fabien Potencier + */ +class ConfirmationQuestion extends Question +{ + private $trueAnswerRegex; + + /** + * @param string $question The question to ask to the user + * @param bool $default The default answer to return, true or false + * @param string $trueAnswerRegex A regex to match the "yes" answer + */ + public function __construct(string $question, bool $default = true, string $trueAnswerRegex = '/^y/i') + { + parent::__construct($question, $default); + + $this->trueAnswerRegex = $trueAnswerRegex; + $this->setNormalizer($this->getDefaultNormalizer()); + } + + /** + * Returns the default answer normalizer. + */ + private function getDefaultNormalizer(): callable + { + $default = $this->getDefault(); + $regex = $this->trueAnswerRegex; + + return function ($answer) use ($default, $regex) { + if (\is_bool($answer)) { + return $answer; + } + + $answerIsTrue = (bool) preg_match($regex, $answer); + if (false === $default) { + return $answer && $answerIsTrue; + } + + return '' === $answer || $answerIsTrue; + }; + } +} diff --git a/vendor/symfony/console/Question/Question.php b/vendor/symfony/console/Question/Question.php new file mode 100644 index 0000000..3a73f04 --- /dev/null +++ b/vendor/symfony/console/Question/Question.php @@ -0,0 +1,299 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Question; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\LogicException; + +/** + * Represents a Question. + * + * @author Fabien Potencier + */ +class Question +{ + private $question; + private $attempts; + private $hidden = false; + private $hiddenFallback = true; + private $autocompleterCallback; + private $validator; + private $default; + private $normalizer; + private $trimmable = true; + private $multiline = false; + + /** + * @param string $question The question to ask to the user + * @param string|bool|int|float|null $default The default answer to return if the user enters nothing + */ + public function __construct(string $question, $default = null) + { + $this->question = $question; + $this->default = $default; + } + + /** + * Returns the question. + * + * @return string + */ + public function getQuestion() + { + return $this->question; + } + + /** + * Returns the default answer. + * + * @return string|bool|int|float|null + */ + public function getDefault() + { + return $this->default; + } + + /** + * Returns whether the user response accepts newline characters. + */ + public function isMultiline(): bool + { + return $this->multiline; + } + + /** + * Sets whether the user response should accept newline characters. + * + * @return $this + */ + public function setMultiline(bool $multiline): self + { + $this->multiline = $multiline; + + return $this; + } + + /** + * Returns whether the user response must be hidden. + * + * @return bool + */ + public function isHidden() + { + return $this->hidden; + } + + /** + * Sets whether the user response must be hidden or not. + * + * @return $this + * + * @throws LogicException In case the autocompleter is also used + */ + public function setHidden(bool $hidden) + { + if ($this->autocompleterCallback) { + throw new LogicException('A hidden question cannot use the autocompleter.'); + } + + $this->hidden = $hidden; + + return $this; + } + + /** + * In case the response cannot be hidden, whether to fallback on non-hidden question or not. + * + * @return bool + */ + public function isHiddenFallback() + { + return $this->hiddenFallback; + } + + /** + * Sets whether to fallback on non-hidden question if the response cannot be hidden. + * + * @return $this + */ + public function setHiddenFallback(bool $fallback) + { + $this->hiddenFallback = $fallback; + + return $this; + } + + /** + * Gets values for the autocompleter. + * + * @return iterable|null + */ + public function getAutocompleterValues() + { + $callback = $this->getAutocompleterCallback(); + + return $callback ? $callback('') : null; + } + + /** + * Sets values for the autocompleter. + * + * @return $this + * + * @throws LogicException + */ + public function setAutocompleterValues(?iterable $values) + { + if (\is_array($values)) { + $values = $this->isAssoc($values) ? array_merge(array_keys($values), array_values($values)) : array_values($values); + + $callback = static function () use ($values) { + return $values; + }; + } elseif ($values instanceof \Traversable) { + $valueCache = null; + $callback = static function () use ($values, &$valueCache) { + return $valueCache ?? $valueCache = iterator_to_array($values, false); + }; + } else { + $callback = null; + } + + return $this->setAutocompleterCallback($callback); + } + + /** + * Gets the callback function used for the autocompleter. + */ + public function getAutocompleterCallback(): ?callable + { + return $this->autocompleterCallback; + } + + /** + * Sets the callback function used for the autocompleter. + * + * The callback is passed the user input as argument and should return an iterable of corresponding suggestions. + * + * @return $this + */ + public function setAutocompleterCallback(callable $callback = null): self + { + if ($this->hidden && null !== $callback) { + throw new LogicException('A hidden question cannot use the autocompleter.'); + } + + $this->autocompleterCallback = $callback; + + return $this; + } + + /** + * Sets a validator for the question. + * + * @return $this + */ + public function setValidator(callable $validator = null) + { + $this->validator = $validator; + + return $this; + } + + /** + * Gets the validator for the question. + * + * @return callable|null + */ + public function getValidator() + { + return $this->validator; + } + + /** + * Sets the maximum number of attempts. + * + * Null means an unlimited number of attempts. + * + * @return $this + * + * @throws InvalidArgumentException in case the number of attempts is invalid + */ + public function setMaxAttempts(?int $attempts) + { + if (null !== $attempts && $attempts < 1) { + throw new InvalidArgumentException('Maximum number of attempts must be a positive value.'); + } + + $this->attempts = $attempts; + + return $this; + } + + /** + * Gets the maximum number of attempts. + * + * Null means an unlimited number of attempts. + * + * @return int|null + */ + public function getMaxAttempts() + { + return $this->attempts; + } + + /** + * Sets a normalizer for the response. + * + * The normalizer can be a callable (a string), a closure or a class implementing __invoke. + * + * @return $this + */ + public function setNormalizer(callable $normalizer) + { + $this->normalizer = $normalizer; + + return $this; + } + + /** + * Gets the normalizer for the response. + * + * The normalizer can ba a callable (a string), a closure or a class implementing __invoke. + * + * @return callable|null + */ + public function getNormalizer() + { + return $this->normalizer; + } + + protected function isAssoc(array $array) + { + return (bool) \count(array_filter(array_keys($array), 'is_string')); + } + + public function isTrimmable(): bool + { + return $this->trimmable; + } + + /** + * @return $this + */ + public function setTrimmable(bool $trimmable): self + { + $this->trimmable = $trimmable; + + return $this; + } +} diff --git a/vendor/symfony/console/README.md b/vendor/symfony/console/README.md new file mode 100644 index 0000000..c4c1299 --- /dev/null +++ b/vendor/symfony/console/README.md @@ -0,0 +1,36 @@ +Console Component +================= + +The Console component eases the creation of beautiful and testable command line +interfaces. + +Sponsor +------- + +The Console component for Symfony 5.4/6.0 is [backed][1] by [Les-Tilleuls.coop][2]. + +Les-Tilleuls.coop is a team of 50+ Symfony experts who can help you design, develop and +fix your projects. We provide a wide range of professional services including development, +consulting, coaching, training and audits. We also are highly skilled in JS, Go and DevOps. +We are a worker cooperative! + +Help Symfony by [sponsoring][3] its development! + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/console.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) + +Credits +------- + +`Resources/bin/hiddeninput.exe` is a third party binary provided within this +component. Find sources and license at https://github.com/Seldaek/hidden-input. + +[1]: https://symfony.com/backers +[2]: https://les-tilleuls.coop +[3]: https://symfony.com/sponsor diff --git a/vendor/symfony/console/Resources/bin/hiddeninput.exe b/vendor/symfony/console/Resources/bin/hiddeninput.exe new file mode 100644 index 0000000..c8cf65e Binary files /dev/null and b/vendor/symfony/console/Resources/bin/hiddeninput.exe differ diff --git a/vendor/symfony/console/Resources/completion.bash b/vendor/symfony/console/Resources/completion.bash new file mode 100644 index 0000000..64b87cc --- /dev/null +++ b/vendor/symfony/console/Resources/completion.bash @@ -0,0 +1,84 @@ +# This file is part of the Symfony package. +# +# (c) Fabien Potencier +# +# For the full copyright and license information, please view +# https://symfony.com/doc/current/contributing/code/license.html + +_sf_{{ COMMAND_NAME }}() { + # Use newline as only separator to allow space in completion values + IFS=$'\n' + local sf_cmd="${COMP_WORDS[0]}" + + # for an alias, get the real script behind it + sf_cmd_type=$(type -t $sf_cmd) + if [[ $sf_cmd_type == "alias" ]]; then + sf_cmd=$(alias $sf_cmd | sed -E "s/alias $sf_cmd='(.*)'/\1/") + elif [[ $sf_cmd_type == "file" ]]; then + sf_cmd=$(type -p $sf_cmd) + fi + + if [[ $sf_cmd_type != "function" && ! -x $sf_cmd ]]; then + return 1 + fi + + local cur prev words cword + _get_comp_words_by_ref -n := cur prev words cword + + local completecmd=("$sf_cmd" "_complete" "--no-interaction" "-sbash" "-c$cword" "-S{{ VERSION }}") + for w in ${words[@]}; do + w=$(printf -- '%b' "$w") + # remove quotes from typed values + quote="${w:0:1}" + if [ "$quote" == \' ]; then + w="${w%\'}" + w="${w#\'}" + elif [ "$quote" == \" ]; then + w="${w%\"}" + w="${w#\"}" + fi + # empty values are ignored + if [ ! -z "$w" ]; then + completecmd+=("-i$w") + fi + done + + local sfcomplete + if sfcomplete=$(${completecmd[@]} 2>&1); then + local quote suggestions + quote=${cur:0:1} + + # Use single quotes by default if suggestions contains backslash (FQCN) + if [ "$quote" == '' ] && [[ "$sfcomplete" =~ \\ ]]; then + quote=\' + fi + + if [ "$quote" == \' ]; then + # single quotes: no additional escaping (does not accept ' in values) + suggestions=$(for s in $sfcomplete; do printf $'%q%q%q\n' "$quote" "$s" "$quote"; done) + elif [ "$quote" == \" ]; then + # double quotes: double escaping for \ $ ` " + suggestions=$(for s in $sfcomplete; do + s=${s//\\/\\\\} + s=${s//\$/\\\$} + s=${s//\`/\\\`} + s=${s//\"/\\\"} + printf $'%q%q%q\n' "$quote" "$s" "$quote"; + done) + else + # no quotes: double escaping + suggestions=$(for s in $sfcomplete; do printf $'%q\n' $(printf '%q' "$s"); done) + fi + COMPREPLY=($(IFS=$'\n' compgen -W "$suggestions" -- $(printf -- "%q" "$cur"))) + __ltrim_colon_completions "$cur" + else + if [[ "$sfcomplete" != *"Command \"_complete\" is not defined."* ]]; then + >&2 echo + >&2 echo $sfcomplete + fi + + return 1 + fi +} + +complete -F _sf_{{ COMMAND_NAME }} {{ COMMAND_NAME }} diff --git a/vendor/symfony/console/SignalRegistry/SignalRegistry.php b/vendor/symfony/console/SignalRegistry/SignalRegistry.php new file mode 100644 index 0000000..6bee24a --- /dev/null +++ b/vendor/symfony/console/SignalRegistry/SignalRegistry.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\SignalRegistry; + +final class SignalRegistry +{ + private $signalHandlers = []; + + public function __construct() + { + if (\function_exists('pcntl_async_signals')) { + pcntl_async_signals(true); + } + } + + public function register(int $signal, callable $signalHandler): void + { + if (!isset($this->signalHandlers[$signal])) { + $previousCallback = pcntl_signal_get_handler($signal); + + if (\is_callable($previousCallback)) { + $this->signalHandlers[$signal][] = $previousCallback; + } + } + + $this->signalHandlers[$signal][] = $signalHandler; + + pcntl_signal($signal, [$this, 'handle']); + } + + public static function isSupported(): bool + { + if (!\function_exists('pcntl_signal')) { + return false; + } + + if (\in_array('pcntl_signal', explode(',', \ini_get('disable_functions')))) { + return false; + } + + return true; + } + + /** + * @internal + */ + public function handle(int $signal): void + { + $count = \count($this->signalHandlers[$signal]); + + foreach ($this->signalHandlers[$signal] as $i => $signalHandler) { + $hasNext = $i !== $count - 1; + $signalHandler($signal, $hasNext); + } + } +} diff --git a/vendor/symfony/console/SingleCommandApplication.php b/vendor/symfony/console/SingleCommandApplication.php new file mode 100644 index 0000000..e93c182 --- /dev/null +++ b/vendor/symfony/console/SingleCommandApplication.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Grégoire Pineau + */ +class SingleCommandApplication extends Command +{ + private $version = 'UNKNOWN'; + private $autoExit = true; + private $running = false; + + /** + * @return $this + */ + public function setVersion(string $version): self + { + $this->version = $version; + + return $this; + } + + /** + * @final + * + * @return $this + */ + public function setAutoExit(bool $autoExit): self + { + $this->autoExit = $autoExit; + + return $this; + } + + public function run(InputInterface $input = null, OutputInterface $output = null): int + { + if ($this->running) { + return parent::run($input, $output); + } + + // We use the command name as the application name + $application = new Application($this->getName() ?: 'UNKNOWN', $this->version); + $application->setAutoExit($this->autoExit); + // Fix the usage of the command displayed with "--help" + $this->setName($_SERVER['argv'][0]); + $application->add($this); + $application->setDefaultCommand($this->getName(), true); + + $this->running = true; + try { + $ret = $application->run($input, $output); + } finally { + $this->running = false; + } + + return $ret ?? 1; + } +} diff --git a/vendor/symfony/console/Style/OutputStyle.php b/vendor/symfony/console/Style/OutputStyle.php new file mode 100644 index 0000000..67a98ff --- /dev/null +++ b/vendor/symfony/console/Style/OutputStyle.php @@ -0,0 +1,153 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Style; + +use Symfony\Component\Console\Formatter\OutputFormatterInterface; +use Symfony\Component\Console\Helper\ProgressBar; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Decorates output to add console style guide helpers. + * + * @author Kevin Bond + */ +abstract class OutputStyle implements OutputInterface, StyleInterface +{ + private $output; + + public function __construct(OutputInterface $output) + { + $this->output = $output; + } + + /** + * {@inheritdoc} + */ + public function newLine(int $count = 1) + { + $this->output->write(str_repeat(\PHP_EOL, $count)); + } + + /** + * @return ProgressBar + */ + public function createProgressBar(int $max = 0) + { + return new ProgressBar($this->output, $max); + } + + /** + * {@inheritdoc} + */ + public function write($messages, bool $newline = false, int $type = self::OUTPUT_NORMAL) + { + $this->output->write($messages, $newline, $type); + } + + /** + * {@inheritdoc} + */ + public function writeln($messages, int $type = self::OUTPUT_NORMAL) + { + $this->output->writeln($messages, $type); + } + + /** + * {@inheritdoc} + */ + public function setVerbosity(int $level) + { + $this->output->setVerbosity($level); + } + + /** + * {@inheritdoc} + */ + public function getVerbosity() + { + return $this->output->getVerbosity(); + } + + /** + * {@inheritdoc} + */ + public function setDecorated(bool $decorated) + { + $this->output->setDecorated($decorated); + } + + /** + * {@inheritdoc} + */ + public function isDecorated() + { + return $this->output->isDecorated(); + } + + /** + * {@inheritdoc} + */ + public function setFormatter(OutputFormatterInterface $formatter) + { + $this->output->setFormatter($formatter); + } + + /** + * {@inheritdoc} + */ + public function getFormatter() + { + return $this->output->getFormatter(); + } + + /** + * {@inheritdoc} + */ + public function isQuiet() + { + return $this->output->isQuiet(); + } + + /** + * {@inheritdoc} + */ + public function isVerbose() + { + return $this->output->isVerbose(); + } + + /** + * {@inheritdoc} + */ + public function isVeryVerbose() + { + return $this->output->isVeryVerbose(); + } + + /** + * {@inheritdoc} + */ + public function isDebug() + { + return $this->output->isDebug(); + } + + protected function getErrorOutput() + { + if (!$this->output instanceof ConsoleOutputInterface) { + return $this->output; + } + + return $this->output->getErrorOutput(); + } +} diff --git a/vendor/symfony/console/Style/StyleInterface.php b/vendor/symfony/console/Style/StyleInterface.php new file mode 100644 index 0000000..38d23b7 --- /dev/null +++ b/vendor/symfony/console/Style/StyleInterface.php @@ -0,0 +1,132 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Style; + +/** + * Output style helpers. + * + * @author Kevin Bond + */ +interface StyleInterface +{ + /** + * Formats a command title. + */ + public function title(string $message); + + /** + * Formats a section title. + */ + public function section(string $message); + + /** + * Formats a list. + */ + public function listing(array $elements); + + /** + * Formats informational text. + * + * @param string|array $message + */ + public function text($message); + + /** + * Formats a success result bar. + * + * @param string|array $message + */ + public function success($message); + + /** + * Formats an error result bar. + * + * @param string|array $message + */ + public function error($message); + + /** + * Formats an warning result bar. + * + * @param string|array $message + */ + public function warning($message); + + /** + * Formats a note admonition. + * + * @param string|array $message + */ + public function note($message); + + /** + * Formats a caution admonition. + * + * @param string|array $message + */ + public function caution($message); + + /** + * Formats a table. + */ + public function table(array $headers, array $rows); + + /** + * Asks a question. + * + * @return mixed + */ + public function ask(string $question, string $default = null, callable $validator = null); + + /** + * Asks a question with the user input hidden. + * + * @return mixed + */ + public function askHidden(string $question, callable $validator = null); + + /** + * Asks for confirmation. + * + * @return bool + */ + public function confirm(string $question, bool $default = true); + + /** + * Asks a choice question. + * + * @param string|int|null $default + * + * @return mixed + */ + public function choice(string $question, array $choices, $default = null); + + /** + * Add newline(s). + */ + public function newLine(int $count = 1); + + /** + * Starts the progress output. + */ + public function progressStart(int $max = 0); + + /** + * Advances the progress output X steps. + */ + public function progressAdvance(int $step = 1); + + /** + * Finishes the progress output. + */ + public function progressFinish(); +} diff --git a/vendor/symfony/console/Style/SymfonyStyle.php b/vendor/symfony/console/Style/SymfonyStyle.php new file mode 100644 index 0000000..e3c5ac8 --- /dev/null +++ b/vendor/symfony/console/Style/SymfonyStyle.php @@ -0,0 +1,518 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Style; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\RuntimeException; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Helper\Helper; +use Symfony\Component\Console\Helper\ProgressBar; +use Symfony\Component\Console\Helper\SymfonyQuestionHelper; +use Symfony\Component\Console\Helper\Table; +use Symfony\Component\Console\Helper\TableCell; +use Symfony\Component\Console\Helper\TableSeparator; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Output\TrimmedBufferOutput; +use Symfony\Component\Console\Question\ChoiceQuestion; +use Symfony\Component\Console\Question\ConfirmationQuestion; +use Symfony\Component\Console\Question\Question; +use Symfony\Component\Console\Terminal; + +/** + * Output decorator helpers for the Symfony Style Guide. + * + * @author Kevin Bond + */ +class SymfonyStyle extends OutputStyle +{ + public const MAX_LINE_LENGTH = 120; + + private $input; + private $output; + private $questionHelper; + private $progressBar; + private $lineLength; + private $bufferedOutput; + + public function __construct(InputInterface $input, OutputInterface $output) + { + $this->input = $input; + $this->bufferedOutput = new TrimmedBufferOutput(\DIRECTORY_SEPARATOR === '\\' ? 4 : 2, $output->getVerbosity(), false, clone $output->getFormatter()); + // Windows cmd wraps lines as soon as the terminal width is reached, whether there are following chars or not. + $width = (new Terminal())->getWidth() ?: self::MAX_LINE_LENGTH; + $this->lineLength = min($width - (int) (\DIRECTORY_SEPARATOR === '\\'), self::MAX_LINE_LENGTH); + + parent::__construct($this->output = $output); + } + + /** + * Formats a message as a block of text. + * + * @param string|array $messages The message to write in the block + */ + public function block($messages, string $type = null, string $style = null, string $prefix = ' ', bool $padding = false, bool $escape = true) + { + $messages = \is_array($messages) ? array_values($messages) : [$messages]; + + $this->autoPrependBlock(); + $this->writeln($this->createBlock($messages, $type, $style, $prefix, $padding, $escape)); + $this->newLine(); + } + + /** + * {@inheritdoc} + */ + public function title(string $message) + { + $this->autoPrependBlock(); + $this->writeln([ + sprintf('%s', OutputFormatter::escapeTrailingBackslash($message)), + sprintf('%s', str_repeat('=', Helper::width(Helper::removeDecoration($this->getFormatter(), $message)))), + ]); + $this->newLine(); + } + + /** + * {@inheritdoc} + */ + public function section(string $message) + { + $this->autoPrependBlock(); + $this->writeln([ + sprintf('%s', OutputFormatter::escapeTrailingBackslash($message)), + sprintf('%s', str_repeat('-', Helper::width(Helper::removeDecoration($this->getFormatter(), $message)))), + ]); + $this->newLine(); + } + + /** + * {@inheritdoc} + */ + public function listing(array $elements) + { + $this->autoPrependText(); + $elements = array_map(function ($element) { + return sprintf(' * %s', $element); + }, $elements); + + $this->writeln($elements); + $this->newLine(); + } + + /** + * {@inheritdoc} + */ + public function text($message) + { + $this->autoPrependText(); + + $messages = \is_array($message) ? array_values($message) : [$message]; + foreach ($messages as $message) { + $this->writeln(sprintf(' %s', $message)); + } + } + + /** + * Formats a command comment. + * + * @param string|array $message + */ + public function comment($message) + { + $this->block($message, null, null, ' // ', false, false); + } + + /** + * {@inheritdoc} + */ + public function success($message) + { + $this->block($message, 'OK', 'fg=black;bg=green', ' ', true); + } + + /** + * {@inheritdoc} + */ + public function error($message) + { + $this->block($message, 'ERROR', 'fg=white;bg=red', ' ', true); + } + + /** + * {@inheritdoc} + */ + public function warning($message) + { + $this->block($message, 'WARNING', 'fg=black;bg=yellow', ' ', true); + } + + /** + * {@inheritdoc} + */ + public function note($message) + { + $this->block($message, 'NOTE', 'fg=yellow', ' ! '); + } + + /** + * Formats an info message. + * + * @param string|array $message + */ + public function info($message) + { + $this->block($message, 'INFO', 'fg=green', ' ', true); + } + + /** + * {@inheritdoc} + */ + public function caution($message) + { + $this->block($message, 'CAUTION', 'fg=white;bg=red', ' ! ', true); + } + + /** + * {@inheritdoc} + */ + public function table(array $headers, array $rows) + { + $this->createTable() + ->setHeaders($headers) + ->setRows($rows) + ->render() + ; + + $this->newLine(); + } + + /** + * Formats a horizontal table. + */ + public function horizontalTable(array $headers, array $rows) + { + $this->createTable() + ->setHorizontal(true) + ->setHeaders($headers) + ->setRows($rows) + ->render() + ; + + $this->newLine(); + } + + /** + * Formats a list of key/value horizontally. + * + * Each row can be one of: + * * 'A title' + * * ['key' => 'value'] + * * new TableSeparator() + * + * @param string|array|TableSeparator ...$list + */ + public function definitionList(...$list) + { + $headers = []; + $row = []; + foreach ($list as $value) { + if ($value instanceof TableSeparator) { + $headers[] = $value; + $row[] = $value; + continue; + } + if (\is_string($value)) { + $headers[] = new TableCell($value, ['colspan' => 2]); + $row[] = null; + continue; + } + if (!\is_array($value)) { + throw new InvalidArgumentException('Value should be an array, string, or an instance of TableSeparator.'); + } + $headers[] = key($value); + $row[] = current($value); + } + + $this->horizontalTable($headers, [$row]); + } + + /** + * {@inheritdoc} + */ + public function ask(string $question, string $default = null, callable $validator = null) + { + $question = new Question($question, $default); + $question->setValidator($validator); + + return $this->askQuestion($question); + } + + /** + * {@inheritdoc} + */ + public function askHidden(string $question, callable $validator = null) + { + $question = new Question($question); + + $question->setHidden(true); + $question->setValidator($validator); + + return $this->askQuestion($question); + } + + /** + * {@inheritdoc} + */ + public function confirm(string $question, bool $default = true) + { + return $this->askQuestion(new ConfirmationQuestion($question, $default)); + } + + /** + * {@inheritdoc} + */ + public function choice(string $question, array $choices, $default = null) + { + if (null !== $default) { + $values = array_flip($choices); + $default = $values[$default] ?? $default; + } + + return $this->askQuestion(new ChoiceQuestion($question, $choices, $default)); + } + + /** + * {@inheritdoc} + */ + public function progressStart(int $max = 0) + { + $this->progressBar = $this->createProgressBar($max); + $this->progressBar->start(); + } + + /** + * {@inheritdoc} + */ + public function progressAdvance(int $step = 1) + { + $this->getProgressBar()->advance($step); + } + + /** + * {@inheritdoc} + */ + public function progressFinish() + { + $this->getProgressBar()->finish(); + $this->newLine(2); + $this->progressBar = null; + } + + /** + * {@inheritdoc} + */ + public function createProgressBar(int $max = 0) + { + $progressBar = parent::createProgressBar($max); + + if ('\\' !== \DIRECTORY_SEPARATOR || 'Hyper' === getenv('TERM_PROGRAM')) { + $progressBar->setEmptyBarCharacter('░'); // light shade character \u2591 + $progressBar->setProgressCharacter(''); + $progressBar->setBarCharacter('▓'); // dark shade character \u2593 + } + + return $progressBar; + } + + /** + * @see ProgressBar::iterate() + */ + public function progressIterate(iterable $iterable, int $max = null): iterable + { + yield from $this->createProgressBar()->iterate($iterable, $max); + + $this->newLine(2); + } + + /** + * @return mixed + */ + public function askQuestion(Question $question) + { + if ($this->input->isInteractive()) { + $this->autoPrependBlock(); + } + + if (!$this->questionHelper) { + $this->questionHelper = new SymfonyQuestionHelper(); + } + + $answer = $this->questionHelper->ask($this->input, $this, $question); + + if ($this->input->isInteractive()) { + $this->newLine(); + $this->bufferedOutput->write("\n"); + } + + return $answer; + } + + /** + * {@inheritdoc} + */ + public function writeln($messages, int $type = self::OUTPUT_NORMAL) + { + if (!is_iterable($messages)) { + $messages = [$messages]; + } + + foreach ($messages as $message) { + parent::writeln($message, $type); + $this->writeBuffer($message, true, $type); + } + } + + /** + * {@inheritdoc} + */ + public function write($messages, bool $newline = false, int $type = self::OUTPUT_NORMAL) + { + if (!is_iterable($messages)) { + $messages = [$messages]; + } + + foreach ($messages as $message) { + parent::write($message, $newline, $type); + $this->writeBuffer($message, $newline, $type); + } + } + + /** + * {@inheritdoc} + */ + public function newLine(int $count = 1) + { + parent::newLine($count); + $this->bufferedOutput->write(str_repeat("\n", $count)); + } + + /** + * Returns a new instance which makes use of stderr if available. + * + * @return self + */ + public function getErrorStyle() + { + return new self($this->input, $this->getErrorOutput()); + } + + public function createTable(): Table + { + $output = $this->output instanceof ConsoleOutputInterface ? $this->output->section() : $this->output; + $style = clone Table::getStyleDefinition('symfony-style-guide'); + $style->setCellHeaderFormat('%s'); + + return (new Table($output))->setStyle($style); + } + + private function getProgressBar(): ProgressBar + { + if (!$this->progressBar) { + throw new RuntimeException('The ProgressBar is not started.'); + } + + return $this->progressBar; + } + + private function autoPrependBlock(): void + { + $chars = substr(str_replace(\PHP_EOL, "\n", $this->bufferedOutput->fetch()), -2); + + if (!isset($chars[0])) { + $this->newLine(); // empty history, so we should start with a new line. + + return; + } + // Prepend new line for each non LF chars (This means no blank line was output before) + $this->newLine(2 - substr_count($chars, "\n")); + } + + private function autoPrependText(): void + { + $fetched = $this->bufferedOutput->fetch(); + // Prepend new line if last char isn't EOL: + if (!str_ends_with($fetched, "\n")) { + $this->newLine(); + } + } + + private function writeBuffer(string $message, bool $newLine, int $type): void + { + // We need to know if the last chars are PHP_EOL + $this->bufferedOutput->write($message, $newLine, $type); + } + + private function createBlock(iterable $messages, string $type = null, string $style = null, string $prefix = ' ', bool $padding = false, bool $escape = false): array + { + $indentLength = 0; + $prefixLength = Helper::width(Helper::removeDecoration($this->getFormatter(), $prefix)); + $lines = []; + + if (null !== $type) { + $type = sprintf('[%s] ', $type); + $indentLength = \strlen($type); + $lineIndentation = str_repeat(' ', $indentLength); + } + + // wrap and add newlines for each element + foreach ($messages as $key => $message) { + if ($escape) { + $message = OutputFormatter::escape($message); + } + + $decorationLength = Helper::width($message) - Helper::width(Helper::removeDecoration($this->getFormatter(), $message)); + $messageLineLength = min($this->lineLength - $prefixLength - $indentLength + $decorationLength, $this->lineLength); + $messageLines = explode(\PHP_EOL, wordwrap($message, $messageLineLength, \PHP_EOL, true)); + foreach ($messageLines as $messageLine) { + $lines[] = $messageLine; + } + + if (\count($messages) > 1 && $key < \count($messages) - 1) { + $lines[] = ''; + } + } + + $firstLineIndex = 0; + if ($padding && $this->isDecorated()) { + $firstLineIndex = 1; + array_unshift($lines, ''); + $lines[] = ''; + } + + foreach ($lines as $i => &$line) { + if (null !== $type) { + $line = $firstLineIndex === $i ? $type.$line : $lineIndentation.$line; + } + + $line = $prefix.$line; + $line .= str_repeat(' ', max($this->lineLength - Helper::width(Helper::removeDecoration($this->getFormatter(), $line)), 0)); + + if ($style) { + $line = sprintf('<%s>%s', $style, $line); + } + } + + return $lines; + } +} diff --git a/vendor/symfony/console/Terminal.php b/vendor/symfony/console/Terminal.php new file mode 100644 index 0000000..b91e8af --- /dev/null +++ b/vendor/symfony/console/Terminal.php @@ -0,0 +1,177 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console; + +class Terminal +{ + private static $width; + private static $height; + private static $stty; + + /** + * Gets the terminal width. + * + * @return int + */ + public function getWidth() + { + $width = getenv('COLUMNS'); + if (false !== $width) { + return (int) trim($width); + } + + if (null === self::$width) { + self::initDimensions(); + } + + return self::$width ?: 80; + } + + /** + * Gets the terminal height. + * + * @return int + */ + public function getHeight() + { + $height = getenv('LINES'); + if (false !== $height) { + return (int) trim($height); + } + + if (null === self::$height) { + self::initDimensions(); + } + + return self::$height ?: 50; + } + + /** + * @internal + */ + public static function hasSttyAvailable(): bool + { + if (null !== self::$stty) { + return self::$stty; + } + + // skip check if shell_exec function is disabled + if (!\function_exists('shell_exec')) { + return false; + } + + return self::$stty = (bool) shell_exec('stty 2> '.('\\' === \DIRECTORY_SEPARATOR ? 'NUL' : '/dev/null')); + } + + private static function initDimensions() + { + if ('\\' === \DIRECTORY_SEPARATOR) { + $ansicon = getenv('ANSICON'); + if (false !== $ansicon && preg_match('/^(\d+)x(\d+)(?: \((\d+)x(\d+)\))?$/', trim($ansicon), $matches)) { + // extract [w, H] from "wxh (WxH)" + // or [w, h] from "wxh" + self::$width = (int) $matches[1]; + self::$height = isset($matches[4]) ? (int) $matches[4] : (int) $matches[2]; + } elseif (!self::hasVt100Support() && self::hasSttyAvailable()) { + // only use stty on Windows if the terminal does not support vt100 (e.g. Windows 7 + git-bash) + // testing for stty in a Windows 10 vt100-enabled console will implicitly disable vt100 support on STDOUT + self::initDimensionsUsingStty(); + } elseif (null !== $dimensions = self::getConsoleMode()) { + // extract [w, h] from "wxh" + self::$width = (int) $dimensions[0]; + self::$height = (int) $dimensions[1]; + } + } else { + self::initDimensionsUsingStty(); + } + } + + /** + * Returns whether STDOUT has vt100 support (some Windows 10+ configurations). + */ + private static function hasVt100Support(): bool + { + return \function_exists('sapi_windows_vt100_support') && sapi_windows_vt100_support(fopen('php://stdout', 'w')); + } + + /** + * Initializes dimensions using the output of an stty columns line. + */ + private static function initDimensionsUsingStty() + { + if ($sttyString = self::getSttyColumns()) { + if (preg_match('/rows.(\d+);.columns.(\d+);/i', $sttyString, $matches)) { + // extract [w, h] from "rows h; columns w;" + self::$width = (int) $matches[2]; + self::$height = (int) $matches[1]; + } elseif (preg_match('/;.(\d+).rows;.(\d+).columns/i', $sttyString, $matches)) { + // extract [w, h] from "; h rows; w columns" + self::$width = (int) $matches[2]; + self::$height = (int) $matches[1]; + } + } + } + + /** + * Runs and parses mode CON if it's available, suppressing any error output. + * + * @return int[]|null An array composed of the width and the height or null if it could not be parsed + */ + private static function getConsoleMode(): ?array + { + $info = self::readFromProcess('mode CON'); + + if (null === $info || !preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', $info, $matches)) { + return null; + } + + return [(int) $matches[2], (int) $matches[1]]; + } + + /** + * Runs and parses stty -a if it's available, suppressing any error output. + */ + private static function getSttyColumns(): ?string + { + return self::readFromProcess('stty -a | grep columns'); + } + + private static function readFromProcess(string $command): ?string + { + if (!\function_exists('proc_open')) { + return null; + } + + $descriptorspec = [ + 1 => ['pipe', 'w'], + 2 => ['pipe', 'w'], + ]; + + $cp = \function_exists('sapi_windows_cp_set') ? sapi_windows_cp_get() : 0; + + $process = proc_open($command, $descriptorspec, $pipes, null, null, ['suppress_errors' => true]); + if (!\is_resource($process)) { + return null; + } + + $info = stream_get_contents($pipes[1]); + fclose($pipes[1]); + fclose($pipes[2]); + proc_close($process); + + if ($cp) { + sapi_windows_cp_set($cp); + } + + return $info; + } +} diff --git a/vendor/symfony/console/Tester/ApplicationTester.php b/vendor/symfony/console/Tester/ApplicationTester.php new file mode 100644 index 0000000..3a262e8 --- /dev/null +++ b/vendor/symfony/console/Tester/ApplicationTester.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tester; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Input\ArrayInput; + +/** + * Eases the testing of console applications. + * + * When testing an application, don't forget to disable the auto exit flag: + * + * $application = new Application(); + * $application->setAutoExit(false); + * + * @author Fabien Potencier + */ +class ApplicationTester +{ + use TesterTrait; + + private $application; + + public function __construct(Application $application) + { + $this->application = $application; + } + + /** + * Executes the application. + * + * Available options: + * + * * interactive: Sets the input interactive flag + * * decorated: Sets the output decorated flag + * * verbosity: Sets the output verbosity flag + * * capture_stderr_separately: Make output of stdOut and stdErr separately available + * + * @return int The command exit code + */ + public function run(array $input, array $options = []) + { + $prevShellVerbosity = getenv('SHELL_VERBOSITY'); + + try { + $this->input = new ArrayInput($input); + if (isset($options['interactive'])) { + $this->input->setInteractive($options['interactive']); + } + + if ($this->inputs) { + $this->input->setStream(self::createStream($this->inputs)); + } + + $this->initOutput($options); + + return $this->statusCode = $this->application->run($this->input, $this->output); + } finally { + // SHELL_VERBOSITY is set by Application::configureIO so we need to unset/reset it + // to its previous value to avoid one test's verbosity to spread to the following tests + if (false === $prevShellVerbosity) { + if (\function_exists('putenv')) { + @putenv('SHELL_VERBOSITY'); + } + unset($_ENV['SHELL_VERBOSITY']); + unset($_SERVER['SHELL_VERBOSITY']); + } else { + if (\function_exists('putenv')) { + @putenv('SHELL_VERBOSITY='.$prevShellVerbosity); + } + $_ENV['SHELL_VERBOSITY'] = $prevShellVerbosity; + $_SERVER['SHELL_VERBOSITY'] = $prevShellVerbosity; + } + } + } +} diff --git a/vendor/symfony/console/Tester/CommandCompletionTester.php b/vendor/symfony/console/Tester/CommandCompletionTester.php new file mode 100644 index 0000000..ade7327 --- /dev/null +++ b/vendor/symfony/console/Tester/CommandCompletionTester.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tester; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; + +/** + * Eases the testing of command completion. + * + * @author Jérôme Tamarelle + */ +class CommandCompletionTester +{ + private $command; + + public function __construct(Command $command) + { + $this->command = $command; + } + + /** + * Create completion suggestions from input tokens. + */ + public function complete(array $input): array + { + $currentIndex = \count($input); + if ('' === end($input)) { + array_pop($input); + } + array_unshift($input, $this->command->getName()); + + $completionInput = CompletionInput::fromTokens($input, $currentIndex); + $completionInput->bind($this->command->getDefinition()); + $suggestions = new CompletionSuggestions(); + + $this->command->complete($completionInput, $suggestions); + + $options = []; + foreach ($suggestions->getOptionSuggestions() as $option) { + $options[] = '--'.$option->getName(); + } + + return array_map('strval', array_merge($options, $suggestions->getValueSuggestions())); + } +} diff --git a/vendor/symfony/console/Tester/CommandTester.php b/vendor/symfony/console/Tester/CommandTester.php new file mode 100644 index 0000000..6c15c25 --- /dev/null +++ b/vendor/symfony/console/Tester/CommandTester.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tester; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\ArrayInput; + +/** + * Eases the testing of console commands. + * + * @author Fabien Potencier + * @author Robin Chalas + */ +class CommandTester +{ + use TesterTrait; + + private $command; + + public function __construct(Command $command) + { + $this->command = $command; + } + + /** + * Executes the command. + * + * Available execution options: + * + * * interactive: Sets the input interactive flag + * * decorated: Sets the output decorated flag + * * verbosity: Sets the output verbosity flag + * * capture_stderr_separately: Make output of stdOut and stdErr separately available + * + * @param array $input An array of command arguments and options + * @param array $options An array of execution options + * + * @return int The command exit code + */ + public function execute(array $input, array $options = []) + { + // set the command name automatically if the application requires + // this argument and no command name was passed + if (!isset($input['command']) + && (null !== $application = $this->command->getApplication()) + && $application->getDefinition()->hasArgument('command') + ) { + $input = array_merge(['command' => $this->command->getName()], $input); + } + + $this->input = new ArrayInput($input); + // Use an in-memory input stream even if no inputs are set so that QuestionHelper::ask() does not rely on the blocking STDIN. + $this->input->setStream(self::createStream($this->inputs)); + + if (isset($options['interactive'])) { + $this->input->setInteractive($options['interactive']); + } + + if (!isset($options['decorated'])) { + $options['decorated'] = false; + } + + $this->initOutput($options); + + return $this->statusCode = $this->command->run($this->input, $this->output); + } +} diff --git a/vendor/symfony/console/Tester/Constraint/CommandIsSuccessful.php b/vendor/symfony/console/Tester/Constraint/CommandIsSuccessful.php new file mode 100644 index 0000000..a473242 --- /dev/null +++ b/vendor/symfony/console/Tester/Constraint/CommandIsSuccessful.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tester\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\Console\Command\Command; + +final class CommandIsSuccessful extends Constraint +{ + /** + * {@inheritdoc} + */ + public function toString(): string + { + return 'is successful'; + } + + /** + * {@inheritdoc} + */ + protected function matches($other): bool + { + return Command::SUCCESS === $other; + } + + /** + * {@inheritdoc} + */ + protected function failureDescription($other): string + { + return 'the command '.$this->toString(); + } + + /** + * {@inheritdoc} + */ + protected function additionalFailureDescription($other): string + { + $mapping = [ + Command::FAILURE => 'Command failed.', + Command::INVALID => 'Command was invalid.', + ]; + + return $mapping[$other] ?? sprintf('Command returned exit status %d.', $other); + } +} diff --git a/vendor/symfony/console/Tester/TesterTrait.php b/vendor/symfony/console/Tester/TesterTrait.php new file mode 100644 index 0000000..f454bbf --- /dev/null +++ b/vendor/symfony/console/Tester/TesterTrait.php @@ -0,0 +1,197 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tester; + +use PHPUnit\Framework\Assert; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\ConsoleOutput; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Output\StreamOutput; +use Symfony\Component\Console\Tester\Constraint\CommandIsSuccessful; + +/** + * @author Amrouche Hamza + */ +trait TesterTrait +{ + /** @var StreamOutput */ + private $output; + private $inputs = []; + private $captureStreamsIndependently = false; + /** @var InputInterface */ + private $input; + /** @var int */ + private $statusCode; + + /** + * Gets the display returned by the last execution of the command or application. + * + * @return string + * + * @throws \RuntimeException If it's called before the execute method + */ + public function getDisplay(bool $normalize = false) + { + if (null === $this->output) { + throw new \RuntimeException('Output not initialized, did you execute the command before requesting the display?'); + } + + rewind($this->output->getStream()); + + $display = stream_get_contents($this->output->getStream()); + + if ($normalize) { + $display = str_replace(\PHP_EOL, "\n", $display); + } + + return $display; + } + + /** + * Gets the output written to STDERR by the application. + * + * @param bool $normalize Whether to normalize end of lines to \n or not + * + * @return string + */ + public function getErrorOutput(bool $normalize = false) + { + if (!$this->captureStreamsIndependently) { + throw new \LogicException('The error output is not available when the tester is run without "capture_stderr_separately" option set.'); + } + + rewind($this->output->getErrorOutput()->getStream()); + + $display = stream_get_contents($this->output->getErrorOutput()->getStream()); + + if ($normalize) { + $display = str_replace(\PHP_EOL, "\n", $display); + } + + return $display; + } + + /** + * Gets the input instance used by the last execution of the command or application. + * + * @return InputInterface + */ + public function getInput() + { + return $this->input; + } + + /** + * Gets the output instance used by the last execution of the command or application. + * + * @return OutputInterface + */ + public function getOutput() + { + return $this->output; + } + + /** + * Gets the status code returned by the last execution of the command or application. + * + * @return int + * + * @throws \RuntimeException If it's called before the execute method + */ + public function getStatusCode() + { + if (null === $this->statusCode) { + throw new \RuntimeException('Status code not initialized, did you execute the command before requesting the status code?'); + } + + return $this->statusCode; + } + + public function assertCommandIsSuccessful(string $message = ''): void + { + Assert::assertThat($this->statusCode, new CommandIsSuccessful(), $message); + } + + /** + * Sets the user inputs. + * + * @param array $inputs An array of strings representing each input + * passed to the command input stream + * + * @return $this + */ + public function setInputs(array $inputs) + { + $this->inputs = $inputs; + + return $this; + } + + /** + * Initializes the output property. + * + * Available options: + * + * * decorated: Sets the output decorated flag + * * verbosity: Sets the output verbosity flag + * * capture_stderr_separately: Make output of stdOut and stdErr separately available + */ + private function initOutput(array $options) + { + $this->captureStreamsIndependently = \array_key_exists('capture_stderr_separately', $options) && $options['capture_stderr_separately']; + if (!$this->captureStreamsIndependently) { + $this->output = new StreamOutput(fopen('php://memory', 'w', false)); + if (isset($options['decorated'])) { + $this->output->setDecorated($options['decorated']); + } + if (isset($options['verbosity'])) { + $this->output->setVerbosity($options['verbosity']); + } + } else { + $this->output = new ConsoleOutput( + $options['verbosity'] ?? ConsoleOutput::VERBOSITY_NORMAL, + $options['decorated'] ?? null + ); + + $errorOutput = new StreamOutput(fopen('php://memory', 'w', false)); + $errorOutput->setFormatter($this->output->getFormatter()); + $errorOutput->setVerbosity($this->output->getVerbosity()); + $errorOutput->setDecorated($this->output->isDecorated()); + + $reflectedOutput = new \ReflectionObject($this->output); + $strErrProperty = $reflectedOutput->getProperty('stderr'); + $strErrProperty->setAccessible(true); + $strErrProperty->setValue($this->output, $errorOutput); + + $reflectedParent = $reflectedOutput->getParentClass(); + $streamProperty = $reflectedParent->getProperty('stream'); + $streamProperty->setAccessible(true); + $streamProperty->setValue($this->output, fopen('php://memory', 'w', false)); + } + } + + /** + * @return resource + */ + private static function createStream(array $inputs) + { + $stream = fopen('php://memory', 'r+', false); + + foreach ($inputs as $input) { + fwrite($stream, $input.\PHP_EOL); + } + + rewind($stream); + + return $stream; + } +} diff --git a/vendor/symfony/deprecation-contracts/.gitignore b/vendor/symfony/deprecation-contracts/.gitignore new file mode 100644 index 0000000..c49a5d8 --- /dev/null +++ b/vendor/symfony/deprecation-contracts/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/vendor/symfony/deprecation-contracts/CHANGELOG.md b/vendor/symfony/deprecation-contracts/CHANGELOG.md new file mode 100644 index 0000000..7932e26 --- /dev/null +++ b/vendor/symfony/deprecation-contracts/CHANGELOG.md @@ -0,0 +1,5 @@ +CHANGELOG +========= + +The changelog is maintained for all Symfony contracts at the following URL: +https://github.com/symfony/contracts/blob/main/CHANGELOG.md diff --git a/vendor/symfony/deprecation-contracts/LICENSE b/vendor/symfony/deprecation-contracts/LICENSE new file mode 100644 index 0000000..406242f --- /dev/null +++ b/vendor/symfony/deprecation-contracts/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2020-2022 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/deprecation-contracts/README.md b/vendor/symfony/deprecation-contracts/README.md new file mode 100644 index 0000000..4957933 --- /dev/null +++ b/vendor/symfony/deprecation-contracts/README.md @@ -0,0 +1,26 @@ +Symfony Deprecation Contracts +============================= + +A generic function and convention to trigger deprecation notices. + +This package provides a single global function named `trigger_deprecation()` that triggers silenced deprecation notices. + +By using a custom PHP error handler such as the one provided by the Symfony ErrorHandler component, +the triggered deprecations can be caught and logged for later discovery, both on dev and prod environments. + +The function requires at least 3 arguments: + - the name of the Composer package that is triggering the deprecation + - the version of the package that introduced the deprecation + - the message of the deprecation + - more arguments can be provided: they will be inserted in the message using `printf()` formatting + +Example: +```php +trigger_deprecation('symfony/blockchain', '8.9', 'Using "%s" is deprecated, use "%s" instead.', 'bitcoin', 'fabcoin'); +``` + +This will generate the following message: +`Since symfony/blockchain 8.9: Using "bitcoin" is deprecated, use "fabcoin" instead.` + +While not necessarily recommended, the deprecation notices can be completely ignored by declaring an empty +`function trigger_deprecation() {}` in your application. diff --git a/vendor/symfony/deprecation-contracts/function.php b/vendor/symfony/deprecation-contracts/function.php new file mode 100644 index 0000000..d437150 --- /dev/null +++ b/vendor/symfony/deprecation-contracts/function.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (!function_exists('trigger_deprecation')) { + /** + * Triggers a silenced deprecation notice. + * + * @param string $package The name of the Composer package that is triggering the deprecation + * @param string $version The version of the package that introduced the deprecation + * @param string $message The message of the deprecation + * @param mixed ...$args Values to insert in the message using printf() formatting + * + * @author Nicolas Grekas + */ + function trigger_deprecation(string $package, string $version, string $message, ...$args): void + { + @trigger_error(($package || $version ? "Since $package $version: " : '').($args ? vsprintf($message, $args) : $message), \E_USER_DEPRECATED); + } +} diff --git a/vendor/symfony/polyfill-ctype/Ctype.php b/vendor/symfony/polyfill-ctype/Ctype.php new file mode 100644 index 0000000..ba75a2c --- /dev/null +++ b/vendor/symfony/polyfill-ctype/Ctype.php @@ -0,0 +1,232 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Ctype; + +/** + * Ctype implementation through regex. + * + * @internal + * + * @author Gert de Pagter + */ +final class Ctype +{ + /** + * Returns TRUE if every character in text is either a letter or a digit, FALSE otherwise. + * + * @see https://php.net/ctype-alnum + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_alnum($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^A-Za-z0-9]/', $text); + } + + /** + * Returns TRUE if every character in text is a letter, FALSE otherwise. + * + * @see https://php.net/ctype-alpha + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_alpha($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^A-Za-z]/', $text); + } + + /** + * Returns TRUE if every character in text is a control character from the current locale, FALSE otherwise. + * + * @see https://php.net/ctype-cntrl + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_cntrl($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^\x00-\x1f\x7f]/', $text); + } + + /** + * Returns TRUE if every character in the string text is a decimal digit, FALSE otherwise. + * + * @see https://php.net/ctype-digit + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_digit($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^0-9]/', $text); + } + + /** + * Returns TRUE if every character in text is printable and actually creates visible output (no white space), FALSE otherwise. + * + * @see https://php.net/ctype-graph + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_graph($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^!-~]/', $text); + } + + /** + * Returns TRUE if every character in text is a lowercase letter. + * + * @see https://php.net/ctype-lower + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_lower($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^a-z]/', $text); + } + + /** + * Returns TRUE if every character in text will actually create output (including blanks). Returns FALSE if text contains control characters or characters that do not have any output or control function at all. + * + * @see https://php.net/ctype-print + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_print($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^ -~]/', $text); + } + + /** + * Returns TRUE if every character in text is printable, but neither letter, digit or blank, FALSE otherwise. + * + * @see https://php.net/ctype-punct + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_punct($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^!-\/\:-@\[-`\{-~]/', $text); + } + + /** + * Returns TRUE if every character in text creates some sort of white space, FALSE otherwise. Besides the blank character this also includes tab, vertical tab, line feed, carriage return and form feed characters. + * + * @see https://php.net/ctype-space + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_space($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^\s]/', $text); + } + + /** + * Returns TRUE if every character in text is an uppercase letter. + * + * @see https://php.net/ctype-upper + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_upper($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^A-Z]/', $text); + } + + /** + * Returns TRUE if every character in text is a hexadecimal 'digit', that is a decimal digit or a character from [A-Fa-f] , FALSE otherwise. + * + * @see https://php.net/ctype-xdigit + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_xdigit($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^A-Fa-f0-9]/', $text); + } + + /** + * Converts integers to their char versions according to normal ctype behaviour, if needed. + * + * If an integer between -128 and 255 inclusive is provided, + * it is interpreted as the ASCII value of a single character + * (negative values have 256 added in order to allow characters in the Extended ASCII range). + * Any other integer is interpreted as a string containing the decimal digits of the integer. + * + * @param mixed $int + * @param string $function + * + * @return mixed + */ + private static function convert_int_to_char_for_ctype($int, $function) + { + if (!\is_int($int)) { + return $int; + } + + if ($int < -128 || $int > 255) { + return (string) $int; + } + + if (\PHP_VERSION_ID >= 80100) { + @trigger_error($function.'(): Argument of type int will be interpreted as string in the future', \E_USER_DEPRECATED); + } + + if ($int < 0) { + $int += 256; + } + + return \chr($int); + } +} diff --git a/vendor/symfony/polyfill-ctype/LICENSE b/vendor/symfony/polyfill-ctype/LICENSE new file mode 100644 index 0000000..3f853aa --- /dev/null +++ b/vendor/symfony/polyfill-ctype/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-2019 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/polyfill-ctype/README.md b/vendor/symfony/polyfill-ctype/README.md new file mode 100644 index 0000000..b144d03 --- /dev/null +++ b/vendor/symfony/polyfill-ctype/README.md @@ -0,0 +1,12 @@ +Symfony Polyfill / Ctype +======================== + +This component provides `ctype_*` functions to users who run php versions without the ctype extension. + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/vendor/symfony/polyfill-ctype/bootstrap.php b/vendor/symfony/polyfill-ctype/bootstrap.php new file mode 100644 index 0000000..d54524b --- /dev/null +++ b/vendor/symfony/polyfill-ctype/bootstrap.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Ctype as p; + +if (\PHP_VERSION_ID >= 80000) { + return require __DIR__.'/bootstrap80.php'; +} + +if (!function_exists('ctype_alnum')) { + function ctype_alnum($text) { return p\Ctype::ctype_alnum($text); } +} +if (!function_exists('ctype_alpha')) { + function ctype_alpha($text) { return p\Ctype::ctype_alpha($text); } +} +if (!function_exists('ctype_cntrl')) { + function ctype_cntrl($text) { return p\Ctype::ctype_cntrl($text); } +} +if (!function_exists('ctype_digit')) { + function ctype_digit($text) { return p\Ctype::ctype_digit($text); } +} +if (!function_exists('ctype_graph')) { + function ctype_graph($text) { return p\Ctype::ctype_graph($text); } +} +if (!function_exists('ctype_lower')) { + function ctype_lower($text) { return p\Ctype::ctype_lower($text); } +} +if (!function_exists('ctype_print')) { + function ctype_print($text) { return p\Ctype::ctype_print($text); } +} +if (!function_exists('ctype_punct')) { + function ctype_punct($text) { return p\Ctype::ctype_punct($text); } +} +if (!function_exists('ctype_space')) { + function ctype_space($text) { return p\Ctype::ctype_space($text); } +} +if (!function_exists('ctype_upper')) { + function ctype_upper($text) { return p\Ctype::ctype_upper($text); } +} +if (!function_exists('ctype_xdigit')) { + function ctype_xdigit($text) { return p\Ctype::ctype_xdigit($text); } +} diff --git a/vendor/symfony/polyfill-ctype/bootstrap80.php b/vendor/symfony/polyfill-ctype/bootstrap80.php new file mode 100644 index 0000000..ab2f861 --- /dev/null +++ b/vendor/symfony/polyfill-ctype/bootstrap80.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Ctype as p; + +if (!function_exists('ctype_alnum')) { + function ctype_alnum(mixed $text): bool { return p\Ctype::ctype_alnum($text); } +} +if (!function_exists('ctype_alpha')) { + function ctype_alpha(mixed $text): bool { return p\Ctype::ctype_alpha($text); } +} +if (!function_exists('ctype_cntrl')) { + function ctype_cntrl(mixed $text): bool { return p\Ctype::ctype_cntrl($text); } +} +if (!function_exists('ctype_digit')) { + function ctype_digit(mixed $text): bool { return p\Ctype::ctype_digit($text); } +} +if (!function_exists('ctype_graph')) { + function ctype_graph(mixed $text): bool { return p\Ctype::ctype_graph($text); } +} +if (!function_exists('ctype_lower')) { + function ctype_lower(mixed $text): bool { return p\Ctype::ctype_lower($text); } +} +if (!function_exists('ctype_print')) { + function ctype_print(mixed $text): bool { return p\Ctype::ctype_print($text); } +} +if (!function_exists('ctype_punct')) { + function ctype_punct(mixed $text): bool { return p\Ctype::ctype_punct($text); } +} +if (!function_exists('ctype_space')) { + function ctype_space(mixed $text): bool { return p\Ctype::ctype_space($text); } +} +if (!function_exists('ctype_upper')) { + function ctype_upper(mixed $text): bool { return p\Ctype::ctype_upper($text); } +} +if (!function_exists('ctype_xdigit')) { + function ctype_xdigit(mixed $text): bool { return p\Ctype::ctype_xdigit($text); } +} diff --git a/vendor/symfony/polyfill-intl-grapheme/Grapheme.php b/vendor/symfony/polyfill-intl-grapheme/Grapheme.php new file mode 100644 index 0000000..5373f16 --- /dev/null +++ b/vendor/symfony/polyfill-intl-grapheme/Grapheme.php @@ -0,0 +1,247 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Intl\Grapheme; + +\define('SYMFONY_GRAPHEME_CLUSTER_RX', ((float) \PCRE_VERSION < 10 ? (float) \PCRE_VERSION >= 8.32 : (float) \PCRE_VERSION >= 10.39) ? '\X' : Grapheme::GRAPHEME_CLUSTER_RX); + +/** + * Partial intl implementation in pure PHP. + * + * Implemented: + * - grapheme_extract - Extract a sequence of grapheme clusters from a text buffer, which must be encoded in UTF-8 + * - grapheme_stripos - Find position (in grapheme units) of first occurrence of a case-insensitive string + * - grapheme_stristr - Returns part of haystack string from the first occurrence of case-insensitive needle to the end of haystack + * - grapheme_strlen - Get string length in grapheme units + * - grapheme_strpos - Find position (in grapheme units) of first occurrence of a string + * - grapheme_strripos - Find position (in grapheme units) of last occurrence of a case-insensitive string + * - grapheme_strrpos - Find position (in grapheme units) of last occurrence of a string + * - grapheme_strstr - Returns part of haystack string from the first occurrence of needle to the end of haystack + * - grapheme_substr - Return part of a string + * + * @author Nicolas Grekas + * + * @internal + */ +final class Grapheme +{ + // (CRLF|([ZWNJ-ZWJ]|T+|L*(LV?V+|LV|LVT)T*|L+|[^Control])[Extend]*|[Control]) + // This regular expression is a work around for http://bugs.exim.org/1279 + public const GRAPHEME_CLUSTER_RX = '(?:\r\n|(?:[ -~\x{200C}\x{200D}]|[ᆨ-ᇹ]+|[ᄀ-ᅟ]*(?:[가개갸걔거게겨계고과괘괴교구궈궤귀규그긔기까깨꺄꺠꺼께껴꼐꼬꽈꽤꾀꾜꾸꿔꿰뀌뀨끄끠끼나내냐냬너네녀녜노놔놰뇌뇨누눠눼뉘뉴느늬니다대댜댸더데뎌뎨도돠돼되됴두둬뒈뒤듀드듸디따때땨떄떠떼뗘뗴또똬뙈뙤뚀뚜뚸뛔뛰뜌뜨띄띠라래랴럐러레려례로롸뢔뢰료루뤄뤠뤼류르릐리마매먀먜머메며몌모뫄뫠뫼묘무뭐뭬뮈뮤므믜미바배뱌뱨버베벼볘보봐봬뵈뵤부붜붸뷔뷰브븨비빠빼뺘뺴뻐뻬뼈뼤뽀뽜뽸뾔뾰뿌뿨쀄쀠쀼쁘쁴삐사새샤섀서세셔셰소솨쇄쇠쇼수숴쉐쉬슈스싀시싸쌔쌰썌써쎄쎠쎼쏘쏴쐐쐬쑈쑤쒀쒜쒸쓔쓰씌씨아애야얘어에여예오와왜외요우워웨위유으의이자재쟈쟤저제져졔조좌좨죄죠주줘줴쥐쥬즈즤지짜째쨔쨰쩌쩨쪄쪠쪼쫘쫴쬐쬬쭈쭤쮀쮜쮸쯔쯰찌차채챠챼처체쳐쳬초촤쵀최쵸추춰췌취츄츠츼치카캐캬컈커케켜켸코콰쾌쾨쿄쿠쿼퀘퀴큐크킈키타태탸턔터테텨톄토톼퇘퇴툐투퉈퉤튀튜트틔티파패퍄퍠퍼페펴폐포퐈퐤푀표푸풔풰퓌퓨프픠피하해햐햬허헤혀혜호화홰회효후훠훼휘휴흐희히]?[ᅠ-ᆢ]+|[가-힣])[ᆨ-ᇹ]*|[ᄀ-ᅟ]+|[^\p{Cc}\p{Cf}\p{Zl}\p{Zp}])[\p{Mn}\p{Me}\x{09BE}\x{09D7}\x{0B3E}\x{0B57}\x{0BBE}\x{0BD7}\x{0CC2}\x{0CD5}\x{0CD6}\x{0D3E}\x{0D57}\x{0DCF}\x{0DDF}\x{200C}\x{200D}\x{1D165}\x{1D16E}-\x{1D172}]*|[\p{Cc}\p{Cf}\p{Zl}\p{Zp}])'; + + private const CASE_FOLD = [ + ['µ', 'ſ', "\xCD\x85", 'ς', "\xCF\x90", "\xCF\x91", "\xCF\x95", "\xCF\x96", "\xCF\xB0", "\xCF\xB1", "\xCF\xB5", "\xE1\xBA\x9B", "\xE1\xBE\xBE"], + ['μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', "\xE1\xB9\xA1", 'ι'], + ]; + + public static function grapheme_extract($s, $size, $type = \GRAPHEME_EXTR_COUNT, $start = 0, &$next = 0) + { + if (0 > $start) { + $start = \strlen($s) + $start; + } + + if (!\is_scalar($s)) { + $hasError = false; + set_error_handler(function () use (&$hasError) { $hasError = true; }); + $next = substr($s, $start); + restore_error_handler(); + if ($hasError) { + substr($s, $start); + $s = ''; + } else { + $s = $next; + } + } else { + $s = substr($s, $start); + } + $size = (int) $size; + $type = (int) $type; + $start = (int) $start; + + if (\GRAPHEME_EXTR_COUNT !== $type && \GRAPHEME_EXTR_MAXBYTES !== $type && \GRAPHEME_EXTR_MAXCHARS !== $type) { + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError('grapheme_extract(): Argument #3 ($type) must be one of GRAPHEME_EXTR_COUNT, GRAPHEME_EXTR_MAXBYTES, or GRAPHEME_EXTR_MAXCHARS'); + } + + if (!isset($s[0]) || 0 > $size || 0 > $start) { + return false; + } + if (0 === $size) { + return ''; + } + + $next = $start; + + $s = preg_split('/('.SYMFONY_GRAPHEME_CLUSTER_RX.')/u', "\r\n".$s, $size + 1, \PREG_SPLIT_NO_EMPTY | \PREG_SPLIT_DELIM_CAPTURE); + + if (!isset($s[1])) { + return false; + } + + $i = 1; + $ret = ''; + + do { + if (\GRAPHEME_EXTR_COUNT === $type) { + --$size; + } elseif (\GRAPHEME_EXTR_MAXBYTES === $type) { + $size -= \strlen($s[$i]); + } else { + $size -= iconv_strlen($s[$i], 'UTF-8//IGNORE'); + } + + if ($size >= 0) { + $ret .= $s[$i]; + } + } while (isset($s[++$i]) && $size > 0); + + $next += \strlen($ret); + + return $ret; + } + + public static function grapheme_strlen($s) + { + preg_replace('/'.SYMFONY_GRAPHEME_CLUSTER_RX.'/u', '', $s, -1, $len); + + return 0 === $len && '' !== $s ? null : $len; + } + + public static function grapheme_substr($s, $start, $len = null) + { + if (null === $len) { + $len = 2147483647; + } + + preg_match_all('/'.SYMFONY_GRAPHEME_CLUSTER_RX.'/u', $s, $s); + + $slen = \count($s[0]); + $start = (int) $start; + + if (0 > $start) { + $start += $slen; + } + if (0 > $start) { + if (\PHP_VERSION_ID < 80000) { + return false; + } + + $start = 0; + } + if ($start >= $slen) { + return \PHP_VERSION_ID >= 80000 ? '' : false; + } + + $rem = $slen - $start; + + if (0 > $len) { + $len += $rem; + } + if (0 === $len) { + return ''; + } + if (0 > $len) { + return \PHP_VERSION_ID >= 80000 ? '' : false; + } + if ($len > $rem) { + $len = $rem; + } + + return implode('', \array_slice($s[0], $start, $len)); + } + + public static function grapheme_strpos($s, $needle, $offset = 0) + { + return self::grapheme_position($s, $needle, $offset, 0); + } + + public static function grapheme_stripos($s, $needle, $offset = 0) + { + return self::grapheme_position($s, $needle, $offset, 1); + } + + public static function grapheme_strrpos($s, $needle, $offset = 0) + { + return self::grapheme_position($s, $needle, $offset, 2); + } + + public static function grapheme_strripos($s, $needle, $offset = 0) + { + return self::grapheme_position($s, $needle, $offset, 3); + } + + public static function grapheme_stristr($s, $needle, $beforeNeedle = false) + { + return mb_stristr($s, $needle, $beforeNeedle, 'UTF-8'); + } + + public static function grapheme_strstr($s, $needle, $beforeNeedle = false) + { + return mb_strstr($s, $needle, $beforeNeedle, 'UTF-8'); + } + + private static function grapheme_position($s, $needle, $offset, $mode) + { + $needle = (string) $needle; + if (80000 > \PHP_VERSION_ID && !preg_match('/./us', $needle)) { + return false; + } + $s = (string) $s; + if (!preg_match('/./us', $s)) { + return false; + } + if ($offset > 0) { + $s = self::grapheme_substr($s, $offset); + } elseif ($offset < 0) { + if (2 > $mode) { + $offset += self::grapheme_strlen($s); + $s = self::grapheme_substr($s, $offset); + if (0 > $offset) { + $offset = 0; + } + } elseif (0 > $offset += self::grapheme_strlen($needle)) { + $s = self::grapheme_substr($s, 0, $offset); + $offset = 0; + } else { + $offset = 0; + } + } + + // As UTF-8 is self-synchronizing, and we have ensured the strings are valid UTF-8, + // we can use normal binary string functions here. For case-insensitive searches, + // case fold the strings first. + $caseInsensitive = $mode & 1; + $reverse = $mode & 2; + if ($caseInsensitive) { + // Use the same case folding mode as mbstring does for mb_stripos(). + // Stick to SIMPLE case folding to avoid changing the length of the string, which + // might result in offsets being shifted. + $mode = \defined('MB_CASE_FOLD_SIMPLE') ? \MB_CASE_FOLD_SIMPLE : \MB_CASE_LOWER; + $s = mb_convert_case($s, $mode, 'UTF-8'); + $needle = mb_convert_case($needle, $mode, 'UTF-8'); + + if (!\defined('MB_CASE_FOLD_SIMPLE')) { + $s = str_replace(self::CASE_FOLD[0], self::CASE_FOLD[1], $s); + $needle = str_replace(self::CASE_FOLD[0], self::CASE_FOLD[1], $needle); + } + } + if ($reverse) { + $needlePos = strrpos($s, $needle); + } else { + $needlePos = strpos($s, $needle); + } + + return false !== $needlePos ? self::grapheme_strlen(substr($s, 0, $needlePos)) + $offset : false; + } +} diff --git a/vendor/symfony/polyfill-intl-grapheme/LICENSE b/vendor/symfony/polyfill-intl-grapheme/LICENSE new file mode 100644 index 0000000..4cd8bdd --- /dev/null +++ b/vendor/symfony/polyfill-intl-grapheme/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015-2019 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/polyfill-intl-grapheme/README.md b/vendor/symfony/polyfill-intl-grapheme/README.md new file mode 100644 index 0000000..f55d92c --- /dev/null +++ b/vendor/symfony/polyfill-intl-grapheme/README.md @@ -0,0 +1,31 @@ +Symfony Polyfill / Intl: Grapheme +================================= + +This component provides a partial, native PHP implementation of the +[Grapheme functions](https://php.net/intl.grapheme) from the +[Intl](https://php.net/intl) extension. + +- [`grapheme_extract`](https://php.net/grapheme_extract): Extract a sequence of grapheme + clusters from a text buffer, which must be encoded in UTF-8 +- [`grapheme_stripos`](https://php.net/grapheme_stripos): Find position (in grapheme units) + of first occurrence of a case-insensitive string +- [`grapheme_stristr`](https://php.net/grapheme_stristr): Returns part of haystack string + from the first occurrence of case-insensitive needle to the end of haystack +- [`grapheme_strlen`](https://php.net/grapheme_strlen): Get string length in grapheme units +- [`grapheme_strpos`](https://php.net/grapheme_strpos): Find position (in grapheme units) + of first occurrence of a string +- [`grapheme_strripos`](https://php.net/grapheme_strripos): Find position (in grapheme units) + of last occurrence of a case-insensitive string +- [`grapheme_strrpos`](https://php.net/grapheme_strrpos): Find position (in grapheme units) + of last occurrence of a string +- [`grapheme_strstr`](https://php.net/grapheme_strstr): Returns part of haystack string from + the first occurrence of needle to the end of haystack +- [`grapheme_substr`](https://php.net/grapheme_substr): Return part of a string + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/vendor/symfony/polyfill-intl-grapheme/bootstrap.php b/vendor/symfony/polyfill-intl-grapheme/bootstrap.php new file mode 100644 index 0000000..a9ea03c --- /dev/null +++ b/vendor/symfony/polyfill-intl-grapheme/bootstrap.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Intl\Grapheme as p; + +if (extension_loaded('intl')) { + return; +} + +if (\PHP_VERSION_ID >= 80000) { + return require __DIR__.'/bootstrap80.php'; +} + +if (!defined('GRAPHEME_EXTR_COUNT')) { + define('GRAPHEME_EXTR_COUNT', 0); +} +if (!defined('GRAPHEME_EXTR_MAXBYTES')) { + define('GRAPHEME_EXTR_MAXBYTES', 1); +} +if (!defined('GRAPHEME_EXTR_MAXCHARS')) { + define('GRAPHEME_EXTR_MAXCHARS', 2); +} + +if (!function_exists('grapheme_extract')) { + function grapheme_extract($haystack, $size, $type = 0, $start = 0, &$next = 0) { return p\Grapheme::grapheme_extract($haystack, $size, $type, $start, $next); } +} +if (!function_exists('grapheme_stripos')) { + function grapheme_stripos($haystack, $needle, $offset = 0) { return p\Grapheme::grapheme_stripos($haystack, $needle, $offset); } +} +if (!function_exists('grapheme_stristr')) { + function grapheme_stristr($haystack, $needle, $beforeNeedle = false) { return p\Grapheme::grapheme_stristr($haystack, $needle, $beforeNeedle); } +} +if (!function_exists('grapheme_strlen')) { + function grapheme_strlen($input) { return p\Grapheme::grapheme_strlen($input); } +} +if (!function_exists('grapheme_strpos')) { + function grapheme_strpos($haystack, $needle, $offset = 0) { return p\Grapheme::grapheme_strpos($haystack, $needle, $offset); } +} +if (!function_exists('grapheme_strripos')) { + function grapheme_strripos($haystack, $needle, $offset = 0) { return p\Grapheme::grapheme_strripos($haystack, $needle, $offset); } +} +if (!function_exists('grapheme_strrpos')) { + function grapheme_strrpos($haystack, $needle, $offset = 0) { return p\Grapheme::grapheme_strrpos($haystack, $needle, $offset); } +} +if (!function_exists('grapheme_strstr')) { + function grapheme_strstr($haystack, $needle, $beforeNeedle = false) { return p\Grapheme::grapheme_strstr($haystack, $needle, $beforeNeedle); } +} +if (!function_exists('grapheme_substr')) { + function grapheme_substr($string, $offset, $length = null) { return p\Grapheme::grapheme_substr($string, $offset, $length); } +} diff --git a/vendor/symfony/polyfill-intl-grapheme/bootstrap80.php b/vendor/symfony/polyfill-intl-grapheme/bootstrap80.php new file mode 100644 index 0000000..b8c0786 --- /dev/null +++ b/vendor/symfony/polyfill-intl-grapheme/bootstrap80.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Intl\Grapheme as p; + +if (!defined('GRAPHEME_EXTR_COUNT')) { + define('GRAPHEME_EXTR_COUNT', 0); +} +if (!defined('GRAPHEME_EXTR_MAXBYTES')) { + define('GRAPHEME_EXTR_MAXBYTES', 1); +} +if (!defined('GRAPHEME_EXTR_MAXCHARS')) { + define('GRAPHEME_EXTR_MAXCHARS', 2); +} + +if (!function_exists('grapheme_extract')) { + function grapheme_extract(?string $haystack, ?int $size, ?int $type = GRAPHEME_EXTR_COUNT, ?int $offset = 0, &$next = null): string|false { return p\Grapheme::grapheme_extract((string) $haystack, (int) $size, (int) $type, (int) $offset, $next); } +} +if (!function_exists('grapheme_stripos')) { + function grapheme_stripos(?string $haystack, ?string $needle, ?int $offset = 0): int|false { return p\Grapheme::grapheme_stripos((string) $haystack, (string) $needle, (int) $offset); } +} +if (!function_exists('grapheme_stristr')) { + function grapheme_stristr(?string $haystack, ?string $needle, ?bool $beforeNeedle = false): string|false { return p\Grapheme::grapheme_stristr((string) $haystack, (string) $needle, (bool) $beforeNeedle); } +} +if (!function_exists('grapheme_strlen')) { + function grapheme_strlen(?string $string): int|false|null { return p\Grapheme::grapheme_strlen((string) $string); } +} +if (!function_exists('grapheme_strpos')) { + function grapheme_strpos(?string $haystack, ?string $needle, ?int $offset = 0): int|false { return p\Grapheme::grapheme_strpos((string) $haystack, (string) $needle, (int) $offset); } +} +if (!function_exists('grapheme_strripos')) { + function grapheme_strripos(?string $haystack, ?string $needle, ?int $offset = 0): int|false { return p\Grapheme::grapheme_strripos((string) $haystack, (string) $needle, (int) $offset); } +} +if (!function_exists('grapheme_strrpos')) { + function grapheme_strrpos(?string $haystack, ?string $needle, ?int $offset = 0): int|false { return p\Grapheme::grapheme_strrpos((string) $haystack, (string) $needle, (int) $offset); } +} +if (!function_exists('grapheme_strstr')) { + function grapheme_strstr(?string $haystack, ?string $needle, ?bool $beforeNeedle = false): string|false { return p\Grapheme::grapheme_strstr((string) $haystack, (string) $needle, (bool) $beforeNeedle); } +} +if (!function_exists('grapheme_substr')) { + function grapheme_substr(?string $string, ?int $offset, ?int $length = null): string|false { return p\Grapheme::grapheme_substr((string) $string, (int) $offset, $length); } +} diff --git a/vendor/symfony/polyfill-intl-normalizer/LICENSE b/vendor/symfony/polyfill-intl-normalizer/LICENSE new file mode 100644 index 0000000..4cd8bdd --- /dev/null +++ b/vendor/symfony/polyfill-intl-normalizer/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015-2019 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/polyfill-intl-normalizer/Normalizer.php b/vendor/symfony/polyfill-intl-normalizer/Normalizer.php new file mode 100644 index 0000000..81704ab --- /dev/null +++ b/vendor/symfony/polyfill-intl-normalizer/Normalizer.php @@ -0,0 +1,310 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Intl\Normalizer; + +/** + * Normalizer is a PHP fallback implementation of the Normalizer class provided by the intl extension. + * + * It has been validated with Unicode 6.3 Normalization Conformance Test. + * See http://www.unicode.org/reports/tr15/ for detailed info about Unicode normalizations. + * + * @author Nicolas Grekas + * + * @internal + */ +class Normalizer +{ + public const FORM_D = \Normalizer::FORM_D; + public const FORM_KD = \Normalizer::FORM_KD; + public const FORM_C = \Normalizer::FORM_C; + public const FORM_KC = \Normalizer::FORM_KC; + public const NFD = \Normalizer::NFD; + public const NFKD = \Normalizer::NFKD; + public const NFC = \Normalizer::NFC; + public const NFKC = \Normalizer::NFKC; + + private static $C; + private static $D; + private static $KD; + private static $cC; + private static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4]; + private static $ASCII = "\x20\x65\x69\x61\x73\x6E\x74\x72\x6F\x6C\x75\x64\x5D\x5B\x63\x6D\x70\x27\x0A\x67\x7C\x68\x76\x2E\x66\x62\x2C\x3A\x3D\x2D\x71\x31\x30\x43\x32\x2A\x79\x78\x29\x28\x4C\x39\x41\x53\x2F\x50\x22\x45\x6A\x4D\x49\x6B\x33\x3E\x35\x54\x3C\x44\x34\x7D\x42\x7B\x38\x46\x77\x52\x36\x37\x55\x47\x4E\x3B\x4A\x7A\x56\x23\x48\x4F\x57\x5F\x26\x21\x4B\x3F\x58\x51\x25\x59\x5C\x09\x5A\x2B\x7E\x5E\x24\x40\x60\x7F\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"; + + public static function isNormalized(string $s, int $form = self::FORM_C) + { + if (!\in_array($form, [self::NFD, self::NFKD, self::NFC, self::NFKC])) { + return false; + } + if (!isset($s[strspn($s, self::$ASCII)])) { + return true; + } + if (self::NFC == $form && preg_match('//u', $s) && !preg_match('/[^\x00-\x{2FF}]/u', $s)) { + return true; + } + + return self::normalize($s, $form) === $s; + } + + public static function normalize(string $s, int $form = self::FORM_C) + { + if (!preg_match('//u', $s)) { + return false; + } + + switch ($form) { + case self::NFC: $C = true; $K = false; break; + case self::NFD: $C = false; $K = false; break; + case self::NFKC: $C = true; $K = true; break; + case self::NFKD: $C = false; $K = true; break; + default: + if (\defined('Normalizer::NONE') && \Normalizer::NONE == $form) { + return $s; + } + + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError('normalizer_normalize(): Argument #2 ($form) must be a a valid normalization form'); + } + + if ('' === $s) { + return ''; + } + + if ($K && null === self::$KD) { + self::$KD = self::getData('compatibilityDecomposition'); + } + + if (null === self::$D) { + self::$D = self::getData('canonicalDecomposition'); + self::$cC = self::getData('combiningClass'); + } + + if (null !== $mbEncoding = (2 /* MB_OVERLOAD_STRING */ & (int) \ini_get('mbstring.func_overload')) ? mb_internal_encoding() : null) { + mb_internal_encoding('8bit'); + } + + $r = self::decompose($s, $K); + + if ($C) { + if (null === self::$C) { + self::$C = self::getData('canonicalComposition'); + } + + $r = self::recompose($r); + } + if (null !== $mbEncoding) { + mb_internal_encoding($mbEncoding); + } + + return $r; + } + + private static function recompose($s) + { + $ASCII = self::$ASCII; + $compMap = self::$C; + $combClass = self::$cC; + $ulenMask = self::$ulenMask; + + $result = $tail = ''; + + $i = $s[0] < "\x80" ? 1 : $ulenMask[$s[0] & "\xF0"]; + $len = \strlen($s); + + $lastUchr = substr($s, 0, $i); + $lastUcls = isset($combClass[$lastUchr]) ? 256 : 0; + + while ($i < $len) { + if ($s[$i] < "\x80") { + // ASCII chars + + if ($tail) { + $lastUchr .= $tail; + $tail = ''; + } + + if ($j = strspn($s, $ASCII, $i + 1)) { + $lastUchr .= substr($s, $i, $j); + $i += $j; + } + + $result .= $lastUchr; + $lastUchr = $s[$i]; + $lastUcls = 0; + ++$i; + continue; + } + + $ulen = $ulenMask[$s[$i] & "\xF0"]; + $uchr = substr($s, $i, $ulen); + + if ($lastUchr < "\xE1\x84\x80" || "\xE1\x84\x92" < $lastUchr + || $uchr < "\xE1\x85\xA1" || "\xE1\x85\xB5" < $uchr + || $lastUcls) { + // Table lookup and combining chars composition + + $ucls = $combClass[$uchr] ?? 0; + + if (isset($compMap[$lastUchr.$uchr]) && (!$lastUcls || $lastUcls < $ucls)) { + $lastUchr = $compMap[$lastUchr.$uchr]; + } elseif ($lastUcls = $ucls) { + $tail .= $uchr; + } else { + if ($tail) { + $lastUchr .= $tail; + $tail = ''; + } + + $result .= $lastUchr; + $lastUchr = $uchr; + } + } else { + // Hangul chars + + $L = \ord($lastUchr[2]) - 0x80; + $V = \ord($uchr[2]) - 0xA1; + $T = 0; + + $uchr = substr($s, $i + $ulen, 3); + + if ("\xE1\x86\xA7" <= $uchr && $uchr <= "\xE1\x87\x82") { + $T = \ord($uchr[2]) - 0xA7; + 0 > $T && $T += 0x40; + $ulen += 3; + } + + $L = 0xAC00 + ($L * 21 + $V) * 28 + $T; + $lastUchr = \chr(0xE0 | $L >> 12).\chr(0x80 | $L >> 6 & 0x3F).\chr(0x80 | $L & 0x3F); + } + + $i += $ulen; + } + + return $result.$lastUchr.$tail; + } + + private static function decompose($s, $c) + { + $result = ''; + + $ASCII = self::$ASCII; + $decompMap = self::$D; + $combClass = self::$cC; + $ulenMask = self::$ulenMask; + if ($c) { + $compatMap = self::$KD; + } + + $c = []; + $i = 0; + $len = \strlen($s); + + while ($i < $len) { + if ($s[$i] < "\x80") { + // ASCII chars + + if ($c) { + ksort($c); + $result .= implode('', $c); + $c = []; + } + + $j = 1 + strspn($s, $ASCII, $i + 1); + $result .= substr($s, $i, $j); + $i += $j; + continue; + } + + $ulen = $ulenMask[$s[$i] & "\xF0"]; + $uchr = substr($s, $i, $ulen); + $i += $ulen; + + if ($uchr < "\xEA\xB0\x80" || "\xED\x9E\xA3" < $uchr) { + // Table lookup + + if ($uchr !== $j = $compatMap[$uchr] ?? ($decompMap[$uchr] ?? $uchr)) { + $uchr = $j; + + $j = \strlen($uchr); + $ulen = $uchr[0] < "\x80" ? 1 : $ulenMask[$uchr[0] & "\xF0"]; + + if ($ulen != $j) { + // Put trailing chars in $s + + $j -= $ulen; + $i -= $j; + + if (0 > $i) { + $s = str_repeat(' ', -$i).$s; + $len -= $i; + $i = 0; + } + + while ($j--) { + $s[$i + $j] = $uchr[$ulen + $j]; + } + + $uchr = substr($uchr, 0, $ulen); + } + } + if (isset($combClass[$uchr])) { + // Combining chars, for sorting + + if (!isset($c[$combClass[$uchr]])) { + $c[$combClass[$uchr]] = ''; + } + $c[$combClass[$uchr]] .= $uchr; + continue; + } + } else { + // Hangul chars + + $uchr = unpack('C*', $uchr); + $j = (($uchr[1] - 224) << 12) + (($uchr[2] - 128) << 6) + $uchr[3] - 0xAC80; + + $uchr = "\xE1\x84".\chr(0x80 + (int) ($j / 588)) + ."\xE1\x85".\chr(0xA1 + (int) (($j % 588) / 28)); + + if ($j %= 28) { + $uchr .= $j < 25 + ? ("\xE1\x86".\chr(0xA7 + $j)) + : ("\xE1\x87".\chr(0x67 + $j)); + } + } + if ($c) { + ksort($c); + $result .= implode('', $c); + $c = []; + } + + $result .= $uchr; + } + + if ($c) { + ksort($c); + $result .= implode('', $c); + } + + return $result; + } + + private static function getData($file) + { + if (file_exists($file = __DIR__.'/Resources/unidata/'.$file.'.php')) { + return require $file; + } + + return false; + } +} diff --git a/vendor/symfony/polyfill-intl-normalizer/README.md b/vendor/symfony/polyfill-intl-normalizer/README.md new file mode 100644 index 0000000..b9b762e --- /dev/null +++ b/vendor/symfony/polyfill-intl-normalizer/README.md @@ -0,0 +1,14 @@ +Symfony Polyfill / Intl: Normalizer +=================================== + +This component provides a fallback implementation for the +[`Normalizer`](https://php.net/Normalizer) class provided +by the [Intl](https://php.net/intl) extension. + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/vendor/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php b/vendor/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php new file mode 100644 index 0000000..0fdfc89 --- /dev/null +++ b/vendor/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php @@ -0,0 +1,17 @@ + 'À', + 'Á' => 'Á', + 'Â' => 'Â', + 'Ã' => 'Ã', + 'Ä' => 'Ä', + 'Å' => 'Å', + 'Ç' => 'Ç', + 'È' => 'È', + 'É' => 'É', + 'Ê' => 'Ê', + 'Ë' => 'Ë', + 'Ì' => 'Ì', + 'Í' => 'Í', + 'Î' => 'Î', + 'Ï' => 'Ï', + 'Ñ' => 'Ñ', + 'Ò' => 'Ò', + 'Ó' => 'Ó', + 'Ô' => 'Ô', + 'Õ' => 'Õ', + 'Ö' => 'Ö', + 'Ù' => 'Ù', + 'Ú' => 'Ú', + 'Û' => 'Û', + 'Ü' => 'Ü', + 'Ý' => 'Ý', + 'à' => 'à', + 'á' => 'á', + 'â' => 'â', + 'ã' => 'ã', + 'ä' => 'ä', + 'å' => 'å', + 'ç' => 'ç', + 'è' => 'è', + 'é' => 'é', + 'ê' => 'ê', + 'ë' => 'ë', + 'ì' => 'ì', + 'í' => 'í', + 'î' => 'î', + 'ï' => 'ï', + 'ñ' => 'ñ', + 'ò' => 'ò', + 'ó' => 'ó', + 'ô' => 'ô', + 'õ' => 'õ', + 'ö' => 'ö', + 'ù' => 'ù', + 'ú' => 'ú', + 'û' => 'û', + 'ü' => 'ü', + 'ý' => 'ý', + 'ÿ' => 'ÿ', + 'Ā' => 'Ā', + 'ā' => 'ā', + 'Ă' => 'Ă', + 'ă' => 'ă', + 'Ą' => 'Ą', + 'ą' => 'ą', + 'Ć' => 'Ć', + 'ć' => 'ć', + 'Ĉ' => 'Ĉ', + 'ĉ' => 'ĉ', + 'Ċ' => 'Ċ', + 'ċ' => 'ċ', + 'Č' => 'Č', + 'č' => 'č', + 'Ď' => 'Ď', + 'ď' => 'ď', + 'Ē' => 'Ē', + 'ē' => 'ē', + 'Ĕ' => 'Ĕ', + 'ĕ' => 'ĕ', + 'Ė' => 'Ė', + 'ė' => 'ė', + 'Ę' => 'Ę', + 'ę' => 'ę', + 'Ě' => 'Ě', + 'ě' => 'ě', + 'Ĝ' => 'Ĝ', + 'ĝ' => 'ĝ', + 'Ğ' => 'Ğ', + 'ğ' => 'ğ', + 'Ġ' => 'Ġ', + 'ġ' => 'ġ', + 'Ģ' => 'Ģ', + 'ģ' => 'ģ', + 'Ĥ' => 'Ĥ', + 'ĥ' => 'ĥ', + 'Ĩ' => 'Ĩ', + 'ĩ' => 'ĩ', + 'Ī' => 'Ī', + 'ī' => 'ī', + 'Ĭ' => 'Ĭ', + 'ĭ' => 'ĭ', + 'Į' => 'Į', + 'į' => 'į', + 'İ' => 'İ', + 'Ĵ' => 'Ĵ', + 'ĵ' => 'ĵ', + 'Ķ' => 'Ķ', + 'ķ' => 'ķ', + 'Ĺ' => 'Ĺ', + 'ĺ' => 'ĺ', + 'Ļ' => 'Ļ', + 'ļ' => 'ļ', + 'Ľ' => 'Ľ', + 'ľ' => 'ľ', + 'Ń' => 'Ń', + 'ń' => 'ń', + 'Ņ' => 'Ņ', + 'ņ' => 'ņ', + 'Ň' => 'Ň', + 'ň' => 'ň', + 'Ō' => 'Ō', + 'ō' => 'ō', + 'Ŏ' => 'Ŏ', + 'ŏ' => 'ŏ', + 'Ő' => 'Ő', + 'ő' => 'ő', + 'Ŕ' => 'Ŕ', + 'ŕ' => 'ŕ', + 'Ŗ' => 'Ŗ', + 'ŗ' => 'ŗ', + 'Ř' => 'Ř', + 'ř' => 'ř', + 'Ś' => 'Ś', + 'ś' => 'ś', + 'Ŝ' => 'Ŝ', + 'ŝ' => 'ŝ', + 'Ş' => 'Ş', + 'ş' => 'ş', + 'Š' => 'Š', + 'š' => 'š', + 'Ţ' => 'Ţ', + 'ţ' => 'ţ', + 'Ť' => 'Ť', + 'ť' => 'ť', + 'Ũ' => 'Ũ', + 'ũ' => 'ũ', + 'Ū' => 'Ū', + 'ū' => 'ū', + 'Ŭ' => 'Ŭ', + 'ŭ' => 'ŭ', + 'Ů' => 'Ů', + 'ů' => 'ů', + 'Ű' => 'Ű', + 'ű' => 'ű', + 'Ų' => 'Ų', + 'ų' => 'ų', + 'Ŵ' => 'Ŵ', + 'ŵ' => 'ŵ', + 'Ŷ' => 'Ŷ', + 'ŷ' => 'ŷ', + 'Ÿ' => 'Ÿ', + 'Ź' => 'Ź', + 'ź' => 'ź', + 'Ż' => 'Ż', + 'ż' => 'ż', + 'Ž' => 'Ž', + 'ž' => 'ž', + 'Ơ' => 'Ơ', + 'ơ' => 'ơ', + 'Ư' => 'Ư', + 'ư' => 'ư', + 'Ǎ' => 'Ǎ', + 'ǎ' => 'ǎ', + 'Ǐ' => 'Ǐ', + 'ǐ' => 'ǐ', + 'Ǒ' => 'Ǒ', + 'ǒ' => 'ǒ', + 'Ǔ' => 'Ǔ', + 'ǔ' => 'ǔ', + 'Ǖ' => 'Ǖ', + 'ǖ' => 'ǖ', + 'Ǘ' => 'Ǘ', + 'ǘ' => 'ǘ', + 'Ǚ' => 'Ǚ', + 'ǚ' => 'ǚ', + 'Ǜ' => 'Ǜ', + 'ǜ' => 'ǜ', + 'Ǟ' => 'Ǟ', + 'ǟ' => 'ǟ', + 'Ǡ' => 'Ǡ', + 'ǡ' => 'ǡ', + 'Ǣ' => 'Ǣ', + 'ǣ' => 'ǣ', + 'Ǧ' => 'Ǧ', + 'ǧ' => 'ǧ', + 'Ǩ' => 'Ǩ', + 'ǩ' => 'ǩ', + 'Ǫ' => 'Ǫ', + 'ǫ' => 'ǫ', + 'Ǭ' => 'Ǭ', + 'ǭ' => 'ǭ', + 'Ǯ' => 'Ǯ', + 'ǯ' => 'ǯ', + 'ǰ' => 'ǰ', + 'Ǵ' => 'Ǵ', + 'ǵ' => 'ǵ', + 'Ǹ' => 'Ǹ', + 'ǹ' => 'ǹ', + 'Ǻ' => 'Ǻ', + 'ǻ' => 'ǻ', + 'Ǽ' => 'Ǽ', + 'ǽ' => 'ǽ', + 'Ǿ' => 'Ǿ', + 'ǿ' => 'ǿ', + 'Ȁ' => 'Ȁ', + 'ȁ' => 'ȁ', + 'Ȃ' => 'Ȃ', + 'ȃ' => 'ȃ', + 'Ȅ' => 'Ȅ', + 'ȅ' => 'ȅ', + 'Ȇ' => 'Ȇ', + 'ȇ' => 'ȇ', + 'Ȉ' => 'Ȉ', + 'ȉ' => 'ȉ', + 'Ȋ' => 'Ȋ', + 'ȋ' => 'ȋ', + 'Ȍ' => 'Ȍ', + 'ȍ' => 'ȍ', + 'Ȏ' => 'Ȏ', + 'ȏ' => 'ȏ', + 'Ȑ' => 'Ȑ', + 'ȑ' => 'ȑ', + 'Ȓ' => 'Ȓ', + 'ȓ' => 'ȓ', + 'Ȕ' => 'Ȕ', + 'ȕ' => 'ȕ', + 'Ȗ' => 'Ȗ', + 'ȗ' => 'ȗ', + 'Ș' => 'Ș', + 'ș' => 'ș', + 'Ț' => 'Ț', + 'ț' => 'ț', + 'Ȟ' => 'Ȟ', + 'ȟ' => 'ȟ', + 'Ȧ' => 'Ȧ', + 'ȧ' => 'ȧ', + 'Ȩ' => 'Ȩ', + 'ȩ' => 'ȩ', + 'Ȫ' => 'Ȫ', + 'ȫ' => 'ȫ', + 'Ȭ' => 'Ȭ', + 'ȭ' => 'ȭ', + 'Ȯ' => 'Ȯ', + 'ȯ' => 'ȯ', + 'Ȱ' => 'Ȱ', + 'ȱ' => 'ȱ', + 'Ȳ' => 'Ȳ', + 'ȳ' => 'ȳ', + '΅' => '΅', + 'Ά' => 'Ά', + 'Έ' => 'Έ', + 'Ή' => 'Ή', + 'Ί' => 'Ί', + 'Ό' => 'Ό', + 'Ύ' => 'Ύ', + 'Ώ' => 'Ώ', + 'ΐ' => 'ΐ', + 'Ϊ' => 'Ϊ', + 'Ϋ' => 'Ϋ', + 'ά' => 'ά', + 'έ' => 'έ', + 'ή' => 'ή', + 'ί' => 'ί', + 'ΰ' => 'ΰ', + 'ϊ' => 'ϊ', + 'ϋ' => 'ϋ', + 'ό' => 'ό', + 'ύ' => 'ύ', + 'ώ' => 'ώ', + 'ϓ' => 'ϓ', + 'ϔ' => 'ϔ', + 'Ѐ' => 'Ѐ', + 'Ё' => 'Ё', + 'Ѓ' => 'Ѓ', + 'Ї' => 'Ї', + 'Ќ' => 'Ќ', + 'Ѝ' => 'Ѝ', + 'Ў' => 'Ў', + 'Й' => 'Й', + 'й' => 'й', + 'ѐ' => 'ѐ', + 'ё' => 'ё', + 'ѓ' => 'ѓ', + 'ї' => 'ї', + 'ќ' => 'ќ', + 'ѝ' => 'ѝ', + 'ў' => 'ў', + 'Ѷ' => 'Ѷ', + 'ѷ' => 'ѷ', + 'Ӂ' => 'Ӂ', + 'ӂ' => 'ӂ', + 'Ӑ' => 'Ӑ', + 'ӑ' => 'ӑ', + 'Ӓ' => 'Ӓ', + 'ӓ' => 'ӓ', + 'Ӗ' => 'Ӗ', + 'ӗ' => 'ӗ', + 'Ӛ' => 'Ӛ', + 'ӛ' => 'ӛ', + 'Ӝ' => 'Ӝ', + 'ӝ' => 'ӝ', + 'Ӟ' => 'Ӟ', + 'ӟ' => 'ӟ', + 'Ӣ' => 'Ӣ', + 'ӣ' => 'ӣ', + 'Ӥ' => 'Ӥ', + 'ӥ' => 'ӥ', + 'Ӧ' => 'Ӧ', + 'ӧ' => 'ӧ', + 'Ӫ' => 'Ӫ', + 'ӫ' => 'ӫ', + 'Ӭ' => 'Ӭ', + 'ӭ' => 'ӭ', + 'Ӯ' => 'Ӯ', + 'ӯ' => 'ӯ', + 'Ӱ' => 'Ӱ', + 'ӱ' => 'ӱ', + 'Ӳ' => 'Ӳ', + 'ӳ' => 'ӳ', + 'Ӵ' => 'Ӵ', + 'ӵ' => 'ӵ', + 'Ӹ' => 'Ӹ', + 'ӹ' => 'ӹ', + 'آ' => 'آ', + 'أ' => 'أ', + 'ؤ' => 'ؤ', + 'إ' => 'إ', + 'ئ' => 'ئ', + 'ۀ' => 'ۀ', + 'ۂ' => 'ۂ', + 'ۓ' => 'ۓ', + 'ऩ' => 'ऩ', + 'ऱ' => 'ऱ', + 'ऴ' => 'ऴ', + 'ো' => 'ো', + 'ৌ' => 'ৌ', + 'ୈ' => 'ୈ', + 'ୋ' => 'ୋ', + 'ୌ' => 'ୌ', + 'ஔ' => 'ஔ', + 'ொ' => 'ொ', + 'ோ' => 'ோ', + 'ௌ' => 'ௌ', + 'ై' => 'ై', + 'ೀ' => 'ೀ', + 'ೇ' => 'ೇ', + 'ೈ' => 'ೈ', + 'ೊ' => 'ೊ', + 'ೋ' => 'ೋ', + 'ൊ' => 'ൊ', + 'ോ' => 'ോ', + 'ൌ' => 'ൌ', + 'ේ' => 'ේ', + 'ො' => 'ො', + 'ෝ' => 'ෝ', + 'ෞ' => 'ෞ', + 'ဦ' => 'ဦ', + 'ᬆ' => 'ᬆ', + 'ᬈ' => 'ᬈ', + 'ᬊ' => 'ᬊ', + 'ᬌ' => 'ᬌ', + 'ᬎ' => 'ᬎ', + 'ᬒ' => 'ᬒ', + 'ᬻ' => 'ᬻ', + 'ᬽ' => 'ᬽ', + 'ᭀ' => 'ᭀ', + 'ᭁ' => 'ᭁ', + 'ᭃ' => 'ᭃ', + 'Ḁ' => 'Ḁ', + 'ḁ' => 'ḁ', + 'Ḃ' => 'Ḃ', + 'ḃ' => 'ḃ', + 'Ḅ' => 'Ḅ', + 'ḅ' => 'ḅ', + 'Ḇ' => 'Ḇ', + 'ḇ' => 'ḇ', + 'Ḉ' => 'Ḉ', + 'ḉ' => 'ḉ', + 'Ḋ' => 'Ḋ', + 'ḋ' => 'ḋ', + 'Ḍ' => 'Ḍ', + 'ḍ' => 'ḍ', + 'Ḏ' => 'Ḏ', + 'ḏ' => 'ḏ', + 'Ḑ' => 'Ḑ', + 'ḑ' => 'ḑ', + 'Ḓ' => 'Ḓ', + 'ḓ' => 'ḓ', + 'Ḕ' => 'Ḕ', + 'ḕ' => 'ḕ', + 'Ḗ' => 'Ḗ', + 'ḗ' => 'ḗ', + 'Ḙ' => 'Ḙ', + 'ḙ' => 'ḙ', + 'Ḛ' => 'Ḛ', + 'ḛ' => 'ḛ', + 'Ḝ' => 'Ḝ', + 'ḝ' => 'ḝ', + 'Ḟ' => 'Ḟ', + 'ḟ' => 'ḟ', + 'Ḡ' => 'Ḡ', + 'ḡ' => 'ḡ', + 'Ḣ' => 'Ḣ', + 'ḣ' => 'ḣ', + 'Ḥ' => 'Ḥ', + 'ḥ' => 'ḥ', + 'Ḧ' => 'Ḧ', + 'ḧ' => 'ḧ', + 'Ḩ' => 'Ḩ', + 'ḩ' => 'ḩ', + 'Ḫ' => 'Ḫ', + 'ḫ' => 'ḫ', + 'Ḭ' => 'Ḭ', + 'ḭ' => 'ḭ', + 'Ḯ' => 'Ḯ', + 'ḯ' => 'ḯ', + 'Ḱ' => 'Ḱ', + 'ḱ' => 'ḱ', + 'Ḳ' => 'Ḳ', + 'ḳ' => 'ḳ', + 'Ḵ' => 'Ḵ', + 'ḵ' => 'ḵ', + 'Ḷ' => 'Ḷ', + 'ḷ' => 'ḷ', + 'Ḹ' => 'Ḹ', + 'ḹ' => 'ḹ', + 'Ḻ' => 'Ḻ', + 'ḻ' => 'ḻ', + 'Ḽ' => 'Ḽ', + 'ḽ' => 'ḽ', + 'Ḿ' => 'Ḿ', + 'ḿ' => 'ḿ', + 'Ṁ' => 'Ṁ', + 'ṁ' => 'ṁ', + 'Ṃ' => 'Ṃ', + 'ṃ' => 'ṃ', + 'Ṅ' => 'Ṅ', + 'ṅ' => 'ṅ', + 'Ṇ' => 'Ṇ', + 'ṇ' => 'ṇ', + 'Ṉ' => 'Ṉ', + 'ṉ' => 'ṉ', + 'Ṋ' => 'Ṋ', + 'ṋ' => 'ṋ', + 'Ṍ' => 'Ṍ', + 'ṍ' => 'ṍ', + 'Ṏ' => 'Ṏ', + 'ṏ' => 'ṏ', + 'Ṑ' => 'Ṑ', + 'ṑ' => 'ṑ', + 'Ṓ' => 'Ṓ', + 'ṓ' => 'ṓ', + 'Ṕ' => 'Ṕ', + 'ṕ' => 'ṕ', + 'Ṗ' => 'Ṗ', + 'ṗ' => 'ṗ', + 'Ṙ' => 'Ṙ', + 'ṙ' => 'ṙ', + 'Ṛ' => 'Ṛ', + 'ṛ' => 'ṛ', + 'Ṝ' => 'Ṝ', + 'ṝ' => 'ṝ', + 'Ṟ' => 'Ṟ', + 'ṟ' => 'ṟ', + 'Ṡ' => 'Ṡ', + 'ṡ' => 'ṡ', + 'Ṣ' => 'Ṣ', + 'ṣ' => 'ṣ', + 'Ṥ' => 'Ṥ', + 'ṥ' => 'ṥ', + 'Ṧ' => 'Ṧ', + 'ṧ' => 'ṧ', + 'Ṩ' => 'Ṩ', + 'ṩ' => 'ṩ', + 'Ṫ' => 'Ṫ', + 'ṫ' => 'ṫ', + 'Ṭ' => 'Ṭ', + 'ṭ' => 'ṭ', + 'Ṯ' => 'Ṯ', + 'ṯ' => 'ṯ', + 'Ṱ' => 'Ṱ', + 'ṱ' => 'ṱ', + 'Ṳ' => 'Ṳ', + 'ṳ' => 'ṳ', + 'Ṵ' => 'Ṵ', + 'ṵ' => 'ṵ', + 'Ṷ' => 'Ṷ', + 'ṷ' => 'ṷ', + 'Ṹ' => 'Ṹ', + 'ṹ' => 'ṹ', + 'Ṻ' => 'Ṻ', + 'ṻ' => 'ṻ', + 'Ṽ' => 'Ṽ', + 'ṽ' => 'ṽ', + 'Ṿ' => 'Ṿ', + 'ṿ' => 'ṿ', + 'Ẁ' => 'Ẁ', + 'ẁ' => 'ẁ', + 'Ẃ' => 'Ẃ', + 'ẃ' => 'ẃ', + 'Ẅ' => 'Ẅ', + 'ẅ' => 'ẅ', + 'Ẇ' => 'Ẇ', + 'ẇ' => 'ẇ', + 'Ẉ' => 'Ẉ', + 'ẉ' => 'ẉ', + 'Ẋ' => 'Ẋ', + 'ẋ' => 'ẋ', + 'Ẍ' => 'Ẍ', + 'ẍ' => 'ẍ', + 'Ẏ' => 'Ẏ', + 'ẏ' => 'ẏ', + 'Ẑ' => 'Ẑ', + 'ẑ' => 'ẑ', + 'Ẓ' => 'Ẓ', + 'ẓ' => 'ẓ', + 'Ẕ' => 'Ẕ', + 'ẕ' => 'ẕ', + 'ẖ' => 'ẖ', + 'ẗ' => 'ẗ', + 'ẘ' => 'ẘ', + 'ẙ' => 'ẙ', + 'ẛ' => 'ẛ', + 'Ạ' => 'Ạ', + 'ạ' => 'ạ', + 'Ả' => 'Ả', + 'ả' => 'ả', + 'Ấ' => 'Ấ', + 'ấ' => 'ấ', + 'Ầ' => 'Ầ', + 'ầ' => 'ầ', + 'Ẩ' => 'Ẩ', + 'ẩ' => 'ẩ', + 'Ẫ' => 'Ẫ', + 'ẫ' => 'ẫ', + 'Ậ' => 'Ậ', + 'ậ' => 'ậ', + 'Ắ' => 'Ắ', + 'ắ' => 'ắ', + 'Ằ' => 'Ằ', + 'ằ' => 'ằ', + 'Ẳ' => 'Ẳ', + 'ẳ' => 'ẳ', + 'Ẵ' => 'Ẵ', + 'ẵ' => 'ẵ', + 'Ặ' => 'Ặ', + 'ặ' => 'ặ', + 'Ẹ' => 'Ẹ', + 'ẹ' => 'ẹ', + 'Ẻ' => 'Ẻ', + 'ẻ' => 'ẻ', + 'Ẽ' => 'Ẽ', + 'ẽ' => 'ẽ', + 'Ế' => 'Ế', + 'ế' => 'ế', + 'Ề' => 'Ề', + 'ề' => 'ề', + 'Ể' => 'Ể', + 'ể' => 'ể', + 'Ễ' => 'Ễ', + 'ễ' => 'ễ', + 'Ệ' => 'Ệ', + 'ệ' => 'ệ', + 'Ỉ' => 'Ỉ', + 'ỉ' => 'ỉ', + 'Ị' => 'Ị', + 'ị' => 'ị', + 'Ọ' => 'Ọ', + 'ọ' => 'ọ', + 'Ỏ' => 'Ỏ', + 'ỏ' => 'ỏ', + 'Ố' => 'Ố', + 'ố' => 'ố', + 'Ồ' => 'Ồ', + 'ồ' => 'ồ', + 'Ổ' => 'Ổ', + 'ổ' => 'ổ', + 'Ỗ' => 'Ỗ', + 'ỗ' => 'ỗ', + 'Ộ' => 'Ộ', + 'ộ' => 'ộ', + 'Ớ' => 'Ớ', + 'ớ' => 'ớ', + 'Ờ' => 'Ờ', + 'ờ' => 'ờ', + 'Ở' => 'Ở', + 'ở' => 'ở', + 'Ỡ' => 'Ỡ', + 'ỡ' => 'ỡ', + 'Ợ' => 'Ợ', + 'ợ' => 'ợ', + 'Ụ' => 'Ụ', + 'ụ' => 'ụ', + 'Ủ' => 'Ủ', + 'ủ' => 'ủ', + 'Ứ' => 'Ứ', + 'ứ' => 'ứ', + 'Ừ' => 'Ừ', + 'ừ' => 'ừ', + 'Ử' => 'Ử', + 'ử' => 'ử', + 'Ữ' => 'Ữ', + 'ữ' => 'ữ', + 'Ự' => 'Ự', + 'ự' => 'ự', + 'Ỳ' => 'Ỳ', + 'ỳ' => 'ỳ', + 'Ỵ' => 'Ỵ', + 'ỵ' => 'ỵ', + 'Ỷ' => 'Ỷ', + 'ỷ' => 'ỷ', + 'Ỹ' => 'Ỹ', + 'ỹ' => 'ỹ', + 'ἀ' => 'ἀ', + 'ἁ' => 'ἁ', + 'ἂ' => 'ἂ', + 'ἃ' => 'ἃ', + 'ἄ' => 'ἄ', + 'ἅ' => 'ἅ', + 'ἆ' => 'ἆ', + 'ἇ' => 'ἇ', + 'Ἀ' => 'Ἀ', + 'Ἁ' => 'Ἁ', + 'Ἂ' => 'Ἂ', + 'Ἃ' => 'Ἃ', + 'Ἄ' => 'Ἄ', + 'Ἅ' => 'Ἅ', + 'Ἆ' => 'Ἆ', + 'Ἇ' => 'Ἇ', + 'ἐ' => 'ἐ', + 'ἑ' => 'ἑ', + 'ἒ' => 'ἒ', + 'ἓ' => 'ἓ', + 'ἔ' => 'ἔ', + 'ἕ' => 'ἕ', + 'Ἐ' => 'Ἐ', + 'Ἑ' => 'Ἑ', + 'Ἒ' => 'Ἒ', + 'Ἓ' => 'Ἓ', + 'Ἔ' => 'Ἔ', + 'Ἕ' => 'Ἕ', + 'ἠ' => 'ἠ', + 'ἡ' => 'ἡ', + 'ἢ' => 'ἢ', + 'ἣ' => 'ἣ', + 'ἤ' => 'ἤ', + 'ἥ' => 'ἥ', + 'ἦ' => 'ἦ', + 'ἧ' => 'ἧ', + 'Ἠ' => 'Ἠ', + 'Ἡ' => 'Ἡ', + 'Ἢ' => 'Ἢ', + 'Ἣ' => 'Ἣ', + 'Ἤ' => 'Ἤ', + 'Ἥ' => 'Ἥ', + 'Ἦ' => 'Ἦ', + 'Ἧ' => 'Ἧ', + 'ἰ' => 'ἰ', + 'ἱ' => 'ἱ', + 'ἲ' => 'ἲ', + 'ἳ' => 'ἳ', + 'ἴ' => 'ἴ', + 'ἵ' => 'ἵ', + 'ἶ' => 'ἶ', + 'ἷ' => 'ἷ', + 'Ἰ' => 'Ἰ', + 'Ἱ' => 'Ἱ', + 'Ἲ' => 'Ἲ', + 'Ἳ' => 'Ἳ', + 'Ἴ' => 'Ἴ', + 'Ἵ' => 'Ἵ', + 'Ἶ' => 'Ἶ', + 'Ἷ' => 'Ἷ', + 'ὀ' => 'ὀ', + 'ὁ' => 'ὁ', + 'ὂ' => 'ὂ', + 'ὃ' => 'ὃ', + 'ὄ' => 'ὄ', + 'ὅ' => 'ὅ', + 'Ὀ' => 'Ὀ', + 'Ὁ' => 'Ὁ', + 'Ὂ' => 'Ὂ', + 'Ὃ' => 'Ὃ', + 'Ὄ' => 'Ὄ', + 'Ὅ' => 'Ὅ', + 'ὐ' => 'ὐ', + 'ὑ' => 'ὑ', + 'ὒ' => 'ὒ', + 'ὓ' => 'ὓ', + 'ὔ' => 'ὔ', + 'ὕ' => 'ὕ', + 'ὖ' => 'ὖ', + 'ὗ' => 'ὗ', + 'Ὑ' => 'Ὑ', + 'Ὓ' => 'Ὓ', + 'Ὕ' => 'Ὕ', + 'Ὗ' => 'Ὗ', + 'ὠ' => 'ὠ', + 'ὡ' => 'ὡ', + 'ὢ' => 'ὢ', + 'ὣ' => 'ὣ', + 'ὤ' => 'ὤ', + 'ὥ' => 'ὥ', + 'ὦ' => 'ὦ', + 'ὧ' => 'ὧ', + 'Ὠ' => 'Ὠ', + 'Ὡ' => 'Ὡ', + 'Ὢ' => 'Ὢ', + 'Ὣ' => 'Ὣ', + 'Ὤ' => 'Ὤ', + 'Ὥ' => 'Ὥ', + 'Ὦ' => 'Ὦ', + 'Ὧ' => 'Ὧ', + 'ὰ' => 'ὰ', + 'ὲ' => 'ὲ', + 'ὴ' => 'ὴ', + 'ὶ' => 'ὶ', + 'ὸ' => 'ὸ', + 'ὺ' => 'ὺ', + 'ὼ' => 'ὼ', + 'ᾀ' => 'ᾀ', + 'ᾁ' => 'ᾁ', + 'ᾂ' => 'ᾂ', + 'ᾃ' => 'ᾃ', + 'ᾄ' => 'ᾄ', + 'ᾅ' => 'ᾅ', + 'ᾆ' => 'ᾆ', + 'ᾇ' => 'ᾇ', + 'ᾈ' => 'ᾈ', + 'ᾉ' => 'ᾉ', + 'ᾊ' => 'ᾊ', + 'ᾋ' => 'ᾋ', + 'ᾌ' => 'ᾌ', + 'ᾍ' => 'ᾍ', + 'ᾎ' => 'ᾎ', + 'ᾏ' => 'ᾏ', + 'ᾐ' => 'ᾐ', + 'ᾑ' => 'ᾑ', + 'ᾒ' => 'ᾒ', + 'ᾓ' => 'ᾓ', + 'ᾔ' => 'ᾔ', + 'ᾕ' => 'ᾕ', + 'ᾖ' => 'ᾖ', + 'ᾗ' => 'ᾗ', + 'ᾘ' => 'ᾘ', + 'ᾙ' => 'ᾙ', + 'ᾚ' => 'ᾚ', + 'ᾛ' => 'ᾛ', + 'ᾜ' => 'ᾜ', + 'ᾝ' => 'ᾝ', + 'ᾞ' => 'ᾞ', + 'ᾟ' => 'ᾟ', + 'ᾠ' => 'ᾠ', + 'ᾡ' => 'ᾡ', + 'ᾢ' => 'ᾢ', + 'ᾣ' => 'ᾣ', + 'ᾤ' => 'ᾤ', + 'ᾥ' => 'ᾥ', + 'ᾦ' => 'ᾦ', + 'ᾧ' => 'ᾧ', + 'ᾨ' => 'ᾨ', + 'ᾩ' => 'ᾩ', + 'ᾪ' => 'ᾪ', + 'ᾫ' => 'ᾫ', + 'ᾬ' => 'ᾬ', + 'ᾭ' => 'ᾭ', + 'ᾮ' => 'ᾮ', + 'ᾯ' => 'ᾯ', + 'ᾰ' => 'ᾰ', + 'ᾱ' => 'ᾱ', + 'ᾲ' => 'ᾲ', + 'ᾳ' => 'ᾳ', + 'ᾴ' => 'ᾴ', + 'ᾶ' => 'ᾶ', + 'ᾷ' => 'ᾷ', + 'Ᾰ' => 'Ᾰ', + 'Ᾱ' => 'Ᾱ', + 'Ὰ' => 'Ὰ', + 'ᾼ' => 'ᾼ', + '῁' => '῁', + 'ῂ' => 'ῂ', + 'ῃ' => 'ῃ', + 'ῄ' => 'ῄ', + 'ῆ' => 'ῆ', + 'ῇ' => 'ῇ', + 'Ὲ' => 'Ὲ', + 'Ὴ' => 'Ὴ', + 'ῌ' => 'ῌ', + '῍' => '῍', + '῎' => '῎', + '῏' => '῏', + 'ῐ' => 'ῐ', + 'ῑ' => 'ῑ', + 'ῒ' => 'ῒ', + 'ῖ' => 'ῖ', + 'ῗ' => 'ῗ', + 'Ῐ' => 'Ῐ', + 'Ῑ' => 'Ῑ', + 'Ὶ' => 'Ὶ', + '῝' => '῝', + '῞' => '῞', + '῟' => '῟', + 'ῠ' => 'ῠ', + 'ῡ' => 'ῡ', + 'ῢ' => 'ῢ', + 'ῤ' => 'ῤ', + 'ῥ' => 'ῥ', + 'ῦ' => 'ῦ', + 'ῧ' => 'ῧ', + 'Ῠ' => 'Ῠ', + 'Ῡ' => 'Ῡ', + 'Ὺ' => 'Ὺ', + 'Ῥ' => 'Ῥ', + '῭' => '῭', + 'ῲ' => 'ῲ', + 'ῳ' => 'ῳ', + 'ῴ' => 'ῴ', + 'ῶ' => 'ῶ', + 'ῷ' => 'ῷ', + 'Ὸ' => 'Ὸ', + 'Ὼ' => 'Ὼ', + 'ῼ' => 'ῼ', + '↚' => '↚', + '↛' => '↛', + '↮' => '↮', + '⇍' => '⇍', + '⇎' => '⇎', + '⇏' => '⇏', + '∄' => '∄', + '∉' => '∉', + '∌' => '∌', + '∤' => '∤', + '∦' => '∦', + '≁' => '≁', + '≄' => '≄', + '≇' => '≇', + '≉' => '≉', + '≠' => '≠', + '≢' => '≢', + '≭' => '≭', + '≮' => '≮', + '≯' => '≯', + '≰' => '≰', + '≱' => '≱', + '≴' => '≴', + '≵' => '≵', + '≸' => '≸', + '≹' => '≹', + '⊀' => '⊀', + '⊁' => '⊁', + '⊄' => '⊄', + '⊅' => '⊅', + '⊈' => '⊈', + '⊉' => '⊉', + '⊬' => '⊬', + '⊭' => '⊭', + '⊮' => '⊮', + '⊯' => '⊯', + '⋠' => '⋠', + '⋡' => '⋡', + '⋢' => '⋢', + '⋣' => '⋣', + '⋪' => '⋪', + '⋫' => '⋫', + '⋬' => '⋬', + '⋭' => '⋭', + 'が' => 'が', + 'ぎ' => 'ぎ', + 'ぐ' => 'ぐ', + 'げ' => 'げ', + 'ご' => 'ご', + 'ざ' => 'ざ', + 'じ' => 'じ', + 'ず' => 'ず', + 'ぜ' => 'ぜ', + 'ぞ' => 'ぞ', + 'だ' => 'だ', + 'ぢ' => 'ぢ', + 'づ' => 'づ', + 'で' => 'で', + 'ど' => 'ど', + 'ば' => 'ば', + 'ぱ' => 'ぱ', + 'び' => 'び', + 'ぴ' => 'ぴ', + 'ぶ' => 'ぶ', + 'ぷ' => 'ぷ', + 'べ' => 'べ', + 'ぺ' => 'ぺ', + 'ぼ' => 'ぼ', + 'ぽ' => 'ぽ', + 'ゔ' => 'ゔ', + 'ゞ' => 'ゞ', + 'ガ' => 'ガ', + 'ギ' => 'ギ', + 'グ' => 'グ', + 'ゲ' => 'ゲ', + 'ゴ' => 'ゴ', + 'ザ' => 'ザ', + 'ジ' => 'ジ', + 'ズ' => 'ズ', + 'ゼ' => 'ゼ', + 'ゾ' => 'ゾ', + 'ダ' => 'ダ', + 'ヂ' => 'ヂ', + 'ヅ' => 'ヅ', + 'デ' => 'デ', + 'ド' => 'ド', + 'バ' => 'バ', + 'パ' => 'パ', + 'ビ' => 'ビ', + 'ピ' => 'ピ', + 'ブ' => 'ブ', + 'プ' => 'プ', + 'ベ' => 'ベ', + 'ペ' => 'ペ', + 'ボ' => 'ボ', + 'ポ' => 'ポ', + 'ヴ' => 'ヴ', + 'ヷ' => 'ヷ', + 'ヸ' => 'ヸ', + 'ヹ' => 'ヹ', + 'ヺ' => 'ヺ', + 'ヾ' => 'ヾ', + '𑂚' => '𑂚', + '𑂜' => '𑂜', + '𑂫' => '𑂫', + '𑄮' => '𑄮', + '𑄯' => '𑄯', + '𑍋' => '𑍋', + '𑍌' => '𑍌', + '𑒻' => '𑒻', + '𑒼' => '𑒼', + '𑒾' => '𑒾', + '𑖺' => '𑖺', + '𑖻' => '𑖻', + '𑤸' => '𑤸', +); diff --git a/vendor/symfony/polyfill-intl-normalizer/Resources/unidata/canonicalDecomposition.php b/vendor/symfony/polyfill-intl-normalizer/Resources/unidata/canonicalDecomposition.php new file mode 100644 index 0000000..5a3e8e0 --- /dev/null +++ b/vendor/symfony/polyfill-intl-normalizer/Resources/unidata/canonicalDecomposition.php @@ -0,0 +1,2065 @@ + 'À', + 'Á' => 'Á', + 'Â' => 'Â', + 'Ã' => 'Ã', + 'Ä' => 'Ä', + 'Å' => 'Å', + 'Ç' => 'Ç', + 'È' => 'È', + 'É' => 'É', + 'Ê' => 'Ê', + 'Ë' => 'Ë', + 'Ì' => 'Ì', + 'Í' => 'Í', + 'Î' => 'Î', + 'Ï' => 'Ï', + 'Ñ' => 'Ñ', + 'Ò' => 'Ò', + 'Ó' => 'Ó', + 'Ô' => 'Ô', + 'Õ' => 'Õ', + 'Ö' => 'Ö', + 'Ù' => 'Ù', + 'Ú' => 'Ú', + 'Û' => 'Û', + 'Ü' => 'Ü', + 'Ý' => 'Ý', + 'à' => 'à', + 'á' => 'á', + 'â' => 'â', + 'ã' => 'ã', + 'ä' => 'ä', + 'å' => 'å', + 'ç' => 'ç', + 'è' => 'è', + 'é' => 'é', + 'ê' => 'ê', + 'ë' => 'ë', + 'ì' => 'ì', + 'í' => 'í', + 'î' => 'î', + 'ï' => 'ï', + 'ñ' => 'ñ', + 'ò' => 'ò', + 'ó' => 'ó', + 'ô' => 'ô', + 'õ' => 'õ', + 'ö' => 'ö', + 'ù' => 'ù', + 'ú' => 'ú', + 'û' => 'û', + 'ü' => 'ü', + 'ý' => 'ý', + 'ÿ' => 'ÿ', + 'Ā' => 'Ā', + 'ā' => 'ā', + 'Ă' => 'Ă', + 'ă' => 'ă', + 'Ą' => 'Ą', + 'ą' => 'ą', + 'Ć' => 'Ć', + 'ć' => 'ć', + 'Ĉ' => 'Ĉ', + 'ĉ' => 'ĉ', + 'Ċ' => 'Ċ', + 'ċ' => 'ċ', + 'Č' => 'Č', + 'č' => 'č', + 'Ď' => 'Ď', + 'ď' => 'ď', + 'Ē' => 'Ē', + 'ē' => 'ē', + 'Ĕ' => 'Ĕ', + 'ĕ' => 'ĕ', + 'Ė' => 'Ė', + 'ė' => 'ė', + 'Ę' => 'Ę', + 'ę' => 'ę', + 'Ě' => 'Ě', + 'ě' => 'ě', + 'Ĝ' => 'Ĝ', + 'ĝ' => 'ĝ', + 'Ğ' => 'Ğ', + 'ğ' => 'ğ', + 'Ġ' => 'Ġ', + 'ġ' => 'ġ', + 'Ģ' => 'Ģ', + 'ģ' => 'ģ', + 'Ĥ' => 'Ĥ', + 'ĥ' => 'ĥ', + 'Ĩ' => 'Ĩ', + 'ĩ' => 'ĩ', + 'Ī' => 'Ī', + 'ī' => 'ī', + 'Ĭ' => 'Ĭ', + 'ĭ' => 'ĭ', + 'Į' => 'Į', + 'į' => 'į', + 'İ' => 'İ', + 'Ĵ' => 'Ĵ', + 'ĵ' => 'ĵ', + 'Ķ' => 'Ķ', + 'ķ' => 'ķ', + 'Ĺ' => 'Ĺ', + 'ĺ' => 'ĺ', + 'Ļ' => 'Ļ', + 'ļ' => 'ļ', + 'Ľ' => 'Ľ', + 'ľ' => 'ľ', + 'Ń' => 'Ń', + 'ń' => 'ń', + 'Ņ' => 'Ņ', + 'ņ' => 'ņ', + 'Ň' => 'Ň', + 'ň' => 'ň', + 'Ō' => 'Ō', + 'ō' => 'ō', + 'Ŏ' => 'Ŏ', + 'ŏ' => 'ŏ', + 'Ő' => 'Ő', + 'ő' => 'ő', + 'Ŕ' => 'Ŕ', + 'ŕ' => 'ŕ', + 'Ŗ' => 'Ŗ', + 'ŗ' => 'ŗ', + 'Ř' => 'Ř', + 'ř' => 'ř', + 'Ś' => 'Ś', + 'ś' => 'ś', + 'Ŝ' => 'Ŝ', + 'ŝ' => 'ŝ', + 'Ş' => 'Ş', + 'ş' => 'ş', + 'Š' => 'Š', + 'š' => 'š', + 'Ţ' => 'Ţ', + 'ţ' => 'ţ', + 'Ť' => 'Ť', + 'ť' => 'ť', + 'Ũ' => 'Ũ', + 'ũ' => 'ũ', + 'Ū' => 'Ū', + 'ū' => 'ū', + 'Ŭ' => 'Ŭ', + 'ŭ' => 'ŭ', + 'Ů' => 'Ů', + 'ů' => 'ů', + 'Ű' => 'Ű', + 'ű' => 'ű', + 'Ų' => 'Ų', + 'ų' => 'ų', + 'Ŵ' => 'Ŵ', + 'ŵ' => 'ŵ', + 'Ŷ' => 'Ŷ', + 'ŷ' => 'ŷ', + 'Ÿ' => 'Ÿ', + 'Ź' => 'Ź', + 'ź' => 'ź', + 'Ż' => 'Ż', + 'ż' => 'ż', + 'Ž' => 'Ž', + 'ž' => 'ž', + 'Ơ' => 'Ơ', + 'ơ' => 'ơ', + 'Ư' => 'Ư', + 'ư' => 'ư', + 'Ǎ' => 'Ǎ', + 'ǎ' => 'ǎ', + 'Ǐ' => 'Ǐ', + 'ǐ' => 'ǐ', + 'Ǒ' => 'Ǒ', + 'ǒ' => 'ǒ', + 'Ǔ' => 'Ǔ', + 'ǔ' => 'ǔ', + 'Ǖ' => 'Ǖ', + 'ǖ' => 'ǖ', + 'Ǘ' => 'Ǘ', + 'ǘ' => 'ǘ', + 'Ǚ' => 'Ǚ', + 'ǚ' => 'ǚ', + 'Ǜ' => 'Ǜ', + 'ǜ' => 'ǜ', + 'Ǟ' => 'Ǟ', + 'ǟ' => 'ǟ', + 'Ǡ' => 'Ǡ', + 'ǡ' => 'ǡ', + 'Ǣ' => 'Ǣ', + 'ǣ' => 'ǣ', + 'Ǧ' => 'Ǧ', + 'ǧ' => 'ǧ', + 'Ǩ' => 'Ǩ', + 'ǩ' => 'ǩ', + 'Ǫ' => 'Ǫ', + 'ǫ' => 'ǫ', + 'Ǭ' => 'Ǭ', + 'ǭ' => 'ǭ', + 'Ǯ' => 'Ǯ', + 'ǯ' => 'ǯ', + 'ǰ' => 'ǰ', + 'Ǵ' => 'Ǵ', + 'ǵ' => 'ǵ', + 'Ǹ' => 'Ǹ', + 'ǹ' => 'ǹ', + 'Ǻ' => 'Ǻ', + 'ǻ' => 'ǻ', + 'Ǽ' => 'Ǽ', + 'ǽ' => 'ǽ', + 'Ǿ' => 'Ǿ', + 'ǿ' => 'ǿ', + 'Ȁ' => 'Ȁ', + 'ȁ' => 'ȁ', + 'Ȃ' => 'Ȃ', + 'ȃ' => 'ȃ', + 'Ȅ' => 'Ȅ', + 'ȅ' => 'ȅ', + 'Ȇ' => 'Ȇ', + 'ȇ' => 'ȇ', + 'Ȉ' => 'Ȉ', + 'ȉ' => 'ȉ', + 'Ȋ' => 'Ȋ', + 'ȋ' => 'ȋ', + 'Ȍ' => 'Ȍ', + 'ȍ' => 'ȍ', + 'Ȏ' => 'Ȏ', + 'ȏ' => 'ȏ', + 'Ȑ' => 'Ȑ', + 'ȑ' => 'ȑ', + 'Ȓ' => 'Ȓ', + 'ȓ' => 'ȓ', + 'Ȕ' => 'Ȕ', + 'ȕ' => 'ȕ', + 'Ȗ' => 'Ȗ', + 'ȗ' => 'ȗ', + 'Ș' => 'Ș', + 'ș' => 'ș', + 'Ț' => 'Ț', + 'ț' => 'ț', + 'Ȟ' => 'Ȟ', + 'ȟ' => 'ȟ', + 'Ȧ' => 'Ȧ', + 'ȧ' => 'ȧ', + 'Ȩ' => 'Ȩ', + 'ȩ' => 'ȩ', + 'Ȫ' => 'Ȫ', + 'ȫ' => 'ȫ', + 'Ȭ' => 'Ȭ', + 'ȭ' => 'ȭ', + 'Ȯ' => 'Ȯ', + 'ȯ' => 'ȯ', + 'Ȱ' => 'Ȱ', + 'ȱ' => 'ȱ', + 'Ȳ' => 'Ȳ', + 'ȳ' => 'ȳ', + '̀' => '̀', + '́' => '́', + '̓' => '̓', + '̈́' => '̈́', + 'ʹ' => 'ʹ', + ';' => ';', + '΅' => '΅', + 'Ά' => 'Ά', + '·' => '·', + 'Έ' => 'Έ', + 'Ή' => 'Ή', + 'Ί' => 'Ί', + 'Ό' => 'Ό', + 'Ύ' => 'Ύ', + 'Ώ' => 'Ώ', + 'ΐ' => 'ΐ', + 'Ϊ' => 'Ϊ', + 'Ϋ' => 'Ϋ', + 'ά' => 'ά', + 'έ' => 'έ', + 'ή' => 'ή', + 'ί' => 'ί', + 'ΰ' => 'ΰ', + 'ϊ' => 'ϊ', + 'ϋ' => 'ϋ', + 'ό' => 'ό', + 'ύ' => 'ύ', + 'ώ' => 'ώ', + 'ϓ' => 'ϓ', + 'ϔ' => 'ϔ', + 'Ѐ' => 'Ѐ', + 'Ё' => 'Ё', + 'Ѓ' => 'Ѓ', + 'Ї' => 'Ї', + 'Ќ' => 'Ќ', + 'Ѝ' => 'Ѝ', + 'Ў' => 'Ў', + 'Й' => 'Й', + 'й' => 'й', + 'ѐ' => 'ѐ', + 'ё' => 'ё', + 'ѓ' => 'ѓ', + 'ї' => 'ї', + 'ќ' => 'ќ', + 'ѝ' => 'ѝ', + 'ў' => 'ў', + 'Ѷ' => 'Ѷ', + 'ѷ' => 'ѷ', + 'Ӂ' => 'Ӂ', + 'ӂ' => 'ӂ', + 'Ӑ' => 'Ӑ', + 'ӑ' => 'ӑ', + 'Ӓ' => 'Ӓ', + 'ӓ' => 'ӓ', + 'Ӗ' => 'Ӗ', + 'ӗ' => 'ӗ', + 'Ӛ' => 'Ӛ', + 'ӛ' => 'ӛ', + 'Ӝ' => 'Ӝ', + 'ӝ' => 'ӝ', + 'Ӟ' => 'Ӟ', + 'ӟ' => 'ӟ', + 'Ӣ' => 'Ӣ', + 'ӣ' => 'ӣ', + 'Ӥ' => 'Ӥ', + 'ӥ' => 'ӥ', + 'Ӧ' => 'Ӧ', + 'ӧ' => 'ӧ', + 'Ӫ' => 'Ӫ', + 'ӫ' => 'ӫ', + 'Ӭ' => 'Ӭ', + 'ӭ' => 'ӭ', + 'Ӯ' => 'Ӯ', + 'ӯ' => 'ӯ', + 'Ӱ' => 'Ӱ', + 'ӱ' => 'ӱ', + 'Ӳ' => 'Ӳ', + 'ӳ' => 'ӳ', + 'Ӵ' => 'Ӵ', + 'ӵ' => 'ӵ', + 'Ӹ' => 'Ӹ', + 'ӹ' => 'ӹ', + 'آ' => 'آ', + 'أ' => 'أ', + 'ؤ' => 'ؤ', + 'إ' => 'إ', + 'ئ' => 'ئ', + 'ۀ' => 'ۀ', + 'ۂ' => 'ۂ', + 'ۓ' => 'ۓ', + 'ऩ' => 'ऩ', + 'ऱ' => 'ऱ', + 'ऴ' => 'ऴ', + 'क़' => 'क़', + 'ख़' => 'ख़', + 'ग़' => 'ग़', + 'ज़' => 'ज़', + 'ड़' => 'ड़', + 'ढ़' => 'ढ़', + 'फ़' => 'फ़', + 'य़' => 'य़', + 'ো' => 'ো', + 'ৌ' => 'ৌ', + 'ড়' => 'ড়', + 'ঢ়' => 'ঢ়', + 'য়' => 'য়', + 'ਲ਼' => 'ਲ਼', + 'ਸ਼' => 'ਸ਼', + 'ਖ਼' => 'ਖ਼', + 'ਗ਼' => 'ਗ਼', + 'ਜ਼' => 'ਜ਼', + 'ਫ਼' => 'ਫ਼', + 'ୈ' => 'ୈ', + 'ୋ' => 'ୋ', + 'ୌ' => 'ୌ', + 'ଡ଼' => 'ଡ଼', + 'ଢ଼' => 'ଢ଼', + 'ஔ' => 'ஔ', + 'ொ' => 'ொ', + 'ோ' => 'ோ', + 'ௌ' => 'ௌ', + 'ై' => 'ై', + 'ೀ' => 'ೀ', + 'ೇ' => 'ೇ', + 'ೈ' => 'ೈ', + 'ೊ' => 'ೊ', + 'ೋ' => 'ೋ', + 'ൊ' => 'ൊ', + 'ോ' => 'ോ', + 'ൌ' => 'ൌ', + 'ේ' => 'ේ', + 'ො' => 'ො', + 'ෝ' => 'ෝ', + 'ෞ' => 'ෞ', + 'གྷ' => 'གྷ', + 'ཌྷ' => 'ཌྷ', + 'དྷ' => 'དྷ', + 'བྷ' => 'བྷ', + 'ཛྷ' => 'ཛྷ', + 'ཀྵ' => 'ཀྵ', + 'ཱི' => 'ཱི', + 'ཱུ' => 'ཱུ', + 'ྲྀ' => 'ྲྀ', + 'ླྀ' => 'ླྀ', + 'ཱྀ' => 'ཱྀ', + 'ྒྷ' => 'ྒྷ', + 'ྜྷ' => 'ྜྷ', + 'ྡྷ' => 'ྡྷ', + 'ྦྷ' => 'ྦྷ', + 'ྫྷ' => 'ྫྷ', + 'ྐྵ' => 'ྐྵ', + 'ဦ' => 'ဦ', + 'ᬆ' => 'ᬆ', + 'ᬈ' => 'ᬈ', + 'ᬊ' => 'ᬊ', + 'ᬌ' => 'ᬌ', + 'ᬎ' => 'ᬎ', + 'ᬒ' => 'ᬒ', + 'ᬻ' => 'ᬻ', + 'ᬽ' => 'ᬽ', + 'ᭀ' => 'ᭀ', + 'ᭁ' => 'ᭁ', + 'ᭃ' => 'ᭃ', + 'Ḁ' => 'Ḁ', + 'ḁ' => 'ḁ', + 'Ḃ' => 'Ḃ', + 'ḃ' => 'ḃ', + 'Ḅ' => 'Ḅ', + 'ḅ' => 'ḅ', + 'Ḇ' => 'Ḇ', + 'ḇ' => 'ḇ', + 'Ḉ' => 'Ḉ', + 'ḉ' => 'ḉ', + 'Ḋ' => 'Ḋ', + 'ḋ' => 'ḋ', + 'Ḍ' => 'Ḍ', + 'ḍ' => 'ḍ', + 'Ḏ' => 'Ḏ', + 'ḏ' => 'ḏ', + 'Ḑ' => 'Ḑ', + 'ḑ' => 'ḑ', + 'Ḓ' => 'Ḓ', + 'ḓ' => 'ḓ', + 'Ḕ' => 'Ḕ', + 'ḕ' => 'ḕ', + 'Ḗ' => 'Ḗ', + 'ḗ' => 'ḗ', + 'Ḙ' => 'Ḙ', + 'ḙ' => 'ḙ', + 'Ḛ' => 'Ḛ', + 'ḛ' => 'ḛ', + 'Ḝ' => 'Ḝ', + 'ḝ' => 'ḝ', + 'Ḟ' => 'Ḟ', + 'ḟ' => 'ḟ', + 'Ḡ' => 'Ḡ', + 'ḡ' => 'ḡ', + 'Ḣ' => 'Ḣ', + 'ḣ' => 'ḣ', + 'Ḥ' => 'Ḥ', + 'ḥ' => 'ḥ', + 'Ḧ' => 'Ḧ', + 'ḧ' => 'ḧ', + 'Ḩ' => 'Ḩ', + 'ḩ' => 'ḩ', + 'Ḫ' => 'Ḫ', + 'ḫ' => 'ḫ', + 'Ḭ' => 'Ḭ', + 'ḭ' => 'ḭ', + 'Ḯ' => 'Ḯ', + 'ḯ' => 'ḯ', + 'Ḱ' => 'Ḱ', + 'ḱ' => 'ḱ', + 'Ḳ' => 'Ḳ', + 'ḳ' => 'ḳ', + 'Ḵ' => 'Ḵ', + 'ḵ' => 'ḵ', + 'Ḷ' => 'Ḷ', + 'ḷ' => 'ḷ', + 'Ḹ' => 'Ḹ', + 'ḹ' => 'ḹ', + 'Ḻ' => 'Ḻ', + 'ḻ' => 'ḻ', + 'Ḽ' => 'Ḽ', + 'ḽ' => 'ḽ', + 'Ḿ' => 'Ḿ', + 'ḿ' => 'ḿ', + 'Ṁ' => 'Ṁ', + 'ṁ' => 'ṁ', + 'Ṃ' => 'Ṃ', + 'ṃ' => 'ṃ', + 'Ṅ' => 'Ṅ', + 'ṅ' => 'ṅ', + 'Ṇ' => 'Ṇ', + 'ṇ' => 'ṇ', + 'Ṉ' => 'Ṉ', + 'ṉ' => 'ṉ', + 'Ṋ' => 'Ṋ', + 'ṋ' => 'ṋ', + 'Ṍ' => 'Ṍ', + 'ṍ' => 'ṍ', + 'Ṏ' => 'Ṏ', + 'ṏ' => 'ṏ', + 'Ṑ' => 'Ṑ', + 'ṑ' => 'ṑ', + 'Ṓ' => 'Ṓ', + 'ṓ' => 'ṓ', + 'Ṕ' => 'Ṕ', + 'ṕ' => 'ṕ', + 'Ṗ' => 'Ṗ', + 'ṗ' => 'ṗ', + 'Ṙ' => 'Ṙ', + 'ṙ' => 'ṙ', + 'Ṛ' => 'Ṛ', + 'ṛ' => 'ṛ', + 'Ṝ' => 'Ṝ', + 'ṝ' => 'ṝ', + 'Ṟ' => 'Ṟ', + 'ṟ' => 'ṟ', + 'Ṡ' => 'Ṡ', + 'ṡ' => 'ṡ', + 'Ṣ' => 'Ṣ', + 'ṣ' => 'ṣ', + 'Ṥ' => 'Ṥ', + 'ṥ' => 'ṥ', + 'Ṧ' => 'Ṧ', + 'ṧ' => 'ṧ', + 'Ṩ' => 'Ṩ', + 'ṩ' => 'ṩ', + 'Ṫ' => 'Ṫ', + 'ṫ' => 'ṫ', + 'Ṭ' => 'Ṭ', + 'ṭ' => 'ṭ', + 'Ṯ' => 'Ṯ', + 'ṯ' => 'ṯ', + 'Ṱ' => 'Ṱ', + 'ṱ' => 'ṱ', + 'Ṳ' => 'Ṳ', + 'ṳ' => 'ṳ', + 'Ṵ' => 'Ṵ', + 'ṵ' => 'ṵ', + 'Ṷ' => 'Ṷ', + 'ṷ' => 'ṷ', + 'Ṹ' => 'Ṹ', + 'ṹ' => 'ṹ', + 'Ṻ' => 'Ṻ', + 'ṻ' => 'ṻ', + 'Ṽ' => 'Ṽ', + 'ṽ' => 'ṽ', + 'Ṿ' => 'Ṿ', + 'ṿ' => 'ṿ', + 'Ẁ' => 'Ẁ', + 'ẁ' => 'ẁ', + 'Ẃ' => 'Ẃ', + 'ẃ' => 'ẃ', + 'Ẅ' => 'Ẅ', + 'ẅ' => 'ẅ', + 'Ẇ' => 'Ẇ', + 'ẇ' => 'ẇ', + 'Ẉ' => 'Ẉ', + 'ẉ' => 'ẉ', + 'Ẋ' => 'Ẋ', + 'ẋ' => 'ẋ', + 'Ẍ' => 'Ẍ', + 'ẍ' => 'ẍ', + 'Ẏ' => 'Ẏ', + 'ẏ' => 'ẏ', + 'Ẑ' => 'Ẑ', + 'ẑ' => 'ẑ', + 'Ẓ' => 'Ẓ', + 'ẓ' => 'ẓ', + 'Ẕ' => 'Ẕ', + 'ẕ' => 'ẕ', + 'ẖ' => 'ẖ', + 'ẗ' => 'ẗ', + 'ẘ' => 'ẘ', + 'ẙ' => 'ẙ', + 'ẛ' => 'ẛ', + 'Ạ' => 'Ạ', + 'ạ' => 'ạ', + 'Ả' => 'Ả', + 'ả' => 'ả', + 'Ấ' => 'Ấ', + 'ấ' => 'ấ', + 'Ầ' => 'Ầ', + 'ầ' => 'ầ', + 'Ẩ' => 'Ẩ', + 'ẩ' => 'ẩ', + 'Ẫ' => 'Ẫ', + 'ẫ' => 'ẫ', + 'Ậ' => 'Ậ', + 'ậ' => 'ậ', + 'Ắ' => 'Ắ', + 'ắ' => 'ắ', + 'Ằ' => 'Ằ', + 'ằ' => 'ằ', + 'Ẳ' => 'Ẳ', + 'ẳ' => 'ẳ', + 'Ẵ' => 'Ẵ', + 'ẵ' => 'ẵ', + 'Ặ' => 'Ặ', + 'ặ' => 'ặ', + 'Ẹ' => 'Ẹ', + 'ẹ' => 'ẹ', + 'Ẻ' => 'Ẻ', + 'ẻ' => 'ẻ', + 'Ẽ' => 'Ẽ', + 'ẽ' => 'ẽ', + 'Ế' => 'Ế', + 'ế' => 'ế', + 'Ề' => 'Ề', + 'ề' => 'ề', + 'Ể' => 'Ể', + 'ể' => 'ể', + 'Ễ' => 'Ễ', + 'ễ' => 'ễ', + 'Ệ' => 'Ệ', + 'ệ' => 'ệ', + 'Ỉ' => 'Ỉ', + 'ỉ' => 'ỉ', + 'Ị' => 'Ị', + 'ị' => 'ị', + 'Ọ' => 'Ọ', + 'ọ' => 'ọ', + 'Ỏ' => 'Ỏ', + 'ỏ' => 'ỏ', + 'Ố' => 'Ố', + 'ố' => 'ố', + 'Ồ' => 'Ồ', + 'ồ' => 'ồ', + 'Ổ' => 'Ổ', + 'ổ' => 'ổ', + 'Ỗ' => 'Ỗ', + 'ỗ' => 'ỗ', + 'Ộ' => 'Ộ', + 'ộ' => 'ộ', + 'Ớ' => 'Ớ', + 'ớ' => 'ớ', + 'Ờ' => 'Ờ', + 'ờ' => 'ờ', + 'Ở' => 'Ở', + 'ở' => 'ở', + 'Ỡ' => 'Ỡ', + 'ỡ' => 'ỡ', + 'Ợ' => 'Ợ', + 'ợ' => 'ợ', + 'Ụ' => 'Ụ', + 'ụ' => 'ụ', + 'Ủ' => 'Ủ', + 'ủ' => 'ủ', + 'Ứ' => 'Ứ', + 'ứ' => 'ứ', + 'Ừ' => 'Ừ', + 'ừ' => 'ừ', + 'Ử' => 'Ử', + 'ử' => 'ử', + 'Ữ' => 'Ữ', + 'ữ' => 'ữ', + 'Ự' => 'Ự', + 'ự' => 'ự', + 'Ỳ' => 'Ỳ', + 'ỳ' => 'ỳ', + 'Ỵ' => 'Ỵ', + 'ỵ' => 'ỵ', + 'Ỷ' => 'Ỷ', + 'ỷ' => 'ỷ', + 'Ỹ' => 'Ỹ', + 'ỹ' => 'ỹ', + 'ἀ' => 'ἀ', + 'ἁ' => 'ἁ', + 'ἂ' => 'ἂ', + 'ἃ' => 'ἃ', + 'ἄ' => 'ἄ', + 'ἅ' => 'ἅ', + 'ἆ' => 'ἆ', + 'ἇ' => 'ἇ', + 'Ἀ' => 'Ἀ', + 'Ἁ' => 'Ἁ', + 'Ἂ' => 'Ἂ', + 'Ἃ' => 'Ἃ', + 'Ἄ' => 'Ἄ', + 'Ἅ' => 'Ἅ', + 'Ἆ' => 'Ἆ', + 'Ἇ' => 'Ἇ', + 'ἐ' => 'ἐ', + 'ἑ' => 'ἑ', + 'ἒ' => 'ἒ', + 'ἓ' => 'ἓ', + 'ἔ' => 'ἔ', + 'ἕ' => 'ἕ', + 'Ἐ' => 'Ἐ', + 'Ἑ' => 'Ἑ', + 'Ἒ' => 'Ἒ', + 'Ἓ' => 'Ἓ', + 'Ἔ' => 'Ἔ', + 'Ἕ' => 'Ἕ', + 'ἠ' => 'ἠ', + 'ἡ' => 'ἡ', + 'ἢ' => 'ἢ', + 'ἣ' => 'ἣ', + 'ἤ' => 'ἤ', + 'ἥ' => 'ἥ', + 'ἦ' => 'ἦ', + 'ἧ' => 'ἧ', + 'Ἠ' => 'Ἠ', + 'Ἡ' => 'Ἡ', + 'Ἢ' => 'Ἢ', + 'Ἣ' => 'Ἣ', + 'Ἤ' => 'Ἤ', + 'Ἥ' => 'Ἥ', + 'Ἦ' => 'Ἦ', + 'Ἧ' => 'Ἧ', + 'ἰ' => 'ἰ', + 'ἱ' => 'ἱ', + 'ἲ' => 'ἲ', + 'ἳ' => 'ἳ', + 'ἴ' => 'ἴ', + 'ἵ' => 'ἵ', + 'ἶ' => 'ἶ', + 'ἷ' => 'ἷ', + 'Ἰ' => 'Ἰ', + 'Ἱ' => 'Ἱ', + 'Ἲ' => 'Ἲ', + 'Ἳ' => 'Ἳ', + 'Ἴ' => 'Ἴ', + 'Ἵ' => 'Ἵ', + 'Ἶ' => 'Ἶ', + 'Ἷ' => 'Ἷ', + 'ὀ' => 'ὀ', + 'ὁ' => 'ὁ', + 'ὂ' => 'ὂ', + 'ὃ' => 'ὃ', + 'ὄ' => 'ὄ', + 'ὅ' => 'ὅ', + 'Ὀ' => 'Ὀ', + 'Ὁ' => 'Ὁ', + 'Ὂ' => 'Ὂ', + 'Ὃ' => 'Ὃ', + 'Ὄ' => 'Ὄ', + 'Ὅ' => 'Ὅ', + 'ὐ' => 'ὐ', + 'ὑ' => 'ὑ', + 'ὒ' => 'ὒ', + 'ὓ' => 'ὓ', + 'ὔ' => 'ὔ', + 'ὕ' => 'ὕ', + 'ὖ' => 'ὖ', + 'ὗ' => 'ὗ', + 'Ὑ' => 'Ὑ', + 'Ὓ' => 'Ὓ', + 'Ὕ' => 'Ὕ', + 'Ὗ' => 'Ὗ', + 'ὠ' => 'ὠ', + 'ὡ' => 'ὡ', + 'ὢ' => 'ὢ', + 'ὣ' => 'ὣ', + 'ὤ' => 'ὤ', + 'ὥ' => 'ὥ', + 'ὦ' => 'ὦ', + 'ὧ' => 'ὧ', + 'Ὠ' => 'Ὠ', + 'Ὡ' => 'Ὡ', + 'Ὢ' => 'Ὢ', + 'Ὣ' => 'Ὣ', + 'Ὤ' => 'Ὤ', + 'Ὥ' => 'Ὥ', + 'Ὦ' => 'Ὦ', + 'Ὧ' => 'Ὧ', + 'ὰ' => 'ὰ', + 'ά' => 'ά', + 'ὲ' => 'ὲ', + 'έ' => 'έ', + 'ὴ' => 'ὴ', + 'ή' => 'ή', + 'ὶ' => 'ὶ', + 'ί' => 'ί', + 'ὸ' => 'ὸ', + 'ό' => 'ό', + 'ὺ' => 'ὺ', + 'ύ' => 'ύ', + 'ὼ' => 'ὼ', + 'ώ' => 'ώ', + 'ᾀ' => 'ᾀ', + 'ᾁ' => 'ᾁ', + 'ᾂ' => 'ᾂ', + 'ᾃ' => 'ᾃ', + 'ᾄ' => 'ᾄ', + 'ᾅ' => 'ᾅ', + 'ᾆ' => 'ᾆ', + 'ᾇ' => 'ᾇ', + 'ᾈ' => 'ᾈ', + 'ᾉ' => 'ᾉ', + 'ᾊ' => 'ᾊ', + 'ᾋ' => 'ᾋ', + 'ᾌ' => 'ᾌ', + 'ᾍ' => 'ᾍ', + 'ᾎ' => 'ᾎ', + 'ᾏ' => 'ᾏ', + 'ᾐ' => 'ᾐ', + 'ᾑ' => 'ᾑ', + 'ᾒ' => 'ᾒ', + 'ᾓ' => 'ᾓ', + 'ᾔ' => 'ᾔ', + 'ᾕ' => 'ᾕ', + 'ᾖ' => 'ᾖ', + 'ᾗ' => 'ᾗ', + 'ᾘ' => 'ᾘ', + 'ᾙ' => 'ᾙ', + 'ᾚ' => 'ᾚ', + 'ᾛ' => 'ᾛ', + 'ᾜ' => 'ᾜ', + 'ᾝ' => 'ᾝ', + 'ᾞ' => 'ᾞ', + 'ᾟ' => 'ᾟ', + 'ᾠ' => 'ᾠ', + 'ᾡ' => 'ᾡ', + 'ᾢ' => 'ᾢ', + 'ᾣ' => 'ᾣ', + 'ᾤ' => 'ᾤ', + 'ᾥ' => 'ᾥ', + 'ᾦ' => 'ᾦ', + 'ᾧ' => 'ᾧ', + 'ᾨ' => 'ᾨ', + 'ᾩ' => 'ᾩ', + 'ᾪ' => 'ᾪ', + 'ᾫ' => 'ᾫ', + 'ᾬ' => 'ᾬ', + 'ᾭ' => 'ᾭ', + 'ᾮ' => 'ᾮ', + 'ᾯ' => 'ᾯ', + 'ᾰ' => 'ᾰ', + 'ᾱ' => 'ᾱ', + 'ᾲ' => 'ᾲ', + 'ᾳ' => 'ᾳ', + 'ᾴ' => 'ᾴ', + 'ᾶ' => 'ᾶ', + 'ᾷ' => 'ᾷ', + 'Ᾰ' => 'Ᾰ', + 'Ᾱ' => 'Ᾱ', + 'Ὰ' => 'Ὰ', + 'Ά' => 'Ά', + 'ᾼ' => 'ᾼ', + 'ι' => 'ι', + '῁' => '῁', + 'ῂ' => 'ῂ', + 'ῃ' => 'ῃ', + 'ῄ' => 'ῄ', + 'ῆ' => 'ῆ', + 'ῇ' => 'ῇ', + 'Ὲ' => 'Ὲ', + 'Έ' => 'Έ', + 'Ὴ' => 'Ὴ', + 'Ή' => 'Ή', + 'ῌ' => 'ῌ', + '῍' => '῍', + '῎' => '῎', + '῏' => '῏', + 'ῐ' => 'ῐ', + 'ῑ' => 'ῑ', + 'ῒ' => 'ῒ', + 'ΐ' => 'ΐ', + 'ῖ' => 'ῖ', + 'ῗ' => 'ῗ', + 'Ῐ' => 'Ῐ', + 'Ῑ' => 'Ῑ', + 'Ὶ' => 'Ὶ', + 'Ί' => 'Ί', + '῝' => '῝', + '῞' => '῞', + '῟' => '῟', + 'ῠ' => 'ῠ', + 'ῡ' => 'ῡ', + 'ῢ' => 'ῢ', + 'ΰ' => 'ΰ', + 'ῤ' => 'ῤ', + 'ῥ' => 'ῥ', + 'ῦ' => 'ῦ', + 'ῧ' => 'ῧ', + 'Ῠ' => 'Ῠ', + 'Ῡ' => 'Ῡ', + 'Ὺ' => 'Ὺ', + 'Ύ' => 'Ύ', + 'Ῥ' => 'Ῥ', + '῭' => '῭', + '΅' => '΅', + '`' => '`', + 'ῲ' => 'ῲ', + 'ῳ' => 'ῳ', + 'ῴ' => 'ῴ', + 'ῶ' => 'ῶ', + 'ῷ' => 'ῷ', + 'Ὸ' => 'Ὸ', + 'Ό' => 'Ό', + 'Ὼ' => 'Ὼ', + 'Ώ' => 'Ώ', + 'ῼ' => 'ῼ', + '´' => '´', + ' ' => ' ', + ' ' => ' ', + 'Ω' => 'Ω', + 'K' => 'K', + 'Å' => 'Å', + '↚' => '↚', + '↛' => '↛', + '↮' => '↮', + '⇍' => '⇍', + '⇎' => '⇎', + '⇏' => '⇏', + '∄' => '∄', + '∉' => '∉', + '∌' => '∌', + '∤' => '∤', + '∦' => '∦', + '≁' => '≁', + '≄' => '≄', + '≇' => '≇', + '≉' => '≉', + '≠' => '≠', + '≢' => '≢', + '≭' => '≭', + '≮' => '≮', + '≯' => '≯', + '≰' => '≰', + '≱' => '≱', + '≴' => '≴', + '≵' => '≵', + '≸' => '≸', + '≹' => '≹', + '⊀' => '⊀', + '⊁' => '⊁', + '⊄' => '⊄', + '⊅' => '⊅', + '⊈' => '⊈', + '⊉' => '⊉', + '⊬' => '⊬', + '⊭' => '⊭', + '⊮' => '⊮', + '⊯' => '⊯', + '⋠' => '⋠', + '⋡' => '⋡', + '⋢' => '⋢', + '⋣' => '⋣', + '⋪' => '⋪', + '⋫' => '⋫', + '⋬' => '⋬', + '⋭' => '⋭', + '〈' => '〈', + '〉' => '〉', + '⫝̸' => '⫝̸', + 'が' => 'が', + 'ぎ' => 'ぎ', + 'ぐ' => 'ぐ', + 'げ' => 'げ', + 'ご' => 'ご', + 'ざ' => 'ざ', + 'じ' => 'じ', + 'ず' => 'ず', + 'ぜ' => 'ぜ', + 'ぞ' => 'ぞ', + 'だ' => 'だ', + 'ぢ' => 'ぢ', + 'づ' => 'づ', + 'で' => 'で', + 'ど' => 'ど', + 'ば' => 'ば', + 'ぱ' => 'ぱ', + 'び' => 'び', + 'ぴ' => 'ぴ', + 'ぶ' => 'ぶ', + 'ぷ' => 'ぷ', + 'べ' => 'べ', + 'ぺ' => 'ぺ', + 'ぼ' => 'ぼ', + 'ぽ' => 'ぽ', + 'ゔ' => 'ゔ', + 'ゞ' => 'ゞ', + 'ガ' => 'ガ', + 'ギ' => 'ギ', + 'グ' => 'グ', + 'ゲ' => 'ゲ', + 'ゴ' => 'ゴ', + 'ザ' => 'ザ', + 'ジ' => 'ジ', + 'ズ' => 'ズ', + 'ゼ' => 'ゼ', + 'ゾ' => 'ゾ', + 'ダ' => 'ダ', + 'ヂ' => 'ヂ', + 'ヅ' => 'ヅ', + 'デ' => 'デ', + 'ド' => 'ド', + 'バ' => 'バ', + 'パ' => 'パ', + 'ビ' => 'ビ', + 'ピ' => 'ピ', + 'ブ' => 'ブ', + 'プ' => 'プ', + 'ベ' => 'ベ', + 'ペ' => 'ペ', + 'ボ' => 'ボ', + 'ポ' => 'ポ', + 'ヴ' => 'ヴ', + 'ヷ' => 'ヷ', + 'ヸ' => 'ヸ', + 'ヹ' => 'ヹ', + 'ヺ' => 'ヺ', + 'ヾ' => 'ヾ', + '豈' => '豈', + '更' => '更', + '車' => '車', + '賈' => '賈', + '滑' => '滑', + '串' => '串', + '句' => '句', + '龜' => '龜', + '龜' => '龜', + '契' => '契', + '金' => '金', + '喇' => '喇', + '奈' => '奈', + '懶' => '懶', + '癩' => '癩', + '羅' => '羅', + '蘿' => '蘿', + '螺' => '螺', + '裸' => '裸', + '邏' => '邏', + '樂' => '樂', + '洛' => '洛', + '烙' => '烙', + '珞' => '珞', + '落' => '落', + '酪' => '酪', + '駱' => '駱', + '亂' => '亂', + '卵' => '卵', + '欄' => '欄', + '爛' => '爛', + '蘭' => '蘭', + '鸞' => '鸞', + '嵐' => '嵐', + '濫' => '濫', + '藍' => '藍', + '襤' => '襤', + '拉' => '拉', + '臘' => '臘', + '蠟' => '蠟', + '廊' => '廊', + '朗' => '朗', + '浪' => '浪', + '狼' => '狼', + '郎' => '郎', + '來' => '來', + '冷' => '冷', + '勞' => '勞', + '擄' => '擄', + '櫓' => '櫓', + '爐' => '爐', + '盧' => '盧', + '老' => '老', + '蘆' => '蘆', + '虜' => '虜', + '路' => '路', + '露' => '露', + '魯' => '魯', + '鷺' => '鷺', + '碌' => '碌', + '祿' => '祿', + '綠' => '綠', + '菉' => '菉', + '錄' => '錄', + '鹿' => '鹿', + '論' => '論', + '壟' => '壟', + '弄' => '弄', + '籠' => '籠', + '聾' => '聾', + '牢' => '牢', + '磊' => '磊', + '賂' => '賂', + '雷' => '雷', + '壘' => '壘', + '屢' => '屢', + '樓' => '樓', + '淚' => '淚', + '漏' => '漏', + '累' => '累', + '縷' => '縷', + '陋' => '陋', + '勒' => '勒', + '肋' => '肋', + '凜' => '凜', + '凌' => '凌', + '稜' => '稜', + '綾' => '綾', + '菱' => '菱', + '陵' => '陵', + '讀' => '讀', + '拏' => '拏', + '樂' => '樂', + '諾' => '諾', + '丹' => '丹', + '寧' => '寧', + '怒' => '怒', + '率' => '率', + '異' => '異', + '北' => '北', + '磻' => '磻', + '便' => '便', + '復' => '復', + '不' => '不', + '泌' => '泌', + '數' => '數', + '索' => '索', + '參' => '參', + '塞' => '塞', + '省' => '省', + '葉' => '葉', + '說' => '說', + '殺' => '殺', + '辰' => '辰', + '沈' => '沈', + '拾' => '拾', + '若' => '若', + '掠' => '掠', + '略' => '略', + '亮' => '亮', + '兩' => '兩', + '凉' => '凉', + '梁' => '梁', + '糧' => '糧', + '良' => '良', + '諒' => '諒', + '量' => '量', + '勵' => '勵', + '呂' => '呂', + '女' => '女', + '廬' => '廬', + '旅' => '旅', + '濾' => '濾', + '礪' => '礪', + '閭' => '閭', + '驪' => '驪', + '麗' => '麗', + '黎' => '黎', + '力' => '力', + '曆' => '曆', + '歷' => '歷', + '轢' => '轢', + '年' => '年', + '憐' => '憐', + '戀' => '戀', + '撚' => '撚', + '漣' => '漣', + '煉' => '煉', + '璉' => '璉', + '秊' => '秊', + '練' => '練', + '聯' => '聯', + '輦' => '輦', + '蓮' => '蓮', + '連' => '連', + '鍊' => '鍊', + '列' => '列', + '劣' => '劣', + '咽' => '咽', + '烈' => '烈', + '裂' => '裂', + '說' => '說', + '廉' => '廉', + '念' => '念', + '捻' => '捻', + '殮' => '殮', + '簾' => '簾', + '獵' => '獵', + '令' => '令', + '囹' => '囹', + '寧' => '寧', + '嶺' => '嶺', + '怜' => '怜', + '玲' => '玲', + '瑩' => '瑩', + '羚' => '羚', + '聆' => '聆', + '鈴' => '鈴', + '零' => '零', + '靈' => '靈', + '領' => '領', + '例' => '例', + '禮' => '禮', + '醴' => '醴', + '隸' => '隸', + '惡' => '惡', + '了' => '了', + '僚' => '僚', + '寮' => '寮', + '尿' => '尿', + '料' => '料', + '樂' => '樂', + '燎' => '燎', + '療' => '療', + '蓼' => '蓼', + '遼' => '遼', + '龍' => '龍', + '暈' => '暈', + '阮' => '阮', + '劉' => '劉', + '杻' => '杻', + '柳' => '柳', + '流' => '流', + '溜' => '溜', + '琉' => '琉', + '留' => '留', + '硫' => '硫', + '紐' => '紐', + '類' => '類', + '六' => '六', + '戮' => '戮', + '陸' => '陸', + '倫' => '倫', + '崙' => '崙', + '淪' => '淪', + '輪' => '輪', + '律' => '律', + '慄' => '慄', + '栗' => '栗', + '率' => '率', + '隆' => '隆', + '利' => '利', + '吏' => '吏', + '履' => '履', + '易' => '易', + '李' => '李', + '梨' => '梨', + '泥' => '泥', + '理' => '理', + '痢' => '痢', + '罹' => '罹', + '裏' => '裏', + '裡' => '裡', + '里' => '里', + '離' => '離', + '匿' => '匿', + '溺' => '溺', + '吝' => '吝', + '燐' => '燐', + '璘' => '璘', + '藺' => '藺', + '隣' => '隣', + '鱗' => '鱗', + '麟' => '麟', + '林' => '林', + '淋' => '淋', + '臨' => '臨', + '立' => '立', + '笠' => '笠', + '粒' => '粒', + '狀' => '狀', + '炙' => '炙', + '識' => '識', + '什' => '什', + '茶' => '茶', + '刺' => '刺', + '切' => '切', + '度' => '度', + '拓' => '拓', + '糖' => '糖', + '宅' => '宅', + '洞' => '洞', + '暴' => '暴', + '輻' => '輻', + '行' => '行', + '降' => '降', + '見' => '見', + '廓' => '廓', + '兀' => '兀', + '嗀' => '嗀', + '塚' => '塚', + '晴' => '晴', + '凞' => '凞', + '猪' => '猪', + '益' => '益', + '礼' => '礼', + '神' => '神', + '祥' => '祥', + '福' => '福', + '靖' => '靖', + '精' => '精', + '羽' => '羽', + '蘒' => '蘒', + '諸' => '諸', + '逸' => '逸', + '都' => '都', + '飯' => '飯', + '飼' => '飼', + '館' => '館', + '鶴' => '鶴', + '郞' => '郞', + '隷' => '隷', + '侮' => '侮', + '僧' => '僧', + '免' => '免', + '勉' => '勉', + '勤' => '勤', + '卑' => '卑', + '喝' => '喝', + '嘆' => '嘆', + '器' => '器', + '塀' => '塀', + '墨' => '墨', + '層' => '層', + '屮' => '屮', + '悔' => '悔', + '慨' => '慨', + '憎' => '憎', + '懲' => '懲', + '敏' => '敏', + '既' => '既', + '暑' => '暑', + '梅' => '梅', + '海' => '海', + '渚' => '渚', + '漢' => '漢', + '煮' => '煮', + '爫' => '爫', + '琢' => '琢', + '碑' => '碑', + '社' => '社', + '祉' => '祉', + '祈' => '祈', + '祐' => '祐', + '祖' => '祖', + '祝' => '祝', + '禍' => '禍', + '禎' => '禎', + '穀' => '穀', + '突' => '突', + '節' => '節', + '練' => '練', + '縉' => '縉', + '繁' => '繁', + '署' => '署', + '者' => '者', + '臭' => '臭', + '艹' => '艹', + '艹' => '艹', + '著' => '著', + '褐' => '褐', + '視' => '視', + '謁' => '謁', + '謹' => '謹', + '賓' => '賓', + '贈' => '贈', + '辶' => '辶', + '逸' => '逸', + '難' => '難', + '響' => '響', + '頻' => '頻', + '恵' => '恵', + '𤋮' => '𤋮', + '舘' => '舘', + '並' => '並', + '况' => '况', + '全' => '全', + '侀' => '侀', + '充' => '充', + '冀' => '冀', + '勇' => '勇', + '勺' => '勺', + '喝' => '喝', + '啕' => '啕', + '喙' => '喙', + '嗢' => '嗢', + '塚' => '塚', + '墳' => '墳', + '奄' => '奄', + '奔' => '奔', + '婢' => '婢', + '嬨' => '嬨', + '廒' => '廒', + '廙' => '廙', + '彩' => '彩', + '徭' => '徭', + '惘' => '惘', + '慎' => '慎', + '愈' => '愈', + '憎' => '憎', + '慠' => '慠', + '懲' => '懲', + '戴' => '戴', + '揄' => '揄', + '搜' => '搜', + '摒' => '摒', + '敖' => '敖', + '晴' => '晴', + '朗' => '朗', + '望' => '望', + '杖' => '杖', + '歹' => '歹', + '殺' => '殺', + '流' => '流', + '滛' => '滛', + '滋' => '滋', + '漢' => '漢', + '瀞' => '瀞', + '煮' => '煮', + '瞧' => '瞧', + '爵' => '爵', + '犯' => '犯', + '猪' => '猪', + '瑱' => '瑱', + '甆' => '甆', + '画' => '画', + '瘝' => '瘝', + '瘟' => '瘟', + '益' => '益', + '盛' => '盛', + '直' => '直', + '睊' => '睊', + '着' => '着', + '磌' => '磌', + '窱' => '窱', + '節' => '節', + '类' => '类', + '絛' => '絛', + '練' => '練', + '缾' => '缾', + '者' => '者', + '荒' => '荒', + '華' => '華', + '蝹' => '蝹', + '襁' => '襁', + '覆' => '覆', + '視' => '視', + '調' => '調', + '諸' => '諸', + '請' => '請', + '謁' => '謁', + '諾' => '諾', + '諭' => '諭', + '謹' => '謹', + '變' => '變', + '贈' => '贈', + '輸' => '輸', + '遲' => '遲', + '醙' => '醙', + '鉶' => '鉶', + '陼' => '陼', + '難' => '難', + '靖' => '靖', + '韛' => '韛', + '響' => '響', + '頋' => '頋', + '頻' => '頻', + '鬒' => '鬒', + '龜' => '龜', + '𢡊' => '𢡊', + '𢡄' => '𢡄', + '𣏕' => '𣏕', + '㮝' => '㮝', + '䀘' => '䀘', + '䀹' => '䀹', + '𥉉' => '𥉉', + '𥳐' => '𥳐', + '𧻓' => '𧻓', + '齃' => '齃', + '龎' => '龎', + 'יִ' => 'יִ', + 'ײַ' => 'ײַ', + 'שׁ' => 'שׁ', + 'שׂ' => 'שׂ', + 'שּׁ' => 'שּׁ', + 'שּׂ' => 'שּׂ', + 'אַ' => 'אַ', + 'אָ' => 'אָ', + 'אּ' => 'אּ', + 'בּ' => 'בּ', + 'גּ' => 'גּ', + 'דּ' => 'דּ', + 'הּ' => 'הּ', + 'וּ' => 'וּ', + 'זּ' => 'זּ', + 'טּ' => 'טּ', + 'יּ' => 'יּ', + 'ךּ' => 'ךּ', + 'כּ' => 'כּ', + 'לּ' => 'לּ', + 'מּ' => 'מּ', + 'נּ' => 'נּ', + 'סּ' => 'סּ', + 'ףּ' => 'ףּ', + 'פּ' => 'פּ', + 'צּ' => 'צּ', + 'קּ' => 'קּ', + 'רּ' => 'רּ', + 'שּ' => 'שּ', + 'תּ' => 'תּ', + 'וֹ' => 'וֹ', + 'בֿ' => 'בֿ', + 'כֿ' => 'כֿ', + 'פֿ' => 'פֿ', + '𑂚' => '𑂚', + '𑂜' => '𑂜', + '𑂫' => '𑂫', + '𑄮' => '𑄮', + '𑄯' => '𑄯', + '𑍋' => '𑍋', + '𑍌' => '𑍌', + '𑒻' => '𑒻', + '𑒼' => '𑒼', + '𑒾' => '𑒾', + '𑖺' => '𑖺', + '𑖻' => '𑖻', + '𑤸' => '𑤸', + '𝅗𝅥' => '𝅗𝅥', + '𝅘𝅥' => '𝅘𝅥', + '𝅘𝅥𝅮' => '𝅘𝅥𝅮', + '𝅘𝅥𝅯' => '𝅘𝅥𝅯', + '𝅘𝅥𝅰' => '𝅘𝅥𝅰', + '𝅘𝅥𝅱' => '𝅘𝅥𝅱', + '𝅘𝅥𝅲' => '𝅘𝅥𝅲', + '𝆹𝅥' => '𝆹𝅥', + '𝆺𝅥' => '𝆺𝅥', + '𝆹𝅥𝅮' => '𝆹𝅥𝅮', + '𝆺𝅥𝅮' => '𝆺𝅥𝅮', + '𝆹𝅥𝅯' => '𝆹𝅥𝅯', + '𝆺𝅥𝅯' => '𝆺𝅥𝅯', + '丽' => '丽', + '丸' => '丸', + '乁' => '乁', + '𠄢' => '𠄢', + '你' => '你', + '侮' => '侮', + '侻' => '侻', + '倂' => '倂', + '偺' => '偺', + '備' => '備', + '僧' => '僧', + '像' => '像', + '㒞' => '㒞', + '𠘺' => '𠘺', + '免' => '免', + '兔' => '兔', + '兤' => '兤', + '具' => '具', + '𠔜' => '𠔜', + '㒹' => '㒹', + '內' => '內', + '再' => '再', + '𠕋' => '𠕋', + '冗' => '冗', + '冤' => '冤', + '仌' => '仌', + '冬' => '冬', + '况' => '况', + '𩇟' => '𩇟', + '凵' => '凵', + '刃' => '刃', + '㓟' => '㓟', + '刻' => '刻', + '剆' => '剆', + '割' => '割', + '剷' => '剷', + '㔕' => '㔕', + '勇' => '勇', + '勉' => '勉', + '勤' => '勤', + '勺' => '勺', + '包' => '包', + '匆' => '匆', + '北' => '北', + '卉' => '卉', + '卑' => '卑', + '博' => '博', + '即' => '即', + '卽' => '卽', + '卿' => '卿', + '卿' => '卿', + '卿' => '卿', + '𠨬' => '𠨬', + '灰' => '灰', + '及' => '及', + '叟' => '叟', + '𠭣' => '𠭣', + '叫' => '叫', + '叱' => '叱', + '吆' => '吆', + '咞' => '咞', + '吸' => '吸', + '呈' => '呈', + '周' => '周', + '咢' => '咢', + '哶' => '哶', + '唐' => '唐', + '啓' => '啓', + '啣' => '啣', + '善' => '善', + '善' => '善', + '喙' => '喙', + '喫' => '喫', + '喳' => '喳', + '嗂' => '嗂', + '圖' => '圖', + '嘆' => '嘆', + '圗' => '圗', + '噑' => '噑', + '噴' => '噴', + '切' => '切', + '壮' => '壮', + '城' => '城', + '埴' => '埴', + '堍' => '堍', + '型' => '型', + '堲' => '堲', + '報' => '報', + '墬' => '墬', + '𡓤' => '𡓤', + '売' => '売', + '壷' => '壷', + '夆' => '夆', + '多' => '多', + '夢' => '夢', + '奢' => '奢', + '𡚨' => '𡚨', + '𡛪' => '𡛪', + '姬' => '姬', + '娛' => '娛', + '娧' => '娧', + '姘' => '姘', + '婦' => '婦', + '㛮' => '㛮', + '㛼' => '㛼', + '嬈' => '嬈', + '嬾' => '嬾', + '嬾' => '嬾', + '𡧈' => '𡧈', + '寃' => '寃', + '寘' => '寘', + '寧' => '寧', + '寳' => '寳', + '𡬘' => '𡬘', + '寿' => '寿', + '将' => '将', + '当' => '当', + '尢' => '尢', + '㞁' => '㞁', + '屠' => '屠', + '屮' => '屮', + '峀' => '峀', + '岍' => '岍', + '𡷤' => '𡷤', + '嵃' => '嵃', + '𡷦' => '𡷦', + '嵮' => '嵮', + '嵫' => '嵫', + '嵼' => '嵼', + '巡' => '巡', + '巢' => '巢', + '㠯' => '㠯', + '巽' => '巽', + '帨' => '帨', + '帽' => '帽', + '幩' => '幩', + '㡢' => '㡢', + '𢆃' => '𢆃', + '㡼' => '㡼', + '庰' => '庰', + '庳' => '庳', + '庶' => '庶', + '廊' => '廊', + '𪎒' => '𪎒', + '廾' => '廾', + '𢌱' => '𢌱', + '𢌱' => '𢌱', + '舁' => '舁', + '弢' => '弢', + '弢' => '弢', + '㣇' => '㣇', + '𣊸' => '𣊸', + '𦇚' => '𦇚', + '形' => '形', + '彫' => '彫', + '㣣' => '㣣', + '徚' => '徚', + '忍' => '忍', + '志' => '志', + '忹' => '忹', + '悁' => '悁', + '㤺' => '㤺', + '㤜' => '㤜', + '悔' => '悔', + '𢛔' => '𢛔', + '惇' => '惇', + '慈' => '慈', + '慌' => '慌', + '慎' => '慎', + '慌' => '慌', + '慺' => '慺', + '憎' => '憎', + '憲' => '憲', + '憤' => '憤', + '憯' => '憯', + '懞' => '懞', + '懲' => '懲', + '懶' => '懶', + '成' => '成', + '戛' => '戛', + '扝' => '扝', + '抱' => '抱', + '拔' => '拔', + '捐' => '捐', + '𢬌' => '𢬌', + '挽' => '挽', + '拼' => '拼', + '捨' => '捨', + '掃' => '掃', + '揤' => '揤', + '𢯱' => '𢯱', + '搢' => '搢', + '揅' => '揅', + '掩' => '掩', + '㨮' => '㨮', + '摩' => '摩', + '摾' => '摾', + '撝' => '撝', + '摷' => '摷', + '㩬' => '㩬', + '敏' => '敏', + '敬' => '敬', + '𣀊' => '𣀊', + '旣' => '旣', + '書' => '書', + '晉' => '晉', + '㬙' => '㬙', + '暑' => '暑', + '㬈' => '㬈', + '㫤' => '㫤', + '冒' => '冒', + '冕' => '冕', + '最' => '最', + '暜' => '暜', + '肭' => '肭', + '䏙' => '䏙', + '朗' => '朗', + '望' => '望', + '朡' => '朡', + '杞' => '杞', + '杓' => '杓', + '𣏃' => '𣏃', + '㭉' => '㭉', + '柺' => '柺', + '枅' => '枅', + '桒' => '桒', + '梅' => '梅', + '𣑭' => '𣑭', + '梎' => '梎', + '栟' => '栟', + '椔' => '椔', + '㮝' => '㮝', + '楂' => '楂', + '榣' => '榣', + '槪' => '槪', + '檨' => '檨', + '𣚣' => '𣚣', + '櫛' => '櫛', + '㰘' => '㰘', + '次' => '次', + '𣢧' => '𣢧', + '歔' => '歔', + '㱎' => '㱎', + '歲' => '歲', + '殟' => '殟', + '殺' => '殺', + '殻' => '殻', + '𣪍' => '𣪍', + '𡴋' => '𡴋', + '𣫺' => '𣫺', + '汎' => '汎', + '𣲼' => '𣲼', + '沿' => '沿', + '泍' => '泍', + '汧' => '汧', + '洖' => '洖', + '派' => '派', + '海' => '海', + '流' => '流', + '浩' => '浩', + '浸' => '浸', + '涅' => '涅', + '𣴞' => '𣴞', + '洴' => '洴', + '港' => '港', + '湮' => '湮', + '㴳' => '㴳', + '滋' => '滋', + '滇' => '滇', + '𣻑' => '𣻑', + '淹' => '淹', + '潮' => '潮', + '𣽞' => '𣽞', + '𣾎' => '𣾎', + '濆' => '濆', + '瀹' => '瀹', + '瀞' => '瀞', + '瀛' => '瀛', + '㶖' => '㶖', + '灊' => '灊', + '災' => '災', + '灷' => '灷', + '炭' => '炭', + '𠔥' => '𠔥', + '煅' => '煅', + '𤉣' => '𤉣', + '熜' => '熜', + '𤎫' => '𤎫', + '爨' => '爨', + '爵' => '爵', + '牐' => '牐', + '𤘈' => '𤘈', + '犀' => '犀', + '犕' => '犕', + '𤜵' => '𤜵', + '𤠔' => '𤠔', + '獺' => '獺', + '王' => '王', + '㺬' => '㺬', + '玥' => '玥', + '㺸' => '㺸', + '㺸' => '㺸', + '瑇' => '瑇', + '瑜' => '瑜', + '瑱' => '瑱', + '璅' => '璅', + '瓊' => '瓊', + '㼛' => '㼛', + '甤' => '甤', + '𤰶' => '𤰶', + '甾' => '甾', + '𤲒' => '𤲒', + '異' => '異', + '𢆟' => '𢆟', + '瘐' => '瘐', + '𤾡' => '𤾡', + '𤾸' => '𤾸', + '𥁄' => '𥁄', + '㿼' => '㿼', + '䀈' => '䀈', + '直' => '直', + '𥃳' => '𥃳', + '𥃲' => '𥃲', + '𥄙' => '𥄙', + '𥄳' => '𥄳', + '眞' => '眞', + '真' => '真', + '真' => '真', + '睊' => '睊', + '䀹' => '䀹', + '瞋' => '瞋', + '䁆' => '䁆', + '䂖' => '䂖', + '𥐝' => '𥐝', + '硎' => '硎', + '碌' => '碌', + '磌' => '磌', + '䃣' => '䃣', + '𥘦' => '𥘦', + '祖' => '祖', + '𥚚' => '𥚚', + '𥛅' => '𥛅', + '福' => '福', + '秫' => '秫', + '䄯' => '䄯', + '穀' => '穀', + '穊' => '穊', + '穏' => '穏', + '𥥼' => '𥥼', + '𥪧' => '𥪧', + '𥪧' => '𥪧', + '竮' => '竮', + '䈂' => '䈂', + '𥮫' => '𥮫', + '篆' => '篆', + '築' => '築', + '䈧' => '䈧', + '𥲀' => '𥲀', + '糒' => '糒', + '䊠' => '䊠', + '糨' => '糨', + '糣' => '糣', + '紀' => '紀', + '𥾆' => '𥾆', + '絣' => '絣', + '䌁' => '䌁', + '緇' => '緇', + '縂' => '縂', + '繅' => '繅', + '䌴' => '䌴', + '𦈨' => '𦈨', + '𦉇' => '𦉇', + '䍙' => '䍙', + '𦋙' => '𦋙', + '罺' => '罺', + '𦌾' => '𦌾', + '羕' => '羕', + '翺' => '翺', + '者' => '者', + '𦓚' => '𦓚', + '𦔣' => '𦔣', + '聠' => '聠', + '𦖨' => '𦖨', + '聰' => '聰', + '𣍟' => '𣍟', + '䏕' => '䏕', + '育' => '育', + '脃' => '脃', + '䐋' => '䐋', + '脾' => '脾', + '媵' => '媵', + '𦞧' => '𦞧', + '𦞵' => '𦞵', + '𣎓' => '𣎓', + '𣎜' => '𣎜', + '舁' => '舁', + '舄' => '舄', + '辞' => '辞', + '䑫' => '䑫', + '芑' => '芑', + '芋' => '芋', + '芝' => '芝', + '劳' => '劳', + '花' => '花', + '芳' => '芳', + '芽' => '芽', + '苦' => '苦', + '𦬼' => '𦬼', + '若' => '若', + '茝' => '茝', + '荣' => '荣', + '莭' => '莭', + '茣' => '茣', + '莽' => '莽', + '菧' => '菧', + '著' => '著', + '荓' => '荓', + '菊' => '菊', + '菌' => '菌', + '菜' => '菜', + '𦰶' => '𦰶', + '𦵫' => '𦵫', + '𦳕' => '𦳕', + '䔫' => '䔫', + '蓱' => '蓱', + '蓳' => '蓳', + '蔖' => '蔖', + '𧏊' => '𧏊', + '蕤' => '蕤', + '𦼬' => '𦼬', + '䕝' => '䕝', + '䕡' => '䕡', + '𦾱' => '𦾱', + '𧃒' => '𧃒', + '䕫' => '䕫', + '虐' => '虐', + '虜' => '虜', + '虧' => '虧', + '虩' => '虩', + '蚩' => '蚩', + '蚈' => '蚈', + '蜎' => '蜎', + '蛢' => '蛢', + '蝹' => '蝹', + '蜨' => '蜨', + '蝫' => '蝫', + '螆' => '螆', + '䗗' => '䗗', + '蟡' => '蟡', + '蠁' => '蠁', + '䗹' => '䗹', + '衠' => '衠', + '衣' => '衣', + '𧙧' => '𧙧', + '裗' => '裗', + '裞' => '裞', + '䘵' => '䘵', + '裺' => '裺', + '㒻' => '㒻', + '𧢮' => '𧢮', + '𧥦' => '𧥦', + '䚾' => '䚾', + '䛇' => '䛇', + '誠' => '誠', + '諭' => '諭', + '變' => '變', + '豕' => '豕', + '𧲨' => '𧲨', + '貫' => '貫', + '賁' => '賁', + '贛' => '贛', + '起' => '起', + '𧼯' => '𧼯', + '𠠄' => '𠠄', + '跋' => '跋', + '趼' => '趼', + '跰' => '跰', + '𠣞' => '𠣞', + '軔' => '軔', + '輸' => '輸', + '𨗒' => '𨗒', + '𨗭' => '𨗭', + '邔' => '邔', + '郱' => '郱', + '鄑' => '鄑', + '𨜮' => '𨜮', + '鄛' => '鄛', + '鈸' => '鈸', + '鋗' => '鋗', + '鋘' => '鋘', + '鉼' => '鉼', + '鏹' => '鏹', + '鐕' => '鐕', + '𨯺' => '𨯺', + '開' => '開', + '䦕' => '䦕', + '閷' => '閷', + '𨵷' => '𨵷', + '䧦' => '䧦', + '雃' => '雃', + '嶲' => '嶲', + '霣' => '霣', + '𩅅' => '𩅅', + '𩈚' => '𩈚', + '䩮' => '䩮', + '䩶' => '䩶', + '韠' => '韠', + '𩐊' => '𩐊', + '䪲' => '䪲', + '𩒖' => '𩒖', + '頋' => '頋', + '頋' => '頋', + '頩' => '頩', + '𩖶' => '𩖶', + '飢' => '飢', + '䬳' => '䬳', + '餩' => '餩', + '馧' => '馧', + '駂' => '駂', + '駾' => '駾', + '䯎' => '䯎', + '𩬰' => '𩬰', + '鬒' => '鬒', + '鱀' => '鱀', + '鳽' => '鳽', + '䳎' => '䳎', + '䳭' => '䳭', + '鵧' => '鵧', + '𪃎' => '𪃎', + '䳸' => '䳸', + '𪄅' => '𪄅', + '𪈎' => '𪈎', + '𪊑' => '𪊑', + '麻' => '麻', + '䵖' => '䵖', + '黹' => '黹', + '黾' => '黾', + '鼅' => '鼅', + '鼏' => '鼏', + '鼖' => '鼖', + '鼻' => '鼻', + '𪘀' => '𪘀', +); diff --git a/vendor/symfony/polyfill-intl-normalizer/Resources/unidata/combiningClass.php b/vendor/symfony/polyfill-intl-normalizer/Resources/unidata/combiningClass.php new file mode 100644 index 0000000..ec90f36 --- /dev/null +++ b/vendor/symfony/polyfill-intl-normalizer/Resources/unidata/combiningClass.php @@ -0,0 +1,876 @@ + 230, + '́' => 230, + '̂' => 230, + '̃' => 230, + '̄' => 230, + '̅' => 230, + '̆' => 230, + '̇' => 230, + '̈' => 230, + '̉' => 230, + '̊' => 230, + '̋' => 230, + '̌' => 230, + '̍' => 230, + '̎' => 230, + '̏' => 230, + '̐' => 230, + '̑' => 230, + '̒' => 230, + '̓' => 230, + '̔' => 230, + '̕' => 232, + '̖' => 220, + '̗' => 220, + '̘' => 220, + '̙' => 220, + '̚' => 232, + '̛' => 216, + '̜' => 220, + '̝' => 220, + '̞' => 220, + '̟' => 220, + '̠' => 220, + '̡' => 202, + '̢' => 202, + '̣' => 220, + '̤' => 220, + '̥' => 220, + '̦' => 220, + '̧' => 202, + '̨' => 202, + '̩' => 220, + '̪' => 220, + '̫' => 220, + '̬' => 220, + '̭' => 220, + '̮' => 220, + '̯' => 220, + '̰' => 220, + '̱' => 220, + '̲' => 220, + '̳' => 220, + '̴' => 1, + '̵' => 1, + '̶' => 1, + '̷' => 1, + '̸' => 1, + '̹' => 220, + '̺' => 220, + '̻' => 220, + '̼' => 220, + '̽' => 230, + '̾' => 230, + '̿' => 230, + '̀' => 230, + '́' => 230, + '͂' => 230, + '̓' => 230, + '̈́' => 230, + 'ͅ' => 240, + '͆' => 230, + '͇' => 220, + '͈' => 220, + '͉' => 220, + '͊' => 230, + '͋' => 230, + '͌' => 230, + '͍' => 220, + '͎' => 220, + '͐' => 230, + '͑' => 230, + '͒' => 230, + '͓' => 220, + '͔' => 220, + '͕' => 220, + '͖' => 220, + '͗' => 230, + '͘' => 232, + '͙' => 220, + '͚' => 220, + '͛' => 230, + '͜' => 233, + '͝' => 234, + '͞' => 234, + '͟' => 233, + '͠' => 234, + '͡' => 234, + '͢' => 233, + 'ͣ' => 230, + 'ͤ' => 230, + 'ͥ' => 230, + 'ͦ' => 230, + 'ͧ' => 230, + 'ͨ' => 230, + 'ͩ' => 230, + 'ͪ' => 230, + 'ͫ' => 230, + 'ͬ' => 230, + 'ͭ' => 230, + 'ͮ' => 230, + 'ͯ' => 230, + '҃' => 230, + '҄' => 230, + '҅' => 230, + '҆' => 230, + '҇' => 230, + '֑' => 220, + '֒' => 230, + '֓' => 230, + '֔' => 230, + '֕' => 230, + '֖' => 220, + '֗' => 230, + '֘' => 230, + '֙' => 230, + '֚' => 222, + '֛' => 220, + '֜' => 230, + '֝' => 230, + '֞' => 230, + '֟' => 230, + '֠' => 230, + '֡' => 230, + '֢' => 220, + '֣' => 220, + '֤' => 220, + '֥' => 220, + '֦' => 220, + '֧' => 220, + '֨' => 230, + '֩' => 230, + '֪' => 220, + '֫' => 230, + '֬' => 230, + '֭' => 222, + '֮' => 228, + '֯' => 230, + 'ְ' => 10, + 'ֱ' => 11, + 'ֲ' => 12, + 'ֳ' => 13, + 'ִ' => 14, + 'ֵ' => 15, + 'ֶ' => 16, + 'ַ' => 17, + 'ָ' => 18, + 'ֹ' => 19, + 'ֺ' => 19, + 'ֻ' => 20, + 'ּ' => 21, + 'ֽ' => 22, + 'ֿ' => 23, + 'ׁ' => 24, + 'ׂ' => 25, + 'ׄ' => 230, + 'ׅ' => 220, + 'ׇ' => 18, + 'ؐ' => 230, + 'ؑ' => 230, + 'ؒ' => 230, + 'ؓ' => 230, + 'ؔ' => 230, + 'ؕ' => 230, + 'ؖ' => 230, + 'ؗ' => 230, + 'ؘ' => 30, + 'ؙ' => 31, + 'ؚ' => 32, + 'ً' => 27, + 'ٌ' => 28, + 'ٍ' => 29, + 'َ' => 30, + 'ُ' => 31, + 'ِ' => 32, + 'ّ' => 33, + 'ْ' => 34, + 'ٓ' => 230, + 'ٔ' => 230, + 'ٕ' => 220, + 'ٖ' => 220, + 'ٗ' => 230, + '٘' => 230, + 'ٙ' => 230, + 'ٚ' => 230, + 'ٛ' => 230, + 'ٜ' => 220, + 'ٝ' => 230, + 'ٞ' => 230, + 'ٟ' => 220, + 'ٰ' => 35, + 'ۖ' => 230, + 'ۗ' => 230, + 'ۘ' => 230, + 'ۙ' => 230, + 'ۚ' => 230, + 'ۛ' => 230, + 'ۜ' => 230, + '۟' => 230, + '۠' => 230, + 'ۡ' => 230, + 'ۢ' => 230, + 'ۣ' => 220, + 'ۤ' => 230, + 'ۧ' => 230, + 'ۨ' => 230, + '۪' => 220, + '۫' => 230, + '۬' => 230, + 'ۭ' => 220, + 'ܑ' => 36, + 'ܰ' => 230, + 'ܱ' => 220, + 'ܲ' => 230, + 'ܳ' => 230, + 'ܴ' => 220, + 'ܵ' => 230, + 'ܶ' => 230, + 'ܷ' => 220, + 'ܸ' => 220, + 'ܹ' => 220, + 'ܺ' => 230, + 'ܻ' => 220, + 'ܼ' => 220, + 'ܽ' => 230, + 'ܾ' => 220, + 'ܿ' => 230, + '݀' => 230, + '݁' => 230, + '݂' => 220, + '݃' => 230, + '݄' => 220, + '݅' => 230, + '݆' => 220, + '݇' => 230, + '݈' => 220, + '݉' => 230, + '݊' => 230, + '߫' => 230, + '߬' => 230, + '߭' => 230, + '߮' => 230, + '߯' => 230, + '߰' => 230, + '߱' => 230, + '߲' => 220, + '߳' => 230, + '߽' => 220, + 'ࠖ' => 230, + 'ࠗ' => 230, + '࠘' => 230, + '࠙' => 230, + 'ࠛ' => 230, + 'ࠜ' => 230, + 'ࠝ' => 230, + 'ࠞ' => 230, + 'ࠟ' => 230, + 'ࠠ' => 230, + 'ࠡ' => 230, + 'ࠢ' => 230, + 'ࠣ' => 230, + 'ࠥ' => 230, + 'ࠦ' => 230, + 'ࠧ' => 230, + 'ࠩ' => 230, + 'ࠪ' => 230, + 'ࠫ' => 230, + 'ࠬ' => 230, + '࠭' => 230, + '࡙' => 220, + '࡚' => 220, + '࡛' => 220, + '࣓' => 220, + 'ࣔ' => 230, + 'ࣕ' => 230, + 'ࣖ' => 230, + 'ࣗ' => 230, + 'ࣘ' => 230, + 'ࣙ' => 230, + 'ࣚ' => 230, + 'ࣛ' => 230, + 'ࣜ' => 230, + 'ࣝ' => 230, + 'ࣞ' => 230, + 'ࣟ' => 230, + '࣠' => 230, + '࣡' => 230, + 'ࣣ' => 220, + 'ࣤ' => 230, + 'ࣥ' => 230, + 'ࣦ' => 220, + 'ࣧ' => 230, + 'ࣨ' => 230, + 'ࣩ' => 220, + '࣪' => 230, + '࣫' => 230, + '࣬' => 230, + '࣭' => 220, + '࣮' => 220, + '࣯' => 220, + 'ࣰ' => 27, + 'ࣱ' => 28, + 'ࣲ' => 29, + 'ࣳ' => 230, + 'ࣴ' => 230, + 'ࣵ' => 230, + 'ࣶ' => 220, + 'ࣷ' => 230, + 'ࣸ' => 230, + 'ࣹ' => 220, + 'ࣺ' => 220, + 'ࣻ' => 230, + 'ࣼ' => 230, + 'ࣽ' => 230, + 'ࣾ' => 230, + 'ࣿ' => 230, + '़' => 7, + '्' => 9, + '॑' => 230, + '॒' => 220, + '॓' => 230, + '॔' => 230, + '়' => 7, + '্' => 9, + '৾' => 230, + '਼' => 7, + '੍' => 9, + '઼' => 7, + '્' => 9, + '଼' => 7, + '୍' => 9, + '்' => 9, + '్' => 9, + 'ౕ' => 84, + 'ౖ' => 91, + '಼' => 7, + '್' => 9, + '഻' => 9, + '഼' => 9, + '്' => 9, + '්' => 9, + 'ุ' => 103, + 'ู' => 103, + 'ฺ' => 9, + '่' => 107, + '้' => 107, + '๊' => 107, + '๋' => 107, + 'ຸ' => 118, + 'ູ' => 118, + '຺' => 9, + '່' => 122, + '້' => 122, + '໊' => 122, + '໋' => 122, + '༘' => 220, + '༙' => 220, + '༵' => 220, + '༷' => 220, + '༹' => 216, + 'ཱ' => 129, + 'ི' => 130, + 'ུ' => 132, + 'ེ' => 130, + 'ཻ' => 130, + 'ོ' => 130, + 'ཽ' => 130, + 'ྀ' => 130, + 'ྂ' => 230, + 'ྃ' => 230, + '྄' => 9, + '྆' => 230, + '྇' => 230, + '࿆' => 220, + '့' => 7, + '္' => 9, + '်' => 9, + 'ႍ' => 220, + '፝' => 230, + '፞' => 230, + '፟' => 230, + '᜔' => 9, + '᜴' => 9, + '្' => 9, + '៝' => 230, + 'ᢩ' => 228, + '᤹' => 222, + '᤺' => 230, + '᤻' => 220, + 'ᨗ' => 230, + 'ᨘ' => 220, + '᩠' => 9, + '᩵' => 230, + '᩶' => 230, + '᩷' => 230, + '᩸' => 230, + '᩹' => 230, + '᩺' => 230, + '᩻' => 230, + '᩼' => 230, + '᩿' => 220, + '᪰' => 230, + '᪱' => 230, + '᪲' => 230, + '᪳' => 230, + '᪴' => 230, + '᪵' => 220, + '᪶' => 220, + '᪷' => 220, + '᪸' => 220, + '᪹' => 220, + '᪺' => 220, + '᪻' => 230, + '᪼' => 230, + '᪽' => 220, + 'ᪿ' => 220, + 'ᫀ' => 220, + '᬴' => 7, + '᭄' => 9, + '᭫' => 230, + '᭬' => 220, + '᭭' => 230, + '᭮' => 230, + '᭯' => 230, + '᭰' => 230, + '᭱' => 230, + '᭲' => 230, + '᭳' => 230, + '᮪' => 9, + '᮫' => 9, + '᯦' => 7, + '᯲' => 9, + '᯳' => 9, + '᰷' => 7, + '᳐' => 230, + '᳑' => 230, + '᳒' => 230, + '᳔' => 1, + '᳕' => 220, + '᳖' => 220, + '᳗' => 220, + '᳘' => 220, + '᳙' => 220, + '᳚' => 230, + '᳛' => 230, + '᳜' => 220, + '᳝' => 220, + '᳞' => 220, + '᳟' => 220, + '᳠' => 230, + '᳢' => 1, + '᳣' => 1, + '᳤' => 1, + '᳥' => 1, + '᳦' => 1, + '᳧' => 1, + '᳨' => 1, + '᳭' => 220, + '᳴' => 230, + '᳸' => 230, + '᳹' => 230, + '᷀' => 230, + '᷁' => 230, + '᷂' => 220, + '᷃' => 230, + '᷄' => 230, + '᷅' => 230, + '᷆' => 230, + '᷇' => 230, + '᷈' => 230, + '᷉' => 230, + '᷊' => 220, + '᷋' => 230, + '᷌' => 230, + '᷍' => 234, + '᷎' => 214, + '᷏' => 220, + '᷐' => 202, + '᷑' => 230, + '᷒' => 230, + 'ᷓ' => 230, + 'ᷔ' => 230, + 'ᷕ' => 230, + 'ᷖ' => 230, + 'ᷗ' => 230, + 'ᷘ' => 230, + 'ᷙ' => 230, + 'ᷚ' => 230, + 'ᷛ' => 230, + 'ᷜ' => 230, + 'ᷝ' => 230, + 'ᷞ' => 230, + 'ᷟ' => 230, + 'ᷠ' => 230, + 'ᷡ' => 230, + 'ᷢ' => 230, + 'ᷣ' => 230, + 'ᷤ' => 230, + 'ᷥ' => 230, + 'ᷦ' => 230, + 'ᷧ' => 230, + 'ᷨ' => 230, + 'ᷩ' => 230, + 'ᷪ' => 230, + 'ᷫ' => 230, + 'ᷬ' => 230, + 'ᷭ' => 230, + 'ᷮ' => 230, + 'ᷯ' => 230, + 'ᷰ' => 230, + 'ᷱ' => 230, + 'ᷲ' => 230, + 'ᷳ' => 230, + 'ᷴ' => 230, + '᷵' => 230, + '᷶' => 232, + '᷷' => 228, + '᷸' => 228, + '᷹' => 220, + '᷻' => 230, + '᷼' => 233, + '᷽' => 220, + '᷾' => 230, + '᷿' => 220, + '⃐' => 230, + '⃑' => 230, + '⃒' => 1, + '⃓' => 1, + '⃔' => 230, + '⃕' => 230, + '⃖' => 230, + '⃗' => 230, + '⃘' => 1, + '⃙' => 1, + '⃚' => 1, + '⃛' => 230, + '⃜' => 230, + '⃡' => 230, + '⃥' => 1, + '⃦' => 1, + '⃧' => 230, + '⃨' => 220, + '⃩' => 230, + '⃪' => 1, + '⃫' => 1, + '⃬' => 220, + '⃭' => 220, + '⃮' => 220, + '⃯' => 220, + '⃰' => 230, + '⳯' => 230, + '⳰' => 230, + '⳱' => 230, + '⵿' => 9, + 'ⷠ' => 230, + 'ⷡ' => 230, + 'ⷢ' => 230, + 'ⷣ' => 230, + 'ⷤ' => 230, + 'ⷥ' => 230, + 'ⷦ' => 230, + 'ⷧ' => 230, + 'ⷨ' => 230, + 'ⷩ' => 230, + 'ⷪ' => 230, + 'ⷫ' => 230, + 'ⷬ' => 230, + 'ⷭ' => 230, + 'ⷮ' => 230, + 'ⷯ' => 230, + 'ⷰ' => 230, + 'ⷱ' => 230, + 'ⷲ' => 230, + 'ⷳ' => 230, + 'ⷴ' => 230, + 'ⷵ' => 230, + 'ⷶ' => 230, + 'ⷷ' => 230, + 'ⷸ' => 230, + 'ⷹ' => 230, + 'ⷺ' => 230, + 'ⷻ' => 230, + 'ⷼ' => 230, + 'ⷽ' => 230, + 'ⷾ' => 230, + 'ⷿ' => 230, + '〪' => 218, + '〫' => 228, + '〬' => 232, + '〭' => 222, + '〮' => 224, + '〯' => 224, + '゙' => 8, + '゚' => 8, + '꙯' => 230, + 'ꙴ' => 230, + 'ꙵ' => 230, + 'ꙶ' => 230, + 'ꙷ' => 230, + 'ꙸ' => 230, + 'ꙹ' => 230, + 'ꙺ' => 230, + 'ꙻ' => 230, + '꙼' => 230, + '꙽' => 230, + 'ꚞ' => 230, + 'ꚟ' => 230, + '꛰' => 230, + '꛱' => 230, + '꠆' => 9, + '꠬' => 9, + '꣄' => 9, + '꣠' => 230, + '꣡' => 230, + '꣢' => 230, + '꣣' => 230, + '꣤' => 230, + '꣥' => 230, + '꣦' => 230, + '꣧' => 230, + '꣨' => 230, + '꣩' => 230, + '꣪' => 230, + '꣫' => 230, + '꣬' => 230, + '꣭' => 230, + '꣮' => 230, + '꣯' => 230, + '꣰' => 230, + '꣱' => 230, + '꤫' => 220, + '꤬' => 220, + '꤭' => 220, + '꥓' => 9, + '꦳' => 7, + '꧀' => 9, + 'ꪰ' => 230, + 'ꪲ' => 230, + 'ꪳ' => 230, + 'ꪴ' => 220, + 'ꪷ' => 230, + 'ꪸ' => 230, + 'ꪾ' => 230, + '꪿' => 230, + '꫁' => 230, + '꫶' => 9, + '꯭' => 9, + 'ﬞ' => 26, + '︠' => 230, + '︡' => 230, + '︢' => 230, + '︣' => 230, + '︤' => 230, + '︥' => 230, + '︦' => 230, + '︧' => 220, + '︨' => 220, + '︩' => 220, + '︪' => 220, + '︫' => 220, + '︬' => 220, + '︭' => 220, + '︮' => 230, + '︯' => 230, + '𐇽' => 220, + '𐋠' => 220, + '𐍶' => 230, + '𐍷' => 230, + '𐍸' => 230, + '𐍹' => 230, + '𐍺' => 230, + '𐨍' => 220, + '𐨏' => 230, + '𐨸' => 230, + '𐨹' => 1, + '𐨺' => 220, + '𐨿' => 9, + '𐫥' => 230, + '𐫦' => 220, + '𐴤' => 230, + '𐴥' => 230, + '𐴦' => 230, + '𐴧' => 230, + '𐺫' => 230, + '𐺬' => 230, + '𐽆' => 220, + '𐽇' => 220, + '𐽈' => 230, + '𐽉' => 230, + '𐽊' => 230, + '𐽋' => 220, + '𐽌' => 230, + '𐽍' => 220, + '𐽎' => 220, + '𐽏' => 220, + '𐽐' => 220, + '𑁆' => 9, + '𑁿' => 9, + '𑂹' => 9, + '𑂺' => 7, + '𑄀' => 230, + '𑄁' => 230, + '𑄂' => 230, + '𑄳' => 9, + '𑄴' => 9, + '𑅳' => 7, + '𑇀' => 9, + '𑇊' => 7, + '𑈵' => 9, + '𑈶' => 7, + '𑋩' => 7, + '𑋪' => 9, + '𑌻' => 7, + '𑌼' => 7, + '𑍍' => 9, + '𑍦' => 230, + '𑍧' => 230, + '𑍨' => 230, + '𑍩' => 230, + '𑍪' => 230, + '𑍫' => 230, + '𑍬' => 230, + '𑍰' => 230, + '𑍱' => 230, + '𑍲' => 230, + '𑍳' => 230, + '𑍴' => 230, + '𑑂' => 9, + '𑑆' => 7, + '𑑞' => 230, + '𑓂' => 9, + '𑓃' => 7, + '𑖿' => 9, + '𑗀' => 7, + '𑘿' => 9, + '𑚶' => 9, + '𑚷' => 7, + '𑜫' => 9, + '𑠹' => 9, + '𑠺' => 7, + '𑤽' => 9, + '𑤾' => 9, + '𑥃' => 7, + '𑧠' => 9, + '𑨴' => 9, + '𑩇' => 9, + '𑪙' => 9, + '𑰿' => 9, + '𑵂' => 7, + '𑵄' => 9, + '𑵅' => 9, + '𑶗' => 9, + '𖫰' => 1, + '𖫱' => 1, + '𖫲' => 1, + '𖫳' => 1, + '𖫴' => 1, + '𖬰' => 230, + '𖬱' => 230, + '𖬲' => 230, + '𖬳' => 230, + '𖬴' => 230, + '𖬵' => 230, + '𖬶' => 230, + '𖿰' => 6, + '𖿱' => 6, + '𛲞' => 1, + '𝅥' => 216, + '𝅦' => 216, + '𝅧' => 1, + '𝅨' => 1, + '𝅩' => 1, + '𝅭' => 226, + '𝅮' => 216, + '𝅯' => 216, + '𝅰' => 216, + '𝅱' => 216, + '𝅲' => 216, + '𝅻' => 220, + '𝅼' => 220, + '𝅽' => 220, + '𝅾' => 220, + '𝅿' => 220, + '𝆀' => 220, + '𝆁' => 220, + '𝆂' => 220, + '𝆅' => 230, + '𝆆' => 230, + '𝆇' => 230, + '𝆈' => 230, + '𝆉' => 230, + '𝆊' => 220, + '𝆋' => 220, + '𝆪' => 230, + '𝆫' => 230, + '𝆬' => 230, + '𝆭' => 230, + '𝉂' => 230, + '𝉃' => 230, + '𝉄' => 230, + '𞀀' => 230, + '𞀁' => 230, + '𞀂' => 230, + '𞀃' => 230, + '𞀄' => 230, + '𞀅' => 230, + '𞀆' => 230, + '𞀈' => 230, + '𞀉' => 230, + '𞀊' => 230, + '𞀋' => 230, + '𞀌' => 230, + '𞀍' => 230, + '𞀎' => 230, + '𞀏' => 230, + '𞀐' => 230, + '𞀑' => 230, + '𞀒' => 230, + '𞀓' => 230, + '𞀔' => 230, + '𞀕' => 230, + '𞀖' => 230, + '𞀗' => 230, + '𞀘' => 230, + '𞀛' => 230, + '𞀜' => 230, + '𞀝' => 230, + '𞀞' => 230, + '𞀟' => 230, + '𞀠' => 230, + '𞀡' => 230, + '𞀣' => 230, + '𞀤' => 230, + '𞀦' => 230, + '𞀧' => 230, + '𞀨' => 230, + '𞀩' => 230, + '𞀪' => 230, + '𞄰' => 230, + '𞄱' => 230, + '𞄲' => 230, + '𞄳' => 230, + '𞄴' => 230, + '𞄵' => 230, + '𞄶' => 230, + '𞋬' => 230, + '𞋭' => 230, + '𞋮' => 230, + '𞋯' => 230, + '𞣐' => 220, + '𞣑' => 220, + '𞣒' => 220, + '𞣓' => 220, + '𞣔' => 220, + '𞣕' => 220, + '𞣖' => 220, + '𞥄' => 230, + '𞥅' => 230, + '𞥆' => 230, + '𞥇' => 230, + '𞥈' => 230, + '𞥉' => 230, + '𞥊' => 7, +); diff --git a/vendor/symfony/polyfill-intl-normalizer/Resources/unidata/compatibilityDecomposition.php b/vendor/symfony/polyfill-intl-normalizer/Resources/unidata/compatibilityDecomposition.php new file mode 100644 index 0000000..1574902 --- /dev/null +++ b/vendor/symfony/polyfill-intl-normalizer/Resources/unidata/compatibilityDecomposition.php @@ -0,0 +1,3695 @@ + ' ', + '¨' => ' ̈', + 'ª' => 'a', + '¯' => ' ̄', + '²' => '2', + '³' => '3', + '´' => ' ́', + 'µ' => 'μ', + '¸' => ' ̧', + '¹' => '1', + 'º' => 'o', + '¼' => '1⁄4', + '½' => '1⁄2', + '¾' => '3⁄4', + 'IJ' => 'IJ', + 'ij' => 'ij', + 'Ŀ' => 'L·', + 'ŀ' => 'l·', + 'ʼn' => 'ʼn', + 'ſ' => 's', + 'DŽ' => 'DŽ', + 'Dž' => 'Dž', + 'dž' => 'dž', + 'LJ' => 'LJ', + 'Lj' => 'Lj', + 'lj' => 'lj', + 'NJ' => 'NJ', + 'Nj' => 'Nj', + 'nj' => 'nj', + 'DZ' => 'DZ', + 'Dz' => 'Dz', + 'dz' => 'dz', + 'ʰ' => 'h', + 'ʱ' => 'ɦ', + 'ʲ' => 'j', + 'ʳ' => 'r', + 'ʴ' => 'ɹ', + 'ʵ' => 'ɻ', + 'ʶ' => 'ʁ', + 'ʷ' => 'w', + 'ʸ' => 'y', + '˘' => ' ̆', + '˙' => ' ̇', + '˚' => ' ̊', + '˛' => ' ̨', + '˜' => ' ̃', + '˝' => ' ̋', + 'ˠ' => 'ɣ', + 'ˡ' => 'l', + 'ˢ' => 's', + 'ˣ' => 'x', + 'ˤ' => 'ʕ', + 'ͺ' => ' ͅ', + '΄' => ' ́', + '΅' => ' ̈́', + 'ϐ' => 'β', + 'ϑ' => 'θ', + 'ϒ' => 'Υ', + 'ϓ' => 'Ύ', + 'ϔ' => 'Ϋ', + 'ϕ' => 'φ', + 'ϖ' => 'π', + 'ϰ' => 'κ', + 'ϱ' => 'ρ', + 'ϲ' => 'ς', + 'ϴ' => 'Θ', + 'ϵ' => 'ε', + 'Ϲ' => 'Σ', + 'և' => 'եւ', + 'ٵ' => 'اٴ', + 'ٶ' => 'وٴ', + 'ٷ' => 'ۇٴ', + 'ٸ' => 'يٴ', + 'ำ' => 'ํา', + 'ຳ' => 'ໍາ', + 'ໜ' => 'ຫນ', + 'ໝ' => 'ຫມ', + '༌' => '་', + 'ཷ' => 'ྲཱྀ', + 'ཹ' => 'ླཱྀ', + 'ჼ' => 'ნ', + 'ᴬ' => 'A', + 'ᴭ' => 'Æ', + 'ᴮ' => 'B', + 'ᴰ' => 'D', + 'ᴱ' => 'E', + 'ᴲ' => 'Ǝ', + 'ᴳ' => 'G', + 'ᴴ' => 'H', + 'ᴵ' => 'I', + 'ᴶ' => 'J', + 'ᴷ' => 'K', + 'ᴸ' => 'L', + 'ᴹ' => 'M', + 'ᴺ' => 'N', + 'ᴼ' => 'O', + 'ᴽ' => 'Ȣ', + 'ᴾ' => 'P', + 'ᴿ' => 'R', + 'ᵀ' => 'T', + 'ᵁ' => 'U', + 'ᵂ' => 'W', + 'ᵃ' => 'a', + 'ᵄ' => 'ɐ', + 'ᵅ' => 'ɑ', + 'ᵆ' => 'ᴂ', + 'ᵇ' => 'b', + 'ᵈ' => 'd', + 'ᵉ' => 'e', + 'ᵊ' => 'ə', + 'ᵋ' => 'ɛ', + 'ᵌ' => 'ɜ', + 'ᵍ' => 'g', + 'ᵏ' => 'k', + 'ᵐ' => 'm', + 'ᵑ' => 'ŋ', + 'ᵒ' => 'o', + 'ᵓ' => 'ɔ', + 'ᵔ' => 'ᴖ', + 'ᵕ' => 'ᴗ', + 'ᵖ' => 'p', + 'ᵗ' => 't', + 'ᵘ' => 'u', + 'ᵙ' => 'ᴝ', + 'ᵚ' => 'ɯ', + 'ᵛ' => 'v', + 'ᵜ' => 'ᴥ', + 'ᵝ' => 'β', + 'ᵞ' => 'γ', + 'ᵟ' => 'δ', + 'ᵠ' => 'φ', + 'ᵡ' => 'χ', + 'ᵢ' => 'i', + 'ᵣ' => 'r', + 'ᵤ' => 'u', + 'ᵥ' => 'v', + 'ᵦ' => 'β', + 'ᵧ' => 'γ', + 'ᵨ' => 'ρ', + 'ᵩ' => 'φ', + 'ᵪ' => 'χ', + 'ᵸ' => 'н', + 'ᶛ' => 'ɒ', + 'ᶜ' => 'c', + 'ᶝ' => 'ɕ', + 'ᶞ' => 'ð', + 'ᶟ' => 'ɜ', + 'ᶠ' => 'f', + 'ᶡ' => 'ɟ', + 'ᶢ' => 'ɡ', + 'ᶣ' => 'ɥ', + 'ᶤ' => 'ɨ', + 'ᶥ' => 'ɩ', + 'ᶦ' => 'ɪ', + 'ᶧ' => 'ᵻ', + 'ᶨ' => 'ʝ', + 'ᶩ' => 'ɭ', + 'ᶪ' => 'ᶅ', + 'ᶫ' => 'ʟ', + 'ᶬ' => 'ɱ', + 'ᶭ' => 'ɰ', + 'ᶮ' => 'ɲ', + 'ᶯ' => 'ɳ', + 'ᶰ' => 'ɴ', + 'ᶱ' => 'ɵ', + 'ᶲ' => 'ɸ', + 'ᶳ' => 'ʂ', + 'ᶴ' => 'ʃ', + 'ᶵ' => 'ƫ', + 'ᶶ' => 'ʉ', + 'ᶷ' => 'ʊ', + 'ᶸ' => 'ᴜ', + 'ᶹ' => 'ʋ', + 'ᶺ' => 'ʌ', + 'ᶻ' => 'z', + 'ᶼ' => 'ʐ', + 'ᶽ' => 'ʑ', + 'ᶾ' => 'ʒ', + 'ᶿ' => 'θ', + 'ẚ' => 'aʾ', + 'ẛ' => 'ṡ', + '᾽' => ' ̓', + '᾿' => ' ̓', + '῀' => ' ͂', + '῁' => ' ̈͂', + '῍' => ' ̓̀', + '῎' => ' ̓́', + '῏' => ' ̓͂', + '῝' => ' ̔̀', + '῞' => ' ̔́', + '῟' => ' ̔͂', + '῭' => ' ̈̀', + '΅' => ' ̈́', + '´' => ' ́', + '῾' => ' ̔', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + '‑' => '‐', + '‗' => ' ̳', + '․' => '.', + '‥' => '..', + '…' => '...', + ' ' => ' ', + '″' => '′′', + '‴' => '′′′', + '‶' => '‵‵', + '‷' => '‵‵‵', + '‼' => '!!', + '‾' => ' ̅', + '⁇' => '??', + '⁈' => '?!', + '⁉' => '!?', + '⁗' => '′′′′', + ' ' => ' ', + '⁰' => '0', + 'ⁱ' => 'i', + '⁴' => '4', + '⁵' => '5', + '⁶' => '6', + '⁷' => '7', + '⁸' => '8', + '⁹' => '9', + '⁺' => '+', + '⁻' => '−', + '⁼' => '=', + '⁽' => '(', + '⁾' => ')', + 'ⁿ' => 'n', + '₀' => '0', + '₁' => '1', + '₂' => '2', + '₃' => '3', + '₄' => '4', + '₅' => '5', + '₆' => '6', + '₇' => '7', + '₈' => '8', + '₉' => '9', + '₊' => '+', + '₋' => '−', + '₌' => '=', + '₍' => '(', + '₎' => ')', + 'ₐ' => 'a', + 'ₑ' => 'e', + 'ₒ' => 'o', + 'ₓ' => 'x', + 'ₔ' => 'ə', + 'ₕ' => 'h', + 'ₖ' => 'k', + 'ₗ' => 'l', + 'ₘ' => 'm', + 'ₙ' => 'n', + 'ₚ' => 'p', + 'ₛ' => 's', + 'ₜ' => 't', + '₨' => 'Rs', + '℀' => 'a/c', + '℁' => 'a/s', + 'ℂ' => 'C', + '℃' => '°C', + '℅' => 'c/o', + '℆' => 'c/u', + 'ℇ' => 'Ɛ', + '℉' => '°F', + 'ℊ' => 'g', + 'ℋ' => 'H', + 'ℌ' => 'H', + 'ℍ' => 'H', + 'ℎ' => 'h', + 'ℏ' => 'ħ', + 'ℐ' => 'I', + 'ℑ' => 'I', + 'ℒ' => 'L', + 'ℓ' => 'l', + 'ℕ' => 'N', + '№' => 'No', + 'ℙ' => 'P', + 'ℚ' => 'Q', + 'ℛ' => 'R', + 'ℜ' => 'R', + 'ℝ' => 'R', + '℠' => 'SM', + '℡' => 'TEL', + '™' => 'TM', + 'ℤ' => 'Z', + 'ℨ' => 'Z', + 'ℬ' => 'B', + 'ℭ' => 'C', + 'ℯ' => 'e', + 'ℰ' => 'E', + 'ℱ' => 'F', + 'ℳ' => 'M', + 'ℴ' => 'o', + 'ℵ' => 'א', + 'ℶ' => 'ב', + 'ℷ' => 'ג', + 'ℸ' => 'ד', + 'ℹ' => 'i', + '℻' => 'FAX', + 'ℼ' => 'π', + 'ℽ' => 'γ', + 'ℾ' => 'Γ', + 'ℿ' => 'Π', + '⅀' => '∑', + 'ⅅ' => 'D', + 'ⅆ' => 'd', + 'ⅇ' => 'e', + 'ⅈ' => 'i', + 'ⅉ' => 'j', + '⅐' => '1⁄7', + '⅑' => '1⁄9', + '⅒' => '1⁄10', + '⅓' => '1⁄3', + '⅔' => '2⁄3', + '⅕' => '1⁄5', + '⅖' => '2⁄5', + '⅗' => '3⁄5', + '⅘' => '4⁄5', + '⅙' => '1⁄6', + '⅚' => '5⁄6', + '⅛' => '1⁄8', + '⅜' => '3⁄8', + '⅝' => '5⁄8', + '⅞' => '7⁄8', + '⅟' => '1⁄', + 'Ⅰ' => 'I', + 'Ⅱ' => 'II', + 'Ⅲ' => 'III', + 'Ⅳ' => 'IV', + 'Ⅴ' => 'V', + 'Ⅵ' => 'VI', + 'Ⅶ' => 'VII', + 'Ⅷ' => 'VIII', + 'Ⅸ' => 'IX', + 'Ⅹ' => 'X', + 'Ⅺ' => 'XI', + 'Ⅻ' => 'XII', + 'Ⅼ' => 'L', + 'Ⅽ' => 'C', + 'Ⅾ' => 'D', + 'Ⅿ' => 'M', + 'ⅰ' => 'i', + 'ⅱ' => 'ii', + 'ⅲ' => 'iii', + 'ⅳ' => 'iv', + 'ⅴ' => 'v', + 'ⅵ' => 'vi', + 'ⅶ' => 'vii', + 'ⅷ' => 'viii', + 'ⅸ' => 'ix', + 'ⅹ' => 'x', + 'ⅺ' => 'xi', + 'ⅻ' => 'xii', + 'ⅼ' => 'l', + 'ⅽ' => 'c', + 'ⅾ' => 'd', + 'ⅿ' => 'm', + '↉' => '0⁄3', + '∬' => '∫∫', + '∭' => '∫∫∫', + '∯' => '∮∮', + '∰' => '∮∮∮', + '①' => '1', + '②' => '2', + '③' => '3', + '④' => '4', + '⑤' => '5', + '⑥' => '6', + '⑦' => '7', + '⑧' => '8', + '⑨' => '9', + '⑩' => '10', + '⑪' => '11', + '⑫' => '12', + '⑬' => '13', + '⑭' => '14', + '⑮' => '15', + '⑯' => '16', + '⑰' => '17', + '⑱' => '18', + '⑲' => '19', + '⑳' => '20', + '⑴' => '(1)', + '⑵' => '(2)', + '⑶' => '(3)', + '⑷' => '(4)', + '⑸' => '(5)', + '⑹' => '(6)', + '⑺' => '(7)', + '⑻' => '(8)', + '⑼' => '(9)', + '⑽' => '(10)', + '⑾' => '(11)', + '⑿' => '(12)', + '⒀' => '(13)', + '⒁' => '(14)', + '⒂' => '(15)', + '⒃' => '(16)', + '⒄' => '(17)', + '⒅' => '(18)', + '⒆' => '(19)', + '⒇' => '(20)', + '⒈' => '1.', + '⒉' => '2.', + '⒊' => '3.', + '⒋' => '4.', + '⒌' => '5.', + '⒍' => '6.', + '⒎' => '7.', + '⒏' => '8.', + '⒐' => '9.', + '⒑' => '10.', + '⒒' => '11.', + '⒓' => '12.', + '⒔' => '13.', + '⒕' => '14.', + '⒖' => '15.', + '⒗' => '16.', + '⒘' => '17.', + '⒙' => '18.', + '⒚' => '19.', + '⒛' => '20.', + '⒜' => '(a)', + '⒝' => '(b)', + '⒞' => '(c)', + '⒟' => '(d)', + '⒠' => '(e)', + '⒡' => '(f)', + '⒢' => '(g)', + '⒣' => '(h)', + '⒤' => '(i)', + '⒥' => '(j)', + '⒦' => '(k)', + '⒧' => '(l)', + '⒨' => '(m)', + '⒩' => '(n)', + '⒪' => '(o)', + '⒫' => '(p)', + '⒬' => '(q)', + '⒭' => '(r)', + '⒮' => '(s)', + '⒯' => '(t)', + '⒰' => '(u)', + '⒱' => '(v)', + '⒲' => '(w)', + '⒳' => '(x)', + '⒴' => '(y)', + '⒵' => '(z)', + 'Ⓐ' => 'A', + 'Ⓑ' => 'B', + 'Ⓒ' => 'C', + 'Ⓓ' => 'D', + 'Ⓔ' => 'E', + 'Ⓕ' => 'F', + 'Ⓖ' => 'G', + 'Ⓗ' => 'H', + 'Ⓘ' => 'I', + 'Ⓙ' => 'J', + 'Ⓚ' => 'K', + 'Ⓛ' => 'L', + 'Ⓜ' => 'M', + 'Ⓝ' => 'N', + 'Ⓞ' => 'O', + 'Ⓟ' => 'P', + 'Ⓠ' => 'Q', + 'Ⓡ' => 'R', + 'Ⓢ' => 'S', + 'Ⓣ' => 'T', + 'Ⓤ' => 'U', + 'Ⓥ' => 'V', + 'Ⓦ' => 'W', + 'Ⓧ' => 'X', + 'Ⓨ' => 'Y', + 'Ⓩ' => 'Z', + 'ⓐ' => 'a', + 'ⓑ' => 'b', + 'ⓒ' => 'c', + 'ⓓ' => 'd', + 'ⓔ' => 'e', + 'ⓕ' => 'f', + 'ⓖ' => 'g', + 'ⓗ' => 'h', + 'ⓘ' => 'i', + 'ⓙ' => 'j', + 'ⓚ' => 'k', + 'ⓛ' => 'l', + 'ⓜ' => 'm', + 'ⓝ' => 'n', + 'ⓞ' => 'o', + 'ⓟ' => 'p', + 'ⓠ' => 'q', + 'ⓡ' => 'r', + 'ⓢ' => 's', + 'ⓣ' => 't', + 'ⓤ' => 'u', + 'ⓥ' => 'v', + 'ⓦ' => 'w', + 'ⓧ' => 'x', + 'ⓨ' => 'y', + 'ⓩ' => 'z', + '⓪' => '0', + '⨌' => '∫∫∫∫', + '⩴' => '::=', + '⩵' => '==', + '⩶' => '===', + 'ⱼ' => 'j', + 'ⱽ' => 'V', + 'ⵯ' => 'ⵡ', + '⺟' => '母', + '⻳' => '龟', + '⼀' => '一', + '⼁' => '丨', + '⼂' => '丶', + '⼃' => '丿', + '⼄' => '乙', + '⼅' => '亅', + '⼆' => '二', + '⼇' => '亠', + '⼈' => '人', + '⼉' => '儿', + '⼊' => '入', + '⼋' => '八', + '⼌' => '冂', + '⼍' => '冖', + '⼎' => '冫', + '⼏' => '几', + '⼐' => '凵', + '⼑' => '刀', + '⼒' => '力', + '⼓' => '勹', + '⼔' => '匕', + '⼕' => '匚', + '⼖' => '匸', + '⼗' => '十', + '⼘' => '卜', + '⼙' => '卩', + '⼚' => '厂', + '⼛' => '厶', + '⼜' => '又', + '⼝' => '口', + '⼞' => '囗', + '⼟' => '土', + '⼠' => '士', + '⼡' => '夂', + '⼢' => '夊', + '⼣' => '夕', + '⼤' => '大', + '⼥' => '女', + '⼦' => '子', + '⼧' => '宀', + '⼨' => '寸', + '⼩' => '小', + '⼪' => '尢', + '⼫' => '尸', + '⼬' => '屮', + '⼭' => '山', + '⼮' => '巛', + '⼯' => '工', + '⼰' => '己', + '⼱' => '巾', + '⼲' => '干', + '⼳' => '幺', + '⼴' => '广', + '⼵' => '廴', + '⼶' => '廾', + '⼷' => '弋', + '⼸' => '弓', + '⼹' => '彐', + '⼺' => '彡', + '⼻' => '彳', + '⼼' => '心', + '⼽' => '戈', + '⼾' => '戶', + '⼿' => '手', + '⽀' => '支', + '⽁' => '攴', + '⽂' => '文', + '⽃' => '斗', + '⽄' => '斤', + '⽅' => '方', + '⽆' => '无', + '⽇' => '日', + '⽈' => '曰', + '⽉' => '月', + '⽊' => '木', + '⽋' => '欠', + '⽌' => '止', + '⽍' => '歹', + '⽎' => '殳', + '⽏' => '毋', + '⽐' => '比', + '⽑' => '毛', + '⽒' => '氏', + '⽓' => '气', + '⽔' => '水', + '⽕' => '火', + '⽖' => '爪', + '⽗' => '父', + '⽘' => '爻', + '⽙' => '爿', + '⽚' => '片', + '⽛' => '牙', + '⽜' => '牛', + '⽝' => '犬', + '⽞' => '玄', + '⽟' => '玉', + '⽠' => '瓜', + '⽡' => '瓦', + '⽢' => '甘', + '⽣' => '生', + '⽤' => '用', + '⽥' => '田', + '⽦' => '疋', + '⽧' => '疒', + '⽨' => '癶', + '⽩' => '白', + '⽪' => '皮', + '⽫' => '皿', + '⽬' => '目', + '⽭' => '矛', + '⽮' => '矢', + '⽯' => '石', + '⽰' => '示', + '⽱' => '禸', + '⽲' => '禾', + '⽳' => '穴', + '⽴' => '立', + '⽵' => '竹', + '⽶' => '米', + '⽷' => '糸', + '⽸' => '缶', + '⽹' => '网', + '⽺' => '羊', + '⽻' => '羽', + '⽼' => '老', + '⽽' => '而', + '⽾' => '耒', + '⽿' => '耳', + '⾀' => '聿', + '⾁' => '肉', + '⾂' => '臣', + '⾃' => '自', + '⾄' => '至', + '⾅' => '臼', + '⾆' => '舌', + '⾇' => '舛', + '⾈' => '舟', + '⾉' => '艮', + '⾊' => '色', + '⾋' => '艸', + '⾌' => '虍', + '⾍' => '虫', + '⾎' => '血', + '⾏' => '行', + '⾐' => '衣', + '⾑' => '襾', + '⾒' => '見', + '⾓' => '角', + '⾔' => '言', + '⾕' => '谷', + '⾖' => '豆', + '⾗' => '豕', + '⾘' => '豸', + '⾙' => '貝', + '⾚' => '赤', + '⾛' => '走', + '⾜' => '足', + '⾝' => '身', + '⾞' => '車', + '⾟' => '辛', + '⾠' => '辰', + '⾡' => '辵', + '⾢' => '邑', + '⾣' => '酉', + '⾤' => '釆', + '⾥' => '里', + '⾦' => '金', + '⾧' => '長', + '⾨' => '門', + '⾩' => '阜', + '⾪' => '隶', + '⾫' => '隹', + '⾬' => '雨', + '⾭' => '靑', + '⾮' => '非', + '⾯' => '面', + '⾰' => '革', + '⾱' => '韋', + '⾲' => '韭', + '⾳' => '音', + '⾴' => '頁', + '⾵' => '風', + '⾶' => '飛', + '⾷' => '食', + '⾸' => '首', + '⾹' => '香', + '⾺' => '馬', + '⾻' => '骨', + '⾼' => '高', + '⾽' => '髟', + '⾾' => '鬥', + '⾿' => '鬯', + '⿀' => '鬲', + '⿁' => '鬼', + '⿂' => '魚', + '⿃' => '鳥', + '⿄' => '鹵', + '⿅' => '鹿', + '⿆' => '麥', + '⿇' => '麻', + '⿈' => '黃', + '⿉' => '黍', + '⿊' => '黑', + '⿋' => '黹', + '⿌' => '黽', + '⿍' => '鼎', + '⿎' => '鼓', + '⿏' => '鼠', + '⿐' => '鼻', + '⿑' => '齊', + '⿒' => '齒', + '⿓' => '龍', + '⿔' => '龜', + '⿕' => '龠', + ' ' => ' ', + '〶' => '〒', + '〸' => '十', + '〹' => '卄', + '〺' => '卅', + '゛' => ' ゙', + '゜' => ' ゚', + 'ゟ' => 'より', + 'ヿ' => 'コト', + 'ㄱ' => 'ᄀ', + 'ㄲ' => 'ᄁ', + 'ㄳ' => 'ᆪ', + 'ㄴ' => 'ᄂ', + 'ㄵ' => 'ᆬ', + 'ㄶ' => 'ᆭ', + 'ㄷ' => 'ᄃ', + 'ㄸ' => 'ᄄ', + 'ㄹ' => 'ᄅ', + 'ㄺ' => 'ᆰ', + 'ㄻ' => 'ᆱ', + 'ㄼ' => 'ᆲ', + 'ㄽ' => 'ᆳ', + 'ㄾ' => 'ᆴ', + 'ㄿ' => 'ᆵ', + 'ㅀ' => 'ᄚ', + 'ㅁ' => 'ᄆ', + 'ㅂ' => 'ᄇ', + 'ㅃ' => 'ᄈ', + 'ㅄ' => 'ᄡ', + 'ㅅ' => 'ᄉ', + 'ㅆ' => 'ᄊ', + 'ㅇ' => 'ᄋ', + 'ㅈ' => 'ᄌ', + 'ㅉ' => 'ᄍ', + 'ㅊ' => 'ᄎ', + 'ㅋ' => 'ᄏ', + 'ㅌ' => 'ᄐ', + 'ㅍ' => 'ᄑ', + 'ㅎ' => 'ᄒ', + 'ㅏ' => 'ᅡ', + 'ㅐ' => 'ᅢ', + 'ㅑ' => 'ᅣ', + 'ㅒ' => 'ᅤ', + 'ㅓ' => 'ᅥ', + 'ㅔ' => 'ᅦ', + 'ㅕ' => 'ᅧ', + 'ㅖ' => 'ᅨ', + 'ㅗ' => 'ᅩ', + 'ㅘ' => 'ᅪ', + 'ㅙ' => 'ᅫ', + 'ㅚ' => 'ᅬ', + 'ㅛ' => 'ᅭ', + 'ㅜ' => 'ᅮ', + 'ㅝ' => 'ᅯ', + 'ㅞ' => 'ᅰ', + 'ㅟ' => 'ᅱ', + 'ㅠ' => 'ᅲ', + 'ㅡ' => 'ᅳ', + 'ㅢ' => 'ᅴ', + 'ㅣ' => 'ᅵ', + 'ㅤ' => 'ᅠ', + 'ㅥ' => 'ᄔ', + 'ㅦ' => 'ᄕ', + 'ㅧ' => 'ᇇ', + 'ㅨ' => 'ᇈ', + 'ㅩ' => 'ᇌ', + 'ㅪ' => 'ᇎ', + 'ㅫ' => 'ᇓ', + 'ㅬ' => 'ᇗ', + 'ㅭ' => 'ᇙ', + 'ㅮ' => 'ᄜ', + 'ㅯ' => 'ᇝ', + 'ㅰ' => 'ᇟ', + 'ㅱ' => 'ᄝ', + 'ㅲ' => 'ᄞ', + 'ㅳ' => 'ᄠ', + 'ㅴ' => 'ᄢ', + 'ㅵ' => 'ᄣ', + 'ㅶ' => 'ᄧ', + 'ㅷ' => 'ᄩ', + 'ㅸ' => 'ᄫ', + 'ㅹ' => 'ᄬ', + 'ㅺ' => 'ᄭ', + 'ㅻ' => 'ᄮ', + 'ㅼ' => 'ᄯ', + 'ㅽ' => 'ᄲ', + 'ㅾ' => 'ᄶ', + 'ㅿ' => 'ᅀ', + 'ㆀ' => 'ᅇ', + 'ㆁ' => 'ᅌ', + 'ㆂ' => 'ᇱ', + 'ㆃ' => 'ᇲ', + 'ㆄ' => 'ᅗ', + 'ㆅ' => 'ᅘ', + 'ㆆ' => 'ᅙ', + 'ㆇ' => 'ᆄ', + 'ㆈ' => 'ᆅ', + 'ㆉ' => 'ᆈ', + 'ㆊ' => 'ᆑ', + 'ㆋ' => 'ᆒ', + 'ㆌ' => 'ᆔ', + 'ㆍ' => 'ᆞ', + 'ㆎ' => 'ᆡ', + '㆒' => '一', + '㆓' => '二', + '㆔' => '三', + '㆕' => '四', + '㆖' => '上', + '㆗' => '中', + '㆘' => '下', + '㆙' => '甲', + '㆚' => '乙', + '㆛' => '丙', + '㆜' => '丁', + '㆝' => '天', + '㆞' => '地', + '㆟' => '人', + '㈀' => '(ᄀ)', + '㈁' => '(ᄂ)', + '㈂' => '(ᄃ)', + '㈃' => '(ᄅ)', + '㈄' => '(ᄆ)', + '㈅' => '(ᄇ)', + '㈆' => '(ᄉ)', + '㈇' => '(ᄋ)', + '㈈' => '(ᄌ)', + '㈉' => '(ᄎ)', + '㈊' => '(ᄏ)', + '㈋' => '(ᄐ)', + '㈌' => '(ᄑ)', + '㈍' => '(ᄒ)', + '㈎' => '(가)', + '㈏' => '(나)', + '㈐' => '(다)', + '㈑' => '(라)', + '㈒' => '(마)', + '㈓' => '(바)', + '㈔' => '(사)', + '㈕' => '(아)', + '㈖' => '(자)', + '㈗' => '(차)', + '㈘' => '(카)', + '㈙' => '(타)', + '㈚' => '(파)', + '㈛' => '(하)', + '㈜' => '(주)', + '㈝' => '(오전)', + '㈞' => '(오후)', + '㈠' => '(一)', + '㈡' => '(二)', + '㈢' => '(三)', + '㈣' => '(四)', + '㈤' => '(五)', + '㈥' => '(六)', + '㈦' => '(七)', + '㈧' => '(八)', + '㈨' => '(九)', + '㈩' => '(十)', + '㈪' => '(月)', + '㈫' => '(火)', + '㈬' => '(水)', + '㈭' => '(木)', + '㈮' => '(金)', + '㈯' => '(土)', + '㈰' => '(日)', + '㈱' => '(株)', + '㈲' => '(有)', + '㈳' => '(社)', + '㈴' => '(名)', + '㈵' => '(特)', + '㈶' => '(財)', + '㈷' => '(祝)', + '㈸' => '(労)', + '㈹' => '(代)', + '㈺' => '(呼)', + '㈻' => '(学)', + '㈼' => '(監)', + '㈽' => '(企)', + '㈾' => '(資)', + '㈿' => '(協)', + '㉀' => '(祭)', + '㉁' => '(休)', + '㉂' => '(自)', + '㉃' => '(至)', + '㉄' => '問', + '㉅' => '幼', + '㉆' => '文', + '㉇' => '箏', + '㉐' => 'PTE', + '㉑' => '21', + '㉒' => '22', + '㉓' => '23', + '㉔' => '24', + '㉕' => '25', + '㉖' => '26', + '㉗' => '27', + '㉘' => '28', + '㉙' => '29', + '㉚' => '30', + '㉛' => '31', + '㉜' => '32', + '㉝' => '33', + '㉞' => '34', + '㉟' => '35', + '㉠' => 'ᄀ', + '㉡' => 'ᄂ', + '㉢' => 'ᄃ', + '㉣' => 'ᄅ', + '㉤' => 'ᄆ', + '㉥' => 'ᄇ', + '㉦' => 'ᄉ', + '㉧' => 'ᄋ', + '㉨' => 'ᄌ', + '㉩' => 'ᄎ', + '㉪' => 'ᄏ', + '㉫' => 'ᄐ', + '㉬' => 'ᄑ', + '㉭' => 'ᄒ', + '㉮' => '가', + '㉯' => '나', + '㉰' => '다', + '㉱' => '라', + '㉲' => '마', + '㉳' => '바', + '㉴' => '사', + '㉵' => '아', + '㉶' => '자', + '㉷' => '차', + '㉸' => '카', + '㉹' => '타', + '㉺' => '파', + '㉻' => '하', + '㉼' => '참고', + '㉽' => '주의', + '㉾' => '우', + '㊀' => '一', + '㊁' => '二', + '㊂' => '三', + '㊃' => '四', + '㊄' => '五', + '㊅' => '六', + '㊆' => '七', + '㊇' => '八', + '㊈' => '九', + '㊉' => '十', + '㊊' => '月', + '㊋' => '火', + '㊌' => '水', + '㊍' => '木', + '㊎' => '金', + '㊏' => '土', + '㊐' => '日', + '㊑' => '株', + '㊒' => '有', + '㊓' => '社', + '㊔' => '名', + '㊕' => '特', + '㊖' => '財', + '㊗' => '祝', + '㊘' => '労', + '㊙' => '秘', + '㊚' => '男', + '㊛' => '女', + '㊜' => '適', + '㊝' => '優', + '㊞' => '印', + '㊟' => '注', + '㊠' => '項', + '㊡' => '休', + '㊢' => '写', + '㊣' => '正', + '㊤' => '上', + '㊥' => '中', + '㊦' => '下', + '㊧' => '左', + '㊨' => '右', + '㊩' => '医', + '㊪' => '宗', + '㊫' => '学', + '㊬' => '監', + '㊭' => '企', + '㊮' => '資', + '㊯' => '協', + '㊰' => '夜', + '㊱' => '36', + '㊲' => '37', + '㊳' => '38', + '㊴' => '39', + '㊵' => '40', + '㊶' => '41', + '㊷' => '42', + '㊸' => '43', + '㊹' => '44', + '㊺' => '45', + '㊻' => '46', + '㊼' => '47', + '㊽' => '48', + '㊾' => '49', + '㊿' => '50', + '㋀' => '1月', + '㋁' => '2月', + '㋂' => '3月', + '㋃' => '4月', + '㋄' => '5月', + '㋅' => '6月', + '㋆' => '7月', + '㋇' => '8月', + '㋈' => '9月', + '㋉' => '10月', + '㋊' => '11月', + '㋋' => '12月', + '㋌' => 'Hg', + '㋍' => 'erg', + '㋎' => 'eV', + '㋏' => 'LTD', + '㋐' => 'ア', + '㋑' => 'イ', + '㋒' => 'ウ', + '㋓' => 'エ', + '㋔' => 'オ', + '㋕' => 'カ', + '㋖' => 'キ', + '㋗' => 'ク', + '㋘' => 'ケ', + '㋙' => 'コ', + '㋚' => 'サ', + '㋛' => 'シ', + '㋜' => 'ス', + '㋝' => 'セ', + '㋞' => 'ソ', + '㋟' => 'タ', + '㋠' => 'チ', + '㋡' => 'ツ', + '㋢' => 'テ', + '㋣' => 'ト', + '㋤' => 'ナ', + '㋥' => 'ニ', + '㋦' => 'ヌ', + '㋧' => 'ネ', + '㋨' => 'ノ', + '㋩' => 'ハ', + '㋪' => 'ヒ', + '㋫' => 'フ', + '㋬' => 'ヘ', + '㋭' => 'ホ', + '㋮' => 'マ', + '㋯' => 'ミ', + '㋰' => 'ム', + '㋱' => 'メ', + '㋲' => 'モ', + '㋳' => 'ヤ', + '㋴' => 'ユ', + '㋵' => 'ヨ', + '㋶' => 'ラ', + '㋷' => 'リ', + '㋸' => 'ル', + '㋹' => 'レ', + '㋺' => 'ロ', + '㋻' => 'ワ', + '㋼' => 'ヰ', + '㋽' => 'ヱ', + '㋾' => 'ヲ', + '㋿' => '令和', + '㌀' => 'アパート', + '㌁' => 'アルファ', + '㌂' => 'アンペア', + '㌃' => 'アール', + '㌄' => 'イニング', + '㌅' => 'インチ', + '㌆' => 'ウォン', + '㌇' => 'エスクード', + '㌈' => 'エーカー', + '㌉' => 'オンス', + '㌊' => 'オーム', + '㌋' => 'カイリ', + '㌌' => 'カラット', + '㌍' => 'カロリー', + '㌎' => 'ガロン', + '㌏' => 'ガンマ', + '㌐' => 'ギガ', + '㌑' => 'ギニー', + '㌒' => 'キュリー', + '㌓' => 'ギルダー', + '㌔' => 'キロ', + '㌕' => 'キログラム', + '㌖' => 'キロメートル', + '㌗' => 'キロワット', + '㌘' => 'グラム', + '㌙' => 'グラムトン', + '㌚' => 'クルゼイロ', + '㌛' => 'クローネ', + '㌜' => 'ケース', + '㌝' => 'コルナ', + '㌞' => 'コーポ', + '㌟' => 'サイクル', + '㌠' => 'サンチーム', + '㌡' => 'シリング', + '㌢' => 'センチ', + '㌣' => 'セント', + '㌤' => 'ダース', + '㌥' => 'デシ', + '㌦' => 'ドル', + '㌧' => 'トン', + '㌨' => 'ナノ', + '㌩' => 'ノット', + '㌪' => 'ハイツ', + '㌫' => 'パーセント', + '㌬' => 'パーツ', + '㌭' => 'バーレル', + '㌮' => 'ピアストル', + '㌯' => 'ピクル', + '㌰' => 'ピコ', + '㌱' => 'ビル', + '㌲' => 'ファラッド', + '㌳' => 'フィート', + '㌴' => 'ブッシェル', + '㌵' => 'フラン', + '㌶' => 'ヘクタール', + '㌷' => 'ペソ', + '㌸' => 'ペニヒ', + '㌹' => 'ヘルツ', + '㌺' => 'ペンス', + '㌻' => 'ページ', + '㌼' => 'ベータ', + '㌽' => 'ポイント', + '㌾' => 'ボルト', + '㌿' => 'ホン', + '㍀' => 'ポンド', + '㍁' => 'ホール', + '㍂' => 'ホーン', + '㍃' => 'マイクロ', + '㍄' => 'マイル', + '㍅' => 'マッハ', + '㍆' => 'マルク', + '㍇' => 'マンション', + '㍈' => 'ミクロン', + '㍉' => 'ミリ', + '㍊' => 'ミリバール', + '㍋' => 'メガ', + '㍌' => 'メガトン', + '㍍' => 'メートル', + '㍎' => 'ヤード', + '㍏' => 'ヤール', + '㍐' => 'ユアン', + '㍑' => 'リットル', + '㍒' => 'リラ', + '㍓' => 'ルピー', + '㍔' => 'ルーブル', + '㍕' => 'レム', + '㍖' => 'レントゲン', + '㍗' => 'ワット', + '㍘' => '0点', + '㍙' => '1点', + '㍚' => '2点', + '㍛' => '3点', + '㍜' => '4点', + '㍝' => '5点', + '㍞' => '6点', + '㍟' => '7点', + '㍠' => '8点', + '㍡' => '9点', + '㍢' => '10点', + '㍣' => '11点', + '㍤' => '12点', + '㍥' => '13点', + '㍦' => '14点', + '㍧' => '15点', + '㍨' => '16点', + '㍩' => '17点', + '㍪' => '18点', + '㍫' => '19点', + '㍬' => '20点', + '㍭' => '21点', + '㍮' => '22点', + '㍯' => '23点', + '㍰' => '24点', + '㍱' => 'hPa', + '㍲' => 'da', + '㍳' => 'AU', + '㍴' => 'bar', + '㍵' => 'oV', + '㍶' => 'pc', + '㍷' => 'dm', + '㍸' => 'dm2', + '㍹' => 'dm3', + '㍺' => 'IU', + '㍻' => '平成', + '㍼' => '昭和', + '㍽' => '大正', + '㍾' => '明治', + '㍿' => '株式会社', + '㎀' => 'pA', + '㎁' => 'nA', + '㎂' => 'μA', + '㎃' => 'mA', + '㎄' => 'kA', + '㎅' => 'KB', + '㎆' => 'MB', + '㎇' => 'GB', + '㎈' => 'cal', + '㎉' => 'kcal', + '㎊' => 'pF', + '㎋' => 'nF', + '㎌' => 'μF', + '㎍' => 'μg', + '㎎' => 'mg', + '㎏' => 'kg', + '㎐' => 'Hz', + '㎑' => 'kHz', + '㎒' => 'MHz', + '㎓' => 'GHz', + '㎔' => 'THz', + '㎕' => 'μl', + '㎖' => 'ml', + '㎗' => 'dl', + '㎘' => 'kl', + '㎙' => 'fm', + '㎚' => 'nm', + '㎛' => 'μm', + '㎜' => 'mm', + '㎝' => 'cm', + '㎞' => 'km', + '㎟' => 'mm2', + '㎠' => 'cm2', + '㎡' => 'm2', + '㎢' => 'km2', + '㎣' => 'mm3', + '㎤' => 'cm3', + '㎥' => 'm3', + '㎦' => 'km3', + '㎧' => 'm∕s', + '㎨' => 'm∕s2', + '㎩' => 'Pa', + '㎪' => 'kPa', + '㎫' => 'MPa', + '㎬' => 'GPa', + '㎭' => 'rad', + '㎮' => 'rad∕s', + '㎯' => 'rad∕s2', + '㎰' => 'ps', + '㎱' => 'ns', + '㎲' => 'μs', + '㎳' => 'ms', + '㎴' => 'pV', + '㎵' => 'nV', + '㎶' => 'μV', + '㎷' => 'mV', + '㎸' => 'kV', + '㎹' => 'MV', + '㎺' => 'pW', + '㎻' => 'nW', + '㎼' => 'μW', + '㎽' => 'mW', + '㎾' => 'kW', + '㎿' => 'MW', + '㏀' => 'kΩ', + '㏁' => 'MΩ', + '㏂' => 'a.m.', + '㏃' => 'Bq', + '㏄' => 'cc', + '㏅' => 'cd', + '㏆' => 'C∕kg', + '㏇' => 'Co.', + '㏈' => 'dB', + '㏉' => 'Gy', + '㏊' => 'ha', + '㏋' => 'HP', + '㏌' => 'in', + '㏍' => 'KK', + '㏎' => 'KM', + '㏏' => 'kt', + '㏐' => 'lm', + '㏑' => 'ln', + '㏒' => 'log', + '㏓' => 'lx', + '㏔' => 'mb', + '㏕' => 'mil', + '㏖' => 'mol', + '㏗' => 'PH', + '㏘' => 'p.m.', + '㏙' => 'PPM', + '㏚' => 'PR', + '㏛' => 'sr', + '㏜' => 'Sv', + '㏝' => 'Wb', + '㏞' => 'V∕m', + '㏟' => 'A∕m', + '㏠' => '1日', + '㏡' => '2日', + '㏢' => '3日', + '㏣' => '4日', + '㏤' => '5日', + '㏥' => '6日', + '㏦' => '7日', + '㏧' => '8日', + '㏨' => '9日', + '㏩' => '10日', + '㏪' => '11日', + '㏫' => '12日', + '㏬' => '13日', + '㏭' => '14日', + '㏮' => '15日', + '㏯' => '16日', + '㏰' => '17日', + '㏱' => '18日', + '㏲' => '19日', + '㏳' => '20日', + '㏴' => '21日', + '㏵' => '22日', + '㏶' => '23日', + '㏷' => '24日', + '㏸' => '25日', + '㏹' => '26日', + '㏺' => '27日', + '㏻' => '28日', + '㏼' => '29日', + '㏽' => '30日', + '㏾' => '31日', + '㏿' => 'gal', + 'ꚜ' => 'ъ', + 'ꚝ' => 'ь', + 'ꝰ' => 'ꝯ', + 'ꟸ' => 'Ħ', + 'ꟹ' => 'œ', + 'ꭜ' => 'ꜧ', + 'ꭝ' => 'ꬷ', + 'ꭞ' => 'ɫ', + 'ꭟ' => 'ꭒ', + 'ꭩ' => 'ʍ', + 'ff' => 'ff', + 'fi' => 'fi', + 'fl' => 'fl', + 'ffi' => 'ffi', + 'ffl' => 'ffl', + 'ſt' => 'st', + 'st' => 'st', + 'ﬓ' => 'մն', + 'ﬔ' => 'մե', + 'ﬕ' => 'մի', + 'ﬖ' => 'վն', + 'ﬗ' => 'մխ', + 'ﬠ' => 'ע', + 'ﬡ' => 'א', + 'ﬢ' => 'ד', + 'ﬣ' => 'ה', + 'ﬤ' => 'כ', + 'ﬥ' => 'ל', + 'ﬦ' => 'ם', + 'ﬧ' => 'ר', + 'ﬨ' => 'ת', + '﬩' => '+', + 'ﭏ' => 'אל', + 'ﭐ' => 'ٱ', + 'ﭑ' => 'ٱ', + 'ﭒ' => 'ٻ', + 'ﭓ' => 'ٻ', + 'ﭔ' => 'ٻ', + 'ﭕ' => 'ٻ', + 'ﭖ' => 'پ', + 'ﭗ' => 'پ', + 'ﭘ' => 'پ', + 'ﭙ' => 'پ', + 'ﭚ' => 'ڀ', + 'ﭛ' => 'ڀ', + 'ﭜ' => 'ڀ', + 'ﭝ' => 'ڀ', + 'ﭞ' => 'ٺ', + 'ﭟ' => 'ٺ', + 'ﭠ' => 'ٺ', + 'ﭡ' => 'ٺ', + 'ﭢ' => 'ٿ', + 'ﭣ' => 'ٿ', + 'ﭤ' => 'ٿ', + 'ﭥ' => 'ٿ', + 'ﭦ' => 'ٹ', + 'ﭧ' => 'ٹ', + 'ﭨ' => 'ٹ', + 'ﭩ' => 'ٹ', + 'ﭪ' => 'ڤ', + 'ﭫ' => 'ڤ', + 'ﭬ' => 'ڤ', + 'ﭭ' => 'ڤ', + 'ﭮ' => 'ڦ', + 'ﭯ' => 'ڦ', + 'ﭰ' => 'ڦ', + 'ﭱ' => 'ڦ', + 'ﭲ' => 'ڄ', + 'ﭳ' => 'ڄ', + 'ﭴ' => 'ڄ', + 'ﭵ' => 'ڄ', + 'ﭶ' => 'ڃ', + 'ﭷ' => 'ڃ', + 'ﭸ' => 'ڃ', + 'ﭹ' => 'ڃ', + 'ﭺ' => 'چ', + 'ﭻ' => 'چ', + 'ﭼ' => 'چ', + 'ﭽ' => 'چ', + 'ﭾ' => 'ڇ', + 'ﭿ' => 'ڇ', + 'ﮀ' => 'ڇ', + 'ﮁ' => 'ڇ', + 'ﮂ' => 'ڍ', + 'ﮃ' => 'ڍ', + 'ﮄ' => 'ڌ', + 'ﮅ' => 'ڌ', + 'ﮆ' => 'ڎ', + 'ﮇ' => 'ڎ', + 'ﮈ' => 'ڈ', + 'ﮉ' => 'ڈ', + 'ﮊ' => 'ژ', + 'ﮋ' => 'ژ', + 'ﮌ' => 'ڑ', + 'ﮍ' => 'ڑ', + 'ﮎ' => 'ک', + 'ﮏ' => 'ک', + 'ﮐ' => 'ک', + 'ﮑ' => 'ک', + 'ﮒ' => 'گ', + 'ﮓ' => 'گ', + 'ﮔ' => 'گ', + 'ﮕ' => 'گ', + 'ﮖ' => 'ڳ', + 'ﮗ' => 'ڳ', + 'ﮘ' => 'ڳ', + 'ﮙ' => 'ڳ', + 'ﮚ' => 'ڱ', + 'ﮛ' => 'ڱ', + 'ﮜ' => 'ڱ', + 'ﮝ' => 'ڱ', + 'ﮞ' => 'ں', + 'ﮟ' => 'ں', + 'ﮠ' => 'ڻ', + 'ﮡ' => 'ڻ', + 'ﮢ' => 'ڻ', + 'ﮣ' => 'ڻ', + 'ﮤ' => 'ۀ', + 'ﮥ' => 'ۀ', + 'ﮦ' => 'ہ', + 'ﮧ' => 'ہ', + 'ﮨ' => 'ہ', + 'ﮩ' => 'ہ', + 'ﮪ' => 'ھ', + 'ﮫ' => 'ھ', + 'ﮬ' => 'ھ', + 'ﮭ' => 'ھ', + 'ﮮ' => 'ے', + 'ﮯ' => 'ے', + 'ﮰ' => 'ۓ', + 'ﮱ' => 'ۓ', + 'ﯓ' => 'ڭ', + 'ﯔ' => 'ڭ', + 'ﯕ' => 'ڭ', + 'ﯖ' => 'ڭ', + 'ﯗ' => 'ۇ', + 'ﯘ' => 'ۇ', + 'ﯙ' => 'ۆ', + 'ﯚ' => 'ۆ', + 'ﯛ' => 'ۈ', + 'ﯜ' => 'ۈ', + 'ﯝ' => 'ۇٴ', + 'ﯞ' => 'ۋ', + 'ﯟ' => 'ۋ', + 'ﯠ' => 'ۅ', + 'ﯡ' => 'ۅ', + 'ﯢ' => 'ۉ', + 'ﯣ' => 'ۉ', + 'ﯤ' => 'ې', + 'ﯥ' => 'ې', + 'ﯦ' => 'ې', + 'ﯧ' => 'ې', + 'ﯨ' => 'ى', + 'ﯩ' => 'ى', + 'ﯪ' => 'ئا', + 'ﯫ' => 'ئا', + 'ﯬ' => 'ئە', + 'ﯭ' => 'ئە', + 'ﯮ' => 'ئو', + 'ﯯ' => 'ئو', + 'ﯰ' => 'ئۇ', + 'ﯱ' => 'ئۇ', + 'ﯲ' => 'ئۆ', + 'ﯳ' => 'ئۆ', + 'ﯴ' => 'ئۈ', + 'ﯵ' => 'ئۈ', + 'ﯶ' => 'ئې', + 'ﯷ' => 'ئې', + 'ﯸ' => 'ئې', + 'ﯹ' => 'ئى', + 'ﯺ' => 'ئى', + 'ﯻ' => 'ئى', + 'ﯼ' => 'ی', + 'ﯽ' => 'ی', + 'ﯾ' => 'ی', + 'ﯿ' => 'ی', + 'ﰀ' => 'ئج', + 'ﰁ' => 'ئح', + 'ﰂ' => 'ئم', + 'ﰃ' => 'ئى', + 'ﰄ' => 'ئي', + 'ﰅ' => 'بج', + 'ﰆ' => 'بح', + 'ﰇ' => 'بخ', + 'ﰈ' => 'بم', + 'ﰉ' => 'بى', + 'ﰊ' => 'بي', + 'ﰋ' => 'تج', + 'ﰌ' => 'تح', + 'ﰍ' => 'تخ', + 'ﰎ' => 'تم', + 'ﰏ' => 'تى', + 'ﰐ' => 'تي', + 'ﰑ' => 'ثج', + 'ﰒ' => 'ثم', + 'ﰓ' => 'ثى', + 'ﰔ' => 'ثي', + 'ﰕ' => 'جح', + 'ﰖ' => 'جم', + 'ﰗ' => 'حج', + 'ﰘ' => 'حم', + 'ﰙ' => 'خج', + 'ﰚ' => 'خح', + 'ﰛ' => 'خم', + 'ﰜ' => 'سج', + 'ﰝ' => 'سح', + 'ﰞ' => 'سخ', + 'ﰟ' => 'سم', + 'ﰠ' => 'صح', + 'ﰡ' => 'صم', + 'ﰢ' => 'ضج', + 'ﰣ' => 'ضح', + 'ﰤ' => 'ضخ', + 'ﰥ' => 'ضم', + 'ﰦ' => 'طح', + 'ﰧ' => 'طم', + 'ﰨ' => 'ظم', + 'ﰩ' => 'عج', + 'ﰪ' => 'عم', + 'ﰫ' => 'غج', + 'ﰬ' => 'غم', + 'ﰭ' => 'فج', + 'ﰮ' => 'فح', + 'ﰯ' => 'فخ', + 'ﰰ' => 'فم', + 'ﰱ' => 'فى', + 'ﰲ' => 'في', + 'ﰳ' => 'قح', + 'ﰴ' => 'قم', + 'ﰵ' => 'قى', + 'ﰶ' => 'قي', + 'ﰷ' => 'كا', + 'ﰸ' => 'كج', + 'ﰹ' => 'كح', + 'ﰺ' => 'كخ', + 'ﰻ' => 'كل', + 'ﰼ' => 'كم', + 'ﰽ' => 'كى', + 'ﰾ' => 'كي', + 'ﰿ' => 'لج', + 'ﱀ' => 'لح', + 'ﱁ' => 'لخ', + 'ﱂ' => 'لم', + 'ﱃ' => 'لى', + 'ﱄ' => 'لي', + 'ﱅ' => 'مج', + 'ﱆ' => 'مح', + 'ﱇ' => 'مخ', + 'ﱈ' => 'مم', + 'ﱉ' => 'مى', + 'ﱊ' => 'مي', + 'ﱋ' => 'نج', + 'ﱌ' => 'نح', + 'ﱍ' => 'نخ', + 'ﱎ' => 'نم', + 'ﱏ' => 'نى', + 'ﱐ' => 'ني', + 'ﱑ' => 'هج', + 'ﱒ' => 'هم', + 'ﱓ' => 'هى', + 'ﱔ' => 'هي', + 'ﱕ' => 'يج', + 'ﱖ' => 'يح', + 'ﱗ' => 'يخ', + 'ﱘ' => 'يم', + 'ﱙ' => 'يى', + 'ﱚ' => 'يي', + 'ﱛ' => 'ذٰ', + 'ﱜ' => 'رٰ', + 'ﱝ' => 'ىٰ', + 'ﱞ' => ' ٌّ', + 'ﱟ' => ' ٍّ', + 'ﱠ' => ' َّ', + 'ﱡ' => ' ُّ', + 'ﱢ' => ' ِّ', + 'ﱣ' => ' ّٰ', + 'ﱤ' => 'ئر', + 'ﱥ' => 'ئز', + 'ﱦ' => 'ئم', + 'ﱧ' => 'ئن', + 'ﱨ' => 'ئى', + 'ﱩ' => 'ئي', + 'ﱪ' => 'بر', + 'ﱫ' => 'بز', + 'ﱬ' => 'بم', + 'ﱭ' => 'بن', + 'ﱮ' => 'بى', + 'ﱯ' => 'بي', + 'ﱰ' => 'تر', + 'ﱱ' => 'تز', + 'ﱲ' => 'تم', + 'ﱳ' => 'تن', + 'ﱴ' => 'تى', + 'ﱵ' => 'تي', + 'ﱶ' => 'ثر', + 'ﱷ' => 'ثز', + 'ﱸ' => 'ثم', + 'ﱹ' => 'ثن', + 'ﱺ' => 'ثى', + 'ﱻ' => 'ثي', + 'ﱼ' => 'فى', + 'ﱽ' => 'في', + 'ﱾ' => 'قى', + 'ﱿ' => 'قي', + 'ﲀ' => 'كا', + 'ﲁ' => 'كل', + 'ﲂ' => 'كم', + 'ﲃ' => 'كى', + 'ﲄ' => 'كي', + 'ﲅ' => 'لم', + 'ﲆ' => 'لى', + 'ﲇ' => 'لي', + 'ﲈ' => 'ما', + 'ﲉ' => 'مم', + 'ﲊ' => 'نر', + 'ﲋ' => 'نز', + 'ﲌ' => 'نم', + 'ﲍ' => 'نن', + 'ﲎ' => 'نى', + 'ﲏ' => 'ني', + 'ﲐ' => 'ىٰ', + 'ﲑ' => 'ير', + 'ﲒ' => 'يز', + 'ﲓ' => 'يم', + 'ﲔ' => 'ين', + 'ﲕ' => 'يى', + 'ﲖ' => 'يي', + 'ﲗ' => 'ئج', + 'ﲘ' => 'ئح', + 'ﲙ' => 'ئخ', + 'ﲚ' => 'ئم', + 'ﲛ' => 'ئه', + 'ﲜ' => 'بج', + 'ﲝ' => 'بح', + 'ﲞ' => 'بخ', + 'ﲟ' => 'بم', + 'ﲠ' => 'به', + 'ﲡ' => 'تج', + 'ﲢ' => 'تح', + 'ﲣ' => 'تخ', + 'ﲤ' => 'تم', + 'ﲥ' => 'ته', + 'ﲦ' => 'ثم', + 'ﲧ' => 'جح', + 'ﲨ' => 'جم', + 'ﲩ' => 'حج', + 'ﲪ' => 'حم', + 'ﲫ' => 'خج', + 'ﲬ' => 'خم', + 'ﲭ' => 'سج', + 'ﲮ' => 'سح', + 'ﲯ' => 'سخ', + 'ﲰ' => 'سم', + 'ﲱ' => 'صح', + 'ﲲ' => 'صخ', + 'ﲳ' => 'صم', + 'ﲴ' => 'ضج', + 'ﲵ' => 'ضح', + 'ﲶ' => 'ضخ', + 'ﲷ' => 'ضم', + 'ﲸ' => 'طح', + 'ﲹ' => 'ظم', + 'ﲺ' => 'عج', + 'ﲻ' => 'عم', + 'ﲼ' => 'غج', + 'ﲽ' => 'غم', + 'ﲾ' => 'فج', + 'ﲿ' => 'فح', + 'ﳀ' => 'فخ', + 'ﳁ' => 'فم', + 'ﳂ' => 'قح', + 'ﳃ' => 'قم', + 'ﳄ' => 'كج', + 'ﳅ' => 'كح', + 'ﳆ' => 'كخ', + 'ﳇ' => 'كل', + 'ﳈ' => 'كم', + 'ﳉ' => 'لج', + 'ﳊ' => 'لح', + 'ﳋ' => 'لخ', + 'ﳌ' => 'لم', + 'ﳍ' => 'له', + 'ﳎ' => 'مج', + 'ﳏ' => 'مح', + 'ﳐ' => 'مخ', + 'ﳑ' => 'مم', + 'ﳒ' => 'نج', + 'ﳓ' => 'نح', + 'ﳔ' => 'نخ', + 'ﳕ' => 'نم', + 'ﳖ' => 'نه', + 'ﳗ' => 'هج', + 'ﳘ' => 'هم', + 'ﳙ' => 'هٰ', + 'ﳚ' => 'يج', + 'ﳛ' => 'يح', + 'ﳜ' => 'يخ', + 'ﳝ' => 'يم', + 'ﳞ' => 'يه', + 'ﳟ' => 'ئم', + 'ﳠ' => 'ئه', + 'ﳡ' => 'بم', + 'ﳢ' => 'به', + 'ﳣ' => 'تم', + 'ﳤ' => 'ته', + 'ﳥ' => 'ثم', + 'ﳦ' => 'ثه', + 'ﳧ' => 'سم', + 'ﳨ' => 'سه', + 'ﳩ' => 'شم', + 'ﳪ' => 'شه', + 'ﳫ' => 'كل', + 'ﳬ' => 'كم', + 'ﳭ' => 'لم', + 'ﳮ' => 'نم', + 'ﳯ' => 'نه', + 'ﳰ' => 'يم', + 'ﳱ' => 'يه', + 'ﳲ' => 'ـَّ', + 'ﳳ' => 'ـُّ', + 'ﳴ' => 'ـِّ', + 'ﳵ' => 'طى', + 'ﳶ' => 'طي', + 'ﳷ' => 'عى', + 'ﳸ' => 'عي', + 'ﳹ' => 'غى', + 'ﳺ' => 'غي', + 'ﳻ' => 'سى', + 'ﳼ' => 'سي', + 'ﳽ' => 'شى', + 'ﳾ' => 'شي', + 'ﳿ' => 'حى', + 'ﴀ' => 'حي', + 'ﴁ' => 'جى', + 'ﴂ' => 'جي', + 'ﴃ' => 'خى', + 'ﴄ' => 'خي', + 'ﴅ' => 'صى', + 'ﴆ' => 'صي', + 'ﴇ' => 'ضى', + 'ﴈ' => 'ضي', + 'ﴉ' => 'شج', + 'ﴊ' => 'شح', + 'ﴋ' => 'شخ', + 'ﴌ' => 'شم', + 'ﴍ' => 'شر', + 'ﴎ' => 'سر', + 'ﴏ' => 'صر', + 'ﴐ' => 'ضر', + 'ﴑ' => 'طى', + 'ﴒ' => 'طي', + 'ﴓ' => 'عى', + 'ﴔ' => 'عي', + 'ﴕ' => 'غى', + 'ﴖ' => 'غي', + 'ﴗ' => 'سى', + 'ﴘ' => 'سي', + 'ﴙ' => 'شى', + 'ﴚ' => 'شي', + 'ﴛ' => 'حى', + 'ﴜ' => 'حي', + 'ﴝ' => 'جى', + 'ﴞ' => 'جي', + 'ﴟ' => 'خى', + 'ﴠ' => 'خي', + 'ﴡ' => 'صى', + 'ﴢ' => 'صي', + 'ﴣ' => 'ضى', + 'ﴤ' => 'ضي', + 'ﴥ' => 'شج', + 'ﴦ' => 'شح', + 'ﴧ' => 'شخ', + 'ﴨ' => 'شم', + 'ﴩ' => 'شر', + 'ﴪ' => 'سر', + 'ﴫ' => 'صر', + 'ﴬ' => 'ضر', + 'ﴭ' => 'شج', + 'ﴮ' => 'شح', + 'ﴯ' => 'شخ', + 'ﴰ' => 'شم', + 'ﴱ' => 'سه', + 'ﴲ' => 'شه', + 'ﴳ' => 'طم', + 'ﴴ' => 'سج', + 'ﴵ' => 'سح', + 'ﴶ' => 'سخ', + 'ﴷ' => 'شج', + 'ﴸ' => 'شح', + 'ﴹ' => 'شخ', + 'ﴺ' => 'طم', + 'ﴻ' => 'ظم', + 'ﴼ' => 'اً', + 'ﴽ' => 'اً', + 'ﵐ' => 'تجم', + 'ﵑ' => 'تحج', + 'ﵒ' => 'تحج', + 'ﵓ' => 'تحم', + 'ﵔ' => 'تخم', + 'ﵕ' => 'تمج', + 'ﵖ' => 'تمح', + 'ﵗ' => 'تمخ', + 'ﵘ' => 'جمح', + 'ﵙ' => 'جمح', + 'ﵚ' => 'حمي', + 'ﵛ' => 'حمى', + 'ﵜ' => 'سحج', + 'ﵝ' => 'سجح', + 'ﵞ' => 'سجى', + 'ﵟ' => 'سمح', + 'ﵠ' => 'سمح', + 'ﵡ' => 'سمج', + 'ﵢ' => 'سمم', + 'ﵣ' => 'سمم', + 'ﵤ' => 'صحح', + 'ﵥ' => 'صحح', + 'ﵦ' => 'صمم', + 'ﵧ' => 'شحم', + 'ﵨ' => 'شحم', + 'ﵩ' => 'شجي', + 'ﵪ' => 'شمخ', + 'ﵫ' => 'شمخ', + 'ﵬ' => 'شمم', + 'ﵭ' => 'شمم', + 'ﵮ' => 'ضحى', + 'ﵯ' => 'ضخم', + 'ﵰ' => 'ضخم', + 'ﵱ' => 'طمح', + 'ﵲ' => 'طمح', + 'ﵳ' => 'طمم', + 'ﵴ' => 'طمي', + 'ﵵ' => 'عجم', + 'ﵶ' => 'عمم', + 'ﵷ' => 'عمم', + 'ﵸ' => 'عمى', + 'ﵹ' => 'غمم', + 'ﵺ' => 'غمي', + 'ﵻ' => 'غمى', + 'ﵼ' => 'فخم', + 'ﵽ' => 'فخم', + 'ﵾ' => 'قمح', + 'ﵿ' => 'قمم', + 'ﶀ' => 'لحم', + 'ﶁ' => 'لحي', + 'ﶂ' => 'لحى', + 'ﶃ' => 'لجج', + 'ﶄ' => 'لجج', + 'ﶅ' => 'لخم', + 'ﶆ' => 'لخم', + 'ﶇ' => 'لمح', + 'ﶈ' => 'لمح', + 'ﶉ' => 'محج', + 'ﶊ' => 'محم', + 'ﶋ' => 'محي', + 'ﶌ' => 'مجح', + 'ﶍ' => 'مجم', + 'ﶎ' => 'مخج', + 'ﶏ' => 'مخم', + 'ﶒ' => 'مجخ', + 'ﶓ' => 'همج', + 'ﶔ' => 'همم', + 'ﶕ' => 'نحم', + 'ﶖ' => 'نحى', + 'ﶗ' => 'نجم', + 'ﶘ' => 'نجم', + 'ﶙ' => 'نجى', + 'ﶚ' => 'نمي', + 'ﶛ' => 'نمى', + 'ﶜ' => 'يمم', + 'ﶝ' => 'يمم', + 'ﶞ' => 'بخي', + 'ﶟ' => 'تجي', + 'ﶠ' => 'تجى', + 'ﶡ' => 'تخي', + 'ﶢ' => 'تخى', + 'ﶣ' => 'تمي', + 'ﶤ' => 'تمى', + 'ﶥ' => 'جمي', + 'ﶦ' => 'جحى', + 'ﶧ' => 'جمى', + 'ﶨ' => 'سخى', + 'ﶩ' => 'صحي', + 'ﶪ' => 'شحي', + 'ﶫ' => 'ضحي', + 'ﶬ' => 'لجي', + 'ﶭ' => 'لمي', + 'ﶮ' => 'يحي', + 'ﶯ' => 'يجي', + 'ﶰ' => 'يمي', + 'ﶱ' => 'ممي', + 'ﶲ' => 'قمي', + 'ﶳ' => 'نحي', + 'ﶴ' => 'قمح', + 'ﶵ' => 'لحم', + 'ﶶ' => 'عمي', + 'ﶷ' => 'كمي', + 'ﶸ' => 'نجح', + 'ﶹ' => 'مخي', + 'ﶺ' => 'لجم', + 'ﶻ' => 'كمم', + 'ﶼ' => 'لجم', + 'ﶽ' => 'نجح', + 'ﶾ' => 'جحي', + 'ﶿ' => 'حجي', + 'ﷀ' => 'مجي', + 'ﷁ' => 'فمي', + 'ﷂ' => 'بحي', + 'ﷃ' => 'كمم', + 'ﷄ' => 'عجم', + 'ﷅ' => 'صمم', + 'ﷆ' => 'سخي', + 'ﷇ' => 'نجي', + 'ﷰ' => 'صلے', + 'ﷱ' => 'قلے', + 'ﷲ' => 'الله', + 'ﷳ' => 'اكبر', + 'ﷴ' => 'محمد', + 'ﷵ' => 'صلعم', + 'ﷶ' => 'رسول', + 'ﷷ' => 'عليه', + 'ﷸ' => 'وسلم', + 'ﷹ' => 'صلى', + 'ﷺ' => 'صلى الله عليه وسلم', + 'ﷻ' => 'جل جلاله', + '﷼' => 'ریال', + '︐' => ',', + '︑' => '、', + '︒' => '。', + '︓' => ':', + '︔' => ';', + '︕' => '!', + '︖' => '?', + '︗' => '〖', + '︘' => '〗', + '︙' => '...', + '︰' => '..', + '︱' => '—', + '︲' => '–', + '︳' => '_', + '︴' => '_', + '︵' => '(', + '︶' => ')', + '︷' => '{', + '︸' => '}', + '︹' => '〔', + '︺' => '〕', + '︻' => '【', + '︼' => '】', + '︽' => '《', + '︾' => '》', + '︿' => '〈', + '﹀' => '〉', + '﹁' => '「', + '﹂' => '」', + '﹃' => '『', + '﹄' => '』', + '﹇' => '[', + '﹈' => ']', + '﹉' => ' ̅', + '﹊' => ' ̅', + '﹋' => ' ̅', + '﹌' => ' ̅', + '﹍' => '_', + '﹎' => '_', + '﹏' => '_', + '﹐' => ',', + '﹑' => '、', + '﹒' => '.', + '﹔' => ';', + '﹕' => ':', + '﹖' => '?', + '﹗' => '!', + '﹘' => '—', + '﹙' => '(', + '﹚' => ')', + '﹛' => '{', + '﹜' => '}', + '﹝' => '〔', + '﹞' => '〕', + '﹟' => '#', + '﹠' => '&', + '﹡' => '*', + '﹢' => '+', + '﹣' => '-', + '﹤' => '<', + '﹥' => '>', + '﹦' => '=', + '﹨' => '\\', + '﹩' => '$', + '﹪' => '%', + '﹫' => '@', + 'ﹰ' => ' ً', + 'ﹱ' => 'ـً', + 'ﹲ' => ' ٌ', + 'ﹴ' => ' ٍ', + 'ﹶ' => ' َ', + 'ﹷ' => 'ـَ', + 'ﹸ' => ' ُ', + 'ﹹ' => 'ـُ', + 'ﹺ' => ' ِ', + 'ﹻ' => 'ـِ', + 'ﹼ' => ' ّ', + 'ﹽ' => 'ـّ', + 'ﹾ' => ' ْ', + 'ﹿ' => 'ـْ', + 'ﺀ' => 'ء', + 'ﺁ' => 'آ', + 'ﺂ' => 'آ', + 'ﺃ' => 'أ', + 'ﺄ' => 'أ', + 'ﺅ' => 'ؤ', + 'ﺆ' => 'ؤ', + 'ﺇ' => 'إ', + 'ﺈ' => 'إ', + 'ﺉ' => 'ئ', + 'ﺊ' => 'ئ', + 'ﺋ' => 'ئ', + 'ﺌ' => 'ئ', + 'ﺍ' => 'ا', + 'ﺎ' => 'ا', + 'ﺏ' => 'ب', + 'ﺐ' => 'ب', + 'ﺑ' => 'ب', + 'ﺒ' => 'ب', + 'ﺓ' => 'ة', + 'ﺔ' => 'ة', + 'ﺕ' => 'ت', + 'ﺖ' => 'ت', + 'ﺗ' => 'ت', + 'ﺘ' => 'ت', + 'ﺙ' => 'ث', + 'ﺚ' => 'ث', + 'ﺛ' => 'ث', + 'ﺜ' => 'ث', + 'ﺝ' => 'ج', + 'ﺞ' => 'ج', + 'ﺟ' => 'ج', + 'ﺠ' => 'ج', + 'ﺡ' => 'ح', + 'ﺢ' => 'ح', + 'ﺣ' => 'ح', + 'ﺤ' => 'ح', + 'ﺥ' => 'خ', + 'ﺦ' => 'خ', + 'ﺧ' => 'خ', + 'ﺨ' => 'خ', + 'ﺩ' => 'د', + 'ﺪ' => 'د', + 'ﺫ' => 'ذ', + 'ﺬ' => 'ذ', + 'ﺭ' => 'ر', + 'ﺮ' => 'ر', + 'ﺯ' => 'ز', + 'ﺰ' => 'ز', + 'ﺱ' => 'س', + 'ﺲ' => 'س', + 'ﺳ' => 'س', + 'ﺴ' => 'س', + 'ﺵ' => 'ش', + 'ﺶ' => 'ش', + 'ﺷ' => 'ش', + 'ﺸ' => 'ش', + 'ﺹ' => 'ص', + 'ﺺ' => 'ص', + 'ﺻ' => 'ص', + 'ﺼ' => 'ص', + 'ﺽ' => 'ض', + 'ﺾ' => 'ض', + 'ﺿ' => 'ض', + 'ﻀ' => 'ض', + 'ﻁ' => 'ط', + 'ﻂ' => 'ط', + 'ﻃ' => 'ط', + 'ﻄ' => 'ط', + 'ﻅ' => 'ظ', + 'ﻆ' => 'ظ', + 'ﻇ' => 'ظ', + 'ﻈ' => 'ظ', + 'ﻉ' => 'ع', + 'ﻊ' => 'ع', + 'ﻋ' => 'ع', + 'ﻌ' => 'ع', + 'ﻍ' => 'غ', + 'ﻎ' => 'غ', + 'ﻏ' => 'غ', + 'ﻐ' => 'غ', + 'ﻑ' => 'ف', + 'ﻒ' => 'ف', + 'ﻓ' => 'ف', + 'ﻔ' => 'ف', + 'ﻕ' => 'ق', + 'ﻖ' => 'ق', + 'ﻗ' => 'ق', + 'ﻘ' => 'ق', + 'ﻙ' => 'ك', + 'ﻚ' => 'ك', + 'ﻛ' => 'ك', + 'ﻜ' => 'ك', + 'ﻝ' => 'ل', + 'ﻞ' => 'ل', + 'ﻟ' => 'ل', + 'ﻠ' => 'ل', + 'ﻡ' => 'م', + 'ﻢ' => 'م', + 'ﻣ' => 'م', + 'ﻤ' => 'م', + 'ﻥ' => 'ن', + 'ﻦ' => 'ن', + 'ﻧ' => 'ن', + 'ﻨ' => 'ن', + 'ﻩ' => 'ه', + 'ﻪ' => 'ه', + 'ﻫ' => 'ه', + 'ﻬ' => 'ه', + 'ﻭ' => 'و', + 'ﻮ' => 'و', + 'ﻯ' => 'ى', + 'ﻰ' => 'ى', + 'ﻱ' => 'ي', + 'ﻲ' => 'ي', + 'ﻳ' => 'ي', + 'ﻴ' => 'ي', + 'ﻵ' => 'لآ', + 'ﻶ' => 'لآ', + 'ﻷ' => 'لأ', + 'ﻸ' => 'لأ', + 'ﻹ' => 'لإ', + 'ﻺ' => 'لإ', + 'ﻻ' => 'لا', + 'ﻼ' => 'لا', + '!' => '!', + '"' => '"', + '#' => '#', + '$' => '$', + '%' => '%', + '&' => '&', + ''' => '\'', + '(' => '(', + ')' => ')', + '*' => '*', + '+' => '+', + ',' => ',', + '-' => '-', + '.' => '.', + '/' => '/', + '0' => '0', + '1' => '1', + '2' => '2', + '3' => '3', + '4' => '4', + '5' => '5', + '6' => '6', + '7' => '7', + '8' => '8', + '9' => '9', + ':' => ':', + ';' => ';', + '<' => '<', + '=' => '=', + '>' => '>', + '?' => '?', + '@' => '@', + 'A' => 'A', + 'B' => 'B', + 'C' => 'C', + 'D' => 'D', + 'E' => 'E', + 'F' => 'F', + 'G' => 'G', + 'H' => 'H', + 'I' => 'I', + 'J' => 'J', + 'K' => 'K', + 'L' => 'L', + 'M' => 'M', + 'N' => 'N', + 'O' => 'O', + 'P' => 'P', + 'Q' => 'Q', + 'R' => 'R', + 'S' => 'S', + 'T' => 'T', + 'U' => 'U', + 'V' => 'V', + 'W' => 'W', + 'X' => 'X', + 'Y' => 'Y', + 'Z' => 'Z', + '[' => '[', + '\' => '\\', + ']' => ']', + '^' => '^', + '_' => '_', + '`' => '`', + 'a' => 'a', + 'b' => 'b', + 'c' => 'c', + 'd' => 'd', + 'e' => 'e', + 'f' => 'f', + 'g' => 'g', + 'h' => 'h', + 'i' => 'i', + 'j' => 'j', + 'k' => 'k', + 'l' => 'l', + 'm' => 'm', + 'n' => 'n', + 'o' => 'o', + 'p' => 'p', + 'q' => 'q', + 'r' => 'r', + 's' => 's', + 't' => 't', + 'u' => 'u', + 'v' => 'v', + 'w' => 'w', + 'x' => 'x', + 'y' => 'y', + 'z' => 'z', + '{' => '{', + '|' => '|', + '}' => '}', + '~' => '~', + '⦅' => '⦅', + '⦆' => '⦆', + '。' => '。', + '「' => '「', + '」' => '」', + '、' => '、', + '・' => '・', + 'ヲ' => 'ヲ', + 'ァ' => 'ァ', + 'ィ' => 'ィ', + 'ゥ' => 'ゥ', + 'ェ' => 'ェ', + 'ォ' => 'ォ', + 'ャ' => 'ャ', + 'ュ' => 'ュ', + 'ョ' => 'ョ', + 'ッ' => 'ッ', + 'ー' => 'ー', + 'ア' => 'ア', + 'イ' => 'イ', + 'ウ' => 'ウ', + 'エ' => 'エ', + 'オ' => 'オ', + 'カ' => 'カ', + 'キ' => 'キ', + 'ク' => 'ク', + 'ケ' => 'ケ', + 'コ' => 'コ', + 'サ' => 'サ', + 'シ' => 'シ', + 'ス' => 'ス', + 'セ' => 'セ', + 'ソ' => 'ソ', + 'タ' => 'タ', + 'チ' => 'チ', + 'ツ' => 'ツ', + 'テ' => 'テ', + 'ト' => 'ト', + 'ナ' => 'ナ', + 'ニ' => 'ニ', + 'ヌ' => 'ヌ', + 'ネ' => 'ネ', + 'ノ' => 'ノ', + 'ハ' => 'ハ', + 'ヒ' => 'ヒ', + 'フ' => 'フ', + 'ヘ' => 'ヘ', + 'ホ' => 'ホ', + 'マ' => 'マ', + 'ミ' => 'ミ', + 'ム' => 'ム', + 'メ' => 'メ', + 'モ' => 'モ', + 'ヤ' => 'ヤ', + 'ユ' => 'ユ', + 'ヨ' => 'ヨ', + 'ラ' => 'ラ', + 'リ' => 'リ', + 'ル' => 'ル', + 'レ' => 'レ', + 'ロ' => 'ロ', + 'ワ' => 'ワ', + 'ン' => 'ン', + '゙' => '゙', + '゚' => '゚', + 'ᅠ' => 'ᅠ', + 'ᄀ' => 'ᄀ', + 'ᄁ' => 'ᄁ', + 'ᆪ' => 'ᆪ', + 'ᄂ' => 'ᄂ', + 'ᆬ' => 'ᆬ', + 'ᆭ' => 'ᆭ', + 'ᄃ' => 'ᄃ', + 'ᄄ' => 'ᄄ', + 'ᄅ' => 'ᄅ', + 'ᆰ' => 'ᆰ', + 'ᆱ' => 'ᆱ', + 'ᆲ' => 'ᆲ', + 'ᆳ' => 'ᆳ', + 'ᆴ' => 'ᆴ', + 'ᆵ' => 'ᆵ', + 'ᄚ' => 'ᄚ', + 'ᄆ' => 'ᄆ', + 'ᄇ' => 'ᄇ', + 'ᄈ' => 'ᄈ', + 'ᄡ' => 'ᄡ', + 'ᄉ' => 'ᄉ', + 'ᄊ' => 'ᄊ', + 'ᄋ' => 'ᄋ', + 'ᄌ' => 'ᄌ', + 'ᄍ' => 'ᄍ', + 'ᄎ' => 'ᄎ', + 'ᄏ' => 'ᄏ', + 'ᄐ' => 'ᄐ', + 'ᄑ' => 'ᄑ', + 'ᄒ' => 'ᄒ', + 'ᅡ' => 'ᅡ', + 'ᅢ' => 'ᅢ', + 'ᅣ' => 'ᅣ', + 'ᅤ' => 'ᅤ', + 'ᅥ' => 'ᅥ', + 'ᅦ' => 'ᅦ', + 'ᅧ' => 'ᅧ', + 'ᅨ' => 'ᅨ', + 'ᅩ' => 'ᅩ', + 'ᅪ' => 'ᅪ', + 'ᅫ' => 'ᅫ', + 'ᅬ' => 'ᅬ', + 'ᅭ' => 'ᅭ', + 'ᅮ' => 'ᅮ', + 'ᅯ' => 'ᅯ', + 'ᅰ' => 'ᅰ', + 'ᅱ' => 'ᅱ', + 'ᅲ' => 'ᅲ', + 'ᅳ' => 'ᅳ', + 'ᅴ' => 'ᅴ', + 'ᅵ' => 'ᅵ', + '¢' => '¢', + '£' => '£', + '¬' => '¬', + ' ̄' => ' ̄', + '¦' => '¦', + '¥' => '¥', + '₩' => '₩', + '│' => '│', + '←' => '←', + '↑' => '↑', + '→' => '→', + '↓' => '↓', + '■' => '■', + '○' => '○', + '𝐀' => 'A', + '𝐁' => 'B', + '𝐂' => 'C', + '𝐃' => 'D', + '𝐄' => 'E', + '𝐅' => 'F', + '𝐆' => 'G', + '𝐇' => 'H', + '𝐈' => 'I', + '𝐉' => 'J', + '𝐊' => 'K', + '𝐋' => 'L', + '𝐌' => 'M', + '𝐍' => 'N', + '𝐎' => 'O', + '𝐏' => 'P', + '𝐐' => 'Q', + '𝐑' => 'R', + '𝐒' => 'S', + '𝐓' => 'T', + '𝐔' => 'U', + '𝐕' => 'V', + '𝐖' => 'W', + '𝐗' => 'X', + '𝐘' => 'Y', + '𝐙' => 'Z', + '𝐚' => 'a', + '𝐛' => 'b', + '𝐜' => 'c', + '𝐝' => 'd', + '𝐞' => 'e', + '𝐟' => 'f', + '𝐠' => 'g', + '𝐡' => 'h', + '𝐢' => 'i', + '𝐣' => 'j', + '𝐤' => 'k', + '𝐥' => 'l', + '𝐦' => 'm', + '𝐧' => 'n', + '𝐨' => 'o', + '𝐩' => 'p', + '𝐪' => 'q', + '𝐫' => 'r', + '𝐬' => 's', + '𝐭' => 't', + '𝐮' => 'u', + '𝐯' => 'v', + '𝐰' => 'w', + '𝐱' => 'x', + '𝐲' => 'y', + '𝐳' => 'z', + '𝐴' => 'A', + '𝐵' => 'B', + '𝐶' => 'C', + '𝐷' => 'D', + '𝐸' => 'E', + '𝐹' => 'F', + '𝐺' => 'G', + '𝐻' => 'H', + '𝐼' => 'I', + '𝐽' => 'J', + '𝐾' => 'K', + '𝐿' => 'L', + '𝑀' => 'M', + '𝑁' => 'N', + '𝑂' => 'O', + '𝑃' => 'P', + '𝑄' => 'Q', + '𝑅' => 'R', + '𝑆' => 'S', + '𝑇' => 'T', + '𝑈' => 'U', + '𝑉' => 'V', + '𝑊' => 'W', + '𝑋' => 'X', + '𝑌' => 'Y', + '𝑍' => 'Z', + '𝑎' => 'a', + '𝑏' => 'b', + '𝑐' => 'c', + '𝑑' => 'd', + '𝑒' => 'e', + '𝑓' => 'f', + '𝑔' => 'g', + '𝑖' => 'i', + '𝑗' => 'j', + '𝑘' => 'k', + '𝑙' => 'l', + '𝑚' => 'm', + '𝑛' => 'n', + '𝑜' => 'o', + '𝑝' => 'p', + '𝑞' => 'q', + '𝑟' => 'r', + '𝑠' => 's', + '𝑡' => 't', + '𝑢' => 'u', + '𝑣' => 'v', + '𝑤' => 'w', + '𝑥' => 'x', + '𝑦' => 'y', + '𝑧' => 'z', + '𝑨' => 'A', + '𝑩' => 'B', + '𝑪' => 'C', + '𝑫' => 'D', + '𝑬' => 'E', + '𝑭' => 'F', + '𝑮' => 'G', + '𝑯' => 'H', + '𝑰' => 'I', + '𝑱' => 'J', + '𝑲' => 'K', + '𝑳' => 'L', + '𝑴' => 'M', + '𝑵' => 'N', + '𝑶' => 'O', + '𝑷' => 'P', + '𝑸' => 'Q', + '𝑹' => 'R', + '𝑺' => 'S', + '𝑻' => 'T', + '𝑼' => 'U', + '𝑽' => 'V', + '𝑾' => 'W', + '𝑿' => 'X', + '𝒀' => 'Y', + '𝒁' => 'Z', + '𝒂' => 'a', + '𝒃' => 'b', + '𝒄' => 'c', + '𝒅' => 'd', + '𝒆' => 'e', + '𝒇' => 'f', + '𝒈' => 'g', + '𝒉' => 'h', + '𝒊' => 'i', + '𝒋' => 'j', + '𝒌' => 'k', + '𝒍' => 'l', + '𝒎' => 'm', + '𝒏' => 'n', + '𝒐' => 'o', + '𝒑' => 'p', + '𝒒' => 'q', + '𝒓' => 'r', + '𝒔' => 's', + '𝒕' => 't', + '𝒖' => 'u', + '𝒗' => 'v', + '𝒘' => 'w', + '𝒙' => 'x', + '𝒚' => 'y', + '𝒛' => 'z', + '𝒜' => 'A', + '𝒞' => 'C', + '𝒟' => 'D', + '𝒢' => 'G', + '𝒥' => 'J', + '𝒦' => 'K', + '𝒩' => 'N', + '𝒪' => 'O', + '𝒫' => 'P', + '𝒬' => 'Q', + '𝒮' => 'S', + '𝒯' => 'T', + '𝒰' => 'U', + '𝒱' => 'V', + '𝒲' => 'W', + '𝒳' => 'X', + '𝒴' => 'Y', + '𝒵' => 'Z', + '𝒶' => 'a', + '𝒷' => 'b', + '𝒸' => 'c', + '𝒹' => 'd', + '𝒻' => 'f', + '𝒽' => 'h', + '𝒾' => 'i', + '𝒿' => 'j', + '𝓀' => 'k', + '𝓁' => 'l', + '𝓂' => 'm', + '𝓃' => 'n', + '𝓅' => 'p', + '𝓆' => 'q', + '𝓇' => 'r', + '𝓈' => 's', + '𝓉' => 't', + '𝓊' => 'u', + '𝓋' => 'v', + '𝓌' => 'w', + '𝓍' => 'x', + '𝓎' => 'y', + '𝓏' => 'z', + '𝓐' => 'A', + '𝓑' => 'B', + '𝓒' => 'C', + '𝓓' => 'D', + '𝓔' => 'E', + '𝓕' => 'F', + '𝓖' => 'G', + '𝓗' => 'H', + '𝓘' => 'I', + '𝓙' => 'J', + '𝓚' => 'K', + '𝓛' => 'L', + '𝓜' => 'M', + '𝓝' => 'N', + '𝓞' => 'O', + '𝓟' => 'P', + '𝓠' => 'Q', + '𝓡' => 'R', + '𝓢' => 'S', + '𝓣' => 'T', + '𝓤' => 'U', + '𝓥' => 'V', + '𝓦' => 'W', + '𝓧' => 'X', + '𝓨' => 'Y', + '𝓩' => 'Z', + '𝓪' => 'a', + '𝓫' => 'b', + '𝓬' => 'c', + '𝓭' => 'd', + '𝓮' => 'e', + '𝓯' => 'f', + '𝓰' => 'g', + '𝓱' => 'h', + '𝓲' => 'i', + '𝓳' => 'j', + '𝓴' => 'k', + '𝓵' => 'l', + '𝓶' => 'm', + '𝓷' => 'n', + '𝓸' => 'o', + '𝓹' => 'p', + '𝓺' => 'q', + '𝓻' => 'r', + '𝓼' => 's', + '𝓽' => 't', + '𝓾' => 'u', + '𝓿' => 'v', + '𝔀' => 'w', + '𝔁' => 'x', + '𝔂' => 'y', + '𝔃' => 'z', + '𝔄' => 'A', + '𝔅' => 'B', + '𝔇' => 'D', + '𝔈' => 'E', + '𝔉' => 'F', + '𝔊' => 'G', + '𝔍' => 'J', + '𝔎' => 'K', + '𝔏' => 'L', + '𝔐' => 'M', + '𝔑' => 'N', + '𝔒' => 'O', + '𝔓' => 'P', + '𝔔' => 'Q', + '𝔖' => 'S', + '𝔗' => 'T', + '𝔘' => 'U', + '𝔙' => 'V', + '𝔚' => 'W', + '𝔛' => 'X', + '𝔜' => 'Y', + '𝔞' => 'a', + '𝔟' => 'b', + '𝔠' => 'c', + '𝔡' => 'd', + '𝔢' => 'e', + '𝔣' => 'f', + '𝔤' => 'g', + '𝔥' => 'h', + '𝔦' => 'i', + '𝔧' => 'j', + '𝔨' => 'k', + '𝔩' => 'l', + '𝔪' => 'm', + '𝔫' => 'n', + '𝔬' => 'o', + '𝔭' => 'p', + '𝔮' => 'q', + '𝔯' => 'r', + '𝔰' => 's', + '𝔱' => 't', + '𝔲' => 'u', + '𝔳' => 'v', + '𝔴' => 'w', + '𝔵' => 'x', + '𝔶' => 'y', + '𝔷' => 'z', + '𝔸' => 'A', + '𝔹' => 'B', + '𝔻' => 'D', + '𝔼' => 'E', + '𝔽' => 'F', + '𝔾' => 'G', + '𝕀' => 'I', + '𝕁' => 'J', + '𝕂' => 'K', + '𝕃' => 'L', + '𝕄' => 'M', + '𝕆' => 'O', + '𝕊' => 'S', + '𝕋' => 'T', + '𝕌' => 'U', + '𝕍' => 'V', + '𝕎' => 'W', + '𝕏' => 'X', + '𝕐' => 'Y', + '𝕒' => 'a', + '𝕓' => 'b', + '𝕔' => 'c', + '𝕕' => 'd', + '𝕖' => 'e', + '𝕗' => 'f', + '𝕘' => 'g', + '𝕙' => 'h', + '𝕚' => 'i', + '𝕛' => 'j', + '𝕜' => 'k', + '𝕝' => 'l', + '𝕞' => 'm', + '𝕟' => 'n', + '𝕠' => 'o', + '𝕡' => 'p', + '𝕢' => 'q', + '𝕣' => 'r', + '𝕤' => 's', + '𝕥' => 't', + '𝕦' => 'u', + '𝕧' => 'v', + '𝕨' => 'w', + '𝕩' => 'x', + '𝕪' => 'y', + '𝕫' => 'z', + '𝕬' => 'A', + '𝕭' => 'B', + '𝕮' => 'C', + '𝕯' => 'D', + '𝕰' => 'E', + '𝕱' => 'F', + '𝕲' => 'G', + '𝕳' => 'H', + '𝕴' => 'I', + '𝕵' => 'J', + '𝕶' => 'K', + '𝕷' => 'L', + '𝕸' => 'M', + '𝕹' => 'N', + '𝕺' => 'O', + '𝕻' => 'P', + '𝕼' => 'Q', + '𝕽' => 'R', + '𝕾' => 'S', + '𝕿' => 'T', + '𝖀' => 'U', + '𝖁' => 'V', + '𝖂' => 'W', + '𝖃' => 'X', + '𝖄' => 'Y', + '𝖅' => 'Z', + '𝖆' => 'a', + '𝖇' => 'b', + '𝖈' => 'c', + '𝖉' => 'd', + '𝖊' => 'e', + '𝖋' => 'f', + '𝖌' => 'g', + '𝖍' => 'h', + '𝖎' => 'i', + '𝖏' => 'j', + '𝖐' => 'k', + '𝖑' => 'l', + '𝖒' => 'm', + '𝖓' => 'n', + '𝖔' => 'o', + '𝖕' => 'p', + '𝖖' => 'q', + '𝖗' => 'r', + '𝖘' => 's', + '𝖙' => 't', + '𝖚' => 'u', + '𝖛' => 'v', + '𝖜' => 'w', + '𝖝' => 'x', + '𝖞' => 'y', + '𝖟' => 'z', + '𝖠' => 'A', + '𝖡' => 'B', + '𝖢' => 'C', + '𝖣' => 'D', + '𝖤' => 'E', + '𝖥' => 'F', + '𝖦' => 'G', + '𝖧' => 'H', + '𝖨' => 'I', + '𝖩' => 'J', + '𝖪' => 'K', + '𝖫' => 'L', + '𝖬' => 'M', + '𝖭' => 'N', + '𝖮' => 'O', + '𝖯' => 'P', + '𝖰' => 'Q', + '𝖱' => 'R', + '𝖲' => 'S', + '𝖳' => 'T', + '𝖴' => 'U', + '𝖵' => 'V', + '𝖶' => 'W', + '𝖷' => 'X', + '𝖸' => 'Y', + '𝖹' => 'Z', + '𝖺' => 'a', + '𝖻' => 'b', + '𝖼' => 'c', + '𝖽' => 'd', + '𝖾' => 'e', + '𝖿' => 'f', + '𝗀' => 'g', + '𝗁' => 'h', + '𝗂' => 'i', + '𝗃' => 'j', + '𝗄' => 'k', + '𝗅' => 'l', + '𝗆' => 'm', + '𝗇' => 'n', + '𝗈' => 'o', + '𝗉' => 'p', + '𝗊' => 'q', + '𝗋' => 'r', + '𝗌' => 's', + '𝗍' => 't', + '𝗎' => 'u', + '𝗏' => 'v', + '𝗐' => 'w', + '𝗑' => 'x', + '𝗒' => 'y', + '𝗓' => 'z', + '𝗔' => 'A', + '𝗕' => 'B', + '𝗖' => 'C', + '𝗗' => 'D', + '𝗘' => 'E', + '𝗙' => 'F', + '𝗚' => 'G', + '𝗛' => 'H', + '𝗜' => 'I', + '𝗝' => 'J', + '𝗞' => 'K', + '𝗟' => 'L', + '𝗠' => 'M', + '𝗡' => 'N', + '𝗢' => 'O', + '𝗣' => 'P', + '𝗤' => 'Q', + '𝗥' => 'R', + '𝗦' => 'S', + '𝗧' => 'T', + '𝗨' => 'U', + '𝗩' => 'V', + '𝗪' => 'W', + '𝗫' => 'X', + '𝗬' => 'Y', + '𝗭' => 'Z', + '𝗮' => 'a', + '𝗯' => 'b', + '𝗰' => 'c', + '𝗱' => 'd', + '𝗲' => 'e', + '𝗳' => 'f', + '𝗴' => 'g', + '𝗵' => 'h', + '𝗶' => 'i', + '𝗷' => 'j', + '𝗸' => 'k', + '𝗹' => 'l', + '𝗺' => 'm', + '𝗻' => 'n', + '𝗼' => 'o', + '𝗽' => 'p', + '𝗾' => 'q', + '𝗿' => 'r', + '𝘀' => 's', + '𝘁' => 't', + '𝘂' => 'u', + '𝘃' => 'v', + '𝘄' => 'w', + '𝘅' => 'x', + '𝘆' => 'y', + '𝘇' => 'z', + '𝘈' => 'A', + '𝘉' => 'B', + '𝘊' => 'C', + '𝘋' => 'D', + '𝘌' => 'E', + '𝘍' => 'F', + '𝘎' => 'G', + '𝘏' => 'H', + '𝘐' => 'I', + '𝘑' => 'J', + '𝘒' => 'K', + '𝘓' => 'L', + '𝘔' => 'M', + '𝘕' => 'N', + '𝘖' => 'O', + '𝘗' => 'P', + '𝘘' => 'Q', + '𝘙' => 'R', + '𝘚' => 'S', + '𝘛' => 'T', + '𝘜' => 'U', + '𝘝' => 'V', + '𝘞' => 'W', + '𝘟' => 'X', + '𝘠' => 'Y', + '𝘡' => 'Z', + '𝘢' => 'a', + '𝘣' => 'b', + '𝘤' => 'c', + '𝘥' => 'd', + '𝘦' => 'e', + '𝘧' => 'f', + '𝘨' => 'g', + '𝘩' => 'h', + '𝘪' => 'i', + '𝘫' => 'j', + '𝘬' => 'k', + '𝘭' => 'l', + '𝘮' => 'm', + '𝘯' => 'n', + '𝘰' => 'o', + '𝘱' => 'p', + '𝘲' => 'q', + '𝘳' => 'r', + '𝘴' => 's', + '𝘵' => 't', + '𝘶' => 'u', + '𝘷' => 'v', + '𝘸' => 'w', + '𝘹' => 'x', + '𝘺' => 'y', + '𝘻' => 'z', + '𝘼' => 'A', + '𝘽' => 'B', + '𝘾' => 'C', + '𝘿' => 'D', + '𝙀' => 'E', + '𝙁' => 'F', + '𝙂' => 'G', + '𝙃' => 'H', + '𝙄' => 'I', + '𝙅' => 'J', + '𝙆' => 'K', + '𝙇' => 'L', + '𝙈' => 'M', + '𝙉' => 'N', + '𝙊' => 'O', + '𝙋' => 'P', + '𝙌' => 'Q', + '𝙍' => 'R', + '𝙎' => 'S', + '𝙏' => 'T', + '𝙐' => 'U', + '𝙑' => 'V', + '𝙒' => 'W', + '𝙓' => 'X', + '𝙔' => 'Y', + '𝙕' => 'Z', + '𝙖' => 'a', + '𝙗' => 'b', + '𝙘' => 'c', + '𝙙' => 'd', + '𝙚' => 'e', + '𝙛' => 'f', + '𝙜' => 'g', + '𝙝' => 'h', + '𝙞' => 'i', + '𝙟' => 'j', + '𝙠' => 'k', + '𝙡' => 'l', + '𝙢' => 'm', + '𝙣' => 'n', + '𝙤' => 'o', + '𝙥' => 'p', + '𝙦' => 'q', + '𝙧' => 'r', + '𝙨' => 's', + '𝙩' => 't', + '𝙪' => 'u', + '𝙫' => 'v', + '𝙬' => 'w', + '𝙭' => 'x', + '𝙮' => 'y', + '𝙯' => 'z', + '𝙰' => 'A', + '𝙱' => 'B', + '𝙲' => 'C', + '𝙳' => 'D', + '𝙴' => 'E', + '𝙵' => 'F', + '𝙶' => 'G', + '𝙷' => 'H', + '𝙸' => 'I', + '𝙹' => 'J', + '𝙺' => 'K', + '𝙻' => 'L', + '𝙼' => 'M', + '𝙽' => 'N', + '𝙾' => 'O', + '𝙿' => 'P', + '𝚀' => 'Q', + '𝚁' => 'R', + '𝚂' => 'S', + '𝚃' => 'T', + '𝚄' => 'U', + '𝚅' => 'V', + '𝚆' => 'W', + '𝚇' => 'X', + '𝚈' => 'Y', + '𝚉' => 'Z', + '𝚊' => 'a', + '𝚋' => 'b', + '𝚌' => 'c', + '𝚍' => 'd', + '𝚎' => 'e', + '𝚏' => 'f', + '𝚐' => 'g', + '𝚑' => 'h', + '𝚒' => 'i', + '𝚓' => 'j', + '𝚔' => 'k', + '𝚕' => 'l', + '𝚖' => 'm', + '𝚗' => 'n', + '𝚘' => 'o', + '𝚙' => 'p', + '𝚚' => 'q', + '𝚛' => 'r', + '𝚜' => 's', + '𝚝' => 't', + '𝚞' => 'u', + '𝚟' => 'v', + '𝚠' => 'w', + '𝚡' => 'x', + '𝚢' => 'y', + '𝚣' => 'z', + '𝚤' => 'ı', + '𝚥' => 'ȷ', + '𝚨' => 'Α', + '𝚩' => 'Β', + '𝚪' => 'Γ', + '𝚫' => 'Δ', + '𝚬' => 'Ε', + '𝚭' => 'Ζ', + '𝚮' => 'Η', + '𝚯' => 'Θ', + '𝚰' => 'Ι', + '𝚱' => 'Κ', + '𝚲' => 'Λ', + '𝚳' => 'Μ', + '𝚴' => 'Ν', + '𝚵' => 'Ξ', + '𝚶' => 'Ο', + '𝚷' => 'Π', + '𝚸' => 'Ρ', + '𝚹' => 'Θ', + '𝚺' => 'Σ', + '𝚻' => 'Τ', + '𝚼' => 'Υ', + '𝚽' => 'Φ', + '𝚾' => 'Χ', + '𝚿' => 'Ψ', + '𝛀' => 'Ω', + '𝛁' => '∇', + '𝛂' => 'α', + '𝛃' => 'β', + '𝛄' => 'γ', + '𝛅' => 'δ', + '𝛆' => 'ε', + '𝛇' => 'ζ', + '𝛈' => 'η', + '𝛉' => 'θ', + '𝛊' => 'ι', + '𝛋' => 'κ', + '𝛌' => 'λ', + '𝛍' => 'μ', + '𝛎' => 'ν', + '𝛏' => 'ξ', + '𝛐' => 'ο', + '𝛑' => 'π', + '𝛒' => 'ρ', + '𝛓' => 'ς', + '𝛔' => 'σ', + '𝛕' => 'τ', + '𝛖' => 'υ', + '𝛗' => 'φ', + '𝛘' => 'χ', + '𝛙' => 'ψ', + '𝛚' => 'ω', + '𝛛' => '∂', + '𝛜' => 'ε', + '𝛝' => 'θ', + '𝛞' => 'κ', + '𝛟' => 'φ', + '𝛠' => 'ρ', + '𝛡' => 'π', + '𝛢' => 'Α', + '𝛣' => 'Β', + '𝛤' => 'Γ', + '𝛥' => 'Δ', + '𝛦' => 'Ε', + '𝛧' => 'Ζ', + '𝛨' => 'Η', + '𝛩' => 'Θ', + '𝛪' => 'Ι', + '𝛫' => 'Κ', + '𝛬' => 'Λ', + '𝛭' => 'Μ', + '𝛮' => 'Ν', + '𝛯' => 'Ξ', + '𝛰' => 'Ο', + '𝛱' => 'Π', + '𝛲' => 'Ρ', + '𝛳' => 'Θ', + '𝛴' => 'Σ', + '𝛵' => 'Τ', + '𝛶' => 'Υ', + '𝛷' => 'Φ', + '𝛸' => 'Χ', + '𝛹' => 'Ψ', + '𝛺' => 'Ω', + '𝛻' => '∇', + '𝛼' => 'α', + '𝛽' => 'β', + '𝛾' => 'γ', + '𝛿' => 'δ', + '𝜀' => 'ε', + '𝜁' => 'ζ', + '𝜂' => 'η', + '𝜃' => 'θ', + '𝜄' => 'ι', + '𝜅' => 'κ', + '𝜆' => 'λ', + '𝜇' => 'μ', + '𝜈' => 'ν', + '𝜉' => 'ξ', + '𝜊' => 'ο', + '𝜋' => 'π', + '𝜌' => 'ρ', + '𝜍' => 'ς', + '𝜎' => 'σ', + '𝜏' => 'τ', + '𝜐' => 'υ', + '𝜑' => 'φ', + '𝜒' => 'χ', + '𝜓' => 'ψ', + '𝜔' => 'ω', + '𝜕' => '∂', + '𝜖' => 'ε', + '𝜗' => 'θ', + '𝜘' => 'κ', + '𝜙' => 'φ', + '𝜚' => 'ρ', + '𝜛' => 'π', + '𝜜' => 'Α', + '𝜝' => 'Β', + '𝜞' => 'Γ', + '𝜟' => 'Δ', + '𝜠' => 'Ε', + '𝜡' => 'Ζ', + '𝜢' => 'Η', + '𝜣' => 'Θ', + '𝜤' => 'Ι', + '𝜥' => 'Κ', + '𝜦' => 'Λ', + '𝜧' => 'Μ', + '𝜨' => 'Ν', + '𝜩' => 'Ξ', + '𝜪' => 'Ο', + '𝜫' => 'Π', + '𝜬' => 'Ρ', + '𝜭' => 'Θ', + '𝜮' => 'Σ', + '𝜯' => 'Τ', + '𝜰' => 'Υ', + '𝜱' => 'Φ', + '𝜲' => 'Χ', + '𝜳' => 'Ψ', + '𝜴' => 'Ω', + '𝜵' => '∇', + '𝜶' => 'α', + '𝜷' => 'β', + '𝜸' => 'γ', + '𝜹' => 'δ', + '𝜺' => 'ε', + '𝜻' => 'ζ', + '𝜼' => 'η', + '𝜽' => 'θ', + '𝜾' => 'ι', + '𝜿' => 'κ', + '𝝀' => 'λ', + '𝝁' => 'μ', + '𝝂' => 'ν', + '𝝃' => 'ξ', + '𝝄' => 'ο', + '𝝅' => 'π', + '𝝆' => 'ρ', + '𝝇' => 'ς', + '𝝈' => 'σ', + '𝝉' => 'τ', + '𝝊' => 'υ', + '𝝋' => 'φ', + '𝝌' => 'χ', + '𝝍' => 'ψ', + '𝝎' => 'ω', + '𝝏' => '∂', + '𝝐' => 'ε', + '𝝑' => 'θ', + '𝝒' => 'κ', + '𝝓' => 'φ', + '𝝔' => 'ρ', + '𝝕' => 'π', + '𝝖' => 'Α', + '𝝗' => 'Β', + '𝝘' => 'Γ', + '𝝙' => 'Δ', + '𝝚' => 'Ε', + '𝝛' => 'Ζ', + '𝝜' => 'Η', + '𝝝' => 'Θ', + '𝝞' => 'Ι', + '𝝟' => 'Κ', + '𝝠' => 'Λ', + '𝝡' => 'Μ', + '𝝢' => 'Ν', + '𝝣' => 'Ξ', + '𝝤' => 'Ο', + '𝝥' => 'Π', + '𝝦' => 'Ρ', + '𝝧' => 'Θ', + '𝝨' => 'Σ', + '𝝩' => 'Τ', + '𝝪' => 'Υ', + '𝝫' => 'Φ', + '𝝬' => 'Χ', + '𝝭' => 'Ψ', + '𝝮' => 'Ω', + '𝝯' => '∇', + '𝝰' => 'α', + '𝝱' => 'β', + '𝝲' => 'γ', + '𝝳' => 'δ', + '𝝴' => 'ε', + '𝝵' => 'ζ', + '𝝶' => 'η', + '𝝷' => 'θ', + '𝝸' => 'ι', + '𝝹' => 'κ', + '𝝺' => 'λ', + '𝝻' => 'μ', + '𝝼' => 'ν', + '𝝽' => 'ξ', + '𝝾' => 'ο', + '𝝿' => 'π', + '𝞀' => 'ρ', + '𝞁' => 'ς', + '𝞂' => 'σ', + '𝞃' => 'τ', + '𝞄' => 'υ', + '𝞅' => 'φ', + '𝞆' => 'χ', + '𝞇' => 'ψ', + '𝞈' => 'ω', + '𝞉' => '∂', + '𝞊' => 'ε', + '𝞋' => 'θ', + '𝞌' => 'κ', + '𝞍' => 'φ', + '𝞎' => 'ρ', + '𝞏' => 'π', + '𝞐' => 'Α', + '𝞑' => 'Β', + '𝞒' => 'Γ', + '𝞓' => 'Δ', + '𝞔' => 'Ε', + '𝞕' => 'Ζ', + '𝞖' => 'Η', + '𝞗' => 'Θ', + '𝞘' => 'Ι', + '𝞙' => 'Κ', + '𝞚' => 'Λ', + '𝞛' => 'Μ', + '𝞜' => 'Ν', + '𝞝' => 'Ξ', + '𝞞' => 'Ο', + '𝞟' => 'Π', + '𝞠' => 'Ρ', + '𝞡' => 'Θ', + '𝞢' => 'Σ', + '𝞣' => 'Τ', + '𝞤' => 'Υ', + '𝞥' => 'Φ', + '𝞦' => 'Χ', + '𝞧' => 'Ψ', + '𝞨' => 'Ω', + '𝞩' => '∇', + '𝞪' => 'α', + '𝞫' => 'β', + '𝞬' => 'γ', + '𝞭' => 'δ', + '𝞮' => 'ε', + '𝞯' => 'ζ', + '𝞰' => 'η', + '𝞱' => 'θ', + '𝞲' => 'ι', + '𝞳' => 'κ', + '𝞴' => 'λ', + '𝞵' => 'μ', + '𝞶' => 'ν', + '𝞷' => 'ξ', + '𝞸' => 'ο', + '𝞹' => 'π', + '𝞺' => 'ρ', + '𝞻' => 'ς', + '𝞼' => 'σ', + '𝞽' => 'τ', + '𝞾' => 'υ', + '𝞿' => 'φ', + '𝟀' => 'χ', + '𝟁' => 'ψ', + '𝟂' => 'ω', + '𝟃' => '∂', + '𝟄' => 'ε', + '𝟅' => 'θ', + '𝟆' => 'κ', + '𝟇' => 'φ', + '𝟈' => 'ρ', + '𝟉' => 'π', + '𝟊' => 'Ϝ', + '𝟋' => 'ϝ', + '𝟎' => '0', + '𝟏' => '1', + '𝟐' => '2', + '𝟑' => '3', + '𝟒' => '4', + '𝟓' => '5', + '𝟔' => '6', + '𝟕' => '7', + '𝟖' => '8', + '𝟗' => '9', + '𝟘' => '0', + '𝟙' => '1', + '𝟚' => '2', + '𝟛' => '3', + '𝟜' => '4', + '𝟝' => '5', + '𝟞' => '6', + '𝟟' => '7', + '𝟠' => '8', + '𝟡' => '9', + '𝟢' => '0', + '𝟣' => '1', + '𝟤' => '2', + '𝟥' => '3', + '𝟦' => '4', + '𝟧' => '5', + '𝟨' => '6', + '𝟩' => '7', + '𝟪' => '8', + '𝟫' => '9', + '𝟬' => '0', + '𝟭' => '1', + '𝟮' => '2', + '𝟯' => '3', + '𝟰' => '4', + '𝟱' => '5', + '𝟲' => '6', + '𝟳' => '7', + '𝟴' => '8', + '𝟵' => '9', + '𝟶' => '0', + '𝟷' => '1', + '𝟸' => '2', + '𝟹' => '3', + '𝟺' => '4', + '𝟻' => '5', + '𝟼' => '6', + '𝟽' => '7', + '𝟾' => '8', + '𝟿' => '9', + '𞸀' => 'ا', + '𞸁' => 'ب', + '𞸂' => 'ج', + '𞸃' => 'د', + '𞸅' => 'و', + '𞸆' => 'ز', + '𞸇' => 'ح', + '𞸈' => 'ط', + '𞸉' => 'ي', + '𞸊' => 'ك', + '𞸋' => 'ل', + '𞸌' => 'م', + '𞸍' => 'ن', + '𞸎' => 'س', + '𞸏' => 'ع', + '𞸐' => 'ف', + '𞸑' => 'ص', + '𞸒' => 'ق', + '𞸓' => 'ر', + '𞸔' => 'ش', + '𞸕' => 'ت', + '𞸖' => 'ث', + '𞸗' => 'خ', + '𞸘' => 'ذ', + '𞸙' => 'ض', + '𞸚' => 'ظ', + '𞸛' => 'غ', + '𞸜' => 'ٮ', + '𞸝' => 'ں', + '𞸞' => 'ڡ', + '𞸟' => 'ٯ', + '𞸡' => 'ب', + '𞸢' => 'ج', + '𞸤' => 'ه', + '𞸧' => 'ح', + '𞸩' => 'ي', + '𞸪' => 'ك', + '𞸫' => 'ل', + '𞸬' => 'م', + '𞸭' => 'ن', + '𞸮' => 'س', + '𞸯' => 'ع', + '𞸰' => 'ف', + '𞸱' => 'ص', + '𞸲' => 'ق', + '𞸴' => 'ش', + '𞸵' => 'ت', + '𞸶' => 'ث', + '𞸷' => 'خ', + '𞸹' => 'ض', + '𞸻' => 'غ', + '𞹂' => 'ج', + '𞹇' => 'ح', + '𞹉' => 'ي', + '𞹋' => 'ل', + '𞹍' => 'ن', + '𞹎' => 'س', + '𞹏' => 'ع', + '𞹑' => 'ص', + '𞹒' => 'ق', + '𞹔' => 'ش', + '𞹗' => 'خ', + '𞹙' => 'ض', + '𞹛' => 'غ', + '𞹝' => 'ں', + '𞹟' => 'ٯ', + '𞹡' => 'ب', + '𞹢' => 'ج', + '𞹤' => 'ه', + '𞹧' => 'ح', + '𞹨' => 'ط', + '𞹩' => 'ي', + '𞹪' => 'ك', + '𞹬' => 'م', + '𞹭' => 'ن', + '𞹮' => 'س', + '𞹯' => 'ع', + '𞹰' => 'ف', + '𞹱' => 'ص', + '𞹲' => 'ق', + '𞹴' => 'ش', + '𞹵' => 'ت', + '𞹶' => 'ث', + '𞹷' => 'خ', + '𞹹' => 'ض', + '𞹺' => 'ظ', + '𞹻' => 'غ', + '𞹼' => 'ٮ', + '𞹾' => 'ڡ', + '𞺀' => 'ا', + '𞺁' => 'ب', + '𞺂' => 'ج', + '𞺃' => 'د', + '𞺄' => 'ه', + '𞺅' => 'و', + '𞺆' => 'ز', + '𞺇' => 'ح', + '𞺈' => 'ط', + '𞺉' => 'ي', + '𞺋' => 'ل', + '𞺌' => 'م', + '𞺍' => 'ن', + '𞺎' => 'س', + '𞺏' => 'ع', + '𞺐' => 'ف', + '𞺑' => 'ص', + '𞺒' => 'ق', + '𞺓' => 'ر', + '𞺔' => 'ش', + '𞺕' => 'ت', + '𞺖' => 'ث', + '𞺗' => 'خ', + '𞺘' => 'ذ', + '𞺙' => 'ض', + '𞺚' => 'ظ', + '𞺛' => 'غ', + '𞺡' => 'ب', + '𞺢' => 'ج', + '𞺣' => 'د', + '𞺥' => 'و', + '𞺦' => 'ز', + '𞺧' => 'ح', + '𞺨' => 'ط', + '𞺩' => 'ي', + '𞺫' => 'ل', + '𞺬' => 'م', + '𞺭' => 'ن', + '𞺮' => 'س', + '𞺯' => 'ع', + '𞺰' => 'ف', + '𞺱' => 'ص', + '𞺲' => 'ق', + '𞺳' => 'ر', + '𞺴' => 'ش', + '𞺵' => 'ت', + '𞺶' => 'ث', + '𞺷' => 'خ', + '𞺸' => 'ذ', + '𞺹' => 'ض', + '𞺺' => 'ظ', + '𞺻' => 'غ', + '🄀' => '0.', + '🄁' => '0,', + '🄂' => '1,', + '🄃' => '2,', + '🄄' => '3,', + '🄅' => '4,', + '🄆' => '5,', + '🄇' => '6,', + '🄈' => '7,', + '🄉' => '8,', + '🄊' => '9,', + '🄐' => '(A)', + '🄑' => '(B)', + '🄒' => '(C)', + '🄓' => '(D)', + '🄔' => '(E)', + '🄕' => '(F)', + '🄖' => '(G)', + '🄗' => '(H)', + '🄘' => '(I)', + '🄙' => '(J)', + '🄚' => '(K)', + '🄛' => '(L)', + '🄜' => '(M)', + '🄝' => '(N)', + '🄞' => '(O)', + '🄟' => '(P)', + '🄠' => '(Q)', + '🄡' => '(R)', + '🄢' => '(S)', + '🄣' => '(T)', + '🄤' => '(U)', + '🄥' => '(V)', + '🄦' => '(W)', + '🄧' => '(X)', + '🄨' => '(Y)', + '🄩' => '(Z)', + '🄪' => '〔S〕', + '🄫' => 'C', + '🄬' => 'R', + '🄭' => 'CD', + '🄮' => 'WZ', + '🄰' => 'A', + '🄱' => 'B', + '🄲' => 'C', + '🄳' => 'D', + '🄴' => 'E', + '🄵' => 'F', + '🄶' => 'G', + '🄷' => 'H', + '🄸' => 'I', + '🄹' => 'J', + '🄺' => 'K', + '🄻' => 'L', + '🄼' => 'M', + '🄽' => 'N', + '🄾' => 'O', + '🄿' => 'P', + '🅀' => 'Q', + '🅁' => 'R', + '🅂' => 'S', + '🅃' => 'T', + '🅄' => 'U', + '🅅' => 'V', + '🅆' => 'W', + '🅇' => 'X', + '🅈' => 'Y', + '🅉' => 'Z', + '🅊' => 'HV', + '🅋' => 'MV', + '🅌' => 'SD', + '🅍' => 'SS', + '🅎' => 'PPV', + '🅏' => 'WC', + '🅪' => 'MC', + '🅫' => 'MD', + '🅬' => 'MR', + '🆐' => 'DJ', + '🈀' => 'ほか', + '🈁' => 'ココ', + '🈂' => 'サ', + '🈐' => '手', + '🈑' => '字', + '🈒' => '双', + '🈓' => 'デ', + '🈔' => '二', + '🈕' => '多', + '🈖' => '解', + '🈗' => '天', + '🈘' => '交', + '🈙' => '映', + '🈚' => '無', + '🈛' => '料', + '🈜' => '前', + '🈝' => '後', + '🈞' => '再', + '🈟' => '新', + '🈠' => '初', + '🈡' => '終', + '🈢' => '生', + '🈣' => '販', + '🈤' => '声', + '🈥' => '吹', + '🈦' => '演', + '🈧' => '投', + '🈨' => '捕', + '🈩' => '一', + '🈪' => '三', + '🈫' => '遊', + '🈬' => '左', + '🈭' => '中', + '🈮' => '右', + '🈯' => '指', + '🈰' => '走', + '🈱' => '打', + '🈲' => '禁', + '🈳' => '空', + '🈴' => '合', + '🈵' => '満', + '🈶' => '有', + '🈷' => '月', + '🈸' => '申', + '🈹' => '割', + '🈺' => '営', + '🈻' => '配', + '🉀' => '〔本〕', + '🉁' => '〔三〕', + '🉂' => '〔二〕', + '🉃' => '〔安〕', + '🉄' => '〔点〕', + '🉅' => '〔打〕', + '🉆' => '〔盗〕', + '🉇' => '〔勝〕', + '🉈' => '〔敗〕', + '🉐' => '得', + '🉑' => '可', + '🯰' => '0', + '🯱' => '1', + '🯲' => '2', + '🯳' => '3', + '🯴' => '4', + '🯵' => '5', + '🯶' => '6', + '🯷' => '7', + '🯸' => '8', + '🯹' => '9', +); diff --git a/vendor/symfony/polyfill-intl-normalizer/bootstrap.php b/vendor/symfony/polyfill-intl-normalizer/bootstrap.php new file mode 100644 index 0000000..3608e5c --- /dev/null +++ b/vendor/symfony/polyfill-intl-normalizer/bootstrap.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Intl\Normalizer as p; + +if (\PHP_VERSION_ID >= 80000) { + return require __DIR__.'/bootstrap80.php'; +} + +if (!function_exists('normalizer_is_normalized')) { + function normalizer_is_normalized($string, $form = p\Normalizer::FORM_C) { return p\Normalizer::isNormalized($string, $form); } +} +if (!function_exists('normalizer_normalize')) { + function normalizer_normalize($string, $form = p\Normalizer::FORM_C) { return p\Normalizer::normalize($string, $form); } +} diff --git a/vendor/symfony/polyfill-intl-normalizer/bootstrap80.php b/vendor/symfony/polyfill-intl-normalizer/bootstrap80.php new file mode 100644 index 0000000..e36d1a9 --- /dev/null +++ b/vendor/symfony/polyfill-intl-normalizer/bootstrap80.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Intl\Normalizer as p; + +if (!function_exists('normalizer_is_normalized')) { + function normalizer_is_normalized(?string $string, ?int $form = p\Normalizer::FORM_C): bool { return p\Normalizer::isNormalized((string) $string, (int) $form); } +} +if (!function_exists('normalizer_normalize')) { + function normalizer_normalize(?string $string, ?int $form = p\Normalizer::FORM_C): string|false { return p\Normalizer::normalize((string) $string, (int) $form); } +} diff --git a/vendor/symfony/polyfill-mbstring/LICENSE b/vendor/symfony/polyfill-mbstring/LICENSE new file mode 100644 index 0000000..4cd8bdd --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015-2019 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/polyfill-mbstring/Mbstring.php b/vendor/symfony/polyfill-mbstring/Mbstring.php new file mode 100644 index 0000000..bce5c4a --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/Mbstring.php @@ -0,0 +1,874 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Mbstring; + +/** + * Partial mbstring implementation in PHP, iconv based, UTF-8 centric. + * + * Implemented: + * - mb_chr - Returns a specific character from its Unicode code point + * - mb_convert_encoding - Convert character encoding + * - mb_convert_variables - Convert character code in variable(s) + * - mb_decode_mimeheader - Decode string in MIME header field + * - mb_encode_mimeheader - Encode string for MIME header XXX NATIVE IMPLEMENTATION IS REALLY BUGGED + * - mb_decode_numericentity - Decode HTML numeric string reference to character + * - mb_encode_numericentity - Encode character to HTML numeric string reference + * - mb_convert_case - Perform case folding on a string + * - mb_detect_encoding - Detect character encoding + * - mb_get_info - Get internal settings of mbstring + * - mb_http_input - Detect HTTP input character encoding + * - mb_http_output - Set/Get HTTP output character encoding + * - mb_internal_encoding - Set/Get internal character encoding + * - mb_list_encodings - Returns an array of all supported encodings + * - mb_ord - Returns the Unicode code point of a character + * - mb_output_handler - Callback function converts character encoding in output buffer + * - mb_scrub - Replaces ill-formed byte sequences with substitute characters + * - mb_strlen - Get string length + * - mb_strpos - Find position of first occurrence of string in a string + * - mb_strrpos - Find position of last occurrence of a string in a string + * - mb_str_split - Convert a string to an array + * - mb_strtolower - Make a string lowercase + * - mb_strtoupper - Make a string uppercase + * - mb_substitute_character - Set/Get substitution character + * - mb_substr - Get part of string + * - mb_stripos - Finds position of first occurrence of a string within another, case insensitive + * - mb_stristr - Finds first occurrence of a string within another, case insensitive + * - mb_strrchr - Finds the last occurrence of a character in a string within another + * - mb_strrichr - Finds the last occurrence of a character in a string within another, case insensitive + * - mb_strripos - Finds position of last occurrence of a string within another, case insensitive + * - mb_strstr - Finds first occurrence of a string within another + * - mb_strwidth - Return width of string + * - mb_substr_count - Count the number of substring occurrences + * + * Not implemented: + * - mb_convert_kana - Convert "kana" one from another ("zen-kaku", "han-kaku" and more) + * - mb_ereg_* - Regular expression with multibyte support + * - mb_parse_str - Parse GET/POST/COOKIE data and set global variable + * - mb_preferred_mime_name - Get MIME charset string + * - mb_regex_encoding - Returns current encoding for multibyte regex as string + * - mb_regex_set_options - Set/Get the default options for mbregex functions + * - mb_send_mail - Send encoded mail + * - mb_split - Split multibyte string using regular expression + * - mb_strcut - Get part of string + * - mb_strimwidth - Get truncated string with specified width + * + * @author Nicolas Grekas + * + * @internal + */ +final class Mbstring +{ + public const MB_CASE_FOLD = \PHP_INT_MAX; + + private const CASE_FOLD = [ + ['µ', 'ſ', "\xCD\x85", 'ς', "\xCF\x90", "\xCF\x91", "\xCF\x95", "\xCF\x96", "\xCF\xB0", "\xCF\xB1", "\xCF\xB5", "\xE1\xBA\x9B", "\xE1\xBE\xBE"], + ['μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', "\xE1\xB9\xA1", 'ι'], + ]; + + private static $encodingList = ['ASCII', 'UTF-8']; + private static $language = 'neutral'; + private static $internalEncoding = 'UTF-8'; + + public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null) + { + if (\is_array($fromEncoding) || (null !== $fromEncoding && false !== strpos($fromEncoding, ','))) { + $fromEncoding = self::mb_detect_encoding($s, $fromEncoding); + } else { + $fromEncoding = self::getEncoding($fromEncoding); + } + + $toEncoding = self::getEncoding($toEncoding); + + if ('BASE64' === $fromEncoding) { + $s = base64_decode($s); + $fromEncoding = $toEncoding; + } + + if ('BASE64' === $toEncoding) { + return base64_encode($s); + } + + if ('HTML-ENTITIES' === $toEncoding || 'HTML' === $toEncoding) { + if ('HTML-ENTITIES' === $fromEncoding || 'HTML' === $fromEncoding) { + $fromEncoding = 'Windows-1252'; + } + if ('UTF-8' !== $fromEncoding) { + $s = iconv($fromEncoding, 'UTF-8//IGNORE', $s); + } + + return preg_replace_callback('/[\x80-\xFF]+/', [__CLASS__, 'html_encoding_callback'], $s); + } + + if ('HTML-ENTITIES' === $fromEncoding) { + $s = html_entity_decode($s, \ENT_COMPAT, 'UTF-8'); + $fromEncoding = 'UTF-8'; + } + + return iconv($fromEncoding, $toEncoding.'//IGNORE', $s); + } + + public static function mb_convert_variables($toEncoding, $fromEncoding, &...$vars) + { + $ok = true; + array_walk_recursive($vars, function (&$v) use (&$ok, $toEncoding, $fromEncoding) { + if (false === $v = self::mb_convert_encoding($v, $toEncoding, $fromEncoding)) { + $ok = false; + } + }); + + return $ok ? $fromEncoding : false; + } + + public static function mb_decode_mimeheader($s) + { + return iconv_mime_decode($s, 2, self::$internalEncoding); + } + + public static function mb_encode_mimeheader($s, $charset = null, $transferEncoding = null, $linefeed = null, $indent = null) + { + trigger_error('mb_encode_mimeheader() is bugged. Please use iconv_mime_encode() instead', \E_USER_WARNING); + } + + public static function mb_decode_numericentity($s, $convmap, $encoding = null) + { + if (null !== $s && !\is_scalar($s) && !(\is_object($s) && method_exists($s, '__toString'))) { + trigger_error('mb_decode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', \E_USER_WARNING); + + return null; + } + + if (!\is_array($convmap) || (80000 > \PHP_VERSION_ID && !$convmap)) { + return false; + } + + if (null !== $encoding && !\is_scalar($encoding)) { + trigger_error('mb_decode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', \E_USER_WARNING); + + return ''; // Instead of null (cf. mb_encode_numericentity). + } + + $s = (string) $s; + if ('' === $s) { + return ''; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding) { + $encoding = null; + if (!preg_match('//u', $s)) { + $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s); + } + } else { + $s = iconv($encoding, 'UTF-8//IGNORE', $s); + } + + $cnt = floor(\count($convmap) / 4) * 4; + + for ($i = 0; $i < $cnt; $i += 4) { + // collector_decode_htmlnumericentity ignores $convmap[$i + 3] + $convmap[$i] += $convmap[$i + 2]; + $convmap[$i + 1] += $convmap[$i + 2]; + } + + $s = preg_replace_callback('/&#(?:0*([0-9]+)|x0*([0-9a-fA-F]+))(?!&);?/', function (array $m) use ($cnt, $convmap) { + $c = isset($m[2]) ? (int) hexdec($m[2]) : $m[1]; + for ($i = 0; $i < $cnt; $i += 4) { + if ($c >= $convmap[$i] && $c <= $convmap[$i + 1]) { + return self::mb_chr($c - $convmap[$i + 2]); + } + } + + return $m[0]; + }, $s); + + if (null === $encoding) { + return $s; + } + + return iconv('UTF-8', $encoding.'//IGNORE', $s); + } + + public static function mb_encode_numericentity($s, $convmap, $encoding = null, $is_hex = false) + { + if (null !== $s && !\is_scalar($s) && !(\is_object($s) && method_exists($s, '__toString'))) { + trigger_error('mb_encode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', \E_USER_WARNING); + + return null; + } + + if (!\is_array($convmap) || (80000 > \PHP_VERSION_ID && !$convmap)) { + return false; + } + + if (null !== $encoding && !\is_scalar($encoding)) { + trigger_error('mb_encode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', \E_USER_WARNING); + + return null; // Instead of '' (cf. mb_decode_numericentity). + } + + if (null !== $is_hex && !\is_scalar($is_hex)) { + trigger_error('mb_encode_numericentity() expects parameter 4 to be boolean, '.\gettype($s).' given', \E_USER_WARNING); + + return null; + } + + $s = (string) $s; + if ('' === $s) { + return ''; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding) { + $encoding = null; + if (!preg_match('//u', $s)) { + $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s); + } + } else { + $s = iconv($encoding, 'UTF-8//IGNORE', $s); + } + + static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4]; + + $cnt = floor(\count($convmap) / 4) * 4; + $i = 0; + $len = \strlen($s); + $result = ''; + + while ($i < $len) { + $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"]; + $uchr = substr($s, $i, $ulen); + $i += $ulen; + $c = self::mb_ord($uchr); + + for ($j = 0; $j < $cnt; $j += 4) { + if ($c >= $convmap[$j] && $c <= $convmap[$j + 1]) { + $cOffset = ($c + $convmap[$j + 2]) & $convmap[$j + 3]; + $result .= $is_hex ? sprintf('&#x%X;', $cOffset) : '&#'.$cOffset.';'; + continue 2; + } + } + $result .= $uchr; + } + + if (null === $encoding) { + return $result; + } + + return iconv('UTF-8', $encoding.'//IGNORE', $result); + } + + public static function mb_convert_case($s, $mode, $encoding = null) + { + $s = (string) $s; + if ('' === $s) { + return ''; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding) { + $encoding = null; + if (!preg_match('//u', $s)) { + $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s); + } + } else { + $s = iconv($encoding, 'UTF-8//IGNORE', $s); + } + + if (\MB_CASE_TITLE == $mode) { + static $titleRegexp = null; + if (null === $titleRegexp) { + $titleRegexp = self::getData('titleCaseRegexp'); + } + $s = preg_replace_callback($titleRegexp, [__CLASS__, 'title_case'], $s); + } else { + if (\MB_CASE_UPPER == $mode) { + static $upper = null; + if (null === $upper) { + $upper = self::getData('upperCase'); + } + $map = $upper; + } else { + if (self::MB_CASE_FOLD === $mode) { + $s = str_replace(self::CASE_FOLD[0], self::CASE_FOLD[1], $s); + } + + static $lower = null; + if (null === $lower) { + $lower = self::getData('lowerCase'); + } + $map = $lower; + } + + static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4]; + + $i = 0; + $len = \strlen($s); + + while ($i < $len) { + $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"]; + $uchr = substr($s, $i, $ulen); + $i += $ulen; + + if (isset($map[$uchr])) { + $uchr = $map[$uchr]; + $nlen = \strlen($uchr); + + if ($nlen == $ulen) { + $nlen = $i; + do { + $s[--$nlen] = $uchr[--$ulen]; + } while ($ulen); + } else { + $s = substr_replace($s, $uchr, $i - $ulen, $ulen); + $len += $nlen - $ulen; + $i += $nlen - $ulen; + } + } + } + } + + if (null === $encoding) { + return $s; + } + + return iconv('UTF-8', $encoding.'//IGNORE', $s); + } + + public static function mb_internal_encoding($encoding = null) + { + if (null === $encoding) { + return self::$internalEncoding; + } + + $normalizedEncoding = self::getEncoding($encoding); + + if ('UTF-8' === $normalizedEncoding || false !== @iconv($normalizedEncoding, $normalizedEncoding, ' ')) { + self::$internalEncoding = $normalizedEncoding; + + return true; + } + + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError(sprintf('Argument #1 ($encoding) must be a valid encoding, "%s" given', $encoding)); + } + + public static function mb_language($lang = null) + { + if (null === $lang) { + return self::$language; + } + + switch ($normalizedLang = strtolower($lang)) { + case 'uni': + case 'neutral': + self::$language = $normalizedLang; + + return true; + } + + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError(sprintf('Argument #1 ($language) must be a valid language, "%s" given', $lang)); + } + + public static function mb_list_encodings() + { + return ['UTF-8']; + } + + public static function mb_encoding_aliases($encoding) + { + switch (strtoupper($encoding)) { + case 'UTF8': + case 'UTF-8': + return ['utf8']; + } + + return false; + } + + public static function mb_check_encoding($var = null, $encoding = null) + { + if (null === $encoding) { + if (null === $var) { + return false; + } + $encoding = self::$internalEncoding; + } + + return self::mb_detect_encoding($var, [$encoding]) || false !== @iconv($encoding, $encoding, $var); + } + + public static function mb_detect_encoding($str, $encodingList = null, $strict = false) + { + if (null === $encodingList) { + $encodingList = self::$encodingList; + } else { + if (!\is_array($encodingList)) { + $encodingList = array_map('trim', explode(',', $encodingList)); + } + $encodingList = array_map('strtoupper', $encodingList); + } + + foreach ($encodingList as $enc) { + switch ($enc) { + case 'ASCII': + if (!preg_match('/[\x80-\xFF]/', $str)) { + return $enc; + } + break; + + case 'UTF8': + case 'UTF-8': + if (preg_match('//u', $str)) { + return 'UTF-8'; + } + break; + + default: + if (0 === strncmp($enc, 'ISO-8859-', 9)) { + return $enc; + } + } + } + + return false; + } + + public static function mb_detect_order($encodingList = null) + { + if (null === $encodingList) { + return self::$encodingList; + } + + if (!\is_array($encodingList)) { + $encodingList = array_map('trim', explode(',', $encodingList)); + } + $encodingList = array_map('strtoupper', $encodingList); + + foreach ($encodingList as $enc) { + switch ($enc) { + default: + if (strncmp($enc, 'ISO-8859-', 9)) { + return false; + } + // no break + case 'ASCII': + case 'UTF8': + case 'UTF-8': + } + } + + self::$encodingList = $encodingList; + + return true; + } + + public static function mb_strlen($s, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return \strlen($s); + } + + return @iconv_strlen($s, $encoding); + } + + public static function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return strpos($haystack, $needle, $offset); + } + + $needle = (string) $needle; + if ('' === $needle) { + if (80000 > \PHP_VERSION_ID) { + trigger_error(__METHOD__.': Empty delimiter', \E_USER_WARNING); + + return false; + } + + return 0; + } + + return iconv_strpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return strrpos($haystack, $needle, $offset); + } + + if ($offset != (int) $offset) { + $offset = 0; + } elseif ($offset = (int) $offset) { + if ($offset < 0) { + if (0 > $offset += self::mb_strlen($needle)) { + $haystack = self::mb_substr($haystack, 0, $offset, $encoding); + } + $offset = 0; + } else { + $haystack = self::mb_substr($haystack, $offset, 2147483647, $encoding); + } + } + + $pos = '' !== $needle || 80000 > \PHP_VERSION_ID + ? iconv_strrpos($haystack, $needle, $encoding) + : self::mb_strlen($haystack, $encoding); + + return false !== $pos ? $offset + $pos : false; + } + + public static function mb_str_split($string, $split_length = 1, $encoding = null) + { + if (null !== $string && !\is_scalar($string) && !(\is_object($string) && method_exists($string, '__toString'))) { + trigger_error('mb_str_split() expects parameter 1 to be string, '.\gettype($string).' given', \E_USER_WARNING); + + return null; + } + + if (1 > $split_length = (int) $split_length) { + if (80000 > \PHP_VERSION_ID) { + trigger_error('The length of each segment must be greater than zero', \E_USER_WARNING); + + return false; + } + + throw new \ValueError('Argument #2 ($length) must be greater than 0'); + } + + if (null === $encoding) { + $encoding = mb_internal_encoding(); + } + + if ('UTF-8' === $encoding = self::getEncoding($encoding)) { + $rx = '/('; + while (65535 < $split_length) { + $rx .= '.{65535}'; + $split_length -= 65535; + } + $rx .= '.{'.$split_length.'})/us'; + + return preg_split($rx, $string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY); + } + + $result = []; + $length = mb_strlen($string, $encoding); + + for ($i = 0; $i < $length; $i += $split_length) { + $result[] = mb_substr($string, $i, $split_length, $encoding); + } + + return $result; + } + + public static function mb_strtolower($s, $encoding = null) + { + return self::mb_convert_case($s, \MB_CASE_LOWER, $encoding); + } + + public static function mb_strtoupper($s, $encoding = null) + { + return self::mb_convert_case($s, \MB_CASE_UPPER, $encoding); + } + + public static function mb_substitute_character($c = null) + { + if (null === $c) { + return 'none'; + } + if (0 === strcasecmp($c, 'none')) { + return true; + } + if (80000 > \PHP_VERSION_ID) { + return false; + } + if (\is_int($c) || 'long' === $c || 'entity' === $c) { + return false; + } + + throw new \ValueError('Argument #1 ($substitute_character) must be "none", "long", "entity" or a valid codepoint'); + } + + public static function mb_substr($s, $start, $length = null, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return (string) substr($s, $start, null === $length ? 2147483647 : $length); + } + + if ($start < 0) { + $start = iconv_strlen($s, $encoding) + $start; + if ($start < 0) { + $start = 0; + } + } + + if (null === $length) { + $length = 2147483647; + } elseif ($length < 0) { + $length = iconv_strlen($s, $encoding) + $length - $start; + if ($length < 0) { + return ''; + } + } + + return (string) iconv_substr($s, $start, $length, $encoding); + } + + public static function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) + { + $haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding); + $needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding); + + return self::mb_strpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_stristr($haystack, $needle, $part = false, $encoding = null) + { + $pos = self::mb_stripos($haystack, $needle, 0, $encoding); + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strrchr($haystack, $needle, $part = false, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + $pos = strrpos($haystack, $needle); + } else { + $needle = self::mb_substr($needle, 0, 1, $encoding); + $pos = iconv_strrpos($haystack, $needle, $encoding); + } + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strrichr($haystack, $needle, $part = false, $encoding = null) + { + $needle = self::mb_substr($needle, 0, 1, $encoding); + $pos = self::mb_strripos($haystack, $needle, $encoding); + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) + { + $haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding); + $needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding); + + return self::mb_strrpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_strstr($haystack, $needle, $part = false, $encoding = null) + { + $pos = strpos($haystack, $needle); + if (false === $pos) { + return false; + } + if ($part) { + return substr($haystack, 0, $pos); + } + + return substr($haystack, $pos); + } + + public static function mb_get_info($type = 'all') + { + $info = [ + 'internal_encoding' => self::$internalEncoding, + 'http_output' => 'pass', + 'http_output_conv_mimetypes' => '^(text/|application/xhtml\+xml)', + 'func_overload' => 0, + 'func_overload_list' => 'no overload', + 'mail_charset' => 'UTF-8', + 'mail_header_encoding' => 'BASE64', + 'mail_body_encoding' => 'BASE64', + 'illegal_chars' => 0, + 'encoding_translation' => 'Off', + 'language' => self::$language, + 'detect_order' => self::$encodingList, + 'substitute_character' => 'none', + 'strict_detection' => 'Off', + ]; + + if ('all' === $type) { + return $info; + } + if (isset($info[$type])) { + return $info[$type]; + } + + return false; + } + + public static function mb_http_input($type = '') + { + return false; + } + + public static function mb_http_output($encoding = null) + { + return null !== $encoding ? 'pass' === $encoding : 'pass'; + } + + public static function mb_strwidth($s, $encoding = null) + { + $encoding = self::getEncoding($encoding); + + if ('UTF-8' !== $encoding) { + $s = iconv($encoding, 'UTF-8//IGNORE', $s); + } + + $s = preg_replace('/[\x{1100}-\x{115F}\x{2329}\x{232A}\x{2E80}-\x{303E}\x{3040}-\x{A4CF}\x{AC00}-\x{D7A3}\x{F900}-\x{FAFF}\x{FE10}-\x{FE19}\x{FE30}-\x{FE6F}\x{FF00}-\x{FF60}\x{FFE0}-\x{FFE6}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}]/u', '', $s, -1, $wide); + + return ($wide << 1) + iconv_strlen($s, 'UTF-8'); + } + + public static function mb_substr_count($haystack, $needle, $encoding = null) + { + return substr_count($haystack, $needle); + } + + public static function mb_output_handler($contents, $status) + { + return $contents; + } + + public static function mb_chr($code, $encoding = null) + { + if (0x80 > $code %= 0x200000) { + $s = \chr($code); + } elseif (0x800 > $code) { + $s = \chr(0xC0 | $code >> 6).\chr(0x80 | $code & 0x3F); + } elseif (0x10000 > $code) { + $s = \chr(0xE0 | $code >> 12).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); + } else { + $s = \chr(0xF0 | $code >> 18).\chr(0x80 | $code >> 12 & 0x3F).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); + } + + if ('UTF-8' !== $encoding = self::getEncoding($encoding)) { + $s = mb_convert_encoding($s, $encoding, 'UTF-8'); + } + + return $s; + } + + public static function mb_ord($s, $encoding = null) + { + if ('UTF-8' !== $encoding = self::getEncoding($encoding)) { + $s = mb_convert_encoding($s, 'UTF-8', $encoding); + } + + if (1 === \strlen($s)) { + return \ord($s); + } + + $code = ($s = unpack('C*', substr($s, 0, 4))) ? $s[1] : 0; + if (0xF0 <= $code) { + return (($code - 0xF0) << 18) + (($s[2] - 0x80) << 12) + (($s[3] - 0x80) << 6) + $s[4] - 0x80; + } + if (0xE0 <= $code) { + return (($code - 0xE0) << 12) + (($s[2] - 0x80) << 6) + $s[3] - 0x80; + } + if (0xC0 <= $code) { + return (($code - 0xC0) << 6) + $s[2] - 0x80; + } + + return $code; + } + + private static function getSubpart($pos, $part, $haystack, $encoding) + { + if (false === $pos) { + return false; + } + if ($part) { + return self::mb_substr($haystack, 0, $pos, $encoding); + } + + return self::mb_substr($haystack, $pos, null, $encoding); + } + + private static function html_encoding_callback(array $m) + { + $i = 1; + $entities = ''; + $m = unpack('C*', htmlentities($m[0], \ENT_COMPAT, 'UTF-8')); + + while (isset($m[$i])) { + if (0x80 > $m[$i]) { + $entities .= \chr($m[$i++]); + continue; + } + if (0xF0 <= $m[$i]) { + $c = (($m[$i++] - 0xF0) << 18) + (($m[$i++] - 0x80) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80; + } elseif (0xE0 <= $m[$i]) { + $c = (($m[$i++] - 0xE0) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80; + } else { + $c = (($m[$i++] - 0xC0) << 6) + $m[$i++] - 0x80; + } + + $entities .= '&#'.$c.';'; + } + + return $entities; + } + + private static function title_case(array $s) + { + return self::mb_convert_case($s[1], \MB_CASE_UPPER, 'UTF-8').self::mb_convert_case($s[2], \MB_CASE_LOWER, 'UTF-8'); + } + + private static function getData($file) + { + if (file_exists($file = __DIR__.'/Resources/unidata/'.$file.'.php')) { + return require $file; + } + + return false; + } + + private static function getEncoding($encoding) + { + if (null === $encoding) { + return self::$internalEncoding; + } + + if ('UTF-8' === $encoding) { + return 'UTF-8'; + } + + $encoding = strtoupper($encoding); + + if ('8BIT' === $encoding || 'BINARY' === $encoding) { + return 'CP850'; + } + + if ('UTF8' === $encoding) { + return 'UTF-8'; + } + + return $encoding; + } +} diff --git a/vendor/symfony/polyfill-mbstring/README.md b/vendor/symfony/polyfill-mbstring/README.md new file mode 100644 index 0000000..478b40d --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/README.md @@ -0,0 +1,13 @@ +Symfony Polyfill / Mbstring +=========================== + +This component provides a partial, native PHP implementation for the +[Mbstring](https://php.net/mbstring) extension. + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php b/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php new file mode 100644 index 0000000..fac60b0 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php @@ -0,0 +1,1397 @@ + 'a', + 'B' => 'b', + 'C' => 'c', + 'D' => 'd', + 'E' => 'e', + 'F' => 'f', + 'G' => 'g', + 'H' => 'h', + 'I' => 'i', + 'J' => 'j', + 'K' => 'k', + 'L' => 'l', + 'M' => 'm', + 'N' => 'n', + 'O' => 'o', + 'P' => 'p', + 'Q' => 'q', + 'R' => 'r', + 'S' => 's', + 'T' => 't', + 'U' => 'u', + 'V' => 'v', + 'W' => 'w', + 'X' => 'x', + 'Y' => 'y', + 'Z' => 'z', + 'À' => 'à', + 'Á' => 'á', + 'Â' => 'â', + 'Ã' => 'ã', + 'Ä' => 'ä', + 'Å' => 'å', + 'Æ' => 'æ', + 'Ç' => 'ç', + 'È' => 'è', + 'É' => 'é', + 'Ê' => 'ê', + 'Ë' => 'ë', + 'Ì' => 'ì', + 'Í' => 'í', + 'Î' => 'î', + 'Ï' => 'ï', + 'Ð' => 'ð', + 'Ñ' => 'ñ', + 'Ò' => 'ò', + 'Ó' => 'ó', + 'Ô' => 'ô', + 'Õ' => 'õ', + 'Ö' => 'ö', + 'Ø' => 'ø', + 'Ù' => 'ù', + 'Ú' => 'ú', + 'Û' => 'û', + 'Ü' => 'ü', + 'Ý' => 'ý', + 'Þ' => 'þ', + 'Ā' => 'ā', + 'Ă' => 'ă', + 'Ą' => 'ą', + 'Ć' => 'ć', + 'Ĉ' => 'ĉ', + 'Ċ' => 'ċ', + 'Č' => 'č', + 'Ď' => 'ď', + 'Đ' => 'đ', + 'Ē' => 'ē', + 'Ĕ' => 'ĕ', + 'Ė' => 'ė', + 'Ę' => 'ę', + 'Ě' => 'ě', + 'Ĝ' => 'ĝ', + 'Ğ' => 'ğ', + 'Ġ' => 'ġ', + 'Ģ' => 'ģ', + 'Ĥ' => 'ĥ', + 'Ħ' => 'ħ', + 'Ĩ' => 'ĩ', + 'Ī' => 'ī', + 'Ĭ' => 'ĭ', + 'Į' => 'į', + 'İ' => 'i̇', + 'IJ' => 'ij', + 'Ĵ' => 'ĵ', + 'Ķ' => 'ķ', + 'Ĺ' => 'ĺ', + 'Ļ' => 'ļ', + 'Ľ' => 'ľ', + 'Ŀ' => 'ŀ', + 'Ł' => 'ł', + 'Ń' => 'ń', + 'Ņ' => 'ņ', + 'Ň' => 'ň', + 'Ŋ' => 'ŋ', + 'Ō' => 'ō', + 'Ŏ' => 'ŏ', + 'Ő' => 'ő', + 'Œ' => 'œ', + 'Ŕ' => 'ŕ', + 'Ŗ' => 'ŗ', + 'Ř' => 'ř', + 'Ś' => 'ś', + 'Ŝ' => 'ŝ', + 'Ş' => 'ş', + 'Š' => 'š', + 'Ţ' => 'ţ', + 'Ť' => 'ť', + 'Ŧ' => 'ŧ', + 'Ũ' => 'ũ', + 'Ū' => 'ū', + 'Ŭ' => 'ŭ', + 'Ů' => 'ů', + 'Ű' => 'ű', + 'Ų' => 'ų', + 'Ŵ' => 'ŵ', + 'Ŷ' => 'ŷ', + 'Ÿ' => 'ÿ', + 'Ź' => 'ź', + 'Ż' => 'ż', + 'Ž' => 'ž', + 'Ɓ' => 'ɓ', + 'Ƃ' => 'ƃ', + 'Ƅ' => 'ƅ', + 'Ɔ' => 'ɔ', + 'Ƈ' => 'ƈ', + 'Ɖ' => 'ɖ', + 'Ɗ' => 'ɗ', + 'Ƌ' => 'ƌ', + 'Ǝ' => 'ǝ', + 'Ə' => 'ə', + 'Ɛ' => 'ɛ', + 'Ƒ' => 'ƒ', + 'Ɠ' => 'ɠ', + 'Ɣ' => 'ɣ', + 'Ɩ' => 'ɩ', + 'Ɨ' => 'ɨ', + 'Ƙ' => 'ƙ', + 'Ɯ' => 'ɯ', + 'Ɲ' => 'ɲ', + 'Ɵ' => 'ɵ', + 'Ơ' => 'ơ', + 'Ƣ' => 'ƣ', + 'Ƥ' => 'ƥ', + 'Ʀ' => 'ʀ', + 'Ƨ' => 'ƨ', + 'Ʃ' => 'ʃ', + 'Ƭ' => 'ƭ', + 'Ʈ' => 'ʈ', + 'Ư' => 'ư', + 'Ʊ' => 'ʊ', + 'Ʋ' => 'ʋ', + 'Ƴ' => 'ƴ', + 'Ƶ' => 'ƶ', + 'Ʒ' => 'ʒ', + 'Ƹ' => 'ƹ', + 'Ƽ' => 'ƽ', + 'DŽ' => 'dž', + 'Dž' => 'dž', + 'LJ' => 'lj', + 'Lj' => 'lj', + 'NJ' => 'nj', + 'Nj' => 'nj', + 'Ǎ' => 'ǎ', + 'Ǐ' => 'ǐ', + 'Ǒ' => 'ǒ', + 'Ǔ' => 'ǔ', + 'Ǖ' => 'ǖ', + 'Ǘ' => 'ǘ', + 'Ǚ' => 'ǚ', + 'Ǜ' => 'ǜ', + 'Ǟ' => 'ǟ', + 'Ǡ' => 'ǡ', + 'Ǣ' => 'ǣ', + 'Ǥ' => 'ǥ', + 'Ǧ' => 'ǧ', + 'Ǩ' => 'ǩ', + 'Ǫ' => 'ǫ', + 'Ǭ' => 'ǭ', + 'Ǯ' => 'ǯ', + 'DZ' => 'dz', + 'Dz' => 'dz', + 'Ǵ' => 'ǵ', + 'Ƕ' => 'ƕ', + 'Ƿ' => 'ƿ', + 'Ǹ' => 'ǹ', + 'Ǻ' => 'ǻ', + 'Ǽ' => 'ǽ', + 'Ǿ' => 'ǿ', + 'Ȁ' => 'ȁ', + 'Ȃ' => 'ȃ', + 'Ȅ' => 'ȅ', + 'Ȇ' => 'ȇ', + 'Ȉ' => 'ȉ', + 'Ȋ' => 'ȋ', + 'Ȍ' => 'ȍ', + 'Ȏ' => 'ȏ', + 'Ȑ' => 'ȑ', + 'Ȓ' => 'ȓ', + 'Ȕ' => 'ȕ', + 'Ȗ' => 'ȗ', + 'Ș' => 'ș', + 'Ț' => 'ț', + 'Ȝ' => 'ȝ', + 'Ȟ' => 'ȟ', + 'Ƞ' => 'ƞ', + 'Ȣ' => 'ȣ', + 'Ȥ' => 'ȥ', + 'Ȧ' => 'ȧ', + 'Ȩ' => 'ȩ', + 'Ȫ' => 'ȫ', + 'Ȭ' => 'ȭ', + 'Ȯ' => 'ȯ', + 'Ȱ' => 'ȱ', + 'Ȳ' => 'ȳ', + 'Ⱥ' => 'ⱥ', + 'Ȼ' => 'ȼ', + 'Ƚ' => 'ƚ', + 'Ⱦ' => 'ⱦ', + 'Ɂ' => 'ɂ', + 'Ƀ' => 'ƀ', + 'Ʉ' => 'ʉ', + 'Ʌ' => 'ʌ', + 'Ɇ' => 'ɇ', + 'Ɉ' => 'ɉ', + 'Ɋ' => 'ɋ', + 'Ɍ' => 'ɍ', + 'Ɏ' => 'ɏ', + 'Ͱ' => 'ͱ', + 'Ͳ' => 'ͳ', + 'Ͷ' => 'ͷ', + 'Ϳ' => 'ϳ', + 'Ά' => 'ά', + 'Έ' => 'έ', + 'Ή' => 'ή', + 'Ί' => 'ί', + 'Ό' => 'ό', + 'Ύ' => 'ύ', + 'Ώ' => 'ώ', + 'Α' => 'α', + 'Β' => 'β', + 'Γ' => 'γ', + 'Δ' => 'δ', + 'Ε' => 'ε', + 'Ζ' => 'ζ', + 'Η' => 'η', + 'Θ' => 'θ', + 'Ι' => 'ι', + 'Κ' => 'κ', + 'Λ' => 'λ', + 'Μ' => 'μ', + 'Ν' => 'ν', + 'Ξ' => 'ξ', + 'Ο' => 'ο', + 'Π' => 'π', + 'Ρ' => 'ρ', + 'Σ' => 'σ', + 'Τ' => 'τ', + 'Υ' => 'υ', + 'Φ' => 'φ', + 'Χ' => 'χ', + 'Ψ' => 'ψ', + 'Ω' => 'ω', + 'Ϊ' => 'ϊ', + 'Ϋ' => 'ϋ', + 'Ϗ' => 'ϗ', + 'Ϙ' => 'ϙ', + 'Ϛ' => 'ϛ', + 'Ϝ' => 'ϝ', + 'Ϟ' => 'ϟ', + 'Ϡ' => 'ϡ', + 'Ϣ' => 'ϣ', + 'Ϥ' => 'ϥ', + 'Ϧ' => 'ϧ', + 'Ϩ' => 'ϩ', + 'Ϫ' => 'ϫ', + 'Ϭ' => 'ϭ', + 'Ϯ' => 'ϯ', + 'ϴ' => 'θ', + 'Ϸ' => 'ϸ', + 'Ϲ' => 'ϲ', + 'Ϻ' => 'ϻ', + 'Ͻ' => 'ͻ', + 'Ͼ' => 'ͼ', + 'Ͽ' => 'ͽ', + 'Ѐ' => 'ѐ', + 'Ё' => 'ё', + 'Ђ' => 'ђ', + 'Ѓ' => 'ѓ', + 'Є' => 'є', + 'Ѕ' => 'ѕ', + 'І' => 'і', + 'Ї' => 'ї', + 'Ј' => 'ј', + 'Љ' => 'љ', + 'Њ' => 'њ', + 'Ћ' => 'ћ', + 'Ќ' => 'ќ', + 'Ѝ' => 'ѝ', + 'Ў' => 'ў', + 'Џ' => 'џ', + 'А' => 'а', + 'Б' => 'б', + 'В' => 'в', + 'Г' => 'г', + 'Д' => 'д', + 'Е' => 'е', + 'Ж' => 'ж', + 'З' => 'з', + 'И' => 'и', + 'Й' => 'й', + 'К' => 'к', + 'Л' => 'л', + 'М' => 'м', + 'Н' => 'н', + 'О' => 'о', + 'П' => 'п', + 'Р' => 'р', + 'С' => 'с', + 'Т' => 'т', + 'У' => 'у', + 'Ф' => 'ф', + 'Х' => 'х', + 'Ц' => 'ц', + 'Ч' => 'ч', + 'Ш' => 'ш', + 'Щ' => 'щ', + 'Ъ' => 'ъ', + 'Ы' => 'ы', + 'Ь' => 'ь', + 'Э' => 'э', + 'Ю' => 'ю', + 'Я' => 'я', + 'Ѡ' => 'ѡ', + 'Ѣ' => 'ѣ', + 'Ѥ' => 'ѥ', + 'Ѧ' => 'ѧ', + 'Ѩ' => 'ѩ', + 'Ѫ' => 'ѫ', + 'Ѭ' => 'ѭ', + 'Ѯ' => 'ѯ', + 'Ѱ' => 'ѱ', + 'Ѳ' => 'ѳ', + 'Ѵ' => 'ѵ', + 'Ѷ' => 'ѷ', + 'Ѹ' => 'ѹ', + 'Ѻ' => 'ѻ', + 'Ѽ' => 'ѽ', + 'Ѿ' => 'ѿ', + 'Ҁ' => 'ҁ', + 'Ҋ' => 'ҋ', + 'Ҍ' => 'ҍ', + 'Ҏ' => 'ҏ', + 'Ґ' => 'ґ', + 'Ғ' => 'ғ', + 'Ҕ' => 'ҕ', + 'Җ' => 'җ', + 'Ҙ' => 'ҙ', + 'Қ' => 'қ', + 'Ҝ' => 'ҝ', + 'Ҟ' => 'ҟ', + 'Ҡ' => 'ҡ', + 'Ң' => 'ң', + 'Ҥ' => 'ҥ', + 'Ҧ' => 'ҧ', + 'Ҩ' => 'ҩ', + 'Ҫ' => 'ҫ', + 'Ҭ' => 'ҭ', + 'Ү' => 'ү', + 'Ұ' => 'ұ', + 'Ҳ' => 'ҳ', + 'Ҵ' => 'ҵ', + 'Ҷ' => 'ҷ', + 'Ҹ' => 'ҹ', + 'Һ' => 'һ', + 'Ҽ' => 'ҽ', + 'Ҿ' => 'ҿ', + 'Ӏ' => 'ӏ', + 'Ӂ' => 'ӂ', + 'Ӄ' => 'ӄ', + 'Ӆ' => 'ӆ', + 'Ӈ' => 'ӈ', + 'Ӊ' => 'ӊ', + 'Ӌ' => 'ӌ', + 'Ӎ' => 'ӎ', + 'Ӑ' => 'ӑ', + 'Ӓ' => 'ӓ', + 'Ӕ' => 'ӕ', + 'Ӗ' => 'ӗ', + 'Ә' => 'ә', + 'Ӛ' => 'ӛ', + 'Ӝ' => 'ӝ', + 'Ӟ' => 'ӟ', + 'Ӡ' => 'ӡ', + 'Ӣ' => 'ӣ', + 'Ӥ' => 'ӥ', + 'Ӧ' => 'ӧ', + 'Ө' => 'ө', + 'Ӫ' => 'ӫ', + 'Ӭ' => 'ӭ', + 'Ӯ' => 'ӯ', + 'Ӱ' => 'ӱ', + 'Ӳ' => 'ӳ', + 'Ӵ' => 'ӵ', + 'Ӷ' => 'ӷ', + 'Ӹ' => 'ӹ', + 'Ӻ' => 'ӻ', + 'Ӽ' => 'ӽ', + 'Ӿ' => 'ӿ', + 'Ԁ' => 'ԁ', + 'Ԃ' => 'ԃ', + 'Ԅ' => 'ԅ', + 'Ԇ' => 'ԇ', + 'Ԉ' => 'ԉ', + 'Ԋ' => 'ԋ', + 'Ԍ' => 'ԍ', + 'Ԏ' => 'ԏ', + 'Ԑ' => 'ԑ', + 'Ԓ' => 'ԓ', + 'Ԕ' => 'ԕ', + 'Ԗ' => 'ԗ', + 'Ԙ' => 'ԙ', + 'Ԛ' => 'ԛ', + 'Ԝ' => 'ԝ', + 'Ԟ' => 'ԟ', + 'Ԡ' => 'ԡ', + 'Ԣ' => 'ԣ', + 'Ԥ' => 'ԥ', + 'Ԧ' => 'ԧ', + 'Ԩ' => 'ԩ', + 'Ԫ' => 'ԫ', + 'Ԭ' => 'ԭ', + 'Ԯ' => 'ԯ', + 'Ա' => 'ա', + 'Բ' => 'բ', + 'Գ' => 'գ', + 'Դ' => 'դ', + 'Ե' => 'ե', + 'Զ' => 'զ', + 'Է' => 'է', + 'Ը' => 'ը', + 'Թ' => 'թ', + 'Ժ' => 'ժ', + 'Ի' => 'ի', + 'Լ' => 'լ', + 'Խ' => 'խ', + 'Ծ' => 'ծ', + 'Կ' => 'կ', + 'Հ' => 'հ', + 'Ձ' => 'ձ', + 'Ղ' => 'ղ', + 'Ճ' => 'ճ', + 'Մ' => 'մ', + 'Յ' => 'յ', + 'Ն' => 'ն', + 'Շ' => 'շ', + 'Ո' => 'ո', + 'Չ' => 'չ', + 'Պ' => 'պ', + 'Ջ' => 'ջ', + 'Ռ' => 'ռ', + 'Ս' => 'ս', + 'Վ' => 'վ', + 'Տ' => 'տ', + 'Ր' => 'ր', + 'Ց' => 'ց', + 'Ւ' => 'ւ', + 'Փ' => 'փ', + 'Ք' => 'ք', + 'Օ' => 'օ', + 'Ֆ' => 'ֆ', + 'Ⴀ' => 'ⴀ', + 'Ⴁ' => 'ⴁ', + 'Ⴂ' => 'ⴂ', + 'Ⴃ' => 'ⴃ', + 'Ⴄ' => 'ⴄ', + 'Ⴅ' => 'ⴅ', + 'Ⴆ' => 'ⴆ', + 'Ⴇ' => 'ⴇ', + 'Ⴈ' => 'ⴈ', + 'Ⴉ' => 'ⴉ', + 'Ⴊ' => 'ⴊ', + 'Ⴋ' => 'ⴋ', + 'Ⴌ' => 'ⴌ', + 'Ⴍ' => 'ⴍ', + 'Ⴎ' => 'ⴎ', + 'Ⴏ' => 'ⴏ', + 'Ⴐ' => 'ⴐ', + 'Ⴑ' => 'ⴑ', + 'Ⴒ' => 'ⴒ', + 'Ⴓ' => 'ⴓ', + 'Ⴔ' => 'ⴔ', + 'Ⴕ' => 'ⴕ', + 'Ⴖ' => 'ⴖ', + 'Ⴗ' => 'ⴗ', + 'Ⴘ' => 'ⴘ', + 'Ⴙ' => 'ⴙ', + 'Ⴚ' => 'ⴚ', + 'Ⴛ' => 'ⴛ', + 'Ⴜ' => 'ⴜ', + 'Ⴝ' => 'ⴝ', + 'Ⴞ' => 'ⴞ', + 'Ⴟ' => 'ⴟ', + 'Ⴠ' => 'ⴠ', + 'Ⴡ' => 'ⴡ', + 'Ⴢ' => 'ⴢ', + 'Ⴣ' => 'ⴣ', + 'Ⴤ' => 'ⴤ', + 'Ⴥ' => 'ⴥ', + 'Ⴧ' => 'ⴧ', + 'Ⴭ' => 'ⴭ', + 'Ꭰ' => 'ꭰ', + 'Ꭱ' => 'ꭱ', + 'Ꭲ' => 'ꭲ', + 'Ꭳ' => 'ꭳ', + 'Ꭴ' => 'ꭴ', + 'Ꭵ' => 'ꭵ', + 'Ꭶ' => 'ꭶ', + 'Ꭷ' => 'ꭷ', + 'Ꭸ' => 'ꭸ', + 'Ꭹ' => 'ꭹ', + 'Ꭺ' => 'ꭺ', + 'Ꭻ' => 'ꭻ', + 'Ꭼ' => 'ꭼ', + 'Ꭽ' => 'ꭽ', + 'Ꭾ' => 'ꭾ', + 'Ꭿ' => 'ꭿ', + 'Ꮀ' => 'ꮀ', + 'Ꮁ' => 'ꮁ', + 'Ꮂ' => 'ꮂ', + 'Ꮃ' => 'ꮃ', + 'Ꮄ' => 'ꮄ', + 'Ꮅ' => 'ꮅ', + 'Ꮆ' => 'ꮆ', + 'Ꮇ' => 'ꮇ', + 'Ꮈ' => 'ꮈ', + 'Ꮉ' => 'ꮉ', + 'Ꮊ' => 'ꮊ', + 'Ꮋ' => 'ꮋ', + 'Ꮌ' => 'ꮌ', + 'Ꮍ' => 'ꮍ', + 'Ꮎ' => 'ꮎ', + 'Ꮏ' => 'ꮏ', + 'Ꮐ' => 'ꮐ', + 'Ꮑ' => 'ꮑ', + 'Ꮒ' => 'ꮒ', + 'Ꮓ' => 'ꮓ', + 'Ꮔ' => 'ꮔ', + 'Ꮕ' => 'ꮕ', + 'Ꮖ' => 'ꮖ', + 'Ꮗ' => 'ꮗ', + 'Ꮘ' => 'ꮘ', + 'Ꮙ' => 'ꮙ', + 'Ꮚ' => 'ꮚ', + 'Ꮛ' => 'ꮛ', + 'Ꮜ' => 'ꮜ', + 'Ꮝ' => 'ꮝ', + 'Ꮞ' => 'ꮞ', + 'Ꮟ' => 'ꮟ', + 'Ꮠ' => 'ꮠ', + 'Ꮡ' => 'ꮡ', + 'Ꮢ' => 'ꮢ', + 'Ꮣ' => 'ꮣ', + 'Ꮤ' => 'ꮤ', + 'Ꮥ' => 'ꮥ', + 'Ꮦ' => 'ꮦ', + 'Ꮧ' => 'ꮧ', + 'Ꮨ' => 'ꮨ', + 'Ꮩ' => 'ꮩ', + 'Ꮪ' => 'ꮪ', + 'Ꮫ' => 'ꮫ', + 'Ꮬ' => 'ꮬ', + 'Ꮭ' => 'ꮭ', + 'Ꮮ' => 'ꮮ', + 'Ꮯ' => 'ꮯ', + 'Ꮰ' => 'ꮰ', + 'Ꮱ' => 'ꮱ', + 'Ꮲ' => 'ꮲ', + 'Ꮳ' => 'ꮳ', + 'Ꮴ' => 'ꮴ', + 'Ꮵ' => 'ꮵ', + 'Ꮶ' => 'ꮶ', + 'Ꮷ' => 'ꮷ', + 'Ꮸ' => 'ꮸ', + 'Ꮹ' => 'ꮹ', + 'Ꮺ' => 'ꮺ', + 'Ꮻ' => 'ꮻ', + 'Ꮼ' => 'ꮼ', + 'Ꮽ' => 'ꮽ', + 'Ꮾ' => 'ꮾ', + 'Ꮿ' => 'ꮿ', + 'Ᏸ' => 'ᏸ', + 'Ᏹ' => 'ᏹ', + 'Ᏺ' => 'ᏺ', + 'Ᏻ' => 'ᏻ', + 'Ᏼ' => 'ᏼ', + 'Ᏽ' => 'ᏽ', + 'Ა' => 'ა', + 'Ბ' => 'ბ', + 'Გ' => 'გ', + 'Დ' => 'დ', + 'Ე' => 'ე', + 'Ვ' => 'ვ', + 'Ზ' => 'ზ', + 'Თ' => 'თ', + 'Ი' => 'ი', + 'Კ' => 'კ', + 'Ლ' => 'ლ', + 'Მ' => 'მ', + 'Ნ' => 'ნ', + 'Ო' => 'ო', + 'Პ' => 'პ', + 'Ჟ' => 'ჟ', + 'Რ' => 'რ', + 'Ს' => 'ს', + 'Ტ' => 'ტ', + 'Უ' => 'უ', + 'Ფ' => 'ფ', + 'Ქ' => 'ქ', + 'Ღ' => 'ღ', + 'Ყ' => 'ყ', + 'Შ' => 'შ', + 'Ჩ' => 'ჩ', + 'Ც' => 'ც', + 'Ძ' => 'ძ', + 'Წ' => 'წ', + 'Ჭ' => 'ჭ', + 'Ხ' => 'ხ', + 'Ჯ' => 'ჯ', + 'Ჰ' => 'ჰ', + 'Ჱ' => 'ჱ', + 'Ჲ' => 'ჲ', + 'Ჳ' => 'ჳ', + 'Ჴ' => 'ჴ', + 'Ჵ' => 'ჵ', + 'Ჶ' => 'ჶ', + 'Ჷ' => 'ჷ', + 'Ჸ' => 'ჸ', + 'Ჹ' => 'ჹ', + 'Ჺ' => 'ჺ', + 'Ჽ' => 'ჽ', + 'Ჾ' => 'ჾ', + 'Ჿ' => 'ჿ', + 'Ḁ' => 'ḁ', + 'Ḃ' => 'ḃ', + 'Ḅ' => 'ḅ', + 'Ḇ' => 'ḇ', + 'Ḉ' => 'ḉ', + 'Ḋ' => 'ḋ', + 'Ḍ' => 'ḍ', + 'Ḏ' => 'ḏ', + 'Ḑ' => 'ḑ', + 'Ḓ' => 'ḓ', + 'Ḕ' => 'ḕ', + 'Ḗ' => 'ḗ', + 'Ḙ' => 'ḙ', + 'Ḛ' => 'ḛ', + 'Ḝ' => 'ḝ', + 'Ḟ' => 'ḟ', + 'Ḡ' => 'ḡ', + 'Ḣ' => 'ḣ', + 'Ḥ' => 'ḥ', + 'Ḧ' => 'ḧ', + 'Ḩ' => 'ḩ', + 'Ḫ' => 'ḫ', + 'Ḭ' => 'ḭ', + 'Ḯ' => 'ḯ', + 'Ḱ' => 'ḱ', + 'Ḳ' => 'ḳ', + 'Ḵ' => 'ḵ', + 'Ḷ' => 'ḷ', + 'Ḹ' => 'ḹ', + 'Ḻ' => 'ḻ', + 'Ḽ' => 'ḽ', + 'Ḿ' => 'ḿ', + 'Ṁ' => 'ṁ', + 'Ṃ' => 'ṃ', + 'Ṅ' => 'ṅ', + 'Ṇ' => 'ṇ', + 'Ṉ' => 'ṉ', + 'Ṋ' => 'ṋ', + 'Ṍ' => 'ṍ', + 'Ṏ' => 'ṏ', + 'Ṑ' => 'ṑ', + 'Ṓ' => 'ṓ', + 'Ṕ' => 'ṕ', + 'Ṗ' => 'ṗ', + 'Ṙ' => 'ṙ', + 'Ṛ' => 'ṛ', + 'Ṝ' => 'ṝ', + 'Ṟ' => 'ṟ', + 'Ṡ' => 'ṡ', + 'Ṣ' => 'ṣ', + 'Ṥ' => 'ṥ', + 'Ṧ' => 'ṧ', + 'Ṩ' => 'ṩ', + 'Ṫ' => 'ṫ', + 'Ṭ' => 'ṭ', + 'Ṯ' => 'ṯ', + 'Ṱ' => 'ṱ', + 'Ṳ' => 'ṳ', + 'Ṵ' => 'ṵ', + 'Ṷ' => 'ṷ', + 'Ṹ' => 'ṹ', + 'Ṻ' => 'ṻ', + 'Ṽ' => 'ṽ', + 'Ṿ' => 'ṿ', + 'Ẁ' => 'ẁ', + 'Ẃ' => 'ẃ', + 'Ẅ' => 'ẅ', + 'Ẇ' => 'ẇ', + 'Ẉ' => 'ẉ', + 'Ẋ' => 'ẋ', + 'Ẍ' => 'ẍ', + 'Ẏ' => 'ẏ', + 'Ẑ' => 'ẑ', + 'Ẓ' => 'ẓ', + 'Ẕ' => 'ẕ', + 'ẞ' => 'ß', + 'Ạ' => 'ạ', + 'Ả' => 'ả', + 'Ấ' => 'ấ', + 'Ầ' => 'ầ', + 'Ẩ' => 'ẩ', + 'Ẫ' => 'ẫ', + 'Ậ' => 'ậ', + 'Ắ' => 'ắ', + 'Ằ' => 'ằ', + 'Ẳ' => 'ẳ', + 'Ẵ' => 'ẵ', + 'Ặ' => 'ặ', + 'Ẹ' => 'ẹ', + 'Ẻ' => 'ẻ', + 'Ẽ' => 'ẽ', + 'Ế' => 'ế', + 'Ề' => 'ề', + 'Ể' => 'ể', + 'Ễ' => 'ễ', + 'Ệ' => 'ệ', + 'Ỉ' => 'ỉ', + 'Ị' => 'ị', + 'Ọ' => 'ọ', + 'Ỏ' => 'ỏ', + 'Ố' => 'ố', + 'Ồ' => 'ồ', + 'Ổ' => 'ổ', + 'Ỗ' => 'ỗ', + 'Ộ' => 'ộ', + 'Ớ' => 'ớ', + 'Ờ' => 'ờ', + 'Ở' => 'ở', + 'Ỡ' => 'ỡ', + 'Ợ' => 'ợ', + 'Ụ' => 'ụ', + 'Ủ' => 'ủ', + 'Ứ' => 'ứ', + 'Ừ' => 'ừ', + 'Ử' => 'ử', + 'Ữ' => 'ữ', + 'Ự' => 'ự', + 'Ỳ' => 'ỳ', + 'Ỵ' => 'ỵ', + 'Ỷ' => 'ỷ', + 'Ỹ' => 'ỹ', + 'Ỻ' => 'ỻ', + 'Ỽ' => 'ỽ', + 'Ỿ' => 'ỿ', + 'Ἀ' => 'ἀ', + 'Ἁ' => 'ἁ', + 'Ἂ' => 'ἂ', + 'Ἃ' => 'ἃ', + 'Ἄ' => 'ἄ', + 'Ἅ' => 'ἅ', + 'Ἆ' => 'ἆ', + 'Ἇ' => 'ἇ', + 'Ἐ' => 'ἐ', + 'Ἑ' => 'ἑ', + 'Ἒ' => 'ἒ', + 'Ἓ' => 'ἓ', + 'Ἔ' => 'ἔ', + 'Ἕ' => 'ἕ', + 'Ἠ' => 'ἠ', + 'Ἡ' => 'ἡ', + 'Ἢ' => 'ἢ', + 'Ἣ' => 'ἣ', + 'Ἤ' => 'ἤ', + 'Ἥ' => 'ἥ', + 'Ἦ' => 'ἦ', + 'Ἧ' => 'ἧ', + 'Ἰ' => 'ἰ', + 'Ἱ' => 'ἱ', + 'Ἲ' => 'ἲ', + 'Ἳ' => 'ἳ', + 'Ἴ' => 'ἴ', + 'Ἵ' => 'ἵ', + 'Ἶ' => 'ἶ', + 'Ἷ' => 'ἷ', + 'Ὀ' => 'ὀ', + 'Ὁ' => 'ὁ', + 'Ὂ' => 'ὂ', + 'Ὃ' => 'ὃ', + 'Ὄ' => 'ὄ', + 'Ὅ' => 'ὅ', + 'Ὑ' => 'ὑ', + 'Ὓ' => 'ὓ', + 'Ὕ' => 'ὕ', + 'Ὗ' => 'ὗ', + 'Ὠ' => 'ὠ', + 'Ὡ' => 'ὡ', + 'Ὢ' => 'ὢ', + 'Ὣ' => 'ὣ', + 'Ὤ' => 'ὤ', + 'Ὥ' => 'ὥ', + 'Ὦ' => 'ὦ', + 'Ὧ' => 'ὧ', + 'ᾈ' => 'ᾀ', + 'ᾉ' => 'ᾁ', + 'ᾊ' => 'ᾂ', + 'ᾋ' => 'ᾃ', + 'ᾌ' => 'ᾄ', + 'ᾍ' => 'ᾅ', + 'ᾎ' => 'ᾆ', + 'ᾏ' => 'ᾇ', + 'ᾘ' => 'ᾐ', + 'ᾙ' => 'ᾑ', + 'ᾚ' => 'ᾒ', + 'ᾛ' => 'ᾓ', + 'ᾜ' => 'ᾔ', + 'ᾝ' => 'ᾕ', + 'ᾞ' => 'ᾖ', + 'ᾟ' => 'ᾗ', + 'ᾨ' => 'ᾠ', + 'ᾩ' => 'ᾡ', + 'ᾪ' => 'ᾢ', + 'ᾫ' => 'ᾣ', + 'ᾬ' => 'ᾤ', + 'ᾭ' => 'ᾥ', + 'ᾮ' => 'ᾦ', + 'ᾯ' => 'ᾧ', + 'Ᾰ' => 'ᾰ', + 'Ᾱ' => 'ᾱ', + 'Ὰ' => 'ὰ', + 'Ά' => 'ά', + 'ᾼ' => 'ᾳ', + 'Ὲ' => 'ὲ', + 'Έ' => 'έ', + 'Ὴ' => 'ὴ', + 'Ή' => 'ή', + 'ῌ' => 'ῃ', + 'Ῐ' => 'ῐ', + 'Ῑ' => 'ῑ', + 'Ὶ' => 'ὶ', + 'Ί' => 'ί', + 'Ῠ' => 'ῠ', + 'Ῡ' => 'ῡ', + 'Ὺ' => 'ὺ', + 'Ύ' => 'ύ', + 'Ῥ' => 'ῥ', + 'Ὸ' => 'ὸ', + 'Ό' => 'ό', + 'Ὼ' => 'ὼ', + 'Ώ' => 'ώ', + 'ῼ' => 'ῳ', + 'Ω' => 'ω', + 'K' => 'k', + 'Å' => 'å', + 'Ⅎ' => 'ⅎ', + 'Ⅰ' => 'ⅰ', + 'Ⅱ' => 'ⅱ', + 'Ⅲ' => 'ⅲ', + 'Ⅳ' => 'ⅳ', + 'Ⅴ' => 'ⅴ', + 'Ⅵ' => 'ⅵ', + 'Ⅶ' => 'ⅶ', + 'Ⅷ' => 'ⅷ', + 'Ⅸ' => 'ⅸ', + 'Ⅹ' => 'ⅹ', + 'Ⅺ' => 'ⅺ', + 'Ⅻ' => 'ⅻ', + 'Ⅼ' => 'ⅼ', + 'Ⅽ' => 'ⅽ', + 'Ⅾ' => 'ⅾ', + 'Ⅿ' => 'ⅿ', + 'Ↄ' => 'ↄ', + 'Ⓐ' => 'ⓐ', + 'Ⓑ' => 'ⓑ', + 'Ⓒ' => 'ⓒ', + 'Ⓓ' => 'ⓓ', + 'Ⓔ' => 'ⓔ', + 'Ⓕ' => 'ⓕ', + 'Ⓖ' => 'ⓖ', + 'Ⓗ' => 'ⓗ', + 'Ⓘ' => 'ⓘ', + 'Ⓙ' => 'ⓙ', + 'Ⓚ' => 'ⓚ', + 'Ⓛ' => 'ⓛ', + 'Ⓜ' => 'ⓜ', + 'Ⓝ' => 'ⓝ', + 'Ⓞ' => 'ⓞ', + 'Ⓟ' => 'ⓟ', + 'Ⓠ' => 'ⓠ', + 'Ⓡ' => 'ⓡ', + 'Ⓢ' => 'ⓢ', + 'Ⓣ' => 'ⓣ', + 'Ⓤ' => 'ⓤ', + 'Ⓥ' => 'ⓥ', + 'Ⓦ' => 'ⓦ', + 'Ⓧ' => 'ⓧ', + 'Ⓨ' => 'ⓨ', + 'Ⓩ' => 'ⓩ', + 'Ⰰ' => 'ⰰ', + 'Ⰱ' => 'ⰱ', + 'Ⰲ' => 'ⰲ', + 'Ⰳ' => 'ⰳ', + 'Ⰴ' => 'ⰴ', + 'Ⰵ' => 'ⰵ', + 'Ⰶ' => 'ⰶ', + 'Ⰷ' => 'ⰷ', + 'Ⰸ' => 'ⰸ', + 'Ⰹ' => 'ⰹ', + 'Ⰺ' => 'ⰺ', + 'Ⰻ' => 'ⰻ', + 'Ⰼ' => 'ⰼ', + 'Ⰽ' => 'ⰽ', + 'Ⰾ' => 'ⰾ', + 'Ⰿ' => 'ⰿ', + 'Ⱀ' => 'ⱀ', + 'Ⱁ' => 'ⱁ', + 'Ⱂ' => 'ⱂ', + 'Ⱃ' => 'ⱃ', + 'Ⱄ' => 'ⱄ', + 'Ⱅ' => 'ⱅ', + 'Ⱆ' => 'ⱆ', + 'Ⱇ' => 'ⱇ', + 'Ⱈ' => 'ⱈ', + 'Ⱉ' => 'ⱉ', + 'Ⱊ' => 'ⱊ', + 'Ⱋ' => 'ⱋ', + 'Ⱌ' => 'ⱌ', + 'Ⱍ' => 'ⱍ', + 'Ⱎ' => 'ⱎ', + 'Ⱏ' => 'ⱏ', + 'Ⱐ' => 'ⱐ', + 'Ⱑ' => 'ⱑ', + 'Ⱒ' => 'ⱒ', + 'Ⱓ' => 'ⱓ', + 'Ⱔ' => 'ⱔ', + 'Ⱕ' => 'ⱕ', + 'Ⱖ' => 'ⱖ', + 'Ⱗ' => 'ⱗ', + 'Ⱘ' => 'ⱘ', + 'Ⱙ' => 'ⱙ', + 'Ⱚ' => 'ⱚ', + 'Ⱛ' => 'ⱛ', + 'Ⱜ' => 'ⱜ', + 'Ⱝ' => 'ⱝ', + 'Ⱞ' => 'ⱞ', + 'Ⱡ' => 'ⱡ', + 'Ɫ' => 'ɫ', + 'Ᵽ' => 'ᵽ', + 'Ɽ' => 'ɽ', + 'Ⱨ' => 'ⱨ', + 'Ⱪ' => 'ⱪ', + 'Ⱬ' => 'ⱬ', + 'Ɑ' => 'ɑ', + 'Ɱ' => 'ɱ', + 'Ɐ' => 'ɐ', + 'Ɒ' => 'ɒ', + 'Ⱳ' => 'ⱳ', + 'Ⱶ' => 'ⱶ', + 'Ȿ' => 'ȿ', + 'Ɀ' => 'ɀ', + 'Ⲁ' => 'ⲁ', + 'Ⲃ' => 'ⲃ', + 'Ⲅ' => 'ⲅ', + 'Ⲇ' => 'ⲇ', + 'Ⲉ' => 'ⲉ', + 'Ⲋ' => 'ⲋ', + 'Ⲍ' => 'ⲍ', + 'Ⲏ' => 'ⲏ', + 'Ⲑ' => 'ⲑ', + 'Ⲓ' => 'ⲓ', + 'Ⲕ' => 'ⲕ', + 'Ⲗ' => 'ⲗ', + 'Ⲙ' => 'ⲙ', + 'Ⲛ' => 'ⲛ', + 'Ⲝ' => 'ⲝ', + 'Ⲟ' => 'ⲟ', + 'Ⲡ' => 'ⲡ', + 'Ⲣ' => 'ⲣ', + 'Ⲥ' => 'ⲥ', + 'Ⲧ' => 'ⲧ', + 'Ⲩ' => 'ⲩ', + 'Ⲫ' => 'ⲫ', + 'Ⲭ' => 'ⲭ', + 'Ⲯ' => 'ⲯ', + 'Ⲱ' => 'ⲱ', + 'Ⲳ' => 'ⲳ', + 'Ⲵ' => 'ⲵ', + 'Ⲷ' => 'ⲷ', + 'Ⲹ' => 'ⲹ', + 'Ⲻ' => 'ⲻ', + 'Ⲽ' => 'ⲽ', + 'Ⲿ' => 'ⲿ', + 'Ⳁ' => 'ⳁ', + 'Ⳃ' => 'ⳃ', + 'Ⳅ' => 'ⳅ', + 'Ⳇ' => 'ⳇ', + 'Ⳉ' => 'ⳉ', + 'Ⳋ' => 'ⳋ', + 'Ⳍ' => 'ⳍ', + 'Ⳏ' => 'ⳏ', + 'Ⳑ' => 'ⳑ', + 'Ⳓ' => 'ⳓ', + 'Ⳕ' => 'ⳕ', + 'Ⳗ' => 'ⳗ', + 'Ⳙ' => 'ⳙ', + 'Ⳛ' => 'ⳛ', + 'Ⳝ' => 'ⳝ', + 'Ⳟ' => 'ⳟ', + 'Ⳡ' => 'ⳡ', + 'Ⳣ' => 'ⳣ', + 'Ⳬ' => 'ⳬ', + 'Ⳮ' => 'ⳮ', + 'Ⳳ' => 'ⳳ', + 'Ꙁ' => 'ꙁ', + 'Ꙃ' => 'ꙃ', + 'Ꙅ' => 'ꙅ', + 'Ꙇ' => 'ꙇ', + 'Ꙉ' => 'ꙉ', + 'Ꙋ' => 'ꙋ', + 'Ꙍ' => 'ꙍ', + 'Ꙏ' => 'ꙏ', + 'Ꙑ' => 'ꙑ', + 'Ꙓ' => 'ꙓ', + 'Ꙕ' => 'ꙕ', + 'Ꙗ' => 'ꙗ', + 'Ꙙ' => 'ꙙ', + 'Ꙛ' => 'ꙛ', + 'Ꙝ' => 'ꙝ', + 'Ꙟ' => 'ꙟ', + 'Ꙡ' => 'ꙡ', + 'Ꙣ' => 'ꙣ', + 'Ꙥ' => 'ꙥ', + 'Ꙧ' => 'ꙧ', + 'Ꙩ' => 'ꙩ', + 'Ꙫ' => 'ꙫ', + 'Ꙭ' => 'ꙭ', + 'Ꚁ' => 'ꚁ', + 'Ꚃ' => 'ꚃ', + 'Ꚅ' => 'ꚅ', + 'Ꚇ' => 'ꚇ', + 'Ꚉ' => 'ꚉ', + 'Ꚋ' => 'ꚋ', + 'Ꚍ' => 'ꚍ', + 'Ꚏ' => 'ꚏ', + 'Ꚑ' => 'ꚑ', + 'Ꚓ' => 'ꚓ', + 'Ꚕ' => 'ꚕ', + 'Ꚗ' => 'ꚗ', + 'Ꚙ' => 'ꚙ', + 'Ꚛ' => 'ꚛ', + 'Ꜣ' => 'ꜣ', + 'Ꜥ' => 'ꜥ', + 'Ꜧ' => 'ꜧ', + 'Ꜩ' => 'ꜩ', + 'Ꜫ' => 'ꜫ', + 'Ꜭ' => 'ꜭ', + 'Ꜯ' => 'ꜯ', + 'Ꜳ' => 'ꜳ', + 'Ꜵ' => 'ꜵ', + 'Ꜷ' => 'ꜷ', + 'Ꜹ' => 'ꜹ', + 'Ꜻ' => 'ꜻ', + 'Ꜽ' => 'ꜽ', + 'Ꜿ' => 'ꜿ', + 'Ꝁ' => 'ꝁ', + 'Ꝃ' => 'ꝃ', + 'Ꝅ' => 'ꝅ', + 'Ꝇ' => 'ꝇ', + 'Ꝉ' => 'ꝉ', + 'Ꝋ' => 'ꝋ', + 'Ꝍ' => 'ꝍ', + 'Ꝏ' => 'ꝏ', + 'Ꝑ' => 'ꝑ', + 'Ꝓ' => 'ꝓ', + 'Ꝕ' => 'ꝕ', + 'Ꝗ' => 'ꝗ', + 'Ꝙ' => 'ꝙ', + 'Ꝛ' => 'ꝛ', + 'Ꝝ' => 'ꝝ', + 'Ꝟ' => 'ꝟ', + 'Ꝡ' => 'ꝡ', + 'Ꝣ' => 'ꝣ', + 'Ꝥ' => 'ꝥ', + 'Ꝧ' => 'ꝧ', + 'Ꝩ' => 'ꝩ', + 'Ꝫ' => 'ꝫ', + 'Ꝭ' => 'ꝭ', + 'Ꝯ' => 'ꝯ', + 'Ꝺ' => 'ꝺ', + 'Ꝼ' => 'ꝼ', + 'Ᵹ' => 'ᵹ', + 'Ꝿ' => 'ꝿ', + 'Ꞁ' => 'ꞁ', + 'Ꞃ' => 'ꞃ', + 'Ꞅ' => 'ꞅ', + 'Ꞇ' => 'ꞇ', + 'Ꞌ' => 'ꞌ', + 'Ɥ' => 'ɥ', + 'Ꞑ' => 'ꞑ', + 'Ꞓ' => 'ꞓ', + 'Ꞗ' => 'ꞗ', + 'Ꞙ' => 'ꞙ', + 'Ꞛ' => 'ꞛ', + 'Ꞝ' => 'ꞝ', + 'Ꞟ' => 'ꞟ', + 'Ꞡ' => 'ꞡ', + 'Ꞣ' => 'ꞣ', + 'Ꞥ' => 'ꞥ', + 'Ꞧ' => 'ꞧ', + 'Ꞩ' => 'ꞩ', + 'Ɦ' => 'ɦ', + 'Ɜ' => 'ɜ', + 'Ɡ' => 'ɡ', + 'Ɬ' => 'ɬ', + 'Ɪ' => 'ɪ', + 'Ʞ' => 'ʞ', + 'Ʇ' => 'ʇ', + 'Ʝ' => 'ʝ', + 'Ꭓ' => 'ꭓ', + 'Ꞵ' => 'ꞵ', + 'Ꞷ' => 'ꞷ', + 'Ꞹ' => 'ꞹ', + 'Ꞻ' => 'ꞻ', + 'Ꞽ' => 'ꞽ', + 'Ꞿ' => 'ꞿ', + 'Ꟃ' => 'ꟃ', + 'Ꞔ' => 'ꞔ', + 'Ʂ' => 'ʂ', + 'Ᶎ' => 'ᶎ', + 'Ꟈ' => 'ꟈ', + 'Ꟊ' => 'ꟊ', + 'Ꟶ' => 'ꟶ', + 'A' => 'a', + 'B' => 'b', + 'C' => 'c', + 'D' => 'd', + 'E' => 'e', + 'F' => 'f', + 'G' => 'g', + 'H' => 'h', + 'I' => 'i', + 'J' => 'j', + 'K' => 'k', + 'L' => 'l', + 'M' => 'm', + 'N' => 'n', + 'O' => 'o', + 'P' => 'p', + 'Q' => 'q', + 'R' => 'r', + 'S' => 's', + 'T' => 't', + 'U' => 'u', + 'V' => 'v', + 'W' => 'w', + 'X' => 'x', + 'Y' => 'y', + 'Z' => 'z', + '𐐀' => '𐐨', + '𐐁' => '𐐩', + '𐐂' => '𐐪', + '𐐃' => '𐐫', + '𐐄' => '𐐬', + '𐐅' => '𐐭', + '𐐆' => '𐐮', + '𐐇' => '𐐯', + '𐐈' => '𐐰', + '𐐉' => '𐐱', + '𐐊' => '𐐲', + '𐐋' => '𐐳', + '𐐌' => '𐐴', + '𐐍' => '𐐵', + '𐐎' => '𐐶', + '𐐏' => '𐐷', + '𐐐' => '𐐸', + '𐐑' => '𐐹', + '𐐒' => '𐐺', + '𐐓' => '𐐻', + '𐐔' => '𐐼', + '𐐕' => '𐐽', + '𐐖' => '𐐾', + '𐐗' => '𐐿', + '𐐘' => '𐑀', + '𐐙' => '𐑁', + '𐐚' => '𐑂', + '𐐛' => '𐑃', + '𐐜' => '𐑄', + '𐐝' => '𐑅', + '𐐞' => '𐑆', + '𐐟' => '𐑇', + '𐐠' => '𐑈', + '𐐡' => '𐑉', + '𐐢' => '𐑊', + '𐐣' => '𐑋', + '𐐤' => '𐑌', + '𐐥' => '𐑍', + '𐐦' => '𐑎', + '𐐧' => '𐑏', + '𐒰' => '𐓘', + '𐒱' => '𐓙', + '𐒲' => '𐓚', + '𐒳' => '𐓛', + '𐒴' => '𐓜', + '𐒵' => '𐓝', + '𐒶' => '𐓞', + '𐒷' => '𐓟', + '𐒸' => '𐓠', + '𐒹' => '𐓡', + '𐒺' => '𐓢', + '𐒻' => '𐓣', + '𐒼' => '𐓤', + '𐒽' => '𐓥', + '𐒾' => '𐓦', + '𐒿' => '𐓧', + '𐓀' => '𐓨', + '𐓁' => '𐓩', + '𐓂' => '𐓪', + '𐓃' => '𐓫', + '𐓄' => '𐓬', + '𐓅' => '𐓭', + '𐓆' => '𐓮', + '𐓇' => '𐓯', + '𐓈' => '𐓰', + '𐓉' => '𐓱', + '𐓊' => '𐓲', + '𐓋' => '𐓳', + '𐓌' => '𐓴', + '𐓍' => '𐓵', + '𐓎' => '𐓶', + '𐓏' => '𐓷', + '𐓐' => '𐓸', + '𐓑' => '𐓹', + '𐓒' => '𐓺', + '𐓓' => '𐓻', + '𐲀' => '𐳀', + '𐲁' => '𐳁', + '𐲂' => '𐳂', + '𐲃' => '𐳃', + '𐲄' => '𐳄', + '𐲅' => '𐳅', + '𐲆' => '𐳆', + '𐲇' => '𐳇', + '𐲈' => '𐳈', + '𐲉' => '𐳉', + '𐲊' => '𐳊', + '𐲋' => '𐳋', + '𐲌' => '𐳌', + '𐲍' => '𐳍', + '𐲎' => '𐳎', + '𐲏' => '𐳏', + '𐲐' => '𐳐', + '𐲑' => '𐳑', + '𐲒' => '𐳒', + '𐲓' => '𐳓', + '𐲔' => '𐳔', + '𐲕' => '𐳕', + '𐲖' => '𐳖', + '𐲗' => '𐳗', + '𐲘' => '𐳘', + '𐲙' => '𐳙', + '𐲚' => '𐳚', + '𐲛' => '𐳛', + '𐲜' => '𐳜', + '𐲝' => '𐳝', + '𐲞' => '𐳞', + '𐲟' => '𐳟', + '𐲠' => '𐳠', + '𐲡' => '𐳡', + '𐲢' => '𐳢', + '𐲣' => '𐳣', + '𐲤' => '𐳤', + '𐲥' => '𐳥', + '𐲦' => '𐳦', + '𐲧' => '𐳧', + '𐲨' => '𐳨', + '𐲩' => '𐳩', + '𐲪' => '𐳪', + '𐲫' => '𐳫', + '𐲬' => '𐳬', + '𐲭' => '𐳭', + '𐲮' => '𐳮', + '𐲯' => '𐳯', + '𐲰' => '𐳰', + '𐲱' => '𐳱', + '𐲲' => '𐳲', + '𑢠' => '𑣀', + '𑢡' => '𑣁', + '𑢢' => '𑣂', + '𑢣' => '𑣃', + '𑢤' => '𑣄', + '𑢥' => '𑣅', + '𑢦' => '𑣆', + '𑢧' => '𑣇', + '𑢨' => '𑣈', + '𑢩' => '𑣉', + '𑢪' => '𑣊', + '𑢫' => '𑣋', + '𑢬' => '𑣌', + '𑢭' => '𑣍', + '𑢮' => '𑣎', + '𑢯' => '𑣏', + '𑢰' => '𑣐', + '𑢱' => '𑣑', + '𑢲' => '𑣒', + '𑢳' => '𑣓', + '𑢴' => '𑣔', + '𑢵' => '𑣕', + '𑢶' => '𑣖', + '𑢷' => '𑣗', + '𑢸' => '𑣘', + '𑢹' => '𑣙', + '𑢺' => '𑣚', + '𑢻' => '𑣛', + '𑢼' => '𑣜', + '𑢽' => '𑣝', + '𑢾' => '𑣞', + '𑢿' => '𑣟', + '𖹀' => '𖹠', + '𖹁' => '𖹡', + '𖹂' => '𖹢', + '𖹃' => '𖹣', + '𖹄' => '𖹤', + '𖹅' => '𖹥', + '𖹆' => '𖹦', + '𖹇' => '𖹧', + '𖹈' => '𖹨', + '𖹉' => '𖹩', + '𖹊' => '𖹪', + '𖹋' => '𖹫', + '𖹌' => '𖹬', + '𖹍' => '𖹭', + '𖹎' => '𖹮', + '𖹏' => '𖹯', + '𖹐' => '𖹰', + '𖹑' => '𖹱', + '𖹒' => '𖹲', + '𖹓' => '𖹳', + '𖹔' => '𖹴', + '𖹕' => '𖹵', + '𖹖' => '𖹶', + '𖹗' => '𖹷', + '𖹘' => '𖹸', + '𖹙' => '𖹹', + '𖹚' => '𖹺', + '𖹛' => '𖹻', + '𖹜' => '𖹼', + '𖹝' => '𖹽', + '𖹞' => '𖹾', + '𖹟' => '𖹿', + '𞤀' => '𞤢', + '𞤁' => '𞤣', + '𞤂' => '𞤤', + '𞤃' => '𞤥', + '𞤄' => '𞤦', + '𞤅' => '𞤧', + '𞤆' => '𞤨', + '𞤇' => '𞤩', + '𞤈' => '𞤪', + '𞤉' => '𞤫', + '𞤊' => '𞤬', + '𞤋' => '𞤭', + '𞤌' => '𞤮', + '𞤍' => '𞤯', + '𞤎' => '𞤰', + '𞤏' => '𞤱', + '𞤐' => '𞤲', + '𞤑' => '𞤳', + '𞤒' => '𞤴', + '𞤓' => '𞤵', + '𞤔' => '𞤶', + '𞤕' => '𞤷', + '𞤖' => '𞤸', + '𞤗' => '𞤹', + '𞤘' => '𞤺', + '𞤙' => '𞤻', + '𞤚' => '𞤼', + '𞤛' => '𞤽', + '𞤜' => '𞤾', + '𞤝' => '𞤿', + '𞤞' => '𞥀', + '𞤟' => '𞥁', + '𞤠' => '𞥂', + '𞤡' => '𞥃', +); diff --git a/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php b/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php new file mode 100644 index 0000000..2a8f6e7 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php @@ -0,0 +1,5 @@ + 'A', + 'b' => 'B', + 'c' => 'C', + 'd' => 'D', + 'e' => 'E', + 'f' => 'F', + 'g' => 'G', + 'h' => 'H', + 'i' => 'I', + 'j' => 'J', + 'k' => 'K', + 'l' => 'L', + 'm' => 'M', + 'n' => 'N', + 'o' => 'O', + 'p' => 'P', + 'q' => 'Q', + 'r' => 'R', + 's' => 'S', + 't' => 'T', + 'u' => 'U', + 'v' => 'V', + 'w' => 'W', + 'x' => 'X', + 'y' => 'Y', + 'z' => 'Z', + 'µ' => 'Μ', + 'à' => 'À', + 'á' => 'Á', + 'â' => 'Â', + 'ã' => 'Ã', + 'ä' => 'Ä', + 'å' => 'Å', + 'æ' => 'Æ', + 'ç' => 'Ç', + 'è' => 'È', + 'é' => 'É', + 'ê' => 'Ê', + 'ë' => 'Ë', + 'ì' => 'Ì', + 'í' => 'Í', + 'î' => 'Î', + 'ï' => 'Ï', + 'ð' => 'Ð', + 'ñ' => 'Ñ', + 'ò' => 'Ò', + 'ó' => 'Ó', + 'ô' => 'Ô', + 'õ' => 'Õ', + 'ö' => 'Ö', + 'ø' => 'Ø', + 'ù' => 'Ù', + 'ú' => 'Ú', + 'û' => 'Û', + 'ü' => 'Ü', + 'ý' => 'Ý', + 'þ' => 'Þ', + 'ÿ' => 'Ÿ', + 'ā' => 'Ā', + 'ă' => 'Ă', + 'ą' => 'Ą', + 'ć' => 'Ć', + 'ĉ' => 'Ĉ', + 'ċ' => 'Ċ', + 'č' => 'Č', + 'ď' => 'Ď', + 'đ' => 'Đ', + 'ē' => 'Ē', + 'ĕ' => 'Ĕ', + 'ė' => 'Ė', + 'ę' => 'Ę', + 'ě' => 'Ě', + 'ĝ' => 'Ĝ', + 'ğ' => 'Ğ', + 'ġ' => 'Ġ', + 'ģ' => 'Ģ', + 'ĥ' => 'Ĥ', + 'ħ' => 'Ħ', + 'ĩ' => 'Ĩ', + 'ī' => 'Ī', + 'ĭ' => 'Ĭ', + 'į' => 'Į', + 'ı' => 'I', + 'ij' => 'IJ', + 'ĵ' => 'Ĵ', + 'ķ' => 'Ķ', + 'ĺ' => 'Ĺ', + 'ļ' => 'Ļ', + 'ľ' => 'Ľ', + 'ŀ' => 'Ŀ', + 'ł' => 'Ł', + 'ń' => 'Ń', + 'ņ' => 'Ņ', + 'ň' => 'Ň', + 'ŋ' => 'Ŋ', + 'ō' => 'Ō', + 'ŏ' => 'Ŏ', + 'ő' => 'Ő', + 'œ' => 'Œ', + 'ŕ' => 'Ŕ', + 'ŗ' => 'Ŗ', + 'ř' => 'Ř', + 'ś' => 'Ś', + 'ŝ' => 'Ŝ', + 'ş' => 'Ş', + 'š' => 'Š', + 'ţ' => 'Ţ', + 'ť' => 'Ť', + 'ŧ' => 'Ŧ', + 'ũ' => 'Ũ', + 'ū' => 'Ū', + 'ŭ' => 'Ŭ', + 'ů' => 'Ů', + 'ű' => 'Ű', + 'ų' => 'Ų', + 'ŵ' => 'Ŵ', + 'ŷ' => 'Ŷ', + 'ź' => 'Ź', + 'ż' => 'Ż', + 'ž' => 'Ž', + 'ſ' => 'S', + 'ƀ' => 'Ƀ', + 'ƃ' => 'Ƃ', + 'ƅ' => 'Ƅ', + 'ƈ' => 'Ƈ', + 'ƌ' => 'Ƌ', + 'ƒ' => 'Ƒ', + 'ƕ' => 'Ƕ', + 'ƙ' => 'Ƙ', + 'ƚ' => 'Ƚ', + 'ƞ' => 'Ƞ', + 'ơ' => 'Ơ', + 'ƣ' => 'Ƣ', + 'ƥ' => 'Ƥ', + 'ƨ' => 'Ƨ', + 'ƭ' => 'Ƭ', + 'ư' => 'Ư', + 'ƴ' => 'Ƴ', + 'ƶ' => 'Ƶ', + 'ƹ' => 'Ƹ', + 'ƽ' => 'Ƽ', + 'ƿ' => 'Ƿ', + 'Dž' => 'DŽ', + 'dž' => 'DŽ', + 'Lj' => 'LJ', + 'lj' => 'LJ', + 'Nj' => 'NJ', + 'nj' => 'NJ', + 'ǎ' => 'Ǎ', + 'ǐ' => 'Ǐ', + 'ǒ' => 'Ǒ', + 'ǔ' => 'Ǔ', + 'ǖ' => 'Ǖ', + 'ǘ' => 'Ǘ', + 'ǚ' => 'Ǚ', + 'ǜ' => 'Ǜ', + 'ǝ' => 'Ǝ', + 'ǟ' => 'Ǟ', + 'ǡ' => 'Ǡ', + 'ǣ' => 'Ǣ', + 'ǥ' => 'Ǥ', + 'ǧ' => 'Ǧ', + 'ǩ' => 'Ǩ', + 'ǫ' => 'Ǫ', + 'ǭ' => 'Ǭ', + 'ǯ' => 'Ǯ', + 'Dz' => 'DZ', + 'dz' => 'DZ', + 'ǵ' => 'Ǵ', + 'ǹ' => 'Ǹ', + 'ǻ' => 'Ǻ', + 'ǽ' => 'Ǽ', + 'ǿ' => 'Ǿ', + 'ȁ' => 'Ȁ', + 'ȃ' => 'Ȃ', + 'ȅ' => 'Ȅ', + 'ȇ' => 'Ȇ', + 'ȉ' => 'Ȉ', + 'ȋ' => 'Ȋ', + 'ȍ' => 'Ȍ', + 'ȏ' => 'Ȏ', + 'ȑ' => 'Ȑ', + 'ȓ' => 'Ȓ', + 'ȕ' => 'Ȕ', + 'ȗ' => 'Ȗ', + 'ș' => 'Ș', + 'ț' => 'Ț', + 'ȝ' => 'Ȝ', + 'ȟ' => 'Ȟ', + 'ȣ' => 'Ȣ', + 'ȥ' => 'Ȥ', + 'ȧ' => 'Ȧ', + 'ȩ' => 'Ȩ', + 'ȫ' => 'Ȫ', + 'ȭ' => 'Ȭ', + 'ȯ' => 'Ȯ', + 'ȱ' => 'Ȱ', + 'ȳ' => 'Ȳ', + 'ȼ' => 'Ȼ', + 'ȿ' => 'Ȿ', + 'ɀ' => 'Ɀ', + 'ɂ' => 'Ɂ', + 'ɇ' => 'Ɇ', + 'ɉ' => 'Ɉ', + 'ɋ' => 'Ɋ', + 'ɍ' => 'Ɍ', + 'ɏ' => 'Ɏ', + 'ɐ' => 'Ɐ', + 'ɑ' => 'Ɑ', + 'ɒ' => 'Ɒ', + 'ɓ' => 'Ɓ', + 'ɔ' => 'Ɔ', + 'ɖ' => 'Ɖ', + 'ɗ' => 'Ɗ', + 'ə' => 'Ə', + 'ɛ' => 'Ɛ', + 'ɜ' => 'Ɜ', + 'ɠ' => 'Ɠ', + 'ɡ' => 'Ɡ', + 'ɣ' => 'Ɣ', + 'ɥ' => 'Ɥ', + 'ɦ' => 'Ɦ', + 'ɨ' => 'Ɨ', + 'ɩ' => 'Ɩ', + 'ɪ' => 'Ɪ', + 'ɫ' => 'Ɫ', + 'ɬ' => 'Ɬ', + 'ɯ' => 'Ɯ', + 'ɱ' => 'Ɱ', + 'ɲ' => 'Ɲ', + 'ɵ' => 'Ɵ', + 'ɽ' => 'Ɽ', + 'ʀ' => 'Ʀ', + 'ʂ' => 'Ʂ', + 'ʃ' => 'Ʃ', + 'ʇ' => 'Ʇ', + 'ʈ' => 'Ʈ', + 'ʉ' => 'Ʉ', + 'ʊ' => 'Ʊ', + 'ʋ' => 'Ʋ', + 'ʌ' => 'Ʌ', + 'ʒ' => 'Ʒ', + 'ʝ' => 'Ʝ', + 'ʞ' => 'Ʞ', + 'ͅ' => 'Ι', + 'ͱ' => 'Ͱ', + 'ͳ' => 'Ͳ', + 'ͷ' => 'Ͷ', + 'ͻ' => 'Ͻ', + 'ͼ' => 'Ͼ', + 'ͽ' => 'Ͽ', + 'ά' => 'Ά', + 'έ' => 'Έ', + 'ή' => 'Ή', + 'ί' => 'Ί', + 'α' => 'Α', + 'β' => 'Β', + 'γ' => 'Γ', + 'δ' => 'Δ', + 'ε' => 'Ε', + 'ζ' => 'Ζ', + 'η' => 'Η', + 'θ' => 'Θ', + 'ι' => 'Ι', + 'κ' => 'Κ', + 'λ' => 'Λ', + 'μ' => 'Μ', + 'ν' => 'Ν', + 'ξ' => 'Ξ', + 'ο' => 'Ο', + 'π' => 'Π', + 'ρ' => 'Ρ', + 'ς' => 'Σ', + 'σ' => 'Σ', + 'τ' => 'Τ', + 'υ' => 'Υ', + 'φ' => 'Φ', + 'χ' => 'Χ', + 'ψ' => 'Ψ', + 'ω' => 'Ω', + 'ϊ' => 'Ϊ', + 'ϋ' => 'Ϋ', + 'ό' => 'Ό', + 'ύ' => 'Ύ', + 'ώ' => 'Ώ', + 'ϐ' => 'Β', + 'ϑ' => 'Θ', + 'ϕ' => 'Φ', + 'ϖ' => 'Π', + 'ϗ' => 'Ϗ', + 'ϙ' => 'Ϙ', + 'ϛ' => 'Ϛ', + 'ϝ' => 'Ϝ', + 'ϟ' => 'Ϟ', + 'ϡ' => 'Ϡ', + 'ϣ' => 'Ϣ', + 'ϥ' => 'Ϥ', + 'ϧ' => 'Ϧ', + 'ϩ' => 'Ϩ', + 'ϫ' => 'Ϫ', + 'ϭ' => 'Ϭ', + 'ϯ' => 'Ϯ', + 'ϰ' => 'Κ', + 'ϱ' => 'Ρ', + 'ϲ' => 'Ϲ', + 'ϳ' => 'Ϳ', + 'ϵ' => 'Ε', + 'ϸ' => 'Ϸ', + 'ϻ' => 'Ϻ', + 'а' => 'А', + 'б' => 'Б', + 'в' => 'В', + 'г' => 'Г', + 'д' => 'Д', + 'е' => 'Е', + 'ж' => 'Ж', + 'з' => 'З', + 'и' => 'И', + 'й' => 'Й', + 'к' => 'К', + 'л' => 'Л', + 'м' => 'М', + 'н' => 'Н', + 'о' => 'О', + 'п' => 'П', + 'р' => 'Р', + 'с' => 'С', + 'т' => 'Т', + 'у' => 'У', + 'ф' => 'Ф', + 'х' => 'Х', + 'ц' => 'Ц', + 'ч' => 'Ч', + 'ш' => 'Ш', + 'щ' => 'Щ', + 'ъ' => 'Ъ', + 'ы' => 'Ы', + 'ь' => 'Ь', + 'э' => 'Э', + 'ю' => 'Ю', + 'я' => 'Я', + 'ѐ' => 'Ѐ', + 'ё' => 'Ё', + 'ђ' => 'Ђ', + 'ѓ' => 'Ѓ', + 'є' => 'Є', + 'ѕ' => 'Ѕ', + 'і' => 'І', + 'ї' => 'Ї', + 'ј' => 'Ј', + 'љ' => 'Љ', + 'њ' => 'Њ', + 'ћ' => 'Ћ', + 'ќ' => 'Ќ', + 'ѝ' => 'Ѝ', + 'ў' => 'Ў', + 'џ' => 'Џ', + 'ѡ' => 'Ѡ', + 'ѣ' => 'Ѣ', + 'ѥ' => 'Ѥ', + 'ѧ' => 'Ѧ', + 'ѩ' => 'Ѩ', + 'ѫ' => 'Ѫ', + 'ѭ' => 'Ѭ', + 'ѯ' => 'Ѯ', + 'ѱ' => 'Ѱ', + 'ѳ' => 'Ѳ', + 'ѵ' => 'Ѵ', + 'ѷ' => 'Ѷ', + 'ѹ' => 'Ѹ', + 'ѻ' => 'Ѻ', + 'ѽ' => 'Ѽ', + 'ѿ' => 'Ѿ', + 'ҁ' => 'Ҁ', + 'ҋ' => 'Ҋ', + 'ҍ' => 'Ҍ', + 'ҏ' => 'Ҏ', + 'ґ' => 'Ґ', + 'ғ' => 'Ғ', + 'ҕ' => 'Ҕ', + 'җ' => 'Җ', + 'ҙ' => 'Ҙ', + 'қ' => 'Қ', + 'ҝ' => 'Ҝ', + 'ҟ' => 'Ҟ', + 'ҡ' => 'Ҡ', + 'ң' => 'Ң', + 'ҥ' => 'Ҥ', + 'ҧ' => 'Ҧ', + 'ҩ' => 'Ҩ', + 'ҫ' => 'Ҫ', + 'ҭ' => 'Ҭ', + 'ү' => 'Ү', + 'ұ' => 'Ұ', + 'ҳ' => 'Ҳ', + 'ҵ' => 'Ҵ', + 'ҷ' => 'Ҷ', + 'ҹ' => 'Ҹ', + 'һ' => 'Һ', + 'ҽ' => 'Ҽ', + 'ҿ' => 'Ҿ', + 'ӂ' => 'Ӂ', + 'ӄ' => 'Ӄ', + 'ӆ' => 'Ӆ', + 'ӈ' => 'Ӈ', + 'ӊ' => 'Ӊ', + 'ӌ' => 'Ӌ', + 'ӎ' => 'Ӎ', + 'ӏ' => 'Ӏ', + 'ӑ' => 'Ӑ', + 'ӓ' => 'Ӓ', + 'ӕ' => 'Ӕ', + 'ӗ' => 'Ӗ', + 'ә' => 'Ә', + 'ӛ' => 'Ӛ', + 'ӝ' => 'Ӝ', + 'ӟ' => 'Ӟ', + 'ӡ' => 'Ӡ', + 'ӣ' => 'Ӣ', + 'ӥ' => 'Ӥ', + 'ӧ' => 'Ӧ', + 'ө' => 'Ө', + 'ӫ' => 'Ӫ', + 'ӭ' => 'Ӭ', + 'ӯ' => 'Ӯ', + 'ӱ' => 'Ӱ', + 'ӳ' => 'Ӳ', + 'ӵ' => 'Ӵ', + 'ӷ' => 'Ӷ', + 'ӹ' => 'Ӹ', + 'ӻ' => 'Ӻ', + 'ӽ' => 'Ӽ', + 'ӿ' => 'Ӿ', + 'ԁ' => 'Ԁ', + 'ԃ' => 'Ԃ', + 'ԅ' => 'Ԅ', + 'ԇ' => 'Ԇ', + 'ԉ' => 'Ԉ', + 'ԋ' => 'Ԋ', + 'ԍ' => 'Ԍ', + 'ԏ' => 'Ԏ', + 'ԑ' => 'Ԑ', + 'ԓ' => 'Ԓ', + 'ԕ' => 'Ԕ', + 'ԗ' => 'Ԗ', + 'ԙ' => 'Ԙ', + 'ԛ' => 'Ԛ', + 'ԝ' => 'Ԝ', + 'ԟ' => 'Ԟ', + 'ԡ' => 'Ԡ', + 'ԣ' => 'Ԣ', + 'ԥ' => 'Ԥ', + 'ԧ' => 'Ԧ', + 'ԩ' => 'Ԩ', + 'ԫ' => 'Ԫ', + 'ԭ' => 'Ԭ', + 'ԯ' => 'Ԯ', + 'ա' => 'Ա', + 'բ' => 'Բ', + 'գ' => 'Գ', + 'դ' => 'Դ', + 'ե' => 'Ե', + 'զ' => 'Զ', + 'է' => 'Է', + 'ը' => 'Ը', + 'թ' => 'Թ', + 'ժ' => 'Ժ', + 'ի' => 'Ի', + 'լ' => 'Լ', + 'խ' => 'Խ', + 'ծ' => 'Ծ', + 'կ' => 'Կ', + 'հ' => 'Հ', + 'ձ' => 'Ձ', + 'ղ' => 'Ղ', + 'ճ' => 'Ճ', + 'մ' => 'Մ', + 'յ' => 'Յ', + 'ն' => 'Ն', + 'շ' => 'Շ', + 'ո' => 'Ո', + 'չ' => 'Չ', + 'պ' => 'Պ', + 'ջ' => 'Ջ', + 'ռ' => 'Ռ', + 'ս' => 'Ս', + 'վ' => 'Վ', + 'տ' => 'Տ', + 'ր' => 'Ր', + 'ց' => 'Ց', + 'ւ' => 'Ւ', + 'փ' => 'Փ', + 'ք' => 'Ք', + 'օ' => 'Օ', + 'ֆ' => 'Ֆ', + 'ა' => 'Ა', + 'ბ' => 'Ბ', + 'გ' => 'Გ', + 'დ' => 'Დ', + 'ე' => 'Ე', + 'ვ' => 'Ვ', + 'ზ' => 'Ზ', + 'თ' => 'Თ', + 'ი' => 'Ი', + 'კ' => 'Კ', + 'ლ' => 'Ლ', + 'მ' => 'Მ', + 'ნ' => 'Ნ', + 'ო' => 'Ო', + 'პ' => 'Პ', + 'ჟ' => 'Ჟ', + 'რ' => 'Რ', + 'ს' => 'Ს', + 'ტ' => 'Ტ', + 'უ' => 'Უ', + 'ფ' => 'Ფ', + 'ქ' => 'Ქ', + 'ღ' => 'Ღ', + 'ყ' => 'Ყ', + 'შ' => 'Შ', + 'ჩ' => 'Ჩ', + 'ც' => 'Ც', + 'ძ' => 'Ძ', + 'წ' => 'Წ', + 'ჭ' => 'Ჭ', + 'ხ' => 'Ხ', + 'ჯ' => 'Ჯ', + 'ჰ' => 'Ჰ', + 'ჱ' => 'Ჱ', + 'ჲ' => 'Ჲ', + 'ჳ' => 'Ჳ', + 'ჴ' => 'Ჴ', + 'ჵ' => 'Ჵ', + 'ჶ' => 'Ჶ', + 'ჷ' => 'Ჷ', + 'ჸ' => 'Ჸ', + 'ჹ' => 'Ჹ', + 'ჺ' => 'Ჺ', + 'ჽ' => 'Ჽ', + 'ჾ' => 'Ჾ', + 'ჿ' => 'Ჿ', + 'ᏸ' => 'Ᏸ', + 'ᏹ' => 'Ᏹ', + 'ᏺ' => 'Ᏺ', + 'ᏻ' => 'Ᏻ', + 'ᏼ' => 'Ᏼ', + 'ᏽ' => 'Ᏽ', + 'ᲀ' => 'В', + 'ᲁ' => 'Д', + 'ᲂ' => 'О', + 'ᲃ' => 'С', + 'ᲄ' => 'Т', + 'ᲅ' => 'Т', + 'ᲆ' => 'Ъ', + 'ᲇ' => 'Ѣ', + 'ᲈ' => 'Ꙋ', + 'ᵹ' => 'Ᵹ', + 'ᵽ' => 'Ᵽ', + 'ᶎ' => 'Ᶎ', + 'ḁ' => 'Ḁ', + 'ḃ' => 'Ḃ', + 'ḅ' => 'Ḅ', + 'ḇ' => 'Ḇ', + 'ḉ' => 'Ḉ', + 'ḋ' => 'Ḋ', + 'ḍ' => 'Ḍ', + 'ḏ' => 'Ḏ', + 'ḑ' => 'Ḑ', + 'ḓ' => 'Ḓ', + 'ḕ' => 'Ḕ', + 'ḗ' => 'Ḗ', + 'ḙ' => 'Ḙ', + 'ḛ' => 'Ḛ', + 'ḝ' => 'Ḝ', + 'ḟ' => 'Ḟ', + 'ḡ' => 'Ḡ', + 'ḣ' => 'Ḣ', + 'ḥ' => 'Ḥ', + 'ḧ' => 'Ḧ', + 'ḩ' => 'Ḩ', + 'ḫ' => 'Ḫ', + 'ḭ' => 'Ḭ', + 'ḯ' => 'Ḯ', + 'ḱ' => 'Ḱ', + 'ḳ' => 'Ḳ', + 'ḵ' => 'Ḵ', + 'ḷ' => 'Ḷ', + 'ḹ' => 'Ḹ', + 'ḻ' => 'Ḻ', + 'ḽ' => 'Ḽ', + 'ḿ' => 'Ḿ', + 'ṁ' => 'Ṁ', + 'ṃ' => 'Ṃ', + 'ṅ' => 'Ṅ', + 'ṇ' => 'Ṇ', + 'ṉ' => 'Ṉ', + 'ṋ' => 'Ṋ', + 'ṍ' => 'Ṍ', + 'ṏ' => 'Ṏ', + 'ṑ' => 'Ṑ', + 'ṓ' => 'Ṓ', + 'ṕ' => 'Ṕ', + 'ṗ' => 'Ṗ', + 'ṙ' => 'Ṙ', + 'ṛ' => 'Ṛ', + 'ṝ' => 'Ṝ', + 'ṟ' => 'Ṟ', + 'ṡ' => 'Ṡ', + 'ṣ' => 'Ṣ', + 'ṥ' => 'Ṥ', + 'ṧ' => 'Ṧ', + 'ṩ' => 'Ṩ', + 'ṫ' => 'Ṫ', + 'ṭ' => 'Ṭ', + 'ṯ' => 'Ṯ', + 'ṱ' => 'Ṱ', + 'ṳ' => 'Ṳ', + 'ṵ' => 'Ṵ', + 'ṷ' => 'Ṷ', + 'ṹ' => 'Ṹ', + 'ṻ' => 'Ṻ', + 'ṽ' => 'Ṽ', + 'ṿ' => 'Ṿ', + 'ẁ' => 'Ẁ', + 'ẃ' => 'Ẃ', + 'ẅ' => 'Ẅ', + 'ẇ' => 'Ẇ', + 'ẉ' => 'Ẉ', + 'ẋ' => 'Ẋ', + 'ẍ' => 'Ẍ', + 'ẏ' => 'Ẏ', + 'ẑ' => 'Ẑ', + 'ẓ' => 'Ẓ', + 'ẕ' => 'Ẕ', + 'ẛ' => 'Ṡ', + 'ạ' => 'Ạ', + 'ả' => 'Ả', + 'ấ' => 'Ấ', + 'ầ' => 'Ầ', + 'ẩ' => 'Ẩ', + 'ẫ' => 'Ẫ', + 'ậ' => 'Ậ', + 'ắ' => 'Ắ', + 'ằ' => 'Ằ', + 'ẳ' => 'Ẳ', + 'ẵ' => 'Ẵ', + 'ặ' => 'Ặ', + 'ẹ' => 'Ẹ', + 'ẻ' => 'Ẻ', + 'ẽ' => 'Ẽ', + 'ế' => 'Ế', + 'ề' => 'Ề', + 'ể' => 'Ể', + 'ễ' => 'Ễ', + 'ệ' => 'Ệ', + 'ỉ' => 'Ỉ', + 'ị' => 'Ị', + 'ọ' => 'Ọ', + 'ỏ' => 'Ỏ', + 'ố' => 'Ố', + 'ồ' => 'Ồ', + 'ổ' => 'Ổ', + 'ỗ' => 'Ỗ', + 'ộ' => 'Ộ', + 'ớ' => 'Ớ', + 'ờ' => 'Ờ', + 'ở' => 'Ở', + 'ỡ' => 'Ỡ', + 'ợ' => 'Ợ', + 'ụ' => 'Ụ', + 'ủ' => 'Ủ', + 'ứ' => 'Ứ', + 'ừ' => 'Ừ', + 'ử' => 'Ử', + 'ữ' => 'Ữ', + 'ự' => 'Ự', + 'ỳ' => 'Ỳ', + 'ỵ' => 'Ỵ', + 'ỷ' => 'Ỷ', + 'ỹ' => 'Ỹ', + 'ỻ' => 'Ỻ', + 'ỽ' => 'Ỽ', + 'ỿ' => 'Ỿ', + 'ἀ' => 'Ἀ', + 'ἁ' => 'Ἁ', + 'ἂ' => 'Ἂ', + 'ἃ' => 'Ἃ', + 'ἄ' => 'Ἄ', + 'ἅ' => 'Ἅ', + 'ἆ' => 'Ἆ', + 'ἇ' => 'Ἇ', + 'ἐ' => 'Ἐ', + 'ἑ' => 'Ἑ', + 'ἒ' => 'Ἒ', + 'ἓ' => 'Ἓ', + 'ἔ' => 'Ἔ', + 'ἕ' => 'Ἕ', + 'ἠ' => 'Ἠ', + 'ἡ' => 'Ἡ', + 'ἢ' => 'Ἢ', + 'ἣ' => 'Ἣ', + 'ἤ' => 'Ἤ', + 'ἥ' => 'Ἥ', + 'ἦ' => 'Ἦ', + 'ἧ' => 'Ἧ', + 'ἰ' => 'Ἰ', + 'ἱ' => 'Ἱ', + 'ἲ' => 'Ἲ', + 'ἳ' => 'Ἳ', + 'ἴ' => 'Ἴ', + 'ἵ' => 'Ἵ', + 'ἶ' => 'Ἶ', + 'ἷ' => 'Ἷ', + 'ὀ' => 'Ὀ', + 'ὁ' => 'Ὁ', + 'ὂ' => 'Ὂ', + 'ὃ' => 'Ὃ', + 'ὄ' => 'Ὄ', + 'ὅ' => 'Ὅ', + 'ὑ' => 'Ὑ', + 'ὓ' => 'Ὓ', + 'ὕ' => 'Ὕ', + 'ὗ' => 'Ὗ', + 'ὠ' => 'Ὠ', + 'ὡ' => 'Ὡ', + 'ὢ' => 'Ὢ', + 'ὣ' => 'Ὣ', + 'ὤ' => 'Ὤ', + 'ὥ' => 'Ὥ', + 'ὦ' => 'Ὦ', + 'ὧ' => 'Ὧ', + 'ὰ' => 'Ὰ', + 'ά' => 'Ά', + 'ὲ' => 'Ὲ', + 'έ' => 'Έ', + 'ὴ' => 'Ὴ', + 'ή' => 'Ή', + 'ὶ' => 'Ὶ', + 'ί' => 'Ί', + 'ὸ' => 'Ὸ', + 'ό' => 'Ό', + 'ὺ' => 'Ὺ', + 'ύ' => 'Ύ', + 'ὼ' => 'Ὼ', + 'ώ' => 'Ώ', + 'ᾀ' => 'ἈΙ', + 'ᾁ' => 'ἉΙ', + 'ᾂ' => 'ἊΙ', + 'ᾃ' => 'ἋΙ', + 'ᾄ' => 'ἌΙ', + 'ᾅ' => 'ἍΙ', + 'ᾆ' => 'ἎΙ', + 'ᾇ' => 'ἏΙ', + 'ᾐ' => 'ἨΙ', + 'ᾑ' => 'ἩΙ', + 'ᾒ' => 'ἪΙ', + 'ᾓ' => 'ἫΙ', + 'ᾔ' => 'ἬΙ', + 'ᾕ' => 'ἭΙ', + 'ᾖ' => 'ἮΙ', + 'ᾗ' => 'ἯΙ', + 'ᾠ' => 'ὨΙ', + 'ᾡ' => 'ὩΙ', + 'ᾢ' => 'ὪΙ', + 'ᾣ' => 'ὫΙ', + 'ᾤ' => 'ὬΙ', + 'ᾥ' => 'ὭΙ', + 'ᾦ' => 'ὮΙ', + 'ᾧ' => 'ὯΙ', + 'ᾰ' => 'Ᾰ', + 'ᾱ' => 'Ᾱ', + 'ᾳ' => 'ΑΙ', + 'ι' => 'Ι', + 'ῃ' => 'ΗΙ', + 'ῐ' => 'Ῐ', + 'ῑ' => 'Ῑ', + 'ῠ' => 'Ῠ', + 'ῡ' => 'Ῡ', + 'ῥ' => 'Ῥ', + 'ῳ' => 'ΩΙ', + 'ⅎ' => 'Ⅎ', + 'ⅰ' => 'Ⅰ', + 'ⅱ' => 'Ⅱ', + 'ⅲ' => 'Ⅲ', + 'ⅳ' => 'Ⅳ', + 'ⅴ' => 'Ⅴ', + 'ⅵ' => 'Ⅵ', + 'ⅶ' => 'Ⅶ', + 'ⅷ' => 'Ⅷ', + 'ⅸ' => 'Ⅸ', + 'ⅹ' => 'Ⅹ', + 'ⅺ' => 'Ⅺ', + 'ⅻ' => 'Ⅻ', + 'ⅼ' => 'Ⅼ', + 'ⅽ' => 'Ⅽ', + 'ⅾ' => 'Ⅾ', + 'ⅿ' => 'Ⅿ', + 'ↄ' => 'Ↄ', + 'ⓐ' => 'Ⓐ', + 'ⓑ' => 'Ⓑ', + 'ⓒ' => 'Ⓒ', + 'ⓓ' => 'Ⓓ', + 'ⓔ' => 'Ⓔ', + 'ⓕ' => 'Ⓕ', + 'ⓖ' => 'Ⓖ', + 'ⓗ' => 'Ⓗ', + 'ⓘ' => 'Ⓘ', + 'ⓙ' => 'Ⓙ', + 'ⓚ' => 'Ⓚ', + 'ⓛ' => 'Ⓛ', + 'ⓜ' => 'Ⓜ', + 'ⓝ' => 'Ⓝ', + 'ⓞ' => 'Ⓞ', + 'ⓟ' => 'Ⓟ', + 'ⓠ' => 'Ⓠ', + 'ⓡ' => 'Ⓡ', + 'ⓢ' => 'Ⓢ', + 'ⓣ' => 'Ⓣ', + 'ⓤ' => 'Ⓤ', + 'ⓥ' => 'Ⓥ', + 'ⓦ' => 'Ⓦ', + 'ⓧ' => 'Ⓧ', + 'ⓨ' => 'Ⓨ', + 'ⓩ' => 'Ⓩ', + 'ⰰ' => 'Ⰰ', + 'ⰱ' => 'Ⰱ', + 'ⰲ' => 'Ⰲ', + 'ⰳ' => 'Ⰳ', + 'ⰴ' => 'Ⰴ', + 'ⰵ' => 'Ⰵ', + 'ⰶ' => 'Ⰶ', + 'ⰷ' => 'Ⰷ', + 'ⰸ' => 'Ⰸ', + 'ⰹ' => 'Ⰹ', + 'ⰺ' => 'Ⰺ', + 'ⰻ' => 'Ⰻ', + 'ⰼ' => 'Ⰼ', + 'ⰽ' => 'Ⰽ', + 'ⰾ' => 'Ⰾ', + 'ⰿ' => 'Ⰿ', + 'ⱀ' => 'Ⱀ', + 'ⱁ' => 'Ⱁ', + 'ⱂ' => 'Ⱂ', + 'ⱃ' => 'Ⱃ', + 'ⱄ' => 'Ⱄ', + 'ⱅ' => 'Ⱅ', + 'ⱆ' => 'Ⱆ', + 'ⱇ' => 'Ⱇ', + 'ⱈ' => 'Ⱈ', + 'ⱉ' => 'Ⱉ', + 'ⱊ' => 'Ⱊ', + 'ⱋ' => 'Ⱋ', + 'ⱌ' => 'Ⱌ', + 'ⱍ' => 'Ⱍ', + 'ⱎ' => 'Ⱎ', + 'ⱏ' => 'Ⱏ', + 'ⱐ' => 'Ⱐ', + 'ⱑ' => 'Ⱑ', + 'ⱒ' => 'Ⱒ', + 'ⱓ' => 'Ⱓ', + 'ⱔ' => 'Ⱔ', + 'ⱕ' => 'Ⱕ', + 'ⱖ' => 'Ⱖ', + 'ⱗ' => 'Ⱗ', + 'ⱘ' => 'Ⱘ', + 'ⱙ' => 'Ⱙ', + 'ⱚ' => 'Ⱚ', + 'ⱛ' => 'Ⱛ', + 'ⱜ' => 'Ⱜ', + 'ⱝ' => 'Ⱝ', + 'ⱞ' => 'Ⱞ', + 'ⱡ' => 'Ⱡ', + 'ⱥ' => 'Ⱥ', + 'ⱦ' => 'Ⱦ', + 'ⱨ' => 'Ⱨ', + 'ⱪ' => 'Ⱪ', + 'ⱬ' => 'Ⱬ', + 'ⱳ' => 'Ⱳ', + 'ⱶ' => 'Ⱶ', + 'ⲁ' => 'Ⲁ', + 'ⲃ' => 'Ⲃ', + 'ⲅ' => 'Ⲅ', + 'ⲇ' => 'Ⲇ', + 'ⲉ' => 'Ⲉ', + 'ⲋ' => 'Ⲋ', + 'ⲍ' => 'Ⲍ', + 'ⲏ' => 'Ⲏ', + 'ⲑ' => 'Ⲑ', + 'ⲓ' => 'Ⲓ', + 'ⲕ' => 'Ⲕ', + 'ⲗ' => 'Ⲗ', + 'ⲙ' => 'Ⲙ', + 'ⲛ' => 'Ⲛ', + 'ⲝ' => 'Ⲝ', + 'ⲟ' => 'Ⲟ', + 'ⲡ' => 'Ⲡ', + 'ⲣ' => 'Ⲣ', + 'ⲥ' => 'Ⲥ', + 'ⲧ' => 'Ⲧ', + 'ⲩ' => 'Ⲩ', + 'ⲫ' => 'Ⲫ', + 'ⲭ' => 'Ⲭ', + 'ⲯ' => 'Ⲯ', + 'ⲱ' => 'Ⲱ', + 'ⲳ' => 'Ⲳ', + 'ⲵ' => 'Ⲵ', + 'ⲷ' => 'Ⲷ', + 'ⲹ' => 'Ⲹ', + 'ⲻ' => 'Ⲻ', + 'ⲽ' => 'Ⲽ', + 'ⲿ' => 'Ⲿ', + 'ⳁ' => 'Ⳁ', + 'ⳃ' => 'Ⳃ', + 'ⳅ' => 'Ⳅ', + 'ⳇ' => 'Ⳇ', + 'ⳉ' => 'Ⳉ', + 'ⳋ' => 'Ⳋ', + 'ⳍ' => 'Ⳍ', + 'ⳏ' => 'Ⳏ', + 'ⳑ' => 'Ⳑ', + 'ⳓ' => 'Ⳓ', + 'ⳕ' => 'Ⳕ', + 'ⳗ' => 'Ⳗ', + 'ⳙ' => 'Ⳙ', + 'ⳛ' => 'Ⳛ', + 'ⳝ' => 'Ⳝ', + 'ⳟ' => 'Ⳟ', + 'ⳡ' => 'Ⳡ', + 'ⳣ' => 'Ⳣ', + 'ⳬ' => 'Ⳬ', + 'ⳮ' => 'Ⳮ', + 'ⳳ' => 'Ⳳ', + 'ⴀ' => 'Ⴀ', + 'ⴁ' => 'Ⴁ', + 'ⴂ' => 'Ⴂ', + 'ⴃ' => 'Ⴃ', + 'ⴄ' => 'Ⴄ', + 'ⴅ' => 'Ⴅ', + 'ⴆ' => 'Ⴆ', + 'ⴇ' => 'Ⴇ', + 'ⴈ' => 'Ⴈ', + 'ⴉ' => 'Ⴉ', + 'ⴊ' => 'Ⴊ', + 'ⴋ' => 'Ⴋ', + 'ⴌ' => 'Ⴌ', + 'ⴍ' => 'Ⴍ', + 'ⴎ' => 'Ⴎ', + 'ⴏ' => 'Ⴏ', + 'ⴐ' => 'Ⴐ', + 'ⴑ' => 'Ⴑ', + 'ⴒ' => 'Ⴒ', + 'ⴓ' => 'Ⴓ', + 'ⴔ' => 'Ⴔ', + 'ⴕ' => 'Ⴕ', + 'ⴖ' => 'Ⴖ', + 'ⴗ' => 'Ⴗ', + 'ⴘ' => 'Ⴘ', + 'ⴙ' => 'Ⴙ', + 'ⴚ' => 'Ⴚ', + 'ⴛ' => 'Ⴛ', + 'ⴜ' => 'Ⴜ', + 'ⴝ' => 'Ⴝ', + 'ⴞ' => 'Ⴞ', + 'ⴟ' => 'Ⴟ', + 'ⴠ' => 'Ⴠ', + 'ⴡ' => 'Ⴡ', + 'ⴢ' => 'Ⴢ', + 'ⴣ' => 'Ⴣ', + 'ⴤ' => 'Ⴤ', + 'ⴥ' => 'Ⴥ', + 'ⴧ' => 'Ⴧ', + 'ⴭ' => 'Ⴭ', + 'ꙁ' => 'Ꙁ', + 'ꙃ' => 'Ꙃ', + 'ꙅ' => 'Ꙅ', + 'ꙇ' => 'Ꙇ', + 'ꙉ' => 'Ꙉ', + 'ꙋ' => 'Ꙋ', + 'ꙍ' => 'Ꙍ', + 'ꙏ' => 'Ꙏ', + 'ꙑ' => 'Ꙑ', + 'ꙓ' => 'Ꙓ', + 'ꙕ' => 'Ꙕ', + 'ꙗ' => 'Ꙗ', + 'ꙙ' => 'Ꙙ', + 'ꙛ' => 'Ꙛ', + 'ꙝ' => 'Ꙝ', + 'ꙟ' => 'Ꙟ', + 'ꙡ' => 'Ꙡ', + 'ꙣ' => 'Ꙣ', + 'ꙥ' => 'Ꙥ', + 'ꙧ' => 'Ꙧ', + 'ꙩ' => 'Ꙩ', + 'ꙫ' => 'Ꙫ', + 'ꙭ' => 'Ꙭ', + 'ꚁ' => 'Ꚁ', + 'ꚃ' => 'Ꚃ', + 'ꚅ' => 'Ꚅ', + 'ꚇ' => 'Ꚇ', + 'ꚉ' => 'Ꚉ', + 'ꚋ' => 'Ꚋ', + 'ꚍ' => 'Ꚍ', + 'ꚏ' => 'Ꚏ', + 'ꚑ' => 'Ꚑ', + 'ꚓ' => 'Ꚓ', + 'ꚕ' => 'Ꚕ', + 'ꚗ' => 'Ꚗ', + 'ꚙ' => 'Ꚙ', + 'ꚛ' => 'Ꚛ', + 'ꜣ' => 'Ꜣ', + 'ꜥ' => 'Ꜥ', + 'ꜧ' => 'Ꜧ', + 'ꜩ' => 'Ꜩ', + 'ꜫ' => 'Ꜫ', + 'ꜭ' => 'Ꜭ', + 'ꜯ' => 'Ꜯ', + 'ꜳ' => 'Ꜳ', + 'ꜵ' => 'Ꜵ', + 'ꜷ' => 'Ꜷ', + 'ꜹ' => 'Ꜹ', + 'ꜻ' => 'Ꜻ', + 'ꜽ' => 'Ꜽ', + 'ꜿ' => 'Ꜿ', + 'ꝁ' => 'Ꝁ', + 'ꝃ' => 'Ꝃ', + 'ꝅ' => 'Ꝅ', + 'ꝇ' => 'Ꝇ', + 'ꝉ' => 'Ꝉ', + 'ꝋ' => 'Ꝋ', + 'ꝍ' => 'Ꝍ', + 'ꝏ' => 'Ꝏ', + 'ꝑ' => 'Ꝑ', + 'ꝓ' => 'Ꝓ', + 'ꝕ' => 'Ꝕ', + 'ꝗ' => 'Ꝗ', + 'ꝙ' => 'Ꝙ', + 'ꝛ' => 'Ꝛ', + 'ꝝ' => 'Ꝝ', + 'ꝟ' => 'Ꝟ', + 'ꝡ' => 'Ꝡ', + 'ꝣ' => 'Ꝣ', + 'ꝥ' => 'Ꝥ', + 'ꝧ' => 'Ꝧ', + 'ꝩ' => 'Ꝩ', + 'ꝫ' => 'Ꝫ', + 'ꝭ' => 'Ꝭ', + 'ꝯ' => 'Ꝯ', + 'ꝺ' => 'Ꝺ', + 'ꝼ' => 'Ꝼ', + 'ꝿ' => 'Ꝿ', + 'ꞁ' => 'Ꞁ', + 'ꞃ' => 'Ꞃ', + 'ꞅ' => 'Ꞅ', + 'ꞇ' => 'Ꞇ', + 'ꞌ' => 'Ꞌ', + 'ꞑ' => 'Ꞑ', + 'ꞓ' => 'Ꞓ', + 'ꞔ' => 'Ꞔ', + 'ꞗ' => 'Ꞗ', + 'ꞙ' => 'Ꞙ', + 'ꞛ' => 'Ꞛ', + 'ꞝ' => 'Ꞝ', + 'ꞟ' => 'Ꞟ', + 'ꞡ' => 'Ꞡ', + 'ꞣ' => 'Ꞣ', + 'ꞥ' => 'Ꞥ', + 'ꞧ' => 'Ꞧ', + 'ꞩ' => 'Ꞩ', + 'ꞵ' => 'Ꞵ', + 'ꞷ' => 'Ꞷ', + 'ꞹ' => 'Ꞹ', + 'ꞻ' => 'Ꞻ', + 'ꞽ' => 'Ꞽ', + 'ꞿ' => 'Ꞿ', + 'ꟃ' => 'Ꟃ', + 'ꟈ' => 'Ꟈ', + 'ꟊ' => 'Ꟊ', + 'ꟶ' => 'Ꟶ', + 'ꭓ' => 'Ꭓ', + 'ꭰ' => 'Ꭰ', + 'ꭱ' => 'Ꭱ', + 'ꭲ' => 'Ꭲ', + 'ꭳ' => 'Ꭳ', + 'ꭴ' => 'Ꭴ', + 'ꭵ' => 'Ꭵ', + 'ꭶ' => 'Ꭶ', + 'ꭷ' => 'Ꭷ', + 'ꭸ' => 'Ꭸ', + 'ꭹ' => 'Ꭹ', + 'ꭺ' => 'Ꭺ', + 'ꭻ' => 'Ꭻ', + 'ꭼ' => 'Ꭼ', + 'ꭽ' => 'Ꭽ', + 'ꭾ' => 'Ꭾ', + 'ꭿ' => 'Ꭿ', + 'ꮀ' => 'Ꮀ', + 'ꮁ' => 'Ꮁ', + 'ꮂ' => 'Ꮂ', + 'ꮃ' => 'Ꮃ', + 'ꮄ' => 'Ꮄ', + 'ꮅ' => 'Ꮅ', + 'ꮆ' => 'Ꮆ', + 'ꮇ' => 'Ꮇ', + 'ꮈ' => 'Ꮈ', + 'ꮉ' => 'Ꮉ', + 'ꮊ' => 'Ꮊ', + 'ꮋ' => 'Ꮋ', + 'ꮌ' => 'Ꮌ', + 'ꮍ' => 'Ꮍ', + 'ꮎ' => 'Ꮎ', + 'ꮏ' => 'Ꮏ', + 'ꮐ' => 'Ꮐ', + 'ꮑ' => 'Ꮑ', + 'ꮒ' => 'Ꮒ', + 'ꮓ' => 'Ꮓ', + 'ꮔ' => 'Ꮔ', + 'ꮕ' => 'Ꮕ', + 'ꮖ' => 'Ꮖ', + 'ꮗ' => 'Ꮗ', + 'ꮘ' => 'Ꮘ', + 'ꮙ' => 'Ꮙ', + 'ꮚ' => 'Ꮚ', + 'ꮛ' => 'Ꮛ', + 'ꮜ' => 'Ꮜ', + 'ꮝ' => 'Ꮝ', + 'ꮞ' => 'Ꮞ', + 'ꮟ' => 'Ꮟ', + 'ꮠ' => 'Ꮠ', + 'ꮡ' => 'Ꮡ', + 'ꮢ' => 'Ꮢ', + 'ꮣ' => 'Ꮣ', + 'ꮤ' => 'Ꮤ', + 'ꮥ' => 'Ꮥ', + 'ꮦ' => 'Ꮦ', + 'ꮧ' => 'Ꮧ', + 'ꮨ' => 'Ꮨ', + 'ꮩ' => 'Ꮩ', + 'ꮪ' => 'Ꮪ', + 'ꮫ' => 'Ꮫ', + 'ꮬ' => 'Ꮬ', + 'ꮭ' => 'Ꮭ', + 'ꮮ' => 'Ꮮ', + 'ꮯ' => 'Ꮯ', + 'ꮰ' => 'Ꮰ', + 'ꮱ' => 'Ꮱ', + 'ꮲ' => 'Ꮲ', + 'ꮳ' => 'Ꮳ', + 'ꮴ' => 'Ꮴ', + 'ꮵ' => 'Ꮵ', + 'ꮶ' => 'Ꮶ', + 'ꮷ' => 'Ꮷ', + 'ꮸ' => 'Ꮸ', + 'ꮹ' => 'Ꮹ', + 'ꮺ' => 'Ꮺ', + 'ꮻ' => 'Ꮻ', + 'ꮼ' => 'Ꮼ', + 'ꮽ' => 'Ꮽ', + 'ꮾ' => 'Ꮾ', + 'ꮿ' => 'Ꮿ', + 'a' => 'A', + 'b' => 'B', + 'c' => 'C', + 'd' => 'D', + 'e' => 'E', + 'f' => 'F', + 'g' => 'G', + 'h' => 'H', + 'i' => 'I', + 'j' => 'J', + 'k' => 'K', + 'l' => 'L', + 'm' => 'M', + 'n' => 'N', + 'o' => 'O', + 'p' => 'P', + 'q' => 'Q', + 'r' => 'R', + 's' => 'S', + 't' => 'T', + 'u' => 'U', + 'v' => 'V', + 'w' => 'W', + 'x' => 'X', + 'y' => 'Y', + 'z' => 'Z', + '𐐨' => '𐐀', + '𐐩' => '𐐁', + '𐐪' => '𐐂', + '𐐫' => '𐐃', + '𐐬' => '𐐄', + '𐐭' => '𐐅', + '𐐮' => '𐐆', + '𐐯' => '𐐇', + '𐐰' => '𐐈', + '𐐱' => '𐐉', + '𐐲' => '𐐊', + '𐐳' => '𐐋', + '𐐴' => '𐐌', + '𐐵' => '𐐍', + '𐐶' => '𐐎', + '𐐷' => '𐐏', + '𐐸' => '𐐐', + '𐐹' => '𐐑', + '𐐺' => '𐐒', + '𐐻' => '𐐓', + '𐐼' => '𐐔', + '𐐽' => '𐐕', + '𐐾' => '𐐖', + '𐐿' => '𐐗', + '𐑀' => '𐐘', + '𐑁' => '𐐙', + '𐑂' => '𐐚', + '𐑃' => '𐐛', + '𐑄' => '𐐜', + '𐑅' => '𐐝', + '𐑆' => '𐐞', + '𐑇' => '𐐟', + '𐑈' => '𐐠', + '𐑉' => '𐐡', + '𐑊' => '𐐢', + '𐑋' => '𐐣', + '𐑌' => '𐐤', + '𐑍' => '𐐥', + '𐑎' => '𐐦', + '𐑏' => '𐐧', + '𐓘' => '𐒰', + '𐓙' => '𐒱', + '𐓚' => '𐒲', + '𐓛' => '𐒳', + '𐓜' => '𐒴', + '𐓝' => '𐒵', + '𐓞' => '𐒶', + '𐓟' => '𐒷', + '𐓠' => '𐒸', + '𐓡' => '𐒹', + '𐓢' => '𐒺', + '𐓣' => '𐒻', + '𐓤' => '𐒼', + '𐓥' => '𐒽', + '𐓦' => '𐒾', + '𐓧' => '𐒿', + '𐓨' => '𐓀', + '𐓩' => '𐓁', + '𐓪' => '𐓂', + '𐓫' => '𐓃', + '𐓬' => '𐓄', + '𐓭' => '𐓅', + '𐓮' => '𐓆', + '𐓯' => '𐓇', + '𐓰' => '𐓈', + '𐓱' => '𐓉', + '𐓲' => '𐓊', + '𐓳' => '𐓋', + '𐓴' => '𐓌', + '𐓵' => '𐓍', + '𐓶' => '𐓎', + '𐓷' => '𐓏', + '𐓸' => '𐓐', + '𐓹' => '𐓑', + '𐓺' => '𐓒', + '𐓻' => '𐓓', + '𐳀' => '𐲀', + '𐳁' => '𐲁', + '𐳂' => '𐲂', + '𐳃' => '𐲃', + '𐳄' => '𐲄', + '𐳅' => '𐲅', + '𐳆' => '𐲆', + '𐳇' => '𐲇', + '𐳈' => '𐲈', + '𐳉' => '𐲉', + '𐳊' => '𐲊', + '𐳋' => '𐲋', + '𐳌' => '𐲌', + '𐳍' => '𐲍', + '𐳎' => '𐲎', + '𐳏' => '𐲏', + '𐳐' => '𐲐', + '𐳑' => '𐲑', + '𐳒' => '𐲒', + '𐳓' => '𐲓', + '𐳔' => '𐲔', + '𐳕' => '𐲕', + '𐳖' => '𐲖', + '𐳗' => '𐲗', + '𐳘' => '𐲘', + '𐳙' => '𐲙', + '𐳚' => '𐲚', + '𐳛' => '𐲛', + '𐳜' => '𐲜', + '𐳝' => '𐲝', + '𐳞' => '𐲞', + '𐳟' => '𐲟', + '𐳠' => '𐲠', + '𐳡' => '𐲡', + '𐳢' => '𐲢', + '𐳣' => '𐲣', + '𐳤' => '𐲤', + '𐳥' => '𐲥', + '𐳦' => '𐲦', + '𐳧' => '𐲧', + '𐳨' => '𐲨', + '𐳩' => '𐲩', + '𐳪' => '𐲪', + '𐳫' => '𐲫', + '𐳬' => '𐲬', + '𐳭' => '𐲭', + '𐳮' => '𐲮', + '𐳯' => '𐲯', + '𐳰' => '𐲰', + '𐳱' => '𐲱', + '𐳲' => '𐲲', + '𑣀' => '𑢠', + '𑣁' => '𑢡', + '𑣂' => '𑢢', + '𑣃' => '𑢣', + '𑣄' => '𑢤', + '𑣅' => '𑢥', + '𑣆' => '𑢦', + '𑣇' => '𑢧', + '𑣈' => '𑢨', + '𑣉' => '𑢩', + '𑣊' => '𑢪', + '𑣋' => '𑢫', + '𑣌' => '𑢬', + '𑣍' => '𑢭', + '𑣎' => '𑢮', + '𑣏' => '𑢯', + '𑣐' => '𑢰', + '𑣑' => '𑢱', + '𑣒' => '𑢲', + '𑣓' => '𑢳', + '𑣔' => '𑢴', + '𑣕' => '𑢵', + '𑣖' => '𑢶', + '𑣗' => '𑢷', + '𑣘' => '𑢸', + '𑣙' => '𑢹', + '𑣚' => '𑢺', + '𑣛' => '𑢻', + '𑣜' => '𑢼', + '𑣝' => '𑢽', + '𑣞' => '𑢾', + '𑣟' => '𑢿', + '𖹠' => '𖹀', + '𖹡' => '𖹁', + '𖹢' => '𖹂', + '𖹣' => '𖹃', + '𖹤' => '𖹄', + '𖹥' => '𖹅', + '𖹦' => '𖹆', + '𖹧' => '𖹇', + '𖹨' => '𖹈', + '𖹩' => '𖹉', + '𖹪' => '𖹊', + '𖹫' => '𖹋', + '𖹬' => '𖹌', + '𖹭' => '𖹍', + '𖹮' => '𖹎', + '𖹯' => '𖹏', + '𖹰' => '𖹐', + '𖹱' => '𖹑', + '𖹲' => '𖹒', + '𖹳' => '𖹓', + '𖹴' => '𖹔', + '𖹵' => '𖹕', + '𖹶' => '𖹖', + '𖹷' => '𖹗', + '𖹸' => '𖹘', + '𖹹' => '𖹙', + '𖹺' => '𖹚', + '𖹻' => '𖹛', + '𖹼' => '𖹜', + '𖹽' => '𖹝', + '𖹾' => '𖹞', + '𖹿' => '𖹟', + '𞤢' => '𞤀', + '𞤣' => '𞤁', + '𞤤' => '𞤂', + '𞤥' => '𞤃', + '𞤦' => '𞤄', + '𞤧' => '𞤅', + '𞤨' => '𞤆', + '𞤩' => '𞤇', + '𞤪' => '𞤈', + '𞤫' => '𞤉', + '𞤬' => '𞤊', + '𞤭' => '𞤋', + '𞤮' => '𞤌', + '𞤯' => '𞤍', + '𞤰' => '𞤎', + '𞤱' => '𞤏', + '𞤲' => '𞤐', + '𞤳' => '𞤑', + '𞤴' => '𞤒', + '𞤵' => '𞤓', + '𞤶' => '𞤔', + '𞤷' => '𞤕', + '𞤸' => '𞤖', + '𞤹' => '𞤗', + '𞤺' => '𞤘', + '𞤻' => '𞤙', + '𞤼' => '𞤚', + '𞤽' => '𞤛', + '𞤾' => '𞤜', + '𞤿' => '𞤝', + '𞥀' => '𞤞', + '𞥁' => '𞤟', + '𞥂' => '𞤠', + '𞥃' => '𞤡', + 'ß' => 'SS', + 'ff' => 'FF', + 'fi' => 'FI', + 'fl' => 'FL', + 'ffi' => 'FFI', + 'ffl' => 'FFL', + 'ſt' => 'ST', + 'st' => 'ST', + 'և' => 'ԵՒ', + 'ﬓ' => 'ՄՆ', + 'ﬔ' => 'ՄԵ', + 'ﬕ' => 'ՄԻ', + 'ﬖ' => 'ՎՆ', + 'ﬗ' => 'ՄԽ', + 'ʼn' => 'ʼN', + 'ΐ' => 'Ϊ́', + 'ΰ' => 'Ϋ́', + 'ǰ' => 'J̌', + 'ẖ' => 'H̱', + 'ẗ' => 'T̈', + 'ẘ' => 'W̊', + 'ẙ' => 'Y̊', + 'ẚ' => 'Aʾ', + 'ὐ' => 'Υ̓', + 'ὒ' => 'Υ̓̀', + 'ὔ' => 'Υ̓́', + 'ὖ' => 'Υ̓͂', + 'ᾶ' => 'Α͂', + 'ῆ' => 'Η͂', + 'ῒ' => 'Ϊ̀', + 'ΐ' => 'Ϊ́', + 'ῖ' => 'Ι͂', + 'ῗ' => 'Ϊ͂', + 'ῢ' => 'Ϋ̀', + 'ΰ' => 'Ϋ́', + 'ῤ' => 'Ρ̓', + 'ῦ' => 'Υ͂', + 'ῧ' => 'Ϋ͂', + 'ῶ' => 'Ω͂', + 'ᾈ' => 'ἈΙ', + 'ᾉ' => 'ἉΙ', + 'ᾊ' => 'ἊΙ', + 'ᾋ' => 'ἋΙ', + 'ᾌ' => 'ἌΙ', + 'ᾍ' => 'ἍΙ', + 'ᾎ' => 'ἎΙ', + 'ᾏ' => 'ἏΙ', + 'ᾘ' => 'ἨΙ', + 'ᾙ' => 'ἩΙ', + 'ᾚ' => 'ἪΙ', + 'ᾛ' => 'ἫΙ', + 'ᾜ' => 'ἬΙ', + 'ᾝ' => 'ἭΙ', + 'ᾞ' => 'ἮΙ', + 'ᾟ' => 'ἯΙ', + 'ᾨ' => 'ὨΙ', + 'ᾩ' => 'ὩΙ', + 'ᾪ' => 'ὪΙ', + 'ᾫ' => 'ὫΙ', + 'ᾬ' => 'ὬΙ', + 'ᾭ' => 'ὭΙ', + 'ᾮ' => 'ὮΙ', + 'ᾯ' => 'ὯΙ', + 'ᾼ' => 'ΑΙ', + 'ῌ' => 'ΗΙ', + 'ῼ' => 'ΩΙ', + 'ᾲ' => 'ᾺΙ', + 'ᾴ' => 'ΆΙ', + 'ῂ' => 'ῊΙ', + 'ῄ' => 'ΉΙ', + 'ῲ' => 'ῺΙ', + 'ῴ' => 'ΏΙ', + 'ᾷ' => 'Α͂Ι', + 'ῇ' => 'Η͂Ι', + 'ῷ' => 'Ω͂Ι', +); diff --git a/vendor/symfony/polyfill-mbstring/bootstrap.php b/vendor/symfony/polyfill-mbstring/bootstrap.php new file mode 100644 index 0000000..1fedd1f --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/bootstrap.php @@ -0,0 +1,147 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Mbstring as p; + +if (\PHP_VERSION_ID >= 80000) { + return require __DIR__.'/bootstrap80.php'; +} + +if (!function_exists('mb_convert_encoding')) { + function mb_convert_encoding($string, $to_encoding, $from_encoding = null) { return p\Mbstring::mb_convert_encoding($string, $to_encoding, $from_encoding); } +} +if (!function_exists('mb_decode_mimeheader')) { + function mb_decode_mimeheader($string) { return p\Mbstring::mb_decode_mimeheader($string); } +} +if (!function_exists('mb_encode_mimeheader')) { + function mb_encode_mimeheader($string, $charset = null, $transfer_encoding = null, $newline = "\r\n", $indent = 0) { return p\Mbstring::mb_encode_mimeheader($string, $charset, $transfer_encoding, $newline, $indent); } +} +if (!function_exists('mb_decode_numericentity')) { + function mb_decode_numericentity($string, $map, $encoding = null) { return p\Mbstring::mb_decode_numericentity($string, $map, $encoding); } +} +if (!function_exists('mb_encode_numericentity')) { + function mb_encode_numericentity($string, $map, $encoding = null, $hex = false) { return p\Mbstring::mb_encode_numericentity($string, $map, $encoding, $hex); } +} +if (!function_exists('mb_convert_case')) { + function mb_convert_case($string, $mode, $encoding = null) { return p\Mbstring::mb_convert_case($string, $mode, $encoding); } +} +if (!function_exists('mb_internal_encoding')) { + function mb_internal_encoding($encoding = null) { return p\Mbstring::mb_internal_encoding($encoding); } +} +if (!function_exists('mb_language')) { + function mb_language($language = null) { return p\Mbstring::mb_language($language); } +} +if (!function_exists('mb_list_encodings')) { + function mb_list_encodings() { return p\Mbstring::mb_list_encodings(); } +} +if (!function_exists('mb_encoding_aliases')) { + function mb_encoding_aliases($encoding) { return p\Mbstring::mb_encoding_aliases($encoding); } +} +if (!function_exists('mb_check_encoding')) { + function mb_check_encoding($value = null, $encoding = null) { return p\Mbstring::mb_check_encoding($value, $encoding); } +} +if (!function_exists('mb_detect_encoding')) { + function mb_detect_encoding($string, $encodings = null, $strict = false) { return p\Mbstring::mb_detect_encoding($string, $encodings, $strict); } +} +if (!function_exists('mb_detect_order')) { + function mb_detect_order($encoding = null) { return p\Mbstring::mb_detect_order($encoding); } +} +if (!function_exists('mb_parse_str')) { + function mb_parse_str($string, &$result = []) { parse_str($string, $result); return (bool) $result; } +} +if (!function_exists('mb_strlen')) { + function mb_strlen($string, $encoding = null) { return p\Mbstring::mb_strlen($string, $encoding); } +} +if (!function_exists('mb_strpos')) { + function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strpos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_strtolower')) { + function mb_strtolower($string, $encoding = null) { return p\Mbstring::mb_strtolower($string, $encoding); } +} +if (!function_exists('mb_strtoupper')) { + function mb_strtoupper($string, $encoding = null) { return p\Mbstring::mb_strtoupper($string, $encoding); } +} +if (!function_exists('mb_substitute_character')) { + function mb_substitute_character($substitute_character = null) { return p\Mbstring::mb_substitute_character($substitute_character); } +} +if (!function_exists('mb_substr')) { + function mb_substr($string, $start, $length = 2147483647, $encoding = null) { return p\Mbstring::mb_substr($string, $start, $length, $encoding); } +} +if (!function_exists('mb_stripos')) { + function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_stripos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_stristr')) { + function mb_stristr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_stristr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_strrchr')) { + function mb_strrchr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strrchr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_strrichr')) { + function mb_strrichr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strrichr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_strripos')) { + function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strripos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_strrpos')) { + function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strrpos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_strstr')) { + function mb_strstr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strstr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_get_info')) { + function mb_get_info($type = 'all') { return p\Mbstring::mb_get_info($type); } +} +if (!function_exists('mb_http_output')) { + function mb_http_output($encoding = null) { return p\Mbstring::mb_http_output($encoding); } +} +if (!function_exists('mb_strwidth')) { + function mb_strwidth($string, $encoding = null) { return p\Mbstring::mb_strwidth($string, $encoding); } +} +if (!function_exists('mb_substr_count')) { + function mb_substr_count($haystack, $needle, $encoding = null) { return p\Mbstring::mb_substr_count($haystack, $needle, $encoding); } +} +if (!function_exists('mb_output_handler')) { + function mb_output_handler($string, $status) { return p\Mbstring::mb_output_handler($string, $status); } +} +if (!function_exists('mb_http_input')) { + function mb_http_input($type = null) { return p\Mbstring::mb_http_input($type); } +} + +if (!function_exists('mb_convert_variables')) { + function mb_convert_variables($to_encoding, $from_encoding, &...$vars) { return p\Mbstring::mb_convert_variables($to_encoding, $from_encoding, ...$vars); } +} + +if (!function_exists('mb_ord')) { + function mb_ord($string, $encoding = null) { return p\Mbstring::mb_ord($string, $encoding); } +} +if (!function_exists('mb_chr')) { + function mb_chr($codepoint, $encoding = null) { return p\Mbstring::mb_chr($codepoint, $encoding); } +} +if (!function_exists('mb_scrub')) { + function mb_scrub($string, $encoding = null) { $encoding = null === $encoding ? mb_internal_encoding() : $encoding; return mb_convert_encoding($string, $encoding, $encoding); } +} +if (!function_exists('mb_str_split')) { + function mb_str_split($string, $length = 1, $encoding = null) { return p\Mbstring::mb_str_split($string, $length, $encoding); } +} + +if (extension_loaded('mbstring')) { + return; +} + +if (!defined('MB_CASE_UPPER')) { + define('MB_CASE_UPPER', 0); +} +if (!defined('MB_CASE_LOWER')) { + define('MB_CASE_LOWER', 1); +} +if (!defined('MB_CASE_TITLE')) { + define('MB_CASE_TITLE', 2); +} diff --git a/vendor/symfony/polyfill-mbstring/bootstrap80.php b/vendor/symfony/polyfill-mbstring/bootstrap80.php new file mode 100644 index 0000000..82f5ac4 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/bootstrap80.php @@ -0,0 +1,143 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Mbstring as p; + +if (!function_exists('mb_convert_encoding')) { + function mb_convert_encoding(array|string|null $string, ?string $to_encoding, array|string|null $from_encoding = null): array|string|false { return p\Mbstring::mb_convert_encoding($string ?? '', (string) $to_encoding, $from_encoding); } +} +if (!function_exists('mb_decode_mimeheader')) { + function mb_decode_mimeheader(?string $string): string { return p\Mbstring::mb_decode_mimeheader((string) $string); } +} +if (!function_exists('mb_encode_mimeheader')) { + function mb_encode_mimeheader(?string $string, ?string $charset = null, ?string $transfer_encoding = null, ?string $newline = "\r\n", ?int $indent = 0): string { return p\Mbstring::mb_encode_mimeheader((string) $string, $charset, $transfer_encoding, (string) $newline, (int) $indent); } +} +if (!function_exists('mb_decode_numericentity')) { + function mb_decode_numericentity(?string $string, array $map, ?string $encoding = null): string { return p\Mbstring::mb_decode_numericentity((string) $string, $map, $encoding); } +} +if (!function_exists('mb_encode_numericentity')) { + function mb_encode_numericentity(?string $string, array $map, ?string $encoding = null, ?bool $hex = false): string { return p\Mbstring::mb_encode_numericentity((string) $string, $map, $encoding, (bool) $hex); } +} +if (!function_exists('mb_convert_case')) { + function mb_convert_case(?string $string, ?int $mode, ?string $encoding = null): string { return p\Mbstring::mb_convert_case((string) $string, (int) $mode, $encoding); } +} +if (!function_exists('mb_internal_encoding')) { + function mb_internal_encoding(?string $encoding = null): string|bool { return p\Mbstring::mb_internal_encoding($encoding); } +} +if (!function_exists('mb_language')) { + function mb_language(?string $language = null): string|bool { return p\Mbstring::mb_language($language); } +} +if (!function_exists('mb_list_encodings')) { + function mb_list_encodings(): array { return p\Mbstring::mb_list_encodings(); } +} +if (!function_exists('mb_encoding_aliases')) { + function mb_encoding_aliases(?string $encoding): array { return p\Mbstring::mb_encoding_aliases((string) $encoding); } +} +if (!function_exists('mb_check_encoding')) { + function mb_check_encoding(array|string|null $value = null, ?string $encoding = null): bool { return p\Mbstring::mb_check_encoding($value, $encoding); } +} +if (!function_exists('mb_detect_encoding')) { + function mb_detect_encoding(?string $string, array|string|null $encodings = null, ?bool $strict = false): string|false { return p\Mbstring::mb_detect_encoding((string) $string, $encodings, (bool) $strict); } +} +if (!function_exists('mb_detect_order')) { + function mb_detect_order(array|string|null $encoding = null): array|bool { return p\Mbstring::mb_detect_order($encoding); } +} +if (!function_exists('mb_parse_str')) { + function mb_parse_str(?string $string, &$result = []): bool { parse_str((string) $string, $result); return (bool) $result; } +} +if (!function_exists('mb_strlen')) { + function mb_strlen(?string $string, ?string $encoding = null): int { return p\Mbstring::mb_strlen((string) $string, $encoding); } +} +if (!function_exists('mb_strpos')) { + function mb_strpos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strpos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_strtolower')) { + function mb_strtolower(?string $string, ?string $encoding = null): string { return p\Mbstring::mb_strtolower((string) $string, $encoding); } +} +if (!function_exists('mb_strtoupper')) { + function mb_strtoupper(?string $string, ?string $encoding = null): string { return p\Mbstring::mb_strtoupper((string) $string, $encoding); } +} +if (!function_exists('mb_substitute_character')) { + function mb_substitute_character(string|int|null $substitute_character = null): string|int|bool { return p\Mbstring::mb_substitute_character($substitute_character); } +} +if (!function_exists('mb_substr')) { + function mb_substr(?string $string, ?int $start, ?int $length = null, ?string $encoding = null): string { return p\Mbstring::mb_substr((string) $string, (int) $start, $length, $encoding); } +} +if (!function_exists('mb_stripos')) { + function mb_stripos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_stripos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_stristr')) { + function mb_stristr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_stristr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_strrchr')) { + function mb_strrchr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strrchr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_strrichr')) { + function mb_strrichr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strrichr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_strripos')) { + function mb_strripos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strripos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_strrpos')) { + function mb_strrpos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strrpos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_strstr')) { + function mb_strstr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strstr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_get_info')) { + function mb_get_info(?string $type = 'all'): array|string|int|false { return p\Mbstring::mb_get_info((string) $type); } +} +if (!function_exists('mb_http_output')) { + function mb_http_output(?string $encoding = null): string|bool { return p\Mbstring::mb_http_output($encoding); } +} +if (!function_exists('mb_strwidth')) { + function mb_strwidth(?string $string, ?string $encoding = null): int { return p\Mbstring::mb_strwidth((string) $string, $encoding); } +} +if (!function_exists('mb_substr_count')) { + function mb_substr_count(?string $haystack, ?string $needle, ?string $encoding = null): int { return p\Mbstring::mb_substr_count((string) $haystack, (string) $needle, $encoding); } +} +if (!function_exists('mb_output_handler')) { + function mb_output_handler(?string $string, ?int $status): string { return p\Mbstring::mb_output_handler((string) $string, (int) $status); } +} +if (!function_exists('mb_http_input')) { + function mb_http_input(?string $type = null): array|string|false { return p\Mbstring::mb_http_input($type); } +} + +if (!function_exists('mb_convert_variables')) { + function mb_convert_variables(?string $to_encoding, array|string|null $from_encoding, mixed &$var, mixed &...$vars): string|false { return p\Mbstring::mb_convert_variables((string) $to_encoding, $from_encoding ?? '', $var, ...$vars); } +} + +if (!function_exists('mb_ord')) { + function mb_ord(?string $string, ?string $encoding = null): int|false { return p\Mbstring::mb_ord((string) $string, $encoding); } +} +if (!function_exists('mb_chr')) { + function mb_chr(?int $codepoint, ?string $encoding = null): string|false { return p\Mbstring::mb_chr((int) $codepoint, $encoding); } +} +if (!function_exists('mb_scrub')) { + function mb_scrub(?string $string, ?string $encoding = null): string { $encoding ??= mb_internal_encoding(); return mb_convert_encoding((string) $string, $encoding, $encoding); } +} +if (!function_exists('mb_str_split')) { + function mb_str_split(?string $string, ?int $length = 1, ?string $encoding = null): array { return p\Mbstring::mb_str_split((string) $string, (int) $length, $encoding); } +} + +if (extension_loaded('mbstring')) { + return; +} + +if (!defined('MB_CASE_UPPER')) { + define('MB_CASE_UPPER', 0); +} +if (!defined('MB_CASE_LOWER')) { + define('MB_CASE_LOWER', 1); +} +if (!defined('MB_CASE_TITLE')) { + define('MB_CASE_TITLE', 2); +} diff --git a/vendor/symfony/polyfill-php73/LICENSE b/vendor/symfony/polyfill-php73/LICENSE new file mode 100644 index 0000000..3f853aa --- /dev/null +++ b/vendor/symfony/polyfill-php73/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-2019 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/polyfill-php73/Php73.php b/vendor/symfony/polyfill-php73/Php73.php new file mode 100644 index 0000000..65c35a6 --- /dev/null +++ b/vendor/symfony/polyfill-php73/Php73.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php73; + +/** + * @author Gabriel Caruso + * @author Ion Bazan + * + * @internal + */ +final class Php73 +{ + public static $startAt = 1533462603; + + /** + * @param bool $asNum + * + * @return array|float|int + */ + public static function hrtime($asNum = false) + { + $ns = microtime(false); + $s = substr($ns, 11) - self::$startAt; + $ns = 1E9 * (float) $ns; + + if ($asNum) { + $ns += $s * 1E9; + + return \PHP_INT_SIZE === 4 ? $ns : (int) $ns; + } + + return [$s, (int) $ns]; + } +} diff --git a/vendor/symfony/polyfill-php73/README.md b/vendor/symfony/polyfill-php73/README.md new file mode 100644 index 0000000..032fafb --- /dev/null +++ b/vendor/symfony/polyfill-php73/README.md @@ -0,0 +1,18 @@ +Symfony Polyfill / Php73 +======================== + +This component provides functions added to PHP 7.3 core: + +- [`array_key_first`](https://php.net/array_key_first) +- [`array_key_last`](https://php.net/array_key_last) +- [`hrtime`](https://php.net/function.hrtime) +- [`is_countable`](https://php.net/is_countable) +- [`JsonException`](https://php.net/JsonException) + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/vendor/symfony/polyfill-php73/Resources/stubs/JsonException.php b/vendor/symfony/polyfill-php73/Resources/stubs/JsonException.php new file mode 100644 index 0000000..f06d6c2 --- /dev/null +++ b/vendor/symfony/polyfill-php73/Resources/stubs/JsonException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 70300) { + class JsonException extends Exception + { + } +} diff --git a/vendor/symfony/polyfill-php73/bootstrap.php b/vendor/symfony/polyfill-php73/bootstrap.php new file mode 100644 index 0000000..d6b2153 --- /dev/null +++ b/vendor/symfony/polyfill-php73/bootstrap.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Php73 as p; + +if (\PHP_VERSION_ID >= 70300) { + return; +} + +if (!function_exists('is_countable')) { + function is_countable($value) { return is_array($value) || $value instanceof Countable || $value instanceof ResourceBundle || $value instanceof SimpleXmlElement; } +} +if (!function_exists('hrtime')) { + require_once __DIR__.'/Php73.php'; + p\Php73::$startAt = (int) microtime(true); + function hrtime($as_number = false) { return p\Php73::hrtime($as_number); } +} +if (!function_exists('array_key_first')) { + function array_key_first(array $array) { foreach ($array as $key => $value) { return $key; } } +} +if (!function_exists('array_key_last')) { + function array_key_last(array $array) { return key(array_slice($array, -1, 1, true)); } +} diff --git a/vendor/symfony/polyfill-php80/LICENSE b/vendor/symfony/polyfill-php80/LICENSE new file mode 100644 index 0000000..5593b1d --- /dev/null +++ b/vendor/symfony/polyfill-php80/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2020 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/polyfill-php80/Php80.php b/vendor/symfony/polyfill-php80/Php80.php new file mode 100644 index 0000000..362dd1a --- /dev/null +++ b/vendor/symfony/polyfill-php80/Php80.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 Symfony\Polyfill\Php80; + +/** + * @author Ion Bazan + * @author Nico Oelgart + * @author Nicolas Grekas + * + * @internal + */ +final class Php80 +{ + public static function fdiv(float $dividend, float $divisor): float + { + return @($dividend / $divisor); + } + + public static function get_debug_type($value): string + { + switch (true) { + case null === $value: return 'null'; + case \is_bool($value): return 'bool'; + case \is_string($value): return 'string'; + case \is_array($value): return 'array'; + case \is_int($value): return 'int'; + case \is_float($value): return 'float'; + case \is_object($value): break; + case $value instanceof \__PHP_Incomplete_Class: return '__PHP_Incomplete_Class'; + default: + if (null === $type = @get_resource_type($value)) { + return 'unknown'; + } + + if ('Unknown' === $type) { + $type = 'closed'; + } + + return "resource ($type)"; + } + + $class = \get_class($value); + + if (false === strpos($class, '@')) { + return $class; + } + + return (get_parent_class($class) ?: key(class_implements($class)) ?: 'class').'@anonymous'; + } + + public static function get_resource_id($res): int + { + if (!\is_resource($res) && null === @get_resource_type($res)) { + throw new \TypeError(sprintf('Argument 1 passed to get_resource_id() must be of the type resource, %s given', get_debug_type($res))); + } + + return (int) $res; + } + + public static function preg_last_error_msg(): string + { + switch (preg_last_error()) { + case \PREG_INTERNAL_ERROR: + return 'Internal error'; + case \PREG_BAD_UTF8_ERROR: + return 'Malformed UTF-8 characters, possibly incorrectly encoded'; + case \PREG_BAD_UTF8_OFFSET_ERROR: + return 'The offset did not correspond to the beginning of a valid UTF-8 code point'; + case \PREG_BACKTRACK_LIMIT_ERROR: + return 'Backtrack limit exhausted'; + case \PREG_RECURSION_LIMIT_ERROR: + return 'Recursion limit exhausted'; + case \PREG_JIT_STACKLIMIT_ERROR: + return 'JIT stack limit exhausted'; + case \PREG_NO_ERROR: + return 'No error'; + default: + return 'Unknown error'; + } + } + + public static function str_contains(string $haystack, string $needle): bool + { + return '' === $needle || false !== strpos($haystack, $needle); + } + + public static function str_starts_with(string $haystack, string $needle): bool + { + return 0 === strncmp($haystack, $needle, \strlen($needle)); + } + + public static function str_ends_with(string $haystack, string $needle): bool + { + if ('' === $needle || $needle === $haystack) { + return true; + } + + if ('' === $haystack) { + return false; + } + + $needleLength = \strlen($needle); + + return $needleLength <= \strlen($haystack) && 0 === substr_compare($haystack, $needle, -$needleLength); + } +} diff --git a/vendor/symfony/polyfill-php80/PhpToken.php b/vendor/symfony/polyfill-php80/PhpToken.php new file mode 100644 index 0000000..fe6e691 --- /dev/null +++ b/vendor/symfony/polyfill-php80/PhpToken.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php80; + +/** + * @author Fedonyuk Anton + * + * @internal + */ +class PhpToken implements \Stringable +{ + /** + * @var int + */ + public $id; + + /** + * @var string + */ + public $text; + + /** + * @var int + */ + public $line; + + /** + * @var int + */ + public $pos; + + public function __construct(int $id, string $text, int $line = -1, int $position = -1) + { + $this->id = $id; + $this->text = $text; + $this->line = $line; + $this->pos = $position; + } + + public function getTokenName(): ?string + { + if ('UNKNOWN' === $name = token_name($this->id)) { + $name = \strlen($this->text) > 1 || \ord($this->text) < 32 ? null : $this->text; + } + + return $name; + } + + /** + * @param int|string|array $kind + */ + public function is($kind): bool + { + foreach ((array) $kind as $value) { + if (\in_array($value, [$this->id, $this->text], true)) { + return true; + } + } + + return false; + } + + public function isIgnorable(): bool + { + return \in_array($this->id, [\T_WHITESPACE, \T_COMMENT, \T_DOC_COMMENT, \T_OPEN_TAG], true); + } + + public function __toString(): string + { + return (string) $this->text; + } + + /** + * @return static[] + */ + public static function tokenize(string $code, int $flags = 0): array + { + $line = 1; + $position = 0; + $tokens = token_get_all($code, $flags); + foreach ($tokens as $index => $token) { + if (\is_string($token)) { + $id = \ord($token); + $text = $token; + } else { + [$id, $text, $line] = $token; + } + $tokens[$index] = new static($id, $text, $line, $position); + $position += \strlen($text); + } + + return $tokens; + } +} diff --git a/vendor/symfony/polyfill-php80/README.md b/vendor/symfony/polyfill-php80/README.md new file mode 100644 index 0000000..3816c55 --- /dev/null +++ b/vendor/symfony/polyfill-php80/README.md @@ -0,0 +1,25 @@ +Symfony Polyfill / Php80 +======================== + +This component provides features added to PHP 8.0 core: + +- [`Stringable`](https://php.net/stringable) interface +- [`fdiv`](https://php.net/fdiv) +- [`ValueError`](https://php.net/valueerror) class +- [`UnhandledMatchError`](https://php.net/unhandledmatcherror) class +- `FILTER_VALIDATE_BOOL` constant +- [`get_debug_type`](https://php.net/get_debug_type) +- [`PhpToken`](https://php.net/phptoken) class +- [`preg_last_error_msg`](https://php.net/preg_last_error_msg) +- [`str_contains`](https://php.net/str_contains) +- [`str_starts_with`](https://php.net/str_starts_with) +- [`str_ends_with`](https://php.net/str_ends_with) +- [`get_resource_id`](https://php.net/get_resource_id) + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php b/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php new file mode 100644 index 0000000..2b95542 --- /dev/null +++ b/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +#[Attribute(Attribute::TARGET_CLASS)] +final class Attribute +{ + public const TARGET_CLASS = 1; + public const TARGET_FUNCTION = 2; + public const TARGET_METHOD = 4; + public const TARGET_PROPERTY = 8; + public const TARGET_CLASS_CONSTANT = 16; + public const TARGET_PARAMETER = 32; + public const TARGET_ALL = 63; + public const IS_REPEATABLE = 64; + + /** @var int */ + public $flags; + + public function __construct(int $flags = self::TARGET_ALL) + { + $this->flags = $flags; + } +} diff --git a/vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.php b/vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.php new file mode 100644 index 0000000..bd1212f --- /dev/null +++ b/vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80000 && extension_loaded('tokenizer')) { + class PhpToken extends Symfony\Polyfill\Php80\PhpToken + { + } +} diff --git a/vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php b/vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php new file mode 100644 index 0000000..7c62d75 --- /dev/null +++ b/vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80000) { + interface Stringable + { + /** + * @return string + */ + public function __toString(); + } +} diff --git a/vendor/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php b/vendor/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php new file mode 100644 index 0000000..01c6c6c --- /dev/null +++ b/vendor/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80000) { + class UnhandledMatchError extends Error + { + } +} diff --git a/vendor/symfony/polyfill-php80/Resources/stubs/ValueError.php b/vendor/symfony/polyfill-php80/Resources/stubs/ValueError.php new file mode 100644 index 0000000..783dbc2 --- /dev/null +++ b/vendor/symfony/polyfill-php80/Resources/stubs/ValueError.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80000) { + class ValueError extends Error + { + } +} diff --git a/vendor/symfony/polyfill-php80/bootstrap.php b/vendor/symfony/polyfill-php80/bootstrap.php new file mode 100644 index 0000000..e5f7dbc --- /dev/null +++ b/vendor/symfony/polyfill-php80/bootstrap.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Php80 as p; + +if (\PHP_VERSION_ID >= 80000) { + return; +} + +if (!defined('FILTER_VALIDATE_BOOL') && defined('FILTER_VALIDATE_BOOLEAN')) { + define('FILTER_VALIDATE_BOOL', \FILTER_VALIDATE_BOOLEAN); +} + +if (!function_exists('fdiv')) { + function fdiv(float $num1, float $num2): float { return p\Php80::fdiv($num1, $num2); } +} +if (!function_exists('preg_last_error_msg')) { + function preg_last_error_msg(): string { return p\Php80::preg_last_error_msg(); } +} +if (!function_exists('str_contains')) { + function str_contains(?string $haystack, ?string $needle): bool { return p\Php80::str_contains($haystack ?? '', $needle ?? ''); } +} +if (!function_exists('str_starts_with')) { + function str_starts_with(?string $haystack, ?string $needle): bool { return p\Php80::str_starts_with($haystack ?? '', $needle ?? ''); } +} +if (!function_exists('str_ends_with')) { + function str_ends_with(?string $haystack, ?string $needle): bool { return p\Php80::str_ends_with($haystack ?? '', $needle ?? ''); } +} +if (!function_exists('get_debug_type')) { + function get_debug_type($value): string { return p\Php80::get_debug_type($value); } +} +if (!function_exists('get_resource_id')) { + function get_resource_id($resource): int { return p\Php80::get_resource_id($resource); } +} diff --git a/vendor/symfony/service-contracts/.gitignore b/vendor/symfony/service-contracts/.gitignore new file mode 100644 index 0000000..c49a5d8 --- /dev/null +++ b/vendor/symfony/service-contracts/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/vendor/symfony/service-contracts/Attribute/Required.php b/vendor/symfony/service-contracts/Attribute/Required.php new file mode 100644 index 0000000..9df8511 --- /dev/null +++ b/vendor/symfony/service-contracts/Attribute/Required.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 Symfony\Contracts\Service\Attribute; + +/** + * A required dependency. + * + * This attribute indicates that a property holds a required dependency. The annotated property or method should be + * considered during the instantiation process of the containing class. + * + * @author Alexander M. Turek + */ +#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] +final class Required +{ +} diff --git a/vendor/symfony/service-contracts/Attribute/SubscribedService.php b/vendor/symfony/service-contracts/Attribute/SubscribedService.php new file mode 100644 index 0000000..10d1bc3 --- /dev/null +++ b/vendor/symfony/service-contracts/Attribute/SubscribedService.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service\Attribute; + +use Symfony\Contracts\Service\ServiceSubscriberTrait; + +/** + * Use with {@see ServiceSubscriberTrait} to mark a method's return type + * as a subscribed service. + * + * @author Kevin Bond + */ +#[\Attribute(\Attribute::TARGET_METHOD)] +final class SubscribedService +{ + /** + * @param string|null $key The key to use for the service + * If null, use "ClassName::methodName" + */ + public function __construct( + public ?string $key = null + ) { + } +} diff --git a/vendor/symfony/service-contracts/CHANGELOG.md b/vendor/symfony/service-contracts/CHANGELOG.md new file mode 100644 index 0000000..7932e26 --- /dev/null +++ b/vendor/symfony/service-contracts/CHANGELOG.md @@ -0,0 +1,5 @@ +CHANGELOG +========= + +The changelog is maintained for all Symfony contracts at the following URL: +https://github.com/symfony/contracts/blob/main/CHANGELOG.md diff --git a/vendor/symfony/service-contracts/LICENSE b/vendor/symfony/service-contracts/LICENSE new file mode 100644 index 0000000..74cdc2d --- /dev/null +++ b/vendor/symfony/service-contracts/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-2022 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/service-contracts/README.md b/vendor/symfony/service-contracts/README.md new file mode 100644 index 0000000..41e054a --- /dev/null +++ b/vendor/symfony/service-contracts/README.md @@ -0,0 +1,9 @@ +Symfony Service Contracts +========================= + +A set of abstractions extracted out of the Symfony components. + +Can be used to build on semantics that the Symfony components proved useful - and +that already have battle tested implementations. + +See https://github.com/symfony/contracts/blob/main/README.md for more information. diff --git a/vendor/symfony/service-contracts/ResetInterface.php b/vendor/symfony/service-contracts/ResetInterface.php new file mode 100644 index 0000000..1af1075 --- /dev/null +++ b/vendor/symfony/service-contracts/ResetInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +/** + * Provides a way to reset an object to its initial state. + * + * When calling the "reset()" method on an object, it should be put back to its + * initial state. This usually means clearing any internal buffers and forwarding + * the call to internal dependencies. All properties of the object should be put + * back to the same state it had when it was first ready to use. + * + * This method could be called, for example, to recycle objects that are used as + * services, so that they can be used to handle several requests in the same + * process loop (note that we advise making your services stateless instead of + * implementing this interface when possible.) + */ +interface ResetInterface +{ + public function reset(); +} diff --git a/vendor/symfony/service-contracts/ServiceLocatorTrait.php b/vendor/symfony/service-contracts/ServiceLocatorTrait.php new file mode 100644 index 0000000..74dfa43 --- /dev/null +++ b/vendor/symfony/service-contracts/ServiceLocatorTrait.php @@ -0,0 +1,128 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +use Psr\Container\ContainerExceptionInterface; +use Psr\Container\NotFoundExceptionInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(ContainerExceptionInterface::class); +class_exists(NotFoundExceptionInterface::class); + +/** + * A trait to help implement ServiceProviderInterface. + * + * @author Robin Chalas + * @author Nicolas Grekas + */ +trait ServiceLocatorTrait +{ + private $factories; + private $loading = []; + private $providedTypes; + + /** + * @param callable[] $factories + */ + public function __construct(array $factories) + { + $this->factories = $factories; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function has(string $id) + { + return isset($this->factories[$id]); + } + + /** + * {@inheritdoc} + * + * @return mixed + */ + public function get(string $id) + { + if (!isset($this->factories[$id])) { + throw $this->createNotFoundException($id); + } + + if (isset($this->loading[$id])) { + $ids = array_values($this->loading); + $ids = \array_slice($this->loading, array_search($id, $ids)); + $ids[] = $id; + + throw $this->createCircularReferenceException($id, $ids); + } + + $this->loading[$id] = $id; + try { + return $this->factories[$id]($this); + } finally { + unset($this->loading[$id]); + } + } + + /** + * {@inheritdoc} + */ + public function getProvidedServices(): array + { + if (null === $this->providedTypes) { + $this->providedTypes = []; + + foreach ($this->factories as $name => $factory) { + if (!\is_callable($factory)) { + $this->providedTypes[$name] = '?'; + } else { + $type = (new \ReflectionFunction($factory))->getReturnType(); + + $this->providedTypes[$name] = $type ? ($type->allowsNull() ? '?' : '').($type instanceof \ReflectionNamedType ? $type->getName() : $type) : '?'; + } + } + } + + return $this->providedTypes; + } + + private function createNotFoundException(string $id): NotFoundExceptionInterface + { + if (!$alternatives = array_keys($this->factories)) { + $message = 'is empty...'; + } else { + $last = array_pop($alternatives); + if ($alternatives) { + $message = sprintf('only knows about the "%s" and "%s" services.', implode('", "', $alternatives), $last); + } else { + $message = sprintf('only knows about the "%s" service.', $last); + } + } + + if ($this->loading) { + $message = sprintf('The service "%s" has a dependency on a non-existent service "%s". This locator %s', end($this->loading), $id, $message); + } else { + $message = sprintf('Service "%s" not found: the current service locator %s', $id, $message); + } + + return new class($message) extends \InvalidArgumentException implements NotFoundExceptionInterface { + }; + } + + private function createCircularReferenceException(string $id, array $path): ContainerExceptionInterface + { + return new class(sprintf('Circular reference detected for service "%s", path: "%s".', $id, implode(' -> ', $path))) extends \RuntimeException implements ContainerExceptionInterface { + }; + } +} diff --git a/vendor/symfony/service-contracts/ServiceProviderInterface.php b/vendor/symfony/service-contracts/ServiceProviderInterface.php new file mode 100644 index 0000000..c60ad0b --- /dev/null +++ b/vendor/symfony/service-contracts/ServiceProviderInterface.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +use Psr\Container\ContainerInterface; + +/** + * A ServiceProviderInterface exposes the identifiers and the types of services provided by a container. + * + * @author Nicolas Grekas + * @author Mateusz Sip + */ +interface ServiceProviderInterface extends ContainerInterface +{ + /** + * Returns an associative array of service types keyed by the identifiers provided by the current container. + * + * Examples: + * + * * ['logger' => 'Psr\Log\LoggerInterface'] means the object provides a service named "logger" that implements Psr\Log\LoggerInterface + * * ['foo' => '?'] means the container provides service name "foo" of unspecified type + * * ['bar' => '?Bar\Baz'] means the container provides a service "bar" of type Bar\Baz|null + * + * @return string[] The provided service types, keyed by service names + */ + public function getProvidedServices(): array; +} diff --git a/vendor/symfony/service-contracts/ServiceSubscriberInterface.php b/vendor/symfony/service-contracts/ServiceSubscriberInterface.php new file mode 100644 index 0000000..098ab90 --- /dev/null +++ b/vendor/symfony/service-contracts/ServiceSubscriberInterface.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +/** + * A ServiceSubscriber exposes its dependencies via the static {@link getSubscribedServices} method. + * + * The getSubscribedServices method returns an array of service types required by such instances, + * optionally keyed by the service names used internally. Service types that start with an interrogation + * mark "?" are optional, while the other ones are mandatory service dependencies. + * + * The injected service locators SHOULD NOT allow access to any other services not specified by the method. + * + * It is expected that ServiceSubscriber instances consume PSR-11-based service locators internally. + * This interface does not dictate any injection method for these service locators, although constructor + * injection is recommended. + * + * @author Nicolas Grekas + */ +interface ServiceSubscriberInterface +{ + /** + * Returns an array of service types required by such instances, optionally keyed by the service names used internally. + * + * For mandatory dependencies: + * + * * ['logger' => 'Psr\Log\LoggerInterface'] means the objects use the "logger" name + * internally to fetch a service which must implement Psr\Log\LoggerInterface. + * * ['loggers' => 'Psr\Log\LoggerInterface[]'] means the objects use the "loggers" name + * internally to fetch an iterable of Psr\Log\LoggerInterface instances. + * * ['Psr\Log\LoggerInterface'] is a shortcut for + * * ['Psr\Log\LoggerInterface' => 'Psr\Log\LoggerInterface'] + * + * otherwise: + * + * * ['logger' => '?Psr\Log\LoggerInterface'] denotes an optional dependency + * * ['loggers' => '?Psr\Log\LoggerInterface[]'] denotes an optional iterable dependency + * * ['?Psr\Log\LoggerInterface'] is a shortcut for + * * ['Psr\Log\LoggerInterface' => '?Psr\Log\LoggerInterface'] + * + * @return string[] The required service types, optionally keyed by service names + */ + public static function getSubscribedServices(); +} diff --git a/vendor/symfony/service-contracts/ServiceSubscriberTrait.php b/vendor/symfony/service-contracts/ServiceSubscriberTrait.php new file mode 100644 index 0000000..16e3eb2 --- /dev/null +++ b/vendor/symfony/service-contracts/ServiceSubscriberTrait.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +use Psr\Container\ContainerInterface; +use Symfony\Contracts\Service\Attribute\SubscribedService; + +/** + * Implementation of ServiceSubscriberInterface that determines subscribed services from + * method return types. Service ids are available as "ClassName::methodName". + * + * @author Kevin Bond + */ +trait ServiceSubscriberTrait +{ + /** @var ContainerInterface */ + protected $container; + + /** + * {@inheritdoc} + */ + public static function getSubscribedServices(): array + { + $services = method_exists(get_parent_class(self::class) ?: '', __FUNCTION__) ? parent::getSubscribedServices() : []; + $attributeOptIn = false; + + if (\PHP_VERSION_ID >= 80000) { + foreach ((new \ReflectionClass(self::class))->getMethods() as $method) { + if (self::class !== $method->getDeclaringClass()->name) { + continue; + } + + if (!$attribute = $method->getAttributes(SubscribedService::class)[0] ?? null) { + continue; + } + + if ($method->isStatic() || $method->isAbstract() || $method->isGenerator() || $method->isInternal() || $method->getNumberOfRequiredParameters()) { + throw new \LogicException(sprintf('Cannot use "%s" on method "%s::%s()" (can only be used on non-static, non-abstract methods with no parameters).', SubscribedService::class, self::class, $method->name)); + } + + if (!$returnType = $method->getReturnType()) { + throw new \LogicException(sprintf('Cannot use "%s" on methods without a return type in "%s::%s()".', SubscribedService::class, $method->name, self::class)); + } + + $serviceId = $returnType instanceof \ReflectionNamedType ? $returnType->getName() : (string) $returnType; + + if ($returnType->allowsNull()) { + $serviceId = '?'.$serviceId; + } + + $services[$attribute->newInstance()->key ?? self::class.'::'.$method->name] = $serviceId; + $attributeOptIn = true; + } + } + + if (!$attributeOptIn) { + foreach ((new \ReflectionClass(self::class))->getMethods() as $method) { + if ($method->isStatic() || $method->isAbstract() || $method->isGenerator() || $method->isInternal() || $method->getNumberOfRequiredParameters()) { + continue; + } + + if (self::class !== $method->getDeclaringClass()->name) { + continue; + } + + if (!($returnType = $method->getReturnType()) instanceof \ReflectionNamedType) { + continue; + } + + if ($returnType->isBuiltin()) { + continue; + } + + if (\PHP_VERSION_ID >= 80000) { + trigger_deprecation('symfony/service-contracts', '2.5', 'Using "%s" in "%s" without using the "%s" attribute on any method is deprecated.', ServiceSubscriberTrait::class, self::class, SubscribedService::class); + } + + $services[self::class.'::'.$method->name] = '?'.($returnType instanceof \ReflectionNamedType ? $returnType->getName() : $returnType); + } + } + + return $services; + } + + /** + * @required + * + * @return ContainerInterface|null + */ + public function setContainer(ContainerInterface $container) + { + $this->container = $container; + + if (method_exists(get_parent_class(self::class) ?: '', __FUNCTION__)) { + return parent::setContainer($container); + } + + return null; + } +} diff --git a/vendor/symfony/service-contracts/Test/ServiceLocatorTest.php b/vendor/symfony/service-contracts/Test/ServiceLocatorTest.php new file mode 100644 index 0000000..2a1b565 --- /dev/null +++ b/vendor/symfony/service-contracts/Test/ServiceLocatorTest.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service\Test; + +use PHPUnit\Framework\TestCase; +use Psr\Container\ContainerInterface; +use Symfony\Contracts\Service\ServiceLocatorTrait; + +abstract class ServiceLocatorTest extends TestCase +{ + /** + * @return ContainerInterface + */ + protected function getServiceLocator(array $factories) + { + return new class($factories) implements ContainerInterface { + use ServiceLocatorTrait; + }; + } + + public function testHas() + { + $locator = $this->getServiceLocator([ + 'foo' => function () { return 'bar'; }, + 'bar' => function () { return 'baz'; }, + function () { return 'dummy'; }, + ]); + + $this->assertTrue($locator->has('foo')); + $this->assertTrue($locator->has('bar')); + $this->assertFalse($locator->has('dummy')); + } + + public function testGet() + { + $locator = $this->getServiceLocator([ + 'foo' => function () { return 'bar'; }, + 'bar' => function () { return 'baz'; }, + ]); + + $this->assertSame('bar', $locator->get('foo')); + $this->assertSame('baz', $locator->get('bar')); + } + + public function testGetDoesNotMemoize() + { + $i = 0; + $locator = $this->getServiceLocator([ + 'foo' => function () use (&$i) { + ++$i; + + return 'bar'; + }, + ]); + + $this->assertSame('bar', $locator->get('foo')); + $this->assertSame('bar', $locator->get('foo')); + $this->assertSame(2, $i); + } + + public function testThrowsOnUndefinedInternalService() + { + if (!$this->getExpectedException()) { + $this->expectException(\Psr\Container\NotFoundExceptionInterface::class); + $this->expectExceptionMessage('The service "foo" has a dependency on a non-existent service "bar". This locator only knows about the "foo" service.'); + } + $locator = $this->getServiceLocator([ + 'foo' => function () use (&$locator) { return $locator->get('bar'); }, + ]); + + $locator->get('foo'); + } + + public function testThrowsOnCircularReference() + { + $this->expectException(\Psr\Container\ContainerExceptionInterface::class); + $this->expectExceptionMessage('Circular reference detected for service "bar", path: "bar -> baz -> bar".'); + $locator = $this->getServiceLocator([ + 'foo' => function () use (&$locator) { return $locator->get('bar'); }, + 'bar' => function () use (&$locator) { return $locator->get('baz'); }, + 'baz' => function () use (&$locator) { return $locator->get('bar'); }, + ]); + + $locator->get('foo'); + } +} diff --git a/vendor/symfony/string/AbstractString.php b/vendor/symfony/string/AbstractString.php new file mode 100644 index 0000000..13567c7 --- /dev/null +++ b/vendor/symfony/string/AbstractString.php @@ -0,0 +1,795 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String; + +use Symfony\Component\String\Exception\ExceptionInterface; +use Symfony\Component\String\Exception\InvalidArgumentException; +use Symfony\Component\String\Exception\RuntimeException; + +/** + * Represents a string of abstract characters. + * + * Unicode defines 3 types of "characters" (bytes, code points and grapheme clusters). + * This class is the abstract type to use as a type-hint when the logic you want to + * implement doesn't care about the exact variant it deals with. + * + * @author Nicolas Grekas + * @author Hugo Hamon + * + * @throws ExceptionInterface + */ +abstract class AbstractString implements \Stringable, \JsonSerializable +{ + public const PREG_PATTERN_ORDER = \PREG_PATTERN_ORDER; + public const PREG_SET_ORDER = \PREG_SET_ORDER; + public const PREG_OFFSET_CAPTURE = \PREG_OFFSET_CAPTURE; + public const PREG_UNMATCHED_AS_NULL = \PREG_UNMATCHED_AS_NULL; + + public const PREG_SPLIT = 0; + public const PREG_SPLIT_NO_EMPTY = \PREG_SPLIT_NO_EMPTY; + public const PREG_SPLIT_DELIM_CAPTURE = \PREG_SPLIT_DELIM_CAPTURE; + public const PREG_SPLIT_OFFSET_CAPTURE = \PREG_SPLIT_OFFSET_CAPTURE; + + protected $string = ''; + protected $ignoreCase = false; + + abstract public function __construct(string $string = ''); + + /** + * Unwraps instances of AbstractString back to strings. + * + * @return string[]|array + */ + public static function unwrap(array $values): array + { + foreach ($values as $k => $v) { + if ($v instanceof self) { + $values[$k] = $v->__toString(); + } elseif (\is_array($v) && $values[$k] !== $v = static::unwrap($v)) { + $values[$k] = $v; + } + } + + return $values; + } + + /** + * Wraps (and normalizes) strings in instances of AbstractString. + * + * @return static[]|array + */ + public static function wrap(array $values): array + { + $i = 0; + $keys = null; + + foreach ($values as $k => $v) { + if (\is_string($k) && '' !== $k && $k !== $j = (string) new static($k)) { + $keys = $keys ?? array_keys($values); + $keys[$i] = $j; + } + + if (\is_string($v)) { + $values[$k] = new static($v); + } elseif (\is_array($v) && $values[$k] !== $v = static::wrap($v)) { + $values[$k] = $v; + } + + ++$i; + } + + return null !== $keys ? array_combine($keys, $values) : $values; + } + + /** + * @param string|string[] $needle + * + * @return static + */ + public function after($needle, bool $includeNeedle = false, int $offset = 0): self + { + $str = clone $this; + $i = \PHP_INT_MAX; + + foreach ((array) $needle as $n) { + $n = (string) $n; + $j = $this->indexOf($n, $offset); + + if (null !== $j && $j < $i) { + $i = $j; + $str->string = $n; + } + } + + if (\PHP_INT_MAX === $i) { + return $str; + } + + if (!$includeNeedle) { + $i += $str->length(); + } + + return $this->slice($i); + } + + /** + * @param string|string[] $needle + * + * @return static + */ + public function afterLast($needle, bool $includeNeedle = false, int $offset = 0): self + { + $str = clone $this; + $i = null; + + foreach ((array) $needle as $n) { + $n = (string) $n; + $j = $this->indexOfLast($n, $offset); + + if (null !== $j && $j >= $i) { + $i = $offset = $j; + $str->string = $n; + } + } + + if (null === $i) { + return $str; + } + + if (!$includeNeedle) { + $i += $str->length(); + } + + return $this->slice($i); + } + + /** + * @return static + */ + abstract public function append(string ...$suffix): self; + + /** + * @param string|string[] $needle + * + * @return static + */ + public function before($needle, bool $includeNeedle = false, int $offset = 0): self + { + $str = clone $this; + $i = \PHP_INT_MAX; + + foreach ((array) $needle as $n) { + $n = (string) $n; + $j = $this->indexOf($n, $offset); + + if (null !== $j && $j < $i) { + $i = $j; + $str->string = $n; + } + } + + if (\PHP_INT_MAX === $i) { + return $str; + } + + if ($includeNeedle) { + $i += $str->length(); + } + + return $this->slice(0, $i); + } + + /** + * @param string|string[] $needle + * + * @return static + */ + public function beforeLast($needle, bool $includeNeedle = false, int $offset = 0): self + { + $str = clone $this; + $i = null; + + foreach ((array) $needle as $n) { + $n = (string) $n; + $j = $this->indexOfLast($n, $offset); + + if (null !== $j && $j >= $i) { + $i = $offset = $j; + $str->string = $n; + } + } + + if (null === $i) { + return $str; + } + + if ($includeNeedle) { + $i += $str->length(); + } + + return $this->slice(0, $i); + } + + /** + * @return int[] + */ + public function bytesAt(int $offset): array + { + $str = $this->slice($offset, 1); + + return '' === $str->string ? [] : array_values(unpack('C*', $str->string)); + } + + /** + * @return static + */ + abstract public function camel(): self; + + /** + * @return static[] + */ + abstract public function chunk(int $length = 1): array; + + /** + * @return static + */ + public function collapseWhitespace(): self + { + $str = clone $this; + $str->string = trim(preg_replace("/(?:[ \n\r\t\x0C]{2,}+|[\n\r\t\x0C])/", ' ', $str->string), " \n\r\t\x0C"); + + return $str; + } + + /** + * @param string|string[] $needle + */ + public function containsAny($needle): bool + { + return null !== $this->indexOf($needle); + } + + /** + * @param string|string[] $suffix + */ + public function endsWith($suffix): bool + { + if (!\is_array($suffix) && !$suffix instanceof \Traversable) { + throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class)); + } + + foreach ($suffix as $s) { + if ($this->endsWith((string) $s)) { + return true; + } + } + + return false; + } + + /** + * @return static + */ + public function ensureEnd(string $suffix): self + { + if (!$this->endsWith($suffix)) { + return $this->append($suffix); + } + + $suffix = preg_quote($suffix); + $regex = '{('.$suffix.')(?:'.$suffix.')++$}D'; + + return $this->replaceMatches($regex.($this->ignoreCase ? 'i' : ''), '$1'); + } + + /** + * @return static + */ + public function ensureStart(string $prefix): self + { + $prefix = new static($prefix); + + if (!$this->startsWith($prefix)) { + return $this->prepend($prefix); + } + + $str = clone $this; + $i = $prefixLen = $prefix->length(); + + while ($this->indexOf($prefix, $i) === $i) { + $str = $str->slice($prefixLen); + $i += $prefixLen; + } + + return $str; + } + + /** + * @param string|string[] $string + */ + public function equalsTo($string): bool + { + if (!\is_array($string) && !$string instanceof \Traversable) { + throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class)); + } + + foreach ($string as $s) { + if ($this->equalsTo((string) $s)) { + return true; + } + } + + return false; + } + + /** + * @return static + */ + abstract public function folded(): self; + + /** + * @return static + */ + public function ignoreCase(): self + { + $str = clone $this; + $str->ignoreCase = true; + + return $str; + } + + /** + * @param string|string[] $needle + */ + public function indexOf($needle, int $offset = 0): ?int + { + if (!\is_array($needle) && !$needle instanceof \Traversable) { + throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class)); + } + + $i = \PHP_INT_MAX; + + foreach ($needle as $n) { + $j = $this->indexOf((string) $n, $offset); + + if (null !== $j && $j < $i) { + $i = $j; + } + } + + return \PHP_INT_MAX === $i ? null : $i; + } + + /** + * @param string|string[] $needle + */ + public function indexOfLast($needle, int $offset = 0): ?int + { + if (!\is_array($needle) && !$needle instanceof \Traversable) { + throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class)); + } + + $i = null; + + foreach ($needle as $n) { + $j = $this->indexOfLast((string) $n, $offset); + + if (null !== $j && $j >= $i) { + $i = $offset = $j; + } + } + + return $i; + } + + public function isEmpty(): bool + { + return '' === $this->string; + } + + /** + * @return static + */ + abstract public function join(array $strings, string $lastGlue = null): self; + + public function jsonSerialize(): string + { + return $this->string; + } + + abstract public function length(): int; + + /** + * @return static + */ + abstract public function lower(): self; + + /** + * Matches the string using a regular expression. + * + * Pass PREG_PATTERN_ORDER or PREG_SET_ORDER as $flags to get all occurrences matching the regular expression. + * + * @return array All matches in a multi-dimensional array ordered according to flags + */ + abstract public function match(string $regexp, int $flags = 0, int $offset = 0): array; + + /** + * @return static + */ + abstract public function padBoth(int $length, string $padStr = ' '): self; + + /** + * @return static + */ + abstract public function padEnd(int $length, string $padStr = ' '): self; + + /** + * @return static + */ + abstract public function padStart(int $length, string $padStr = ' '): self; + + /** + * @return static + */ + abstract public function prepend(string ...$prefix): self; + + /** + * @return static + */ + public function repeat(int $multiplier): self + { + if (0 > $multiplier) { + throw new InvalidArgumentException(sprintf('Multiplier must be positive, %d given.', $multiplier)); + } + + $str = clone $this; + $str->string = str_repeat($str->string, $multiplier); + + return $str; + } + + /** + * @return static + */ + abstract public function replace(string $from, string $to): self; + + /** + * @param string|callable $to + * + * @return static + */ + abstract public function replaceMatches(string $fromRegexp, $to): self; + + /** + * @return static + */ + abstract public function reverse(): self; + + /** + * @return static + */ + abstract public function slice(int $start = 0, int $length = null): self; + + /** + * @return static + */ + abstract public function snake(): self; + + /** + * @return static + */ + abstract public function splice(string $replacement, int $start = 0, int $length = null): self; + + /** + * @return static[] + */ + public function split(string $delimiter, int $limit = null, int $flags = null): array + { + if (null === $flags) { + throw new \TypeError('Split behavior when $flags is null must be implemented by child classes.'); + } + + if ($this->ignoreCase) { + $delimiter .= 'i'; + } + + set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); }); + + try { + if (false === $chunks = preg_split($delimiter, $this->string, $limit, $flags)) { + $lastError = preg_last_error(); + + foreach (get_defined_constants(true)['pcre'] as $k => $v) { + if ($lastError === $v && '_ERROR' === substr($k, -6)) { + throw new RuntimeException('Splitting failed with '.$k.'.'); + } + } + + throw new RuntimeException('Splitting failed with unknown error code.'); + } + } finally { + restore_error_handler(); + } + + $str = clone $this; + + if (self::PREG_SPLIT_OFFSET_CAPTURE & $flags) { + foreach ($chunks as &$chunk) { + $str->string = $chunk[0]; + $chunk[0] = clone $str; + } + } else { + foreach ($chunks as &$chunk) { + $str->string = $chunk; + $chunk = clone $str; + } + } + + return $chunks; + } + + /** + * @param string|string[] $prefix + */ + public function startsWith($prefix): bool + { + if (!\is_array($prefix) && !$prefix instanceof \Traversable) { + throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class)); + } + + foreach ($prefix as $prefix) { + if ($this->startsWith((string) $prefix)) { + return true; + } + } + + return false; + } + + /** + * @return static + */ + abstract public function title(bool $allWords = false): self; + + public function toByteString(string $toEncoding = null): ByteString + { + $b = new ByteString(); + + $toEncoding = \in_array($toEncoding, ['utf8', 'utf-8', 'UTF8'], true) ? 'UTF-8' : $toEncoding; + + if (null === $toEncoding || $toEncoding === $fromEncoding = $this instanceof AbstractUnicodeString || preg_match('//u', $b->string) ? 'UTF-8' : 'Windows-1252') { + $b->string = $this->string; + + return $b; + } + + set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); }); + + try { + try { + $b->string = mb_convert_encoding($this->string, $toEncoding, 'UTF-8'); + } catch (InvalidArgumentException $e) { + if (!\function_exists('iconv')) { + throw $e; + } + + $b->string = iconv('UTF-8', $toEncoding, $this->string); + } + } finally { + restore_error_handler(); + } + + return $b; + } + + public function toCodePointString(): CodePointString + { + return new CodePointString($this->string); + } + + public function toString(): string + { + return $this->string; + } + + public function toUnicodeString(): UnicodeString + { + return new UnicodeString($this->string); + } + + /** + * @return static + */ + abstract public function trim(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): self; + + /** + * @return static + */ + abstract public function trimEnd(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): self; + + /** + * @param string|string[] $prefix + * + * @return static + */ + public function trimPrefix($prefix): self + { + if (\is_array($prefix) || $prefix instanceof \Traversable) { + foreach ($prefix as $s) { + $t = $this->trimPrefix($s); + + if ($t->string !== $this->string) { + return $t; + } + } + + return clone $this; + } + + $str = clone $this; + + if ($prefix instanceof self) { + $prefix = $prefix->string; + } else { + $prefix = (string) $prefix; + } + + if ('' !== $prefix && \strlen($this->string) >= \strlen($prefix) && 0 === substr_compare($this->string, $prefix, 0, \strlen($prefix), $this->ignoreCase)) { + $str->string = substr($this->string, \strlen($prefix)); + } + + return $str; + } + + /** + * @return static + */ + abstract public function trimStart(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): self; + + /** + * @param string|string[] $suffix + * + * @return static + */ + public function trimSuffix($suffix): self + { + if (\is_array($suffix) || $suffix instanceof \Traversable) { + foreach ($suffix as $s) { + $t = $this->trimSuffix($s); + + if ($t->string !== $this->string) { + return $t; + } + } + + return clone $this; + } + + $str = clone $this; + + if ($suffix instanceof self) { + $suffix = $suffix->string; + } else { + $suffix = (string) $suffix; + } + + if ('' !== $suffix && \strlen($this->string) >= \strlen($suffix) && 0 === substr_compare($this->string, $suffix, -\strlen($suffix), null, $this->ignoreCase)) { + $str->string = substr($this->string, 0, -\strlen($suffix)); + } + + return $str; + } + + /** + * @return static + */ + public function truncate(int $length, string $ellipsis = '', bool $cut = true): self + { + $stringLength = $this->length(); + + if ($stringLength <= $length) { + return clone $this; + } + + $ellipsisLength = '' !== $ellipsis ? (new static($ellipsis))->length() : 0; + + if ($length < $ellipsisLength) { + $ellipsisLength = 0; + } + + if (!$cut) { + if (null === $length = $this->indexOf([' ', "\r", "\n", "\t"], ($length ?: 1) - 1)) { + return clone $this; + } + + $length += $ellipsisLength; + } + + $str = $this->slice(0, $length - $ellipsisLength); + + return $ellipsisLength ? $str->trimEnd()->append($ellipsis) : $str; + } + + /** + * @return static + */ + abstract public function upper(): self; + + /** + * Returns the printable length on a terminal. + */ + abstract public function width(bool $ignoreAnsiDecoration = true): int; + + /** + * @return static + */ + public function wordwrap(int $width = 75, string $break = "\n", bool $cut = false): self + { + $lines = '' !== $break ? $this->split($break) : [clone $this]; + $chars = []; + $mask = ''; + + if (1 === \count($lines) && '' === $lines[0]->string) { + return $lines[0]; + } + + foreach ($lines as $i => $line) { + if ($i) { + $chars[] = $break; + $mask .= '#'; + } + + foreach ($line->chunk() as $char) { + $chars[] = $char->string; + $mask .= ' ' === $char->string ? ' ' : '?'; + } + } + + $string = ''; + $j = 0; + $b = $i = -1; + $mask = wordwrap($mask, $width, '#', $cut); + + while (false !== $b = strpos($mask, '#', $b + 1)) { + for (++$i; $i < $b; ++$i) { + $string .= $chars[$j]; + unset($chars[$j++]); + } + + if ($break === $chars[$j] || ' ' === $chars[$j]) { + unset($chars[$j++]); + } + + $string .= $break; + } + + $str = clone $this; + $str->string = $string.implode('', $chars); + + return $str; + } + + public function __sleep(): array + { + return ['string']; + } + + public function __clone() + { + $this->ignoreCase = false; + } + + public function __toString(): string + { + return $this->string; + } +} diff --git a/vendor/symfony/string/AbstractUnicodeString.php b/vendor/symfony/string/AbstractUnicodeString.php new file mode 100644 index 0000000..80b8326 --- /dev/null +++ b/vendor/symfony/string/AbstractUnicodeString.php @@ -0,0 +1,623 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String; + +use Symfony\Component\String\Exception\ExceptionInterface; +use Symfony\Component\String\Exception\InvalidArgumentException; +use Symfony\Component\String\Exception\RuntimeException; + +/** + * Represents a string of abstract Unicode characters. + * + * Unicode defines 3 types of "characters" (bytes, code points and grapheme clusters). + * This class is the abstract type to use as a type-hint when the logic you want to + * implement is Unicode-aware but doesn't care about code points vs grapheme clusters. + * + * @author Nicolas Grekas + * + * @throws ExceptionInterface + */ +abstract class AbstractUnicodeString extends AbstractString +{ + public const NFC = \Normalizer::NFC; + public const NFD = \Normalizer::NFD; + public const NFKC = \Normalizer::NFKC; + public const NFKD = \Normalizer::NFKD; + + // all ASCII letters sorted by typical frequency of occurrence + private const ASCII = "\x20\x65\x69\x61\x73\x6E\x74\x72\x6F\x6C\x75\x64\x5D\x5B\x63\x6D\x70\x27\x0A\x67\x7C\x68\x76\x2E\x66\x62\x2C\x3A\x3D\x2D\x71\x31\x30\x43\x32\x2A\x79\x78\x29\x28\x4C\x39\x41\x53\x2F\x50\x22\x45\x6A\x4D\x49\x6B\x33\x3E\x35\x54\x3C\x44\x34\x7D\x42\x7B\x38\x46\x77\x52\x36\x37\x55\x47\x4E\x3B\x4A\x7A\x56\x23\x48\x4F\x57\x5F\x26\x21\x4B\x3F\x58\x51\x25\x59\x5C\x09\x5A\x2B\x7E\x5E\x24\x40\x60\x7F\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"; + + // the subset of folded case mappings that is not in lower case mappings + private const FOLD_FROM = ['İ', 'µ', 'ſ', "\xCD\x85", 'ς', 'ϐ', 'ϑ', 'ϕ', 'ϖ', 'ϰ', 'ϱ', 'ϵ', 'ẛ', "\xE1\xBE\xBE", 'ß', 'ʼn', 'ǰ', 'ΐ', 'ΰ', 'և', 'ẖ', 'ẗ', 'ẘ', 'ẙ', 'ẚ', 'ẞ', 'ὐ', 'ὒ', 'ὔ', 'ὖ', 'ᾀ', 'ᾁ', 'ᾂ', 'ᾃ', 'ᾄ', 'ᾅ', 'ᾆ', 'ᾇ', 'ᾈ', 'ᾉ', 'ᾊ', 'ᾋ', 'ᾌ', 'ᾍ', 'ᾎ', 'ᾏ', 'ᾐ', 'ᾑ', 'ᾒ', 'ᾓ', 'ᾔ', 'ᾕ', 'ᾖ', 'ᾗ', 'ᾘ', 'ᾙ', 'ᾚ', 'ᾛ', 'ᾜ', 'ᾝ', 'ᾞ', 'ᾟ', 'ᾠ', 'ᾡ', 'ᾢ', 'ᾣ', 'ᾤ', 'ᾥ', 'ᾦ', 'ᾧ', 'ᾨ', 'ᾩ', 'ᾪ', 'ᾫ', 'ᾬ', 'ᾭ', 'ᾮ', 'ᾯ', 'ᾲ', 'ᾳ', 'ᾴ', 'ᾶ', 'ᾷ', 'ᾼ', 'ῂ', 'ῃ', 'ῄ', 'ῆ', 'ῇ', 'ῌ', 'ῒ', 'ῖ', 'ῗ', 'ῢ', 'ῤ', 'ῦ', 'ῧ', 'ῲ', 'ῳ', 'ῴ', 'ῶ', 'ῷ', 'ῼ', 'ff', 'fi', 'fl', 'ffi', 'ffl', 'ſt', 'st', 'ﬓ', 'ﬔ', 'ﬕ', 'ﬖ', 'ﬗ']; + private const FOLD_TO = ['i̇', 'μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', 'ṡ', 'ι', 'ss', 'ʼn', 'ǰ', 'ΐ', 'ΰ', 'եւ', 'ẖ', 'ẗ', 'ẘ', 'ẙ', 'aʾ', 'ss', 'ὐ', 'ὒ', 'ὔ', 'ὖ', 'ἀι', 'ἁι', 'ἂι', 'ἃι', 'ἄι', 'ἅι', 'ἆι', 'ἇι', 'ἀι', 'ἁι', 'ἂι', 'ἃι', 'ἄι', 'ἅι', 'ἆι', 'ἇι', 'ἠι', 'ἡι', 'ἢι', 'ἣι', 'ἤι', 'ἥι', 'ἦι', 'ἧι', 'ἠι', 'ἡι', 'ἢι', 'ἣι', 'ἤι', 'ἥι', 'ἦι', 'ἧι', 'ὠι', 'ὡι', 'ὢι', 'ὣι', 'ὤι', 'ὥι', 'ὦι', 'ὧι', 'ὠι', 'ὡι', 'ὢι', 'ὣι', 'ὤι', 'ὥι', 'ὦι', 'ὧι', 'ὰι', 'αι', 'άι', 'ᾶ', 'ᾶι', 'αι', 'ὴι', 'ηι', 'ήι', 'ῆ', 'ῆι', 'ηι', 'ῒ', 'ῖ', 'ῗ', 'ῢ', 'ῤ', 'ῦ', 'ῧ', 'ὼι', 'ωι', 'ώι', 'ῶ', 'ῶι', 'ωι', 'ff', 'fi', 'fl', 'ffi', 'ffl', 'st', 'st', 'մն', 'մե', 'մի', 'վն', 'մխ']; + + // the subset of upper case mappings that map one code point to many code points + private const UPPER_FROM = ['ß', 'ff', 'fi', 'fl', 'ffi', 'ffl', 'ſt', 'st', 'և', 'ﬓ', 'ﬔ', 'ﬕ', 'ﬖ', 'ﬗ', 'ʼn', 'ΐ', 'ΰ', 'ǰ', 'ẖ', 'ẗ', 'ẘ', 'ẙ', 'ẚ', 'ὐ', 'ὒ', 'ὔ', 'ὖ', 'ᾶ', 'ῆ', 'ῒ', 'ΐ', 'ῖ', 'ῗ', 'ῢ', 'ΰ', 'ῤ', 'ῦ', 'ῧ', 'ῶ']; + private const UPPER_TO = ['SS', 'FF', 'FI', 'FL', 'FFI', 'FFL', 'ST', 'ST', 'ԵՒ', 'ՄՆ', 'ՄԵ', 'ՄԻ', 'ՎՆ', 'ՄԽ', 'ʼN', 'Ϊ́', 'Ϋ́', 'J̌', 'H̱', 'T̈', 'W̊', 'Y̊', 'Aʾ', 'Υ̓', 'Υ̓̀', 'Υ̓́', 'Υ̓͂', 'Α͂', 'Η͂', 'Ϊ̀', 'Ϊ́', 'Ι͂', 'Ϊ͂', 'Ϋ̀', 'Ϋ́', 'Ρ̓', 'Υ͂', 'Ϋ͂', 'Ω͂']; + + // the subset of https://github.com/unicode-org/cldr/blob/master/common/transforms/Latin-ASCII.xml that is not in NFKD + private const TRANSLIT_FROM = ['Æ', 'Ð', 'Ø', 'Þ', 'ß', 'æ', 'ð', 'ø', 'þ', 'Đ', 'đ', 'Ħ', 'ħ', 'ı', 'ĸ', 'Ŀ', 'ŀ', 'Ł', 'ł', 'ʼn', 'Ŋ', 'ŋ', 'Œ', 'œ', 'Ŧ', 'ŧ', 'ƀ', 'Ɓ', 'Ƃ', 'ƃ', 'Ƈ', 'ƈ', 'Ɖ', 'Ɗ', 'Ƌ', 'ƌ', 'Ɛ', 'Ƒ', 'ƒ', 'Ɠ', 'ƕ', 'Ɩ', 'Ɨ', 'Ƙ', 'ƙ', 'ƚ', 'Ɲ', 'ƞ', 'Ƣ', 'ƣ', 'Ƥ', 'ƥ', 'ƫ', 'Ƭ', 'ƭ', 'Ʈ', 'Ʋ', 'Ƴ', 'ƴ', 'Ƶ', 'ƶ', 'DŽ', 'Dž', 'dž', 'Ǥ', 'ǥ', 'ȡ', 'Ȥ', 'ȥ', 'ȴ', 'ȵ', 'ȶ', 'ȷ', 'ȸ', 'ȹ', 'Ⱥ', 'Ȼ', 'ȼ', 'Ƚ', 'Ⱦ', 'ȿ', 'ɀ', 'Ƀ', 'Ʉ', 'Ɇ', 'ɇ', 'Ɉ', 'ɉ', 'Ɍ', 'ɍ', 'Ɏ', 'ɏ', 'ɓ', 'ɕ', 'ɖ', 'ɗ', 'ɛ', 'ɟ', 'ɠ', 'ɡ', 'ɢ', 'ɦ', 'ɧ', 'ɨ', 'ɪ', 'ɫ', 'ɬ', 'ɭ', 'ɱ', 'ɲ', 'ɳ', 'ɴ', 'ɶ', 'ɼ', 'ɽ', 'ɾ', 'ʀ', 'ʂ', 'ʈ', 'ʉ', 'ʋ', 'ʏ', 'ʐ', 'ʑ', 'ʙ', 'ʛ', 'ʜ', 'ʝ', 'ʟ', 'ʠ', 'ʣ', 'ʥ', 'ʦ', 'ʪ', 'ʫ', 'ᴀ', 'ᴁ', 'ᴃ', 'ᴄ', 'ᴅ', 'ᴆ', 'ᴇ', 'ᴊ', 'ᴋ', 'ᴌ', 'ᴍ', 'ᴏ', 'ᴘ', 'ᴛ', 'ᴜ', 'ᴠ', 'ᴡ', 'ᴢ', 'ᵫ', 'ᵬ', 'ᵭ', 'ᵮ', 'ᵯ', 'ᵰ', 'ᵱ', 'ᵲ', 'ᵳ', 'ᵴ', 'ᵵ', 'ᵶ', 'ᵺ', 'ᵻ', 'ᵽ', 'ᵾ', 'ᶀ', 'ᶁ', 'ᶂ', 'ᶃ', 'ᶄ', 'ᶅ', 'ᶆ', 'ᶇ', 'ᶈ', 'ᶉ', 'ᶊ', 'ᶌ', 'ᶍ', 'ᶎ', 'ᶏ', 'ᶑ', 'ᶒ', 'ᶓ', 'ᶖ', 'ᶙ', 'ẚ', 'ẜ', 'ẝ', 'ẞ', 'Ỻ', 'ỻ', 'Ỽ', 'ỽ', 'Ỿ', 'ỿ', '©', '®', '₠', '₢', '₣', '₤', '₧', '₺', '₹', 'ℌ', '℞', '㎧', '㎮', '㏆', '㏗', '㏞', '㏟', '¼', '½', '¾', '⅓', '⅔', '⅕', '⅖', '⅗', '⅘', '⅙', '⅚', '⅛', '⅜', '⅝', '⅞', '⅟', '〇', '‘', '’', '‚', '‛', '“', '”', '„', '‟', '′', '″', '〝', '〞', '«', '»', '‹', '›', '‐', '‑', '‒', '–', '—', '―', '︱', '︲', '﹘', '‖', '⁄', '⁅', '⁆', '⁎', '、', '。', '〈', '〉', '《', '》', '〔', '〕', '〘', '〙', '〚', '〛', '︑', '︒', '︹', '︺', '︽', '︾', '︿', '﹀', '﹑', '﹝', '﹞', '⦅', '⦆', '。', '、', '×', '÷', '−', '∕', '∖', '∣', '∥', '≪', '≫', '⦅', '⦆']; + private const TRANSLIT_TO = ['AE', 'D', 'O', 'TH', 'ss', 'ae', 'd', 'o', 'th', 'D', 'd', 'H', 'h', 'i', 'q', 'L', 'l', 'L', 'l', '\'n', 'N', 'n', 'OE', 'oe', 'T', 't', 'b', 'B', 'B', 'b', 'C', 'c', 'D', 'D', 'D', 'd', 'E', 'F', 'f', 'G', 'hv', 'I', 'I', 'K', 'k', 'l', 'N', 'n', 'OI', 'oi', 'P', 'p', 't', 'T', 't', 'T', 'V', 'Y', 'y', 'Z', 'z', 'DZ', 'Dz', 'dz', 'G', 'g', 'd', 'Z', 'z', 'l', 'n', 't', 'j', 'db', 'qp', 'A', 'C', 'c', 'L', 'T', 's', 'z', 'B', 'U', 'E', 'e', 'J', 'j', 'R', 'r', 'Y', 'y', 'b', 'c', 'd', 'd', 'e', 'j', 'g', 'g', 'G', 'h', 'h', 'i', 'I', 'l', 'l', 'l', 'm', 'n', 'n', 'N', 'OE', 'r', 'r', 'r', 'R', 's', 't', 'u', 'v', 'Y', 'z', 'z', 'B', 'G', 'H', 'j', 'L', 'q', 'dz', 'dz', 'ts', 'ls', 'lz', 'A', 'AE', 'B', 'C', 'D', 'D', 'E', 'J', 'K', 'L', 'M', 'O', 'P', 'T', 'U', 'V', 'W', 'Z', 'ue', 'b', 'd', 'f', 'm', 'n', 'p', 'r', 'r', 's', 't', 'z', 'th', 'I', 'p', 'U', 'b', 'd', 'f', 'g', 'k', 'l', 'm', 'n', 'p', 'r', 's', 'v', 'x', 'z', 'a', 'd', 'e', 'e', 'i', 'u', 'a', 's', 's', 'SS', 'LL', 'll', 'V', 'v', 'Y', 'y', '(C)', '(R)', 'CE', 'Cr', 'Fr.', 'L.', 'Pts', 'TL', 'Rs', 'x', 'Rx', 'm/s', 'rad/s', 'C/kg', 'pH', 'V/m', 'A/m', ' 1/4', ' 1/2', ' 3/4', ' 1/3', ' 2/3', ' 1/5', ' 2/5', ' 3/5', ' 4/5', ' 1/6', ' 5/6', ' 1/8', ' 3/8', ' 5/8', ' 7/8', ' 1/', '0', '\'', '\'', ',', '\'', '"', '"', ',,', '"', '\'', '"', '"', '"', '<<', '>>', '<', '>', '-', '-', '-', '-', '-', '-', '-', '-', '-', '||', '/', '[', ']', '*', ',', '.', '<', '>', '<<', '>>', '[', ']', '[', ']', '[', ']', ',', '.', '[', ']', '<<', '>>', '<', '>', ',', '[', ']', '((', '))', '.', ',', '*', '/', '-', '/', '\\', '|', '||', '<<', '>>', '((', '))']; + + private static $transliterators = []; + private static $tableZero; + private static $tableWide; + + /** + * @return static + */ + public static function fromCodePoints(int ...$codes): self + { + $string = ''; + + foreach ($codes as $code) { + if (0x80 > $code %= 0x200000) { + $string .= \chr($code); + } elseif (0x800 > $code) { + $string .= \chr(0xC0 | $code >> 6).\chr(0x80 | $code & 0x3F); + } elseif (0x10000 > $code) { + $string .= \chr(0xE0 | $code >> 12).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); + } else { + $string .= \chr(0xF0 | $code >> 18).\chr(0x80 | $code >> 12 & 0x3F).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); + } + } + + return new static($string); + } + + /** + * Generic UTF-8 to ASCII transliteration. + * + * Install the intl extension for best results. + * + * @param string[]|\Transliterator[]|\Closure[] $rules See "*-Latin" rules from Transliterator::listIDs() + */ + public function ascii(array $rules = []): self + { + $str = clone $this; + $s = $str->string; + $str->string = ''; + + array_unshift($rules, 'nfd'); + $rules[] = 'latin-ascii'; + + if (\function_exists('transliterator_transliterate')) { + $rules[] = 'any-latin/bgn'; + } + + $rules[] = 'nfkd'; + $rules[] = '[:nonspacing mark:] remove'; + + while (\strlen($s) - 1 > $i = strspn($s, self::ASCII)) { + if (0 < --$i) { + $str->string .= substr($s, 0, $i); + $s = substr($s, $i); + } + + if (!$rule = array_shift($rules)) { + $rules = []; // An empty rule interrupts the next ones + } + + if ($rule instanceof \Transliterator) { + $s = $rule->transliterate($s); + } elseif ($rule instanceof \Closure) { + $s = $rule($s); + } elseif ($rule) { + if ('nfd' === $rule = strtolower($rule)) { + normalizer_is_normalized($s, self::NFD) ?: $s = normalizer_normalize($s, self::NFD); + } elseif ('nfkd' === $rule) { + normalizer_is_normalized($s, self::NFKD) ?: $s = normalizer_normalize($s, self::NFKD); + } elseif ('[:nonspacing mark:] remove' === $rule) { + $s = preg_replace('/\p{Mn}++/u', '', $s); + } elseif ('latin-ascii' === $rule) { + $s = str_replace(self::TRANSLIT_FROM, self::TRANSLIT_TO, $s); + } elseif ('de-ascii' === $rule) { + $s = preg_replace("/([AUO])\u{0308}(?=\p{Ll})/u", '$1e', $s); + $s = str_replace(["a\u{0308}", "o\u{0308}", "u\u{0308}", "A\u{0308}", "O\u{0308}", "U\u{0308}"], ['ae', 'oe', 'ue', 'AE', 'OE', 'UE'], $s); + } elseif (\function_exists('transliterator_transliterate')) { + if (null === $transliterator = self::$transliterators[$rule] ?? self::$transliterators[$rule] = \Transliterator::create($rule)) { + if ('any-latin/bgn' === $rule) { + $rule = 'any-latin'; + $transliterator = self::$transliterators[$rule] ?? self::$transliterators[$rule] = \Transliterator::create($rule); + } + + if (null === $transliterator) { + throw new InvalidArgumentException(sprintf('Unknown transliteration rule "%s".', $rule)); + } + + self::$transliterators['any-latin/bgn'] = $transliterator; + } + + $s = $transliterator->transliterate($s); + } + } elseif (!\function_exists('iconv')) { + $s = preg_replace('/[^\x00-\x7F]/u', '?', $s); + } else { + $s = @preg_replace_callback('/[^\x00-\x7F]/u', static function ($c) { + $c = (string) iconv('UTF-8', 'ASCII//TRANSLIT', $c[0]); + + if ('' === $c && '' === iconv('UTF-8', 'ASCII//TRANSLIT', '²')) { + throw new \LogicException(sprintf('"%s" requires a translit-able iconv implementation, try installing "gnu-libiconv" if you\'re using Alpine Linux.', static::class)); + } + + return 1 < \strlen($c) ? ltrim($c, '\'`"^~') : ('' !== $c ? $c : '?'); + }, $s); + } + } + + $str->string .= $s; + + return $str; + } + + public function camel(): parent + { + $str = clone $this; + $str->string = str_replace(' ', '', preg_replace_callback('/\b.(?![A-Z]{2,})/u', static function ($m) use (&$i) { + return 1 === ++$i ? ('İ' === $m[0] ? 'i̇' : mb_strtolower($m[0], 'UTF-8')) : mb_convert_case($m[0], \MB_CASE_TITLE, 'UTF-8'); + }, preg_replace('/[^\pL0-9]++/u', ' ', $this->string))); + + return $str; + } + + /** + * @return int[] + */ + public function codePointsAt(int $offset): array + { + $str = $this->slice($offset, 1); + + if ('' === $str->string) { + return []; + } + + $codePoints = []; + + foreach (preg_split('//u', $str->string, -1, \PREG_SPLIT_NO_EMPTY) as $c) { + $codePoints[] = mb_ord($c, 'UTF-8'); + } + + return $codePoints; + } + + public function folded(bool $compat = true): parent + { + $str = clone $this; + + if (!$compat || \PHP_VERSION_ID < 70300 || !\defined('Normalizer::NFKC_CF')) { + $str->string = normalizer_normalize($str->string, $compat ? \Normalizer::NFKC : \Normalizer::NFC); + $str->string = mb_strtolower(str_replace(self::FOLD_FROM, self::FOLD_TO, $this->string), 'UTF-8'); + } else { + $str->string = normalizer_normalize($str->string, \Normalizer::NFKC_CF); + } + + return $str; + } + + public function join(array $strings, string $lastGlue = null): parent + { + $str = clone $this; + + $tail = null !== $lastGlue && 1 < \count($strings) ? $lastGlue.array_pop($strings) : ''; + $str->string = implode($this->string, $strings).$tail; + + if (!preg_match('//u', $str->string)) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + + return $str; + } + + public function lower(): parent + { + $str = clone $this; + $str->string = mb_strtolower(str_replace('İ', 'i̇', $str->string), 'UTF-8'); + + return $str; + } + + public function match(string $regexp, int $flags = 0, int $offset = 0): array + { + $match = ((\PREG_PATTERN_ORDER | \PREG_SET_ORDER) & $flags) ? 'preg_match_all' : 'preg_match'; + + if ($this->ignoreCase) { + $regexp .= 'i'; + } + + set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); }); + + try { + if (false === $match($regexp.'u', $this->string, $matches, $flags | \PREG_UNMATCHED_AS_NULL, $offset)) { + $lastError = preg_last_error(); + + foreach (get_defined_constants(true)['pcre'] as $k => $v) { + if ($lastError === $v && '_ERROR' === substr($k, -6)) { + throw new RuntimeException('Matching failed with '.$k.'.'); + } + } + + throw new RuntimeException('Matching failed with unknown error code.'); + } + } finally { + restore_error_handler(); + } + + return $matches; + } + + /** + * @return static + */ + public function normalize(int $form = self::NFC): self + { + if (!\in_array($form, [self::NFC, self::NFD, self::NFKC, self::NFKD])) { + throw new InvalidArgumentException('Unsupported normalization form.'); + } + + $str = clone $this; + normalizer_is_normalized($str->string, $form) ?: $str->string = normalizer_normalize($str->string, $form); + + return $str; + } + + public function padBoth(int $length, string $padStr = ' '): parent + { + if ('' === $padStr || !preg_match('//u', $padStr)) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + + $pad = clone $this; + $pad->string = $padStr; + + return $this->pad($length, $pad, \STR_PAD_BOTH); + } + + public function padEnd(int $length, string $padStr = ' '): parent + { + if ('' === $padStr || !preg_match('//u', $padStr)) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + + $pad = clone $this; + $pad->string = $padStr; + + return $this->pad($length, $pad, \STR_PAD_RIGHT); + } + + public function padStart(int $length, string $padStr = ' '): parent + { + if ('' === $padStr || !preg_match('//u', $padStr)) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + + $pad = clone $this; + $pad->string = $padStr; + + return $this->pad($length, $pad, \STR_PAD_LEFT); + } + + public function replaceMatches(string $fromRegexp, $to): parent + { + if ($this->ignoreCase) { + $fromRegexp .= 'i'; + } + + if (\is_array($to) || $to instanceof \Closure) { + if (!\is_callable($to)) { + throw new \TypeError(sprintf('Argument 2 passed to "%s::replaceMatches()" must be callable, array given.', static::class)); + } + + $replace = 'preg_replace_callback'; + $to = static function (array $m) use ($to): string { + $to = $to($m); + + if ('' !== $to && (!\is_string($to) || !preg_match('//u', $to))) { + throw new InvalidArgumentException('Replace callback must return a valid UTF-8 string.'); + } + + return $to; + }; + } elseif ('' !== $to && !preg_match('//u', $to)) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } else { + $replace = 'preg_replace'; + } + + set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); }); + + try { + if (null === $string = $replace($fromRegexp.'u', $to, $this->string)) { + $lastError = preg_last_error(); + + foreach (get_defined_constants(true)['pcre'] as $k => $v) { + if ($lastError === $v && '_ERROR' === substr($k, -6)) { + throw new RuntimeException('Matching failed with '.$k.'.'); + } + } + + throw new RuntimeException('Matching failed with unknown error code.'); + } + } finally { + restore_error_handler(); + } + + $str = clone $this; + $str->string = $string; + + return $str; + } + + public function reverse(): parent + { + $str = clone $this; + $str->string = implode('', array_reverse(preg_split('/(\X)/u', $str->string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY))); + + return $str; + } + + public function snake(): parent + { + $str = $this->camel(); + $str->string = mb_strtolower(preg_replace(['/(\p{Lu}+)(\p{Lu}\p{Ll})/u', '/([\p{Ll}0-9])(\p{Lu})/u'], '\1_\2', $str->string), 'UTF-8'); + + return $str; + } + + public function title(bool $allWords = false): parent + { + $str = clone $this; + + $limit = $allWords ? -1 : 1; + + $str->string = preg_replace_callback('/\b./u', static function (array $m): string { + return mb_convert_case($m[0], \MB_CASE_TITLE, 'UTF-8'); + }, $str->string, $limit); + + return $str; + } + + public function trim(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): parent + { + if (" \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}" !== $chars && !preg_match('//u', $chars)) { + throw new InvalidArgumentException('Invalid UTF-8 chars.'); + } + $chars = preg_quote($chars); + + $str = clone $this; + $str->string = preg_replace("{^[$chars]++|[$chars]++$}uD", '', $str->string); + + return $str; + } + + public function trimEnd(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): parent + { + if (" \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}" !== $chars && !preg_match('//u', $chars)) { + throw new InvalidArgumentException('Invalid UTF-8 chars.'); + } + $chars = preg_quote($chars); + + $str = clone $this; + $str->string = preg_replace("{[$chars]++$}uD", '', $str->string); + + return $str; + } + + public function trimPrefix($prefix): parent + { + if (!$this->ignoreCase) { + return parent::trimPrefix($prefix); + } + + $str = clone $this; + + if ($prefix instanceof \Traversable) { + $prefix = iterator_to_array($prefix, false); + } elseif ($prefix instanceof parent) { + $prefix = $prefix->string; + } + + $prefix = implode('|', array_map('preg_quote', (array) $prefix)); + $str->string = preg_replace("{^(?:$prefix)}iuD", '', $this->string); + + return $str; + } + + public function trimStart(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): parent + { + if (" \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}" !== $chars && !preg_match('//u', $chars)) { + throw new InvalidArgumentException('Invalid UTF-8 chars.'); + } + $chars = preg_quote($chars); + + $str = clone $this; + $str->string = preg_replace("{^[$chars]++}uD", '', $str->string); + + return $str; + } + + public function trimSuffix($suffix): parent + { + if (!$this->ignoreCase) { + return parent::trimSuffix($suffix); + } + + $str = clone $this; + + if ($suffix instanceof \Traversable) { + $suffix = iterator_to_array($suffix, false); + } elseif ($suffix instanceof parent) { + $suffix = $suffix->string; + } + + $suffix = implode('|', array_map('preg_quote', (array) $suffix)); + $str->string = preg_replace("{(?:$suffix)$}iuD", '', $this->string); + + return $str; + } + + public function upper(): parent + { + $str = clone $this; + $str->string = mb_strtoupper($str->string, 'UTF-8'); + + if (\PHP_VERSION_ID < 70300) { + $str->string = str_replace(self::UPPER_FROM, self::UPPER_TO, $str->string); + } + + return $str; + } + + public function width(bool $ignoreAnsiDecoration = true): int + { + $width = 0; + $s = str_replace(["\x00", "\x05", "\x07"], '', $this->string); + + if (false !== strpos($s, "\r")) { + $s = str_replace(["\r\n", "\r"], "\n", $s); + } + + if (!$ignoreAnsiDecoration) { + $s = preg_replace('/[\p{Cc}\x7F]++/u', '', $s); + } + + foreach (explode("\n", $s) as $s) { + if ($ignoreAnsiDecoration) { + $s = preg_replace('/(?:\x1B(?: + \[ [\x30-\x3F]*+ [\x20-\x2F]*+ [\x40-\x7E] + | [P\]X^_] .*? \x1B\\\\ + | [\x41-\x7E] + )|[\p{Cc}\x7F]++)/xu', '', $s); + } + + $lineWidth = $this->wcswidth($s); + + if ($lineWidth > $width) { + $width = $lineWidth; + } + } + + return $width; + } + + /** + * @return static + */ + private function pad(int $len, self $pad, int $type): parent + { + $sLen = $this->length(); + + if ($len <= $sLen) { + return clone $this; + } + + $padLen = $pad->length(); + $freeLen = $len - $sLen; + $len = $freeLen % $padLen; + + switch ($type) { + case \STR_PAD_RIGHT: + return $this->append(str_repeat($pad->string, intdiv($freeLen, $padLen)).($len ? $pad->slice(0, $len) : '')); + + case \STR_PAD_LEFT: + return $this->prepend(str_repeat($pad->string, intdiv($freeLen, $padLen)).($len ? $pad->slice(0, $len) : '')); + + case \STR_PAD_BOTH: + $freeLen /= 2; + + $rightLen = ceil($freeLen); + $len = $rightLen % $padLen; + $str = $this->append(str_repeat($pad->string, intdiv($rightLen, $padLen)).($len ? $pad->slice(0, $len) : '')); + + $leftLen = floor($freeLen); + $len = $leftLen % $padLen; + + return $str->prepend(str_repeat($pad->string, intdiv($leftLen, $padLen)).($len ? $pad->slice(0, $len) : '')); + + default: + throw new InvalidArgumentException('Invalid padding type.'); + } + } + + /** + * Based on https://github.com/jquast/wcwidth, a Python implementation of https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c. + */ + private function wcswidth(string $string): int + { + $width = 0; + + foreach (preg_split('//u', $string, -1, \PREG_SPLIT_NO_EMPTY) as $c) { + $codePoint = mb_ord($c, 'UTF-8'); + + if (0 === $codePoint // NULL + || 0x034F === $codePoint // COMBINING GRAPHEME JOINER + || (0x200B <= $codePoint && 0x200F >= $codePoint) // ZERO WIDTH SPACE to RIGHT-TO-LEFT MARK + || 0x2028 === $codePoint // LINE SEPARATOR + || 0x2029 === $codePoint // PARAGRAPH SEPARATOR + || (0x202A <= $codePoint && 0x202E >= $codePoint) // LEFT-TO-RIGHT EMBEDDING to RIGHT-TO-LEFT OVERRIDE + || (0x2060 <= $codePoint && 0x2063 >= $codePoint) // WORD JOINER to INVISIBLE SEPARATOR + ) { + continue; + } + + // Non printable characters + if (32 > $codePoint // C0 control characters + || (0x07F <= $codePoint && 0x0A0 > $codePoint) // C1 control characters and DEL + ) { + return -1; + } + + if (null === self::$tableZero) { + self::$tableZero = require __DIR__.'/Resources/data/wcswidth_table_zero.php'; + } + + if ($codePoint >= self::$tableZero[0][0] && $codePoint <= self::$tableZero[$ubound = \count(self::$tableZero) - 1][1]) { + $lbound = 0; + while ($ubound >= $lbound) { + $mid = floor(($lbound + $ubound) / 2); + + if ($codePoint > self::$tableZero[$mid][1]) { + $lbound = $mid + 1; + } elseif ($codePoint < self::$tableZero[$mid][0]) { + $ubound = $mid - 1; + } else { + continue 2; + } + } + } + + if (null === self::$tableWide) { + self::$tableWide = require __DIR__.'/Resources/data/wcswidth_table_wide.php'; + } + + if ($codePoint >= self::$tableWide[0][0] && $codePoint <= self::$tableWide[$ubound = \count(self::$tableWide) - 1][1]) { + $lbound = 0; + while ($ubound >= $lbound) { + $mid = floor(($lbound + $ubound) / 2); + + if ($codePoint > self::$tableWide[$mid][1]) { + $lbound = $mid + 1; + } elseif ($codePoint < self::$tableWide[$mid][0]) { + $ubound = $mid - 1; + } else { + $width += 2; + + continue 2; + } + } + } + + ++$width; + } + + return $width; + } +} diff --git a/vendor/symfony/string/ByteString.php b/vendor/symfony/string/ByteString.php new file mode 100644 index 0000000..626d8c1 --- /dev/null +++ b/vendor/symfony/string/ByteString.php @@ -0,0 +1,509 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String; + +use Symfony\Component\String\Exception\ExceptionInterface; +use Symfony\Component\String\Exception\InvalidArgumentException; +use Symfony\Component\String\Exception\RuntimeException; + +/** + * Represents a binary-safe string of bytes. + * + * @author Nicolas Grekas + * @author Hugo Hamon + * + * @throws ExceptionInterface + */ +class ByteString extends AbstractString +{ + private const ALPHABET_ALPHANUMERIC = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; + + public function __construct(string $string = '') + { + $this->string = $string; + } + + /* + * The following method was derived from code of the Hack Standard Library (v4.40 - 2020-05-03) + * + * https://github.com/hhvm/hsl/blob/80a42c02f036f72a42f0415e80d6b847f4bf62d5/src/random/private.php#L16 + * + * Code subject to the MIT license (https://github.com/hhvm/hsl/blob/master/LICENSE). + * + * Copyright (c) 2004-2020, Facebook, Inc. (https://www.facebook.com/) + */ + + public static function fromRandom(int $length = 16, string $alphabet = null): self + { + if ($length <= 0) { + throw new InvalidArgumentException(sprintf('A strictly positive length is expected, "%d" given.', $length)); + } + + $alphabet = $alphabet ?? self::ALPHABET_ALPHANUMERIC; + $alphabetSize = \strlen($alphabet); + $bits = (int) ceil(log($alphabetSize, 2.0)); + if ($bits <= 0 || $bits > 56) { + throw new InvalidArgumentException('The length of the alphabet must in the [2^1, 2^56] range.'); + } + + $ret = ''; + while ($length > 0) { + $urandomLength = (int) ceil(2 * $length * $bits / 8.0); + $data = random_bytes($urandomLength); + $unpackedData = 0; + $unpackedBits = 0; + for ($i = 0; $i < $urandomLength && $length > 0; ++$i) { + // Unpack 8 bits + $unpackedData = ($unpackedData << 8) | \ord($data[$i]); + $unpackedBits += 8; + + // While we have enough bits to select a character from the alphabet, keep + // consuming the random data + for (; $unpackedBits >= $bits && $length > 0; $unpackedBits -= $bits) { + $index = ($unpackedData & ((1 << $bits) - 1)); + $unpackedData >>= $bits; + // Unfortunately, the alphabet size is not necessarily a power of two. + // Worst case, it is 2^k + 1, which means we need (k+1) bits and we + // have around a 50% chance of missing as k gets larger + if ($index < $alphabetSize) { + $ret .= $alphabet[$index]; + --$length; + } + } + } + } + + return new static($ret); + } + + public function bytesAt(int $offset): array + { + $str = $this->string[$offset] ?? ''; + + return '' === $str ? [] : [\ord($str)]; + } + + public function append(string ...$suffix): parent + { + $str = clone $this; + $str->string .= 1 >= \count($suffix) ? ($suffix[0] ?? '') : implode('', $suffix); + + return $str; + } + + public function camel(): parent + { + $str = clone $this; + + $parts = explode(' ', trim(ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $this->string)))); + $parts[0] = 1 !== \strlen($parts[0]) && ctype_upper($parts[0]) ? $parts[0] : lcfirst($parts[0]); + $str->string = implode('', $parts); + + return $str; + } + + public function chunk(int $length = 1): array + { + if (1 > $length) { + throw new InvalidArgumentException('The chunk length must be greater than zero.'); + } + + if ('' === $this->string) { + return []; + } + + $str = clone $this; + $chunks = []; + + foreach (str_split($this->string, $length) as $chunk) { + $str->string = $chunk; + $chunks[] = clone $str; + } + + return $chunks; + } + + public function endsWith($suffix): bool + { + if ($suffix instanceof parent) { + $suffix = $suffix->string; + } elseif (\is_array($suffix) || $suffix instanceof \Traversable) { + return parent::endsWith($suffix); + } else { + $suffix = (string) $suffix; + } + + return '' !== $suffix && \strlen($this->string) >= \strlen($suffix) && 0 === substr_compare($this->string, $suffix, -\strlen($suffix), null, $this->ignoreCase); + } + + public function equalsTo($string): bool + { + if ($string instanceof parent) { + $string = $string->string; + } elseif (\is_array($string) || $string instanceof \Traversable) { + return parent::equalsTo($string); + } else { + $string = (string) $string; + } + + if ('' !== $string && $this->ignoreCase) { + return 0 === strcasecmp($string, $this->string); + } + + return $string === $this->string; + } + + public function folded(): parent + { + $str = clone $this; + $str->string = strtolower($str->string); + + return $str; + } + + public function indexOf($needle, int $offset = 0): ?int + { + if ($needle instanceof parent) { + $needle = $needle->string; + } elseif (\is_array($needle) || $needle instanceof \Traversable) { + return parent::indexOf($needle, $offset); + } else { + $needle = (string) $needle; + } + + if ('' === $needle) { + return null; + } + + $i = $this->ignoreCase ? stripos($this->string, $needle, $offset) : strpos($this->string, $needle, $offset); + + return false === $i ? null : $i; + } + + public function indexOfLast($needle, int $offset = 0): ?int + { + if ($needle instanceof parent) { + $needle = $needle->string; + } elseif (\is_array($needle) || $needle instanceof \Traversable) { + return parent::indexOfLast($needle, $offset); + } else { + $needle = (string) $needle; + } + + if ('' === $needle) { + return null; + } + + $i = $this->ignoreCase ? strripos($this->string, $needle, $offset) : strrpos($this->string, $needle, $offset); + + return false === $i ? null : $i; + } + + public function isUtf8(): bool + { + return '' === $this->string || preg_match('//u', $this->string); + } + + public function join(array $strings, string $lastGlue = null): parent + { + $str = clone $this; + + $tail = null !== $lastGlue && 1 < \count($strings) ? $lastGlue.array_pop($strings) : ''; + $str->string = implode($this->string, $strings).$tail; + + return $str; + } + + public function length(): int + { + return \strlen($this->string); + } + + public function lower(): parent + { + $str = clone $this; + $str->string = strtolower($str->string); + + return $str; + } + + public function match(string $regexp, int $flags = 0, int $offset = 0): array + { + $match = ((\PREG_PATTERN_ORDER | \PREG_SET_ORDER) & $flags) ? 'preg_match_all' : 'preg_match'; + + if ($this->ignoreCase) { + $regexp .= 'i'; + } + + set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); }); + + try { + if (false === $match($regexp, $this->string, $matches, $flags | \PREG_UNMATCHED_AS_NULL, $offset)) { + $lastError = preg_last_error(); + + foreach (get_defined_constants(true)['pcre'] as $k => $v) { + if ($lastError === $v && '_ERROR' === substr($k, -6)) { + throw new RuntimeException('Matching failed with '.$k.'.'); + } + } + + throw new RuntimeException('Matching failed with unknown error code.'); + } + } finally { + restore_error_handler(); + } + + return $matches; + } + + public function padBoth(int $length, string $padStr = ' '): parent + { + $str = clone $this; + $str->string = str_pad($this->string, $length, $padStr, \STR_PAD_BOTH); + + return $str; + } + + public function padEnd(int $length, string $padStr = ' '): parent + { + $str = clone $this; + $str->string = str_pad($this->string, $length, $padStr, \STR_PAD_RIGHT); + + return $str; + } + + public function padStart(int $length, string $padStr = ' '): parent + { + $str = clone $this; + $str->string = str_pad($this->string, $length, $padStr, \STR_PAD_LEFT); + + return $str; + } + + public function prepend(string ...$prefix): parent + { + $str = clone $this; + $str->string = (1 >= \count($prefix) ? ($prefix[0] ?? '') : implode('', $prefix)).$str->string; + + return $str; + } + + public function replace(string $from, string $to): parent + { + $str = clone $this; + + if ('' !== $from) { + $str->string = $this->ignoreCase ? str_ireplace($from, $to, $this->string) : str_replace($from, $to, $this->string); + } + + return $str; + } + + public function replaceMatches(string $fromRegexp, $to): parent + { + if ($this->ignoreCase) { + $fromRegexp .= 'i'; + } + + if (\is_array($to)) { + if (!\is_callable($to)) { + throw new \TypeError(sprintf('Argument 2 passed to "%s::replaceMatches()" must be callable, array given.', static::class)); + } + + $replace = 'preg_replace_callback'; + } else { + $replace = $to instanceof \Closure ? 'preg_replace_callback' : 'preg_replace'; + } + + set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); }); + + try { + if (null === $string = $replace($fromRegexp, $to, $this->string)) { + $lastError = preg_last_error(); + + foreach (get_defined_constants(true)['pcre'] as $k => $v) { + if ($lastError === $v && '_ERROR' === substr($k, -6)) { + throw new RuntimeException('Matching failed with '.$k.'.'); + } + } + + throw new RuntimeException('Matching failed with unknown error code.'); + } + } finally { + restore_error_handler(); + } + + $str = clone $this; + $str->string = $string; + + return $str; + } + + public function reverse(): parent + { + $str = clone $this; + $str->string = strrev($str->string); + + return $str; + } + + public function slice(int $start = 0, int $length = null): parent + { + $str = clone $this; + $str->string = (string) substr($this->string, $start, $length ?? \PHP_INT_MAX); + + return $str; + } + + public function snake(): parent + { + $str = $this->camel(); + $str->string = strtolower(preg_replace(['/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'], '\1_\2', $str->string)); + + return $str; + } + + public function splice(string $replacement, int $start = 0, int $length = null): parent + { + $str = clone $this; + $str->string = substr_replace($this->string, $replacement, $start, $length ?? \PHP_INT_MAX); + + return $str; + } + + public function split(string $delimiter, int $limit = null, int $flags = null): array + { + if (1 > $limit = $limit ?? \PHP_INT_MAX) { + throw new InvalidArgumentException('Split limit must be a positive integer.'); + } + + if ('' === $delimiter) { + throw new InvalidArgumentException('Split delimiter is empty.'); + } + + if (null !== $flags) { + return parent::split($delimiter, $limit, $flags); + } + + $str = clone $this; + $chunks = $this->ignoreCase + ? preg_split('{'.preg_quote($delimiter).'}iD', $this->string, $limit) + : explode($delimiter, $this->string, $limit); + + foreach ($chunks as &$chunk) { + $str->string = $chunk; + $chunk = clone $str; + } + + return $chunks; + } + + public function startsWith($prefix): bool + { + if ($prefix instanceof parent) { + $prefix = $prefix->string; + } elseif (!\is_string($prefix)) { + return parent::startsWith($prefix); + } + + return '' !== $prefix && 0 === ($this->ignoreCase ? strncasecmp($this->string, $prefix, \strlen($prefix)) : strncmp($this->string, $prefix, \strlen($prefix))); + } + + public function title(bool $allWords = false): parent + { + $str = clone $this; + $str->string = $allWords ? ucwords($str->string) : ucfirst($str->string); + + return $str; + } + + public function toUnicodeString(string $fromEncoding = null): UnicodeString + { + return new UnicodeString($this->toCodePointString($fromEncoding)->string); + } + + public function toCodePointString(string $fromEncoding = null): CodePointString + { + $u = new CodePointString(); + + if (\in_array($fromEncoding, [null, 'utf8', 'utf-8', 'UTF8', 'UTF-8'], true) && preg_match('//u', $this->string)) { + $u->string = $this->string; + + return $u; + } + + set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); }); + + try { + try { + $validEncoding = false !== mb_detect_encoding($this->string, $fromEncoding ?? 'Windows-1252', true); + } catch (InvalidArgumentException $e) { + if (!\function_exists('iconv')) { + throw $e; + } + + $u->string = iconv($fromEncoding ?? 'Windows-1252', 'UTF-8', $this->string); + + return $u; + } + } finally { + restore_error_handler(); + } + + if (!$validEncoding) { + throw new InvalidArgumentException(sprintf('Invalid "%s" string.', $fromEncoding ?? 'Windows-1252')); + } + + $u->string = mb_convert_encoding($this->string, 'UTF-8', $fromEncoding ?? 'Windows-1252'); + + return $u; + } + + public function trim(string $chars = " \t\n\r\0\x0B\x0C"): parent + { + $str = clone $this; + $str->string = trim($str->string, $chars); + + return $str; + } + + public function trimEnd(string $chars = " \t\n\r\0\x0B\x0C"): parent + { + $str = clone $this; + $str->string = rtrim($str->string, $chars); + + return $str; + } + + public function trimStart(string $chars = " \t\n\r\0\x0B\x0C"): parent + { + $str = clone $this; + $str->string = ltrim($str->string, $chars); + + return $str; + } + + public function upper(): parent + { + $str = clone $this; + $str->string = strtoupper($str->string); + + return $str; + } + + public function width(bool $ignoreAnsiDecoration = true): int + { + $string = preg_match('//u', $this->string) ? $this->string : preg_replace('/[\x80-\xFF]/', '?', $this->string); + + return (new CodePointString($string))->width($ignoreAnsiDecoration); + } +} diff --git a/vendor/symfony/string/CHANGELOG.md b/vendor/symfony/string/CHANGELOG.md new file mode 100644 index 0000000..53af364 --- /dev/null +++ b/vendor/symfony/string/CHANGELOG.md @@ -0,0 +1,35 @@ +CHANGELOG +========= + +5.4 +--- + + * Add `trimSuffix()` and `trimPrefix()` methods + +5.3 +--- + + * Made `AsciiSlugger` fallback to parent locale's symbolsMap + +5.2.0 +----- + + * added a `FrenchInflector` class + +5.1.0 +----- + + * added the `AbstractString::reverse()` method + * made `AbstractString::width()` follow POSIX.1-2001 + * added `LazyString` which provides memoizing stringable objects + * The component is not marked as `@experimental` anymore + * added the `s()` helper method to get either an `UnicodeString` or `ByteString` instance, + depending of the input string UTF-8 compliancy + * added `$cut` parameter to `Symfony\Component\String\AbstractString::truncate()` + * added `AbstractString::containsAny()` + * allow passing a string of custom characters to `ByteString::fromRandom()` + +5.0.0 +----- + + * added the component as experimental diff --git a/vendor/symfony/string/CodePointString.php b/vendor/symfony/string/CodePointString.php new file mode 100644 index 0000000..8ab9209 --- /dev/null +++ b/vendor/symfony/string/CodePointString.php @@ -0,0 +1,270 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String; + +use Symfony\Component\String\Exception\ExceptionInterface; +use Symfony\Component\String\Exception\InvalidArgumentException; + +/** + * Represents a string of Unicode code points encoded as UTF-8. + * + * @author Nicolas Grekas + * @author Hugo Hamon + * + * @throws ExceptionInterface + */ +class CodePointString extends AbstractUnicodeString +{ + public function __construct(string $string = '') + { + if ('' !== $string && !preg_match('//u', $string)) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + + $this->string = $string; + } + + public function append(string ...$suffix): AbstractString + { + $str = clone $this; + $str->string .= 1 >= \count($suffix) ? ($suffix[0] ?? '') : implode('', $suffix); + + if (!preg_match('//u', $str->string)) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + + return $str; + } + + public function chunk(int $length = 1): array + { + if (1 > $length) { + throw new InvalidArgumentException('The chunk length must be greater than zero.'); + } + + if ('' === $this->string) { + return []; + } + + $rx = '/('; + while (65535 < $length) { + $rx .= '.{65535}'; + $length -= 65535; + } + $rx .= '.{'.$length.'})/us'; + + $str = clone $this; + $chunks = []; + + foreach (preg_split($rx, $this->string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY) as $chunk) { + $str->string = $chunk; + $chunks[] = clone $str; + } + + return $chunks; + } + + public function codePointsAt(int $offset): array + { + $str = $offset ? $this->slice($offset, 1) : $this; + + return '' === $str->string ? [] : [mb_ord($str->string, 'UTF-8')]; + } + + public function endsWith($suffix): bool + { + if ($suffix instanceof AbstractString) { + $suffix = $suffix->string; + } elseif (\is_array($suffix) || $suffix instanceof \Traversable) { + return parent::endsWith($suffix); + } else { + $suffix = (string) $suffix; + } + + if ('' === $suffix || !preg_match('//u', $suffix)) { + return false; + } + + if ($this->ignoreCase) { + return preg_match('{'.preg_quote($suffix).'$}iuD', $this->string); + } + + return \strlen($this->string) >= \strlen($suffix) && 0 === substr_compare($this->string, $suffix, -\strlen($suffix)); + } + + public function equalsTo($string): bool + { + if ($string instanceof AbstractString) { + $string = $string->string; + } elseif (\is_array($string) || $string instanceof \Traversable) { + return parent::equalsTo($string); + } else { + $string = (string) $string; + } + + if ('' !== $string && $this->ignoreCase) { + return \strlen($string) === \strlen($this->string) && 0 === mb_stripos($this->string, $string, 0, 'UTF-8'); + } + + return $string === $this->string; + } + + public function indexOf($needle, int $offset = 0): ?int + { + if ($needle instanceof AbstractString) { + $needle = $needle->string; + } elseif (\is_array($needle) || $needle instanceof \Traversable) { + return parent::indexOf($needle, $offset); + } else { + $needle = (string) $needle; + } + + if ('' === $needle) { + return null; + } + + $i = $this->ignoreCase ? mb_stripos($this->string, $needle, $offset, 'UTF-8') : mb_strpos($this->string, $needle, $offset, 'UTF-8'); + + return false === $i ? null : $i; + } + + public function indexOfLast($needle, int $offset = 0): ?int + { + if ($needle instanceof AbstractString) { + $needle = $needle->string; + } elseif (\is_array($needle) || $needle instanceof \Traversable) { + return parent::indexOfLast($needle, $offset); + } else { + $needle = (string) $needle; + } + + if ('' === $needle) { + return null; + } + + $i = $this->ignoreCase ? mb_strripos($this->string, $needle, $offset, 'UTF-8') : mb_strrpos($this->string, $needle, $offset, 'UTF-8'); + + return false === $i ? null : $i; + } + + public function length(): int + { + return mb_strlen($this->string, 'UTF-8'); + } + + public function prepend(string ...$prefix): AbstractString + { + $str = clone $this; + $str->string = (1 >= \count($prefix) ? ($prefix[0] ?? '') : implode('', $prefix)).$this->string; + + if (!preg_match('//u', $str->string)) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + + return $str; + } + + public function replace(string $from, string $to): AbstractString + { + $str = clone $this; + + if ('' === $from || !preg_match('//u', $from)) { + return $str; + } + + if ('' !== $to && !preg_match('//u', $to)) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + + if ($this->ignoreCase) { + $str->string = implode($to, preg_split('{'.preg_quote($from).'}iuD', $this->string)); + } else { + $str->string = str_replace($from, $to, $this->string); + } + + return $str; + } + + public function slice(int $start = 0, int $length = null): AbstractString + { + $str = clone $this; + $str->string = mb_substr($this->string, $start, $length, 'UTF-8'); + + return $str; + } + + public function splice(string $replacement, int $start = 0, int $length = null): AbstractString + { + if (!preg_match('//u', $replacement)) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + + $str = clone $this; + $start = $start ? \strlen(mb_substr($this->string, 0, $start, 'UTF-8')) : 0; + $length = $length ? \strlen(mb_substr($this->string, $start, $length, 'UTF-8')) : $length; + $str->string = substr_replace($this->string, $replacement, $start, $length ?? \PHP_INT_MAX); + + return $str; + } + + public function split(string $delimiter, int $limit = null, int $flags = null): array + { + if (1 > $limit = $limit ?? \PHP_INT_MAX) { + throw new InvalidArgumentException('Split limit must be a positive integer.'); + } + + if ('' === $delimiter) { + throw new InvalidArgumentException('Split delimiter is empty.'); + } + + if (null !== $flags) { + return parent::split($delimiter.'u', $limit, $flags); + } + + if (!preg_match('//u', $delimiter)) { + throw new InvalidArgumentException('Split delimiter is not a valid UTF-8 string.'); + } + + $str = clone $this; + $chunks = $this->ignoreCase + ? preg_split('{'.preg_quote($delimiter).'}iuD', $this->string, $limit) + : explode($delimiter, $this->string, $limit); + + foreach ($chunks as &$chunk) { + $str->string = $chunk; + $chunk = clone $str; + } + + return $chunks; + } + + public function startsWith($prefix): bool + { + if ($prefix instanceof AbstractString) { + $prefix = $prefix->string; + } elseif (\is_array($prefix) || $prefix instanceof \Traversable) { + return parent::startsWith($prefix); + } else { + $prefix = (string) $prefix; + } + + if ('' === $prefix || !preg_match('//u', $prefix)) { + return false; + } + + if ($this->ignoreCase) { + return 0 === mb_stripos($this->string, $prefix, 0, 'UTF-8'); + } + + return 0 === strncmp($this->string, $prefix, \strlen($prefix)); + } +} diff --git a/vendor/symfony/string/Exception/ExceptionInterface.php b/vendor/symfony/string/Exception/ExceptionInterface.php new file mode 100644 index 0000000..3619786 --- /dev/null +++ b/vendor/symfony/string/Exception/ExceptionInterface.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String\Exception; + +interface ExceptionInterface extends \Throwable +{ +} diff --git a/vendor/symfony/string/Exception/InvalidArgumentException.php b/vendor/symfony/string/Exception/InvalidArgumentException.php new file mode 100644 index 0000000..6aa586b --- /dev/null +++ b/vendor/symfony/string/Exception/InvalidArgumentException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String\Exception; + +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/string/Exception/RuntimeException.php b/vendor/symfony/string/Exception/RuntimeException.php new file mode 100644 index 0000000..77cb091 --- /dev/null +++ b/vendor/symfony/string/Exception/RuntimeException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String\Exception; + +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/string/Inflector/EnglishInflector.php b/vendor/symfony/string/Inflector/EnglishInflector.php new file mode 100644 index 0000000..edd94db --- /dev/null +++ b/vendor/symfony/string/Inflector/EnglishInflector.php @@ -0,0 +1,517 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String\Inflector; + +final class EnglishInflector implements InflectorInterface +{ + /** + * Map English plural to singular suffixes. + * + * @see http://english-zone.com/spelling/plurals.html + */ + private const PLURAL_MAP = [ + // First entry: plural suffix, reversed + // Second entry: length of plural suffix + // Third entry: Whether the suffix may succeed a vocal + // Fourth entry: Whether the suffix may succeed a consonant + // Fifth entry: singular suffix, normal + + // bacteria (bacterium), criteria (criterion), phenomena (phenomenon) + ['a', 1, true, true, ['on', 'um']], + + // nebulae (nebula) + ['ea', 2, true, true, 'a'], + + // services (service) + ['secivres', 8, true, true, 'service'], + + // mice (mouse), lice (louse) + ['eci', 3, false, true, 'ouse'], + + // geese (goose) + ['esee', 4, false, true, 'oose'], + + // fungi (fungus), alumni (alumnus), syllabi (syllabus), radii (radius) + ['i', 1, true, true, 'us'], + + // men (man), women (woman) + ['nem', 3, true, true, 'man'], + + // children (child) + ['nerdlihc', 8, true, true, 'child'], + + // oxen (ox) + ['nexo', 4, false, false, 'ox'], + + // indices (index), appendices (appendix), prices (price) + ['seci', 4, false, true, ['ex', 'ix', 'ice']], + + // codes (code) + ['sedoc', 5, false, true, 'code'], + + // selfies (selfie) + ['seifles', 7, true, true, 'selfie'], + + // zombies (zombie) + ['seibmoz', 7, true, true, 'zombie'], + + // movies (movie) + ['seivom', 6, true, true, 'movie'], + + // names (name) + ['seman', 5, true, false, 'name'], + + // conspectuses (conspectus), prospectuses (prospectus) + ['sesutcep', 8, true, true, 'pectus'], + + // feet (foot) + ['teef', 4, true, true, 'foot'], + + // geese (goose) + ['eseeg', 5, true, true, 'goose'], + + // teeth (tooth) + ['hteet', 5, true, true, 'tooth'], + + // news (news) + ['swen', 4, true, true, 'news'], + + // series (series) + ['seires', 6, true, true, 'series'], + + // babies (baby) + ['sei', 3, false, true, 'y'], + + // accesses (access), addresses (address), kisses (kiss) + ['sess', 4, true, false, 'ss'], + + // analyses (analysis), ellipses (ellipsis), fungi (fungus), + // neuroses (neurosis), theses (thesis), emphases (emphasis), + // oases (oasis), crises (crisis), houses (house), bases (base), + // atlases (atlas) + ['ses', 3, true, true, ['s', 'se', 'sis']], + + // objectives (objective), alternative (alternatives) + ['sevit', 5, true, true, 'tive'], + + // drives (drive) + ['sevird', 6, false, true, 'drive'], + + // lives (life), wives (wife) + ['sevi', 4, false, true, 'ife'], + + // moves (move) + ['sevom', 5, true, true, 'move'], + + // hooves (hoof), dwarves (dwarf), elves (elf), leaves (leaf), caves (cave), staves (staff) + ['sev', 3, true, true, ['f', 've', 'ff']], + + // axes (axis), axes (ax), axes (axe) + ['sexa', 4, false, false, ['ax', 'axe', 'axis']], + + // indexes (index), matrixes (matrix) + ['sex', 3, true, false, 'x'], + + // quizzes (quiz) + ['sezz', 4, true, false, 'z'], + + // bureaus (bureau) + ['suae', 4, false, true, 'eau'], + + // fees (fee), trees (tree), employees (employee) + ['see', 3, true, true, 'ee'], + + // edges (edge) + ['segd', 4, true, true, 'dge'], + + // roses (rose), garages (garage), cassettes (cassette), + // waltzes (waltz), heroes (hero), bushes (bush), arches (arch), + // shoes (shoe) + ['se', 2, true, true, ['', 'e']], + + // tags (tag) + ['s', 1, true, true, ''], + + // chateaux (chateau) + ['xuae', 4, false, true, 'eau'], + + // people (person) + ['elpoep', 6, true, true, 'person'], + ]; + + /** + * Map English singular to plural suffixes. + * + * @see http://english-zone.com/spelling/plurals.html + */ + private const SINGULAR_MAP = [ + // First entry: singular suffix, reversed + // Second entry: length of singular suffix + // Third entry: Whether the suffix may succeed a vocal + // Fourth entry: Whether the suffix may succeed a consonant + // Fifth entry: plural suffix, normal + + // criterion (criteria) + ['airetirc', 8, false, false, 'criterion'], + + // nebulae (nebula) + ['aluben', 6, false, false, 'nebulae'], + + // children (child) + ['dlihc', 5, true, true, 'children'], + + // prices (price) + ['eci', 3, false, true, 'ices'], + + // services (service) + ['ecivres', 7, true, true, 'services'], + + // lives (life), wives (wife) + ['efi', 3, false, true, 'ives'], + + // selfies (selfie) + ['eifles', 6, true, true, 'selfies'], + + // movies (movie) + ['eivom', 5, true, true, 'movies'], + + // lice (louse) + ['esuol', 5, false, true, 'lice'], + + // mice (mouse) + ['esuom', 5, false, true, 'mice'], + + // geese (goose) + ['esoo', 4, false, true, 'eese'], + + // houses (house), bases (base) + ['es', 2, true, true, 'ses'], + + // geese (goose) + ['esoog', 5, true, true, 'geese'], + + // caves (cave) + ['ev', 2, true, true, 'ves'], + + // drives (drive) + ['evird', 5, false, true, 'drives'], + + // objectives (objective), alternative (alternatives) + ['evit', 4, true, true, 'tives'], + + // moves (move) + ['evom', 4, true, true, 'moves'], + + // staves (staff) + ['ffats', 5, true, true, 'staves'], + + // hooves (hoof), dwarves (dwarf), elves (elf), leaves (leaf) + ['ff', 2, true, true, 'ffs'], + + // hooves (hoof), dwarves (dwarf), elves (elf), leaves (leaf) + ['f', 1, true, true, ['fs', 'ves']], + + // arches (arch) + ['hc', 2, true, true, 'ches'], + + // bushes (bush) + ['hs', 2, true, true, 'shes'], + + // teeth (tooth) + ['htoot', 5, true, true, 'teeth'], + + // bacteria (bacterium), criteria (criterion), phenomena (phenomenon) + ['mu', 2, true, true, 'a'], + + // men (man), women (woman) + ['nam', 3, true, true, 'men'], + + // people (person) + ['nosrep', 6, true, true, ['persons', 'people']], + + // bacteria (bacterium), criteria (criterion), phenomena (phenomenon) + ['noi', 3, true, true, 'ions'], + + // coupon (coupons) + ['nop', 3, true, true, 'pons'], + + // seasons (season), treasons (treason), poisons (poison), lessons (lesson) + ['nos', 3, true, true, 'sons'], + + // bacteria (bacterium), criteria (criterion), phenomena (phenomenon) + ['no', 2, true, true, 'a'], + + // echoes (echo) + ['ohce', 4, true, true, 'echoes'], + + // heroes (hero) + ['oreh', 4, true, true, 'heroes'], + + // atlases (atlas) + ['salta', 5, true, true, 'atlases'], + + // irises (iris) + ['siri', 4, true, true, 'irises'], + + // analyses (analysis), ellipses (ellipsis), neuroses (neurosis) + // theses (thesis), emphases (emphasis), oases (oasis), + // crises (crisis) + ['sis', 3, true, true, 'ses'], + + // accesses (access), addresses (address), kisses (kiss) + ['ss', 2, true, false, 'sses'], + + // syllabi (syllabus) + ['suballys', 8, true, true, 'syllabi'], + + // buses (bus) + ['sub', 3, true, true, 'buses'], + + // circuses (circus) + ['suc', 3, true, true, 'cuses'], + + // conspectuses (conspectus), prospectuses (prospectus) + ['sutcep', 6, true, true, 'pectuses'], + + // fungi (fungus), alumni (alumnus), syllabi (syllabus), radii (radius) + ['su', 2, true, true, 'i'], + + // news (news) + ['swen', 4, true, true, 'news'], + + // feet (foot) + ['toof', 4, true, true, 'feet'], + + // chateaux (chateau), bureaus (bureau) + ['uae', 3, false, true, ['eaus', 'eaux']], + + // oxen (ox) + ['xo', 2, false, false, 'oxen'], + + // hoaxes (hoax) + ['xaoh', 4, true, false, 'hoaxes'], + + // indices (index) + ['xedni', 5, false, true, ['indicies', 'indexes']], + + // boxes (box) + ['xo', 2, false, true, 'oxes'], + + // indexes (index), matrixes (matrix) + ['x', 1, true, false, ['cies', 'xes']], + + // appendices (appendix) + ['xi', 2, false, true, 'ices'], + + // babies (baby) + ['y', 1, false, true, 'ies'], + + // quizzes (quiz) + ['ziuq', 4, true, false, 'quizzes'], + + // waltzes (waltz) + ['z', 1, true, true, 'zes'], + ]; + + /** + * A list of words which should not be inflected, reversed. + */ + private const UNINFLECTED = [ + '', + + // data + 'atad', + + // deer + 'reed', + + // feedback + 'kcabdeef', + + // fish + 'hsif', + + // info + 'ofni', + + // moose + 'esoom', + + // series + 'seires', + + // sheep + 'peehs', + + // species + 'seiceps', + ]; + + /** + * {@inheritdoc} + */ + public function singularize(string $plural): array + { + $pluralRev = strrev($plural); + $lowerPluralRev = strtolower($pluralRev); + $pluralLength = \strlen($lowerPluralRev); + + // Check if the word is one which is not inflected, return early if so + if (\in_array($lowerPluralRev, self::UNINFLECTED, true)) { + return [$plural]; + } + + // The outer loop iterates over the entries of the plural table + // The inner loop $j iterates over the characters of the plural suffix + // in the plural table to compare them with the characters of the actual + // given plural suffix + foreach (self::PLURAL_MAP as $map) { + $suffix = $map[0]; + $suffixLength = $map[1]; + $j = 0; + + // Compare characters in the plural table and of the suffix of the + // given plural one by one + while ($suffix[$j] === $lowerPluralRev[$j]) { + // Let $j point to the next character + ++$j; + + // Successfully compared the last character + // Add an entry with the singular suffix to the singular array + if ($j === $suffixLength) { + // Is there any character preceding the suffix in the plural string? + if ($j < $pluralLength) { + $nextIsVocal = false !== strpos('aeiou', $lowerPluralRev[$j]); + + if (!$map[2] && $nextIsVocal) { + // suffix may not succeed a vocal but next char is one + break; + } + + if (!$map[3] && !$nextIsVocal) { + // suffix may not succeed a consonant but next char is one + break; + } + } + + $newBase = substr($plural, 0, $pluralLength - $suffixLength); + $newSuffix = $map[4]; + + // Check whether the first character in the plural suffix + // is uppercased. If yes, uppercase the first character in + // the singular suffix too + $firstUpper = ctype_upper($pluralRev[$j - 1]); + + if (\is_array($newSuffix)) { + $singulars = []; + + foreach ($newSuffix as $newSuffixEntry) { + $singulars[] = $newBase.($firstUpper ? ucfirst($newSuffixEntry) : $newSuffixEntry); + } + + return $singulars; + } + + return [$newBase.($firstUpper ? ucfirst($newSuffix) : $newSuffix)]; + } + + // Suffix is longer than word + if ($j === $pluralLength) { + break; + } + } + } + + // Assume that plural and singular is identical + return [$plural]; + } + + /** + * {@inheritdoc} + */ + public function pluralize(string $singular): array + { + $singularRev = strrev($singular); + $lowerSingularRev = strtolower($singularRev); + $singularLength = \strlen($lowerSingularRev); + + // Check if the word is one which is not inflected, return early if so + if (\in_array($lowerSingularRev, self::UNINFLECTED, true)) { + return [$singular]; + } + + // The outer loop iterates over the entries of the singular table + // The inner loop $j iterates over the characters of the singular suffix + // in the singular table to compare them with the characters of the actual + // given singular suffix + foreach (self::SINGULAR_MAP as $map) { + $suffix = $map[0]; + $suffixLength = $map[1]; + $j = 0; + + // Compare characters in the singular table and of the suffix of the + // given plural one by one + + while ($suffix[$j] === $lowerSingularRev[$j]) { + // Let $j point to the next character + ++$j; + + // Successfully compared the last character + // Add an entry with the plural suffix to the plural array + if ($j === $suffixLength) { + // Is there any character preceding the suffix in the plural string? + if ($j < $singularLength) { + $nextIsVocal = false !== strpos('aeiou', $lowerSingularRev[$j]); + + if (!$map[2] && $nextIsVocal) { + // suffix may not succeed a vocal but next char is one + break; + } + + if (!$map[3] && !$nextIsVocal) { + // suffix may not succeed a consonant but next char is one + break; + } + } + + $newBase = substr($singular, 0, $singularLength - $suffixLength); + $newSuffix = $map[4]; + + // Check whether the first character in the singular suffix + // is uppercased. If yes, uppercase the first character in + // the singular suffix too + $firstUpper = ctype_upper($singularRev[$j - 1]); + + if (\is_array($newSuffix)) { + $plurals = []; + + foreach ($newSuffix as $newSuffixEntry) { + $plurals[] = $newBase.($firstUpper ? ucfirst($newSuffixEntry) : $newSuffixEntry); + } + + return $plurals; + } + + return [$newBase.($firstUpper ? ucfirst($newSuffix) : $newSuffix)]; + } + + // Suffix is longer than word + if ($j === $singularLength) { + break; + } + } + } + + // Assume that plural is singular with a trailing `s` + return [$singular.'s']; + } +} diff --git a/vendor/symfony/string/Inflector/FrenchInflector.php b/vendor/symfony/string/Inflector/FrenchInflector.php new file mode 100644 index 0000000..612c8f2 --- /dev/null +++ b/vendor/symfony/string/Inflector/FrenchInflector.php @@ -0,0 +1,157 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String\Inflector; + +/** + * French inflector. + * + * This class does only inflect nouns; not adjectives nor composed words like "soixante-dix". + */ +final class FrenchInflector implements InflectorInterface +{ + /** + * A list of all rules for pluralise. + * + * @see https://la-conjugaison.nouvelobs.com/regles/grammaire/le-pluriel-des-noms-121.php + */ + private const PLURALIZE_REGEXP = [ + // First entry: regexp + // Second entry: replacement + + // Words finishing with "s", "x" or "z" are invariables + // Les mots finissant par "s", "x" ou "z" sont invariables + ['/(s|x|z)$/i', '\1'], + + // Words finishing with "eau" are pluralized with a "x" + // Les mots finissant par "eau" prennent tous un "x" au pluriel + ['/(eau)$/i', '\1x'], + + // Words finishing with "au" are pluralized with a "x" excepted "landau" + // Les mots finissant par "au" prennent un "x" au pluriel sauf "landau" + ['/^(landau)$/i', '\1s'], + ['/(au)$/i', '\1x'], + + // Words finishing with "eu" are pluralized with a "x" excepted "pneu", "bleu", "émeu" + // Les mots finissant en "eu" prennent un "x" au pluriel sauf "pneu", "bleu", "émeu" + ['/^(pneu|bleu|émeu)$/i', '\1s'], + ['/(eu)$/i', '\1x'], + + // Words finishing with "al" are pluralized with a "aux" excepted + // Les mots finissant en "al" se terminent en "aux" sauf + ['/^(bal|carnaval|caracal|chacal|choral|corral|étal|festival|récital|val)$/i', '\1s'], + ['/al$/i', '\1aux'], + + // Aspirail, bail, corail, émail, fermail, soupirail, travail, vantail et vitrail font leur pluriel en -aux + ['/^(aspir|b|cor|ém|ferm|soupir|trav|vant|vitr)ail$/i', '\1aux'], + + // Bijou, caillou, chou, genou, hibou, joujou et pou qui prennent un x au pluriel + ['/^(bij|caill|ch|gen|hib|jouj|p)ou$/i', '\1oux'], + + // Invariable words + ['/^(cinquante|soixante|mille)$/i', '\1'], + + // French titles + ['/^(mon|ma)(sieur|dame|demoiselle|seigneur)$/', 'mes\2s'], + ['/^(Mon|Ma)(sieur|dame|demoiselle|seigneur)$/', 'Mes\2s'], + ]; + + /** + * A list of all rules for singularize. + */ + private const SINGULARIZE_REGEXP = [ + // First entry: regexp + // Second entry: replacement + + // Aspirail, bail, corail, émail, fermail, soupirail, travail, vantail et vitrail font leur pluriel en -aux + ['/((aspir|b|cor|ém|ferm|soupir|trav|vant|vitr))aux$/i', '\1ail'], + + // Words finishing with "eau" are pluralized with a "x" + // Les mots finissant par "eau" prennent tous un "x" au pluriel + ['/(eau)x$/i', '\1'], + + // Words finishing with "al" are pluralized with a "aux" expected + // Les mots finissant en "al" se terminent en "aux" sauf + ['/(amir|anim|arsen|boc|can|capit|capor|chev|crist|génér|hopit|hôpit|idé|journ|littor|loc|m|mét|minér|princip|radic|termin)aux$/i', '\1al'], + + // Words finishing with "au" are pluralized with a "x" excepted "landau" + // Les mots finissant par "au" prennent un "x" au pluriel sauf "landau" + ['/(au)x$/i', '\1'], + + // Words finishing with "eu" are pluralized with a "x" excepted "pneu", "bleu", "émeu" + // Les mots finissant en "eu" prennent un "x" au pluriel sauf "pneu", "bleu", "émeu" + ['/(eu)x$/i', '\1'], + + // Words finishing with "ou" are pluralized with a "s" excepted bijou, caillou, chou, genou, hibou, joujou, pou + // Les mots finissant par "ou" prennent un "s" sauf bijou, caillou, chou, genou, hibou, joujou, pou + ['/(bij|caill|ch|gen|hib|jouj|p)oux$/i', '\1ou'], + + // French titles + ['/^mes(dame|demoiselle)s$/', 'ma\1'], + ['/^Mes(dame|demoiselle)s$/', 'Ma\1'], + ['/^mes(sieur|seigneur)s$/', 'mon\1'], + ['/^Mes(sieur|seigneur)s$/', 'Mon\1'], + + // Default rule + ['/s$/i', ''], + ]; + + /** + * A list of words which should not be inflected. + * This list is only used by singularize. + */ + private const UNINFLECTED = '/^(abcès|accès|abus|albatros|anchois|anglais|autobus|bois|brebis|carquois|cas|chas|colis|concours|corps|cours|cyprès|décès|devis|discours|dos|embarras|engrais|entrelacs|excès|fils|fois|gâchis|gars|glas|héros|intrus|jars|jus|kermès|lacis|legs|lilas|marais|mars|matelas|mépris|mets|mois|mors|obus|os|palais|paradis|parcours|pardessus|pays|plusieurs|poids|pois|pouls|printemps|processus|progrès|puits|pus|rabais|radis|recors|recours|refus|relais|remords|remous|rictus|rhinocéros|repas|rubis|sans|sas|secours|sens|souris|succès|talus|tapis|tas|taudis|temps|tiers|univers|velours|verglas|vernis|virus)$/i'; + + /** + * {@inheritdoc} + */ + public function singularize(string $plural): array + { + if ($this->isInflectedWord($plural)) { + return [$plural]; + } + + foreach (self::SINGULARIZE_REGEXP as $rule) { + [$regexp, $replace] = $rule; + + if (1 === preg_match($regexp, $plural)) { + return [preg_replace($regexp, $replace, $plural)]; + } + } + + return [$plural]; + } + + /** + * {@inheritdoc} + */ + public function pluralize(string $singular): array + { + if ($this->isInflectedWord($singular)) { + return [$singular]; + } + + foreach (self::PLURALIZE_REGEXP as $rule) { + [$regexp, $replace] = $rule; + + if (1 === preg_match($regexp, $singular)) { + return [preg_replace($regexp, $replace, $singular)]; + } + } + + return [$singular.'s']; + } + + private function isInflectedWord(string $word): bool + { + return 1 === preg_match(self::UNINFLECTED, $word); + } +} diff --git a/vendor/symfony/string/Inflector/InflectorInterface.php b/vendor/symfony/string/Inflector/InflectorInterface.php new file mode 100644 index 0000000..67f2834 --- /dev/null +++ b/vendor/symfony/string/Inflector/InflectorInterface.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String\Inflector; + +interface InflectorInterface +{ + /** + * Returns the singular forms of a string. + * + * If the method can't determine the form with certainty, several possible singulars are returned. + * + * @return string[] + */ + public function singularize(string $plural): array; + + /** + * Returns the plural forms of a string. + * + * If the method can't determine the form with certainty, several possible plurals are returned. + * + * @return string[] + */ + public function pluralize(string $singular): array; +} diff --git a/vendor/symfony/string/LICENSE b/vendor/symfony/string/LICENSE new file mode 100644 index 0000000..f37c76b --- /dev/null +++ b/vendor/symfony/string/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2019-present Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/string/LazyString.php b/vendor/symfony/string/LazyString.php new file mode 100644 index 0000000..9c7a9c5 --- /dev/null +++ b/vendor/symfony/string/LazyString.php @@ -0,0 +1,164 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String; + +/** + * A string whose value is computed lazily by a callback. + * + * @author Nicolas Grekas + */ +class LazyString implements \Stringable, \JsonSerializable +{ + private $value; + + /** + * @param callable|array $callback A callable or a [Closure, method] lazy-callable + * + * @return static + */ + public static function fromCallable($callback, ...$arguments): self + { + if (!\is_callable($callback) && !(\is_array($callback) && isset($callback[0]) && $callback[0] instanceof \Closure && 2 >= \count($callback))) { + throw new \TypeError(sprintf('Argument 1 passed to "%s()" must be a callable or a [Closure, method] lazy-callable, "%s" given.', __METHOD__, get_debug_type($callback))); + } + + $lazyString = new static(); + $lazyString->value = static function () use (&$callback, &$arguments, &$value): string { + if (null !== $arguments) { + if (!\is_callable($callback)) { + $callback[0] = $callback[0](); + $callback[1] = $callback[1] ?? '__invoke'; + } + $value = $callback(...$arguments); + $callback = self::getPrettyName($callback); + $arguments = null; + } + + return $value ?? ''; + }; + + return $lazyString; + } + + /** + * @param string|int|float|bool|\Stringable $value + * + * @return static + */ + public static function fromStringable($value): self + { + if (!self::isStringable($value)) { + throw new \TypeError(sprintf('Argument 1 passed to "%s()" must be a scalar or a stringable object, "%s" given.', __METHOD__, get_debug_type($value))); + } + + if (\is_object($value)) { + return static::fromCallable([$value, '__toString']); + } + + $lazyString = new static(); + $lazyString->value = (string) $value; + + return $lazyString; + } + + /** + * Tells whether the provided value can be cast to string. + */ + final public static function isStringable($value): bool + { + return \is_string($value) || $value instanceof self || (\is_object($value) ? method_exists($value, '__toString') : \is_scalar($value)); + } + + /** + * Casts scalars and stringable objects to strings. + * + * @param object|string|int|float|bool $value + * + * @throws \TypeError When the provided value is not stringable + */ + final public static function resolve($value): string + { + return $value; + } + + /** + * @return string + */ + public function __toString() + { + if (\is_string($this->value)) { + return $this->value; + } + + try { + return $this->value = ($this->value)(); + } catch (\Throwable $e) { + if (\TypeError::class === \get_class($e) && __FILE__ === $e->getFile()) { + $type = explode(', ', $e->getMessage()); + $type = substr(array_pop($type), 0, -\strlen(' returned')); + $r = new \ReflectionFunction($this->value); + $callback = $r->getStaticVariables()['callback']; + + $e = new \TypeError(sprintf('Return value of %s() passed to %s::fromCallable() must be of the type string, %s returned.', $callback, static::class, $type)); + } + + if (\PHP_VERSION_ID < 70400) { + // leverage the ErrorHandler component with graceful fallback when it's not available + return trigger_error($e, \E_USER_ERROR); + } + + throw $e; + } + } + + public function __sleep(): array + { + $this->__toString(); + + return ['value']; + } + + public function jsonSerialize(): string + { + return $this->__toString(); + } + + private function __construct() + { + } + + private static function getPrettyName(callable $callback): string + { + if (\is_string($callback)) { + return $callback; + } + + if (\is_array($callback)) { + $class = \is_object($callback[0]) ? get_debug_type($callback[0]) : $callback[0]; + $method = $callback[1]; + } elseif ($callback instanceof \Closure) { + $r = new \ReflectionFunction($callback); + + if (false !== strpos($r->name, '{closure}') || !$class = \PHP_VERSION_ID >= 80111 ? $r->getClosureCalledClass() : $r->getClosureScopeClass()) { + return $r->name; + } + + $class = $class->name; + $method = $r->name; + } else { + $class = get_debug_type($callback); + $method = '__invoke'; + } + + return $class.'::'.$method; + } +} diff --git a/vendor/symfony/string/README.md b/vendor/symfony/string/README.md new file mode 100644 index 0000000..9c7e1e1 --- /dev/null +++ b/vendor/symfony/string/README.md @@ -0,0 +1,14 @@ +String Component +================ + +The String component provides an object-oriented API to strings and deals +with bytes, UTF-8 code points and grapheme clusters in a unified way. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/string.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/string/Resources/data/wcswidth_table_wide.php b/vendor/symfony/string/Resources/data/wcswidth_table_wide.php new file mode 100644 index 0000000..5a647e6 --- /dev/null +++ b/vendor/symfony/string/Resources/data/wcswidth_table_wide.php @@ -0,0 +1,1143 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String; + +if (!\function_exists(u::class)) { + function u(?string $string = ''): UnicodeString + { + return new UnicodeString($string ?? ''); + } +} + +if (!\function_exists(b::class)) { + function b(?string $string = ''): ByteString + { + return new ByteString($string ?? ''); + } +} + +if (!\function_exists(s::class)) { + /** + * @return UnicodeString|ByteString + */ + function s(?string $string = ''): AbstractString + { + $string = $string ?? ''; + + return preg_match('//u', $string) ? new UnicodeString($string) : new ByteString($string); + } +} diff --git a/vendor/symfony/string/Slugger/AsciiSlugger.php b/vendor/symfony/string/Slugger/AsciiSlugger.php new file mode 100644 index 0000000..5aecfeb --- /dev/null +++ b/vendor/symfony/string/Slugger/AsciiSlugger.php @@ -0,0 +1,183 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String\Slugger; + +use Symfony\Component\String\AbstractUnicodeString; +use Symfony\Component\String\UnicodeString; +use Symfony\Contracts\Translation\LocaleAwareInterface; + +if (!interface_exists(LocaleAwareInterface::class)) { + throw new \LogicException('You cannot use the "Symfony\Component\String\Slugger\AsciiSlugger" as the "symfony/translation-contracts" package is not installed. Try running "composer require symfony/translation-contracts".'); +} + +/** + * @author Titouan Galopin + */ +class AsciiSlugger implements SluggerInterface, LocaleAwareInterface +{ + private const LOCALE_TO_TRANSLITERATOR_ID = [ + 'am' => 'Amharic-Latin', + 'ar' => 'Arabic-Latin', + 'az' => 'Azerbaijani-Latin', + 'be' => 'Belarusian-Latin', + 'bg' => 'Bulgarian-Latin', + 'bn' => 'Bengali-Latin', + 'de' => 'de-ASCII', + 'el' => 'Greek-Latin', + 'fa' => 'Persian-Latin', + 'he' => 'Hebrew-Latin', + 'hy' => 'Armenian-Latin', + 'ka' => 'Georgian-Latin', + 'kk' => 'Kazakh-Latin', + 'ky' => 'Kirghiz-Latin', + 'ko' => 'Korean-Latin', + 'mk' => 'Macedonian-Latin', + 'mn' => 'Mongolian-Latin', + 'or' => 'Oriya-Latin', + 'ps' => 'Pashto-Latin', + 'ru' => 'Russian-Latin', + 'sr' => 'Serbian-Latin', + 'sr_Cyrl' => 'Serbian-Latin', + 'th' => 'Thai-Latin', + 'tk' => 'Turkmen-Latin', + 'uk' => 'Ukrainian-Latin', + 'uz' => 'Uzbek-Latin', + 'zh' => 'Han-Latin', + ]; + + private $defaultLocale; + private $symbolsMap = [ + 'en' => ['@' => 'at', '&' => 'and'], + ]; + + /** + * Cache of transliterators per locale. + * + * @var \Transliterator[] + */ + private $transliterators = []; + + /** + * @param array|\Closure|null $symbolsMap + */ + public function __construct(string $defaultLocale = null, $symbolsMap = null) + { + if (null !== $symbolsMap && !\is_array($symbolsMap) && !$symbolsMap instanceof \Closure) { + throw new \TypeError(sprintf('Argument 2 passed to "%s()" must be array, Closure or null, "%s" given.', __METHOD__, \gettype($symbolsMap))); + } + + $this->defaultLocale = $defaultLocale; + $this->symbolsMap = $symbolsMap ?? $this->symbolsMap; + } + + /** + * {@inheritdoc} + */ + public function setLocale($locale) + { + $this->defaultLocale = $locale; + } + + /** + * {@inheritdoc} + */ + public function getLocale() + { + return $this->defaultLocale; + } + + /** + * {@inheritdoc} + */ + public function slug(string $string, string $separator = '-', string $locale = null): AbstractUnicodeString + { + $locale = $locale ?? $this->defaultLocale; + + $transliterator = []; + if ($locale && ('de' === $locale || 0 === strpos($locale, 'de_'))) { + // Use the shortcut for German in UnicodeString::ascii() if possible (faster and no requirement on intl) + $transliterator = ['de-ASCII']; + } elseif (\function_exists('transliterator_transliterate') && $locale) { + $transliterator = (array) $this->createTransliterator($locale); + } + + if ($this->symbolsMap instanceof \Closure) { + // If the symbols map is passed as a closure, there is no need to fallback to the parent locale + // as the closure can just provide substitutions for all locales of interest. + $symbolsMap = $this->symbolsMap; + array_unshift($transliterator, static function ($s) use ($symbolsMap, $locale) { + return $symbolsMap($s, $locale); + }); + } + + $unicodeString = (new UnicodeString($string))->ascii($transliterator); + + if (\is_array($this->symbolsMap)) { + $map = null; + if (isset($this->symbolsMap[$locale])) { + $map = $this->symbolsMap[$locale]; + } else { + $parent = self::getParentLocale($locale); + if ($parent && isset($this->symbolsMap[$parent])) { + $map = $this->symbolsMap[$parent]; + } + } + if ($map) { + foreach ($map as $char => $replace) { + $unicodeString = $unicodeString->replace($char, ' '.$replace.' '); + } + } + } + + return $unicodeString + ->replaceMatches('/[^A-Za-z0-9]++/', $separator) + ->trim($separator) + ; + } + + private function createTransliterator(string $locale): ?\Transliterator + { + if (\array_key_exists($locale, $this->transliterators)) { + return $this->transliterators[$locale]; + } + + // Exact locale supported, cache and return + if ($id = self::LOCALE_TO_TRANSLITERATOR_ID[$locale] ?? null) { + return $this->transliterators[$locale] = \Transliterator::create($id.'/BGN') ?? \Transliterator::create($id); + } + + // Locale not supported and no parent, fallback to any-latin + if (!$parent = self::getParentLocale($locale)) { + return $this->transliterators[$locale] = null; + } + + // Try to use the parent locale (ie. try "de" for "de_AT") and cache both locales + if ($id = self::LOCALE_TO_TRANSLITERATOR_ID[$parent] ?? null) { + $transliterator = \Transliterator::create($id.'/BGN') ?? \Transliterator::create($id); + } + + return $this->transliterators[$locale] = $this->transliterators[$parent] = $transliterator ?? null; + } + + private static function getParentLocale(?string $locale): ?string + { + if (!$locale) { + return null; + } + if (false === $str = strrchr($locale, '_')) { + // no parent locale + return null; + } + + return substr($locale, 0, -\strlen($str)); + } +} diff --git a/vendor/symfony/string/Slugger/SluggerInterface.php b/vendor/symfony/string/Slugger/SluggerInterface.php new file mode 100644 index 0000000..c679ed9 --- /dev/null +++ b/vendor/symfony/string/Slugger/SluggerInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String\Slugger; + +use Symfony\Component\String\AbstractUnicodeString; + +/** + * Creates a URL-friendly slug from a given string. + * + * @author Titouan Galopin + */ +interface SluggerInterface +{ + /** + * Creates a slug for the given string and locale, using appropriate transliteration when needed. + */ + public function slug(string $string, string $separator = '-', string $locale = null): AbstractUnicodeString; +} diff --git a/vendor/symfony/string/UnicodeString.php b/vendor/symfony/string/UnicodeString.php new file mode 100644 index 0000000..9b906c6 --- /dev/null +++ b/vendor/symfony/string/UnicodeString.php @@ -0,0 +1,377 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String; + +use Symfony\Component\String\Exception\ExceptionInterface; +use Symfony\Component\String\Exception\InvalidArgumentException; + +/** + * Represents a string of Unicode grapheme clusters encoded as UTF-8. + * + * A letter followed by combining characters (accents typically) form what Unicode defines + * as a grapheme cluster: a character as humans mean it in written texts. This class knows + * about the concept and won't split a letter apart from its combining accents. It also + * ensures all string comparisons happen on their canonically-composed representation, + * ignoring e.g. the order in which accents are listed when a letter has many of them. + * + * @see https://unicode.org/reports/tr15/ + * + * @author Nicolas Grekas + * @author Hugo Hamon + * + * @throws ExceptionInterface + */ +class UnicodeString extends AbstractUnicodeString +{ + public function __construct(string $string = '') + { + $this->string = normalizer_is_normalized($string) ? $string : normalizer_normalize($string); + + if (false === $this->string) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + } + + public function append(string ...$suffix): AbstractString + { + $str = clone $this; + $str->string = $this->string.(1 >= \count($suffix) ? ($suffix[0] ?? '') : implode('', $suffix)); + normalizer_is_normalized($str->string) ?: $str->string = normalizer_normalize($str->string); + + if (false === $str->string) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + + return $str; + } + + public function chunk(int $length = 1): array + { + if (1 > $length) { + throw new InvalidArgumentException('The chunk length must be greater than zero.'); + } + + if ('' === $this->string) { + return []; + } + + $rx = '/('; + while (65535 < $length) { + $rx .= '\X{65535}'; + $length -= 65535; + } + $rx .= '\X{'.$length.'})/u'; + + $str = clone $this; + $chunks = []; + + foreach (preg_split($rx, $this->string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY) as $chunk) { + $str->string = $chunk; + $chunks[] = clone $str; + } + + return $chunks; + } + + public function endsWith($suffix): bool + { + if ($suffix instanceof AbstractString) { + $suffix = $suffix->string; + } elseif (\is_array($suffix) || $suffix instanceof \Traversable) { + return parent::endsWith($suffix); + } else { + $suffix = (string) $suffix; + } + + $form = null === $this->ignoreCase ? \Normalizer::NFD : \Normalizer::NFC; + normalizer_is_normalized($suffix, $form) ?: $suffix = normalizer_normalize($suffix, $form); + + if ('' === $suffix || false === $suffix) { + return false; + } + + if ($this->ignoreCase) { + return 0 === mb_stripos(grapheme_extract($this->string, \strlen($suffix), \GRAPHEME_EXTR_MAXBYTES, \strlen($this->string) - \strlen($suffix)), $suffix, 0, 'UTF-8'); + } + + return $suffix === grapheme_extract($this->string, \strlen($suffix), \GRAPHEME_EXTR_MAXBYTES, \strlen($this->string) - \strlen($suffix)); + } + + public function equalsTo($string): bool + { + if ($string instanceof AbstractString) { + $string = $string->string; + } elseif (\is_array($string) || $string instanceof \Traversable) { + return parent::equalsTo($string); + } else { + $string = (string) $string; + } + + $form = null === $this->ignoreCase ? \Normalizer::NFD : \Normalizer::NFC; + normalizer_is_normalized($string, $form) ?: $string = normalizer_normalize($string, $form); + + if ('' !== $string && false !== $string && $this->ignoreCase) { + return \strlen($string) === \strlen($this->string) && 0 === mb_stripos($this->string, $string, 0, 'UTF-8'); + } + + return $string === $this->string; + } + + public function indexOf($needle, int $offset = 0): ?int + { + if ($needle instanceof AbstractString) { + $needle = $needle->string; + } elseif (\is_array($needle) || $needle instanceof \Traversable) { + return parent::indexOf($needle, $offset); + } else { + $needle = (string) $needle; + } + + $form = null === $this->ignoreCase ? \Normalizer::NFD : \Normalizer::NFC; + normalizer_is_normalized($needle, $form) ?: $needle = normalizer_normalize($needle, $form); + + if ('' === $needle || false === $needle) { + return null; + } + + try { + $i = $this->ignoreCase ? grapheme_stripos($this->string, $needle, $offset) : grapheme_strpos($this->string, $needle, $offset); + } catch (\ValueError $e) { + return null; + } + + return false === $i ? null : $i; + } + + public function indexOfLast($needle, int $offset = 0): ?int + { + if ($needle instanceof AbstractString) { + $needle = $needle->string; + } elseif (\is_array($needle) || $needle instanceof \Traversable) { + return parent::indexOfLast($needle, $offset); + } else { + $needle = (string) $needle; + } + + $form = null === $this->ignoreCase ? \Normalizer::NFD : \Normalizer::NFC; + normalizer_is_normalized($needle, $form) ?: $needle = normalizer_normalize($needle, $form); + + if ('' === $needle || false === $needle) { + return null; + } + + $string = $this->string; + + if (0 > $offset) { + // workaround https://bugs.php.net/74264 + if (0 > $offset += grapheme_strlen($needle)) { + $string = grapheme_substr($string, 0, $offset); + } + $offset = 0; + } + + $i = $this->ignoreCase ? grapheme_strripos($string, $needle, $offset) : grapheme_strrpos($string, $needle, $offset); + + return false === $i ? null : $i; + } + + public function join(array $strings, string $lastGlue = null): AbstractString + { + $str = parent::join($strings, $lastGlue); + normalizer_is_normalized($str->string) ?: $str->string = normalizer_normalize($str->string); + + return $str; + } + + public function length(): int + { + return grapheme_strlen($this->string); + } + + /** + * @return static + */ + public function normalize(int $form = self::NFC): parent + { + $str = clone $this; + + if (\in_array($form, [self::NFC, self::NFKC], true)) { + normalizer_is_normalized($str->string, $form) ?: $str->string = normalizer_normalize($str->string, $form); + } elseif (!\in_array($form, [self::NFD, self::NFKD], true)) { + throw new InvalidArgumentException('Unsupported normalization form.'); + } elseif (!normalizer_is_normalized($str->string, $form)) { + $str->string = normalizer_normalize($str->string, $form); + $str->ignoreCase = null; + } + + return $str; + } + + public function prepend(string ...$prefix): AbstractString + { + $str = clone $this; + $str->string = (1 >= \count($prefix) ? ($prefix[0] ?? '') : implode('', $prefix)).$this->string; + normalizer_is_normalized($str->string) ?: $str->string = normalizer_normalize($str->string); + + if (false === $str->string) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + + return $str; + } + + public function replace(string $from, string $to): AbstractString + { + $str = clone $this; + normalizer_is_normalized($from) ?: $from = normalizer_normalize($from); + + if ('' !== $from && false !== $from) { + $tail = $str->string; + $result = ''; + $indexOf = $this->ignoreCase ? 'grapheme_stripos' : 'grapheme_strpos'; + + while ('' !== $tail && false !== $i = $indexOf($tail, $from)) { + $slice = grapheme_substr($tail, 0, $i); + $result .= $slice.$to; + $tail = substr($tail, \strlen($slice) + \strlen($from)); + } + + $str->string = $result.$tail; + normalizer_is_normalized($str->string) ?: $str->string = normalizer_normalize($str->string); + + if (false === $str->string) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + } + + return $str; + } + + public function replaceMatches(string $fromRegexp, $to): AbstractString + { + $str = parent::replaceMatches($fromRegexp, $to); + normalizer_is_normalized($str->string) ?: $str->string = normalizer_normalize($str->string); + + return $str; + } + + public function slice(int $start = 0, int $length = null): AbstractString + { + $str = clone $this; + + if (\PHP_VERSION_ID < 80000 && 0 > $start && grapheme_strlen($this->string) < -$start) { + $start = 0; + } + $str->string = (string) grapheme_substr($this->string, $start, $length ?? 2147483647); + + return $str; + } + + public function splice(string $replacement, int $start = 0, int $length = null): AbstractString + { + $str = clone $this; + + if (\PHP_VERSION_ID < 80000 && 0 > $start && grapheme_strlen($this->string) < -$start) { + $start = 0; + } + $start = $start ? \strlen(grapheme_substr($this->string, 0, $start)) : 0; + $length = $length ? \strlen(grapheme_substr($this->string, $start, $length ?? 2147483647)) : $length; + $str->string = substr_replace($this->string, $replacement, $start, $length ?? 2147483647); + normalizer_is_normalized($str->string) ?: $str->string = normalizer_normalize($str->string); + + if (false === $str->string) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + + return $str; + } + + public function split(string $delimiter, int $limit = null, int $flags = null): array + { + if (1 > $limit = $limit ?? 2147483647) { + throw new InvalidArgumentException('Split limit must be a positive integer.'); + } + + if ('' === $delimiter) { + throw new InvalidArgumentException('Split delimiter is empty.'); + } + + if (null !== $flags) { + return parent::split($delimiter.'u', $limit, $flags); + } + + normalizer_is_normalized($delimiter) ?: $delimiter = normalizer_normalize($delimiter); + + if (false === $delimiter) { + throw new InvalidArgumentException('Split delimiter is not a valid UTF-8 string.'); + } + + $str = clone $this; + $tail = $this->string; + $chunks = []; + $indexOf = $this->ignoreCase ? 'grapheme_stripos' : 'grapheme_strpos'; + + while (1 < $limit && false !== $i = $indexOf($tail, $delimiter)) { + $str->string = grapheme_substr($tail, 0, $i); + $chunks[] = clone $str; + $tail = substr($tail, \strlen($str->string) + \strlen($delimiter)); + --$limit; + } + + $str->string = $tail; + $chunks[] = clone $str; + + return $chunks; + } + + public function startsWith($prefix): bool + { + if ($prefix instanceof AbstractString) { + $prefix = $prefix->string; + } elseif (\is_array($prefix) || $prefix instanceof \Traversable) { + return parent::startsWith($prefix); + } else { + $prefix = (string) $prefix; + } + + $form = null === $this->ignoreCase ? \Normalizer::NFD : \Normalizer::NFC; + normalizer_is_normalized($prefix, $form) ?: $prefix = normalizer_normalize($prefix, $form); + + if ('' === $prefix || false === $prefix) { + return false; + } + + if ($this->ignoreCase) { + return 0 === mb_stripos(grapheme_extract($this->string, \strlen($prefix), \GRAPHEME_EXTR_MAXBYTES), $prefix, 0, 'UTF-8'); + } + + return $prefix === grapheme_extract($this->string, \strlen($prefix), \GRAPHEME_EXTR_MAXBYTES); + } + + public function __wakeup() + { + if (!\is_string($this->string)) { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + normalizer_is_normalized($this->string) ?: $this->string = normalizer_normalize($this->string); + } + + public function __clone() + { + if (null === $this->ignoreCase) { + normalizer_is_normalized($this->string) ?: $this->string = normalizer_normalize($this->string); + } + + $this->ignoreCase = false; + } +} diff --git a/vendor/symfony/translation-contracts/.gitignore b/vendor/symfony/translation-contracts/.gitignore new file mode 100644 index 0000000..c49a5d8 --- /dev/null +++ b/vendor/symfony/translation-contracts/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/vendor/symfony/translation-contracts/CHANGELOG.md b/vendor/symfony/translation-contracts/CHANGELOG.md new file mode 100644 index 0000000..7932e26 --- /dev/null +++ b/vendor/symfony/translation-contracts/CHANGELOG.md @@ -0,0 +1,5 @@ +CHANGELOG +========= + +The changelog is maintained for all Symfony contracts at the following URL: +https://github.com/symfony/contracts/blob/main/CHANGELOG.md diff --git a/vendor/symfony/translation-contracts/LICENSE b/vendor/symfony/translation-contracts/LICENSE new file mode 100644 index 0000000..74cdc2d --- /dev/null +++ b/vendor/symfony/translation-contracts/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-2022 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/translation-contracts/LocaleAwareInterface.php b/vendor/symfony/translation-contracts/LocaleAwareInterface.php new file mode 100644 index 0000000..693f92b --- /dev/null +++ b/vendor/symfony/translation-contracts/LocaleAwareInterface.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Translation; + +interface LocaleAwareInterface +{ + /** + * Sets the current locale. + * + * @param string $locale The locale + * + * @throws \InvalidArgumentException If the locale contains invalid characters + */ + public function setLocale(string $locale); + + /** + * Returns the current locale. + * + * @return string + */ + public function getLocale(); +} diff --git a/vendor/symfony/translation-contracts/README.md b/vendor/symfony/translation-contracts/README.md new file mode 100644 index 0000000..42e5c51 --- /dev/null +++ b/vendor/symfony/translation-contracts/README.md @@ -0,0 +1,9 @@ +Symfony Translation Contracts +============================= + +A set of abstractions extracted out of the Symfony components. + +Can be used to build on semantics that the Symfony components proved useful - and +that already have battle tested implementations. + +See https://github.com/symfony/contracts/blob/main/README.md for more information. diff --git a/vendor/symfony/translation-contracts/Test/TranslatorTest.php b/vendor/symfony/translation-contracts/Test/TranslatorTest.php new file mode 100644 index 0000000..a3e9b20 --- /dev/null +++ b/vendor/symfony/translation-contracts/Test/TranslatorTest.php @@ -0,0 +1,390 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Translation\Test; + +use PHPUnit\Framework\TestCase; +use Symfony\Contracts\Translation\TranslatorInterface; +use Symfony\Contracts\Translation\TranslatorTrait; + +/** + * Test should cover all languages mentioned on http://translate.sourceforge.net/wiki/l10n/pluralforms + * and Plural forms mentioned on http://www.gnu.org/software/gettext/manual/gettext.html#Plural-forms. + * + * See also https://developer.mozilla.org/en/Localization_and_Plurals which mentions 15 rules having a maximum of 6 forms. + * The mozilla code is also interesting to check for. + * + * As mentioned by chx http://drupal.org/node/1273968 we can cover all by testing number from 0 to 199 + * + * The goal to cover all languages is to far fetched so this test case is smaller. + * + * @author Clemens Tolboom clemens@build2be.nl + */ +class TranslatorTest extends TestCase +{ + private $defaultLocale; + + protected function setUp(): void + { + $this->defaultLocale = \Locale::getDefault(); + \Locale::setDefault('en'); + } + + protected function tearDown(): void + { + \Locale::setDefault($this->defaultLocale); + } + + /** + * @return TranslatorInterface + */ + public function getTranslator() + { + return new class() implements TranslatorInterface { + use TranslatorTrait; + }; + } + + /** + * @dataProvider getTransTests + */ + public function testTrans($expected, $id, $parameters) + { + $translator = $this->getTranslator(); + + $this->assertEquals($expected, $translator->trans($id, $parameters)); + } + + /** + * @dataProvider getTransChoiceTests + */ + public function testTransChoiceWithExplicitLocale($expected, $id, $number) + { + $translator = $this->getTranslator(); + + $this->assertEquals($expected, $translator->trans($id, ['%count%' => $number])); + } + + /** + * @requires extension intl + * + * @dataProvider getTransChoiceTests + */ + public function testTransChoiceWithDefaultLocale($expected, $id, $number) + { + $translator = $this->getTranslator(); + + $this->assertEquals($expected, $translator->trans($id, ['%count%' => $number])); + } + + /** + * @dataProvider getTransChoiceTests + */ + public function testTransChoiceWithEnUsPosix($expected, $id, $number) + { + $translator = $this->getTranslator(); + $translator->setLocale('en_US_POSIX'); + + $this->assertEquals($expected, $translator->trans($id, ['%count%' => $number])); + } + + public function testGetSetLocale() + { + $translator = $this->getTranslator(); + + $this->assertEquals('en', $translator->getLocale()); + } + + /** + * @requires extension intl + */ + public function testGetLocaleReturnsDefaultLocaleIfNotSet() + { + $translator = $this->getTranslator(); + + \Locale::setDefault('pt_BR'); + $this->assertEquals('pt_BR', $translator->getLocale()); + + \Locale::setDefault('en'); + $this->assertEquals('en', $translator->getLocale()); + } + + public function getTransTests() + { + return [ + ['Symfony is great!', 'Symfony is great!', []], + ['Symfony is awesome!', 'Symfony is %what%!', ['%what%' => 'awesome']], + ]; + } + + public function getTransChoiceTests() + { + return [ + ['There are no apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0], + ['There is one apple', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 1], + ['There are 10 apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 10], + ['There are 0 apples', 'There is 1 apple|There are %count% apples', 0], + ['There is 1 apple', 'There is 1 apple|There are %count% apples', 1], + ['There are 10 apples', 'There is 1 apple|There are %count% apples', 10], + // custom validation messages may be coded with a fixed value + ['There are 2 apples', 'There are 2 apples', 2], + ]; + } + + /** + * @dataProvider getInternal + */ + public function testInterval($expected, $number, $interval) + { + $translator = $this->getTranslator(); + + $this->assertEquals($expected, $translator->trans($interval.' foo|[1,Inf[ bar', ['%count%' => $number])); + } + + public function getInternal() + { + return [ + ['foo', 3, '{1,2, 3 ,4}'], + ['bar', 10, '{1,2, 3 ,4}'], + ['bar', 3, '[1,2]'], + ['foo', 1, '[1,2]'], + ['foo', 2, '[1,2]'], + ['bar', 1, ']1,2['], + ['bar', 2, ']1,2['], + ['foo', log(0), '[-Inf,2['], + ['foo', -log(0), '[-2,+Inf]'], + ]; + } + + /** + * @dataProvider getChooseTests + */ + public function testChoose($expected, $id, $number, $locale = null) + { + $translator = $this->getTranslator(); + + $this->assertEquals($expected, $translator->trans($id, ['%count%' => $number], null, $locale)); + } + + public function testReturnMessageIfExactlyOneStandardRuleIsGiven() + { + $translator = $this->getTranslator(); + + $this->assertEquals('There are two apples', $translator->trans('There are two apples', ['%count%' => 2])); + } + + /** + * @dataProvider getNonMatchingMessages + */ + public function testThrowExceptionIfMatchingMessageCannotBeFound($id, $number) + { + $this->expectException(\InvalidArgumentException::class); + $translator = $this->getTranslator(); + + $translator->trans($id, ['%count%' => $number]); + } + + public function getNonMatchingMessages() + { + return [ + ['{0} There are no apples|{1} There is one apple', 2], + ['{1} There is one apple|]1,Inf] There are %count% apples', 0], + ['{1} There is one apple|]2,Inf] There are %count% apples', 2], + ['{0} There are no apples|There is one apple', 2], + ]; + } + + public function getChooseTests() + { + return [ + ['There are no apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0], + ['There are no apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0], + ['There are no apples', '{0}There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0], + + ['There is one apple', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 1], + + ['There are 10 apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 10], + ['There are 10 apples', '{0} There are no apples|{1} There is one apple|]1,Inf]There are %count% apples', 10], + ['There are 10 apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 10], + + ['There are 0 apples', 'There is one apple|There are %count% apples', 0], + ['There is one apple', 'There is one apple|There are %count% apples', 1], + ['There are 10 apples', 'There is one apple|There are %count% apples', 10], + + ['There are 0 apples', 'one: There is one apple|more: There are %count% apples', 0], + ['There is one apple', 'one: There is one apple|more: There are %count% apples', 1], + ['There are 10 apples', 'one: There is one apple|more: There are %count% apples', 10], + + ['There are no apples', '{0} There are no apples|one: There is one apple|more: There are %count% apples', 0], + ['There is one apple', '{0} There are no apples|one: There is one apple|more: There are %count% apples', 1], + ['There are 10 apples', '{0} There are no apples|one: There is one apple|more: There are %count% apples', 10], + + ['', '{0}|{1} There is one apple|]1,Inf] There are %count% apples', 0], + ['', '{0} There are no apples|{1}|]1,Inf] There are %count% apples', 1], + + // Indexed only tests which are Gettext PoFile* compatible strings. + ['There are 0 apples', 'There is one apple|There are %count% apples', 0], + ['There is one apple', 'There is one apple|There are %count% apples', 1], + ['There are 2 apples', 'There is one apple|There are %count% apples', 2], + + // Tests for float numbers + ['There is almost one apple', '{0} There are no apples|]0,1[ There is almost one apple|{1} There is one apple|[1,Inf] There is more than one apple', 0.7], + ['There is one apple', '{0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 1], + ['There is more than one apple', '{0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 1.7], + ['There are no apples', '{0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 0], + ['There are no apples', '{0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 0.0], + ['There are no apples', '{0.0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 0], + + // Test texts with new-lines + // with double-quotes and \n in id & double-quotes and actual newlines in text + ["This is a text with a\n new-line in it. Selector = 0.", '{0}This is a text with a + new-line in it. Selector = 0.|{1}This is a text with a + new-line in it. Selector = 1.|[1,Inf]This is a text with a + new-line in it. Selector > 1.', 0], + // with double-quotes and \n in id and single-quotes and actual newlines in text + ["This is a text with a\n new-line in it. Selector = 1.", '{0}This is a text with a + new-line in it. Selector = 0.|{1}This is a text with a + new-line in it. Selector = 1.|[1,Inf]This is a text with a + new-line in it. Selector > 1.', 1], + ["This is a text with a\n new-line in it. Selector > 1.", '{0}This is a text with a + new-line in it. Selector = 0.|{1}This is a text with a + new-line in it. Selector = 1.|[1,Inf]This is a text with a + new-line in it. Selector > 1.', 5], + // with double-quotes and id split accros lines + ['This is a text with a + new-line in it. Selector = 1.', '{0}This is a text with a + new-line in it. Selector = 0.|{1}This is a text with a + new-line in it. Selector = 1.|[1,Inf]This is a text with a + new-line in it. Selector > 1.', 1], + // with single-quotes and id split accros lines + ['This is a text with a + new-line in it. Selector > 1.', '{0}This is a text with a + new-line in it. Selector = 0.|{1}This is a text with a + new-line in it. Selector = 1.|[1,Inf]This is a text with a + new-line in it. Selector > 1.', 5], + // with single-quotes and \n in text + ['This is a text with a\nnew-line in it. Selector = 0.', '{0}This is a text with a\nnew-line in it. Selector = 0.|{1}This is a text with a\nnew-line in it. Selector = 1.|[1,Inf]This is a text with a\nnew-line in it. Selector > 1.', 0], + // with double-quotes and id split accros lines + ["This is a text with a\nnew-line in it. Selector = 1.", "{0}This is a text with a\nnew-line in it. Selector = 0.|{1}This is a text with a\nnew-line in it. Selector = 1.|[1,Inf]This is a text with a\nnew-line in it. Selector > 1.", 1], + // esacape pipe + ['This is a text with | in it. Selector = 0.', '{0}This is a text with || in it. Selector = 0.|{1}This is a text with || in it. Selector = 1.', 0], + // Empty plural set (2 plural forms) from a .PO file + ['', '|', 1], + // Empty plural set (3 plural forms) from a .PO file + ['', '||', 1], + + // Floating values + ['1.5 liters', '%count% liter|%count% liters', 1.5], + ['1.5 litre', '%count% litre|%count% litres', 1.5, 'fr'], + + // Negative values + ['-1 degree', '%count% degree|%count% degrees', -1], + ['-1 degré', '%count% degré|%count% degrés', -1], + ['-1.5 degrees', '%count% degree|%count% degrees', -1.5], + ['-1.5 degré', '%count% degré|%count% degrés', -1.5, 'fr'], + ['-2 degrees', '%count% degree|%count% degrees', -2], + ['-2 degrés', '%count% degré|%count% degrés', -2], + ]; + } + + /** + * @dataProvider failingLangcodes + */ + public function testFailedLangcodes($nplural, $langCodes) + { + $matrix = $this->generateTestData($langCodes); + $this->validateMatrix($nplural, $matrix, false); + } + + /** + * @dataProvider successLangcodes + */ + public function testLangcodes($nplural, $langCodes) + { + $matrix = $this->generateTestData($langCodes); + $this->validateMatrix($nplural, $matrix); + } + + /** + * This array should contain all currently known langcodes. + * + * As it is impossible to have this ever complete we should try as hard as possible to have it almost complete. + * + * @return array + */ + public function successLangcodes() + { + return [ + ['1', ['ay', 'bo', 'cgg', 'dz', 'id', 'ja', 'jbo', 'ka', 'kk', 'km', 'ko', 'ky']], + ['2', ['nl', 'fr', 'en', 'de', 'de_GE', 'hy', 'hy_AM', 'en_US_POSIX']], + ['3', ['be', 'bs', 'cs', 'hr']], + ['4', ['cy', 'mt', 'sl']], + ['6', ['ar']], + ]; + } + + /** + * This array should be at least empty within the near future. + * + * This both depends on a complete list trying to add above as understanding + * the plural rules of the current failing languages. + * + * @return array with nplural together with langcodes + */ + public function failingLangcodes() + { + return [ + ['1', ['fa']], + ['2', ['jbo']], + ['3', ['cbs']], + ['4', ['gd', 'kw']], + ['5', ['ga']], + ]; + } + + /** + * We validate only on the plural coverage. Thus the real rules is not tested. + * + * @param string $nplural Plural expected + * @param array $matrix Containing langcodes and their plural index values + * @param bool $expectSuccess + */ + protected function validateMatrix($nplural, $matrix, $expectSuccess = true) + { + foreach ($matrix as $langCode => $data) { + $indexes = array_flip($data); + if ($expectSuccess) { + $this->assertCount($nplural, $indexes, "Langcode '$langCode' has '$nplural' plural forms."); + } else { + $this->assertNotEquals((int) $nplural, \count($indexes), "Langcode '$langCode' has '$nplural' plural forms."); + } + } + } + + protected function generateTestData($langCodes) + { + $translator = new class() { + use TranslatorTrait { + getPluralizationRule as public; + } + }; + + $matrix = []; + foreach ($langCodes as $langCode) { + for ($count = 0; $count < 200; ++$count) { + $plural = $translator->getPluralizationRule($count, $langCode); + $matrix[$langCode][$count] = $plural; + } + } + + return $matrix; + } +} diff --git a/vendor/symfony/translation-contracts/TranslatableInterface.php b/vendor/symfony/translation-contracts/TranslatableInterface.php new file mode 100644 index 0000000..47fd6fa --- /dev/null +++ b/vendor/symfony/translation-contracts/TranslatableInterface.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Translation; + +/** + * @author Nicolas Grekas + */ +interface TranslatableInterface +{ + public function trans(TranslatorInterface $translator, string $locale = null): string; +} diff --git a/vendor/symfony/translation-contracts/TranslatorInterface.php b/vendor/symfony/translation-contracts/TranslatorInterface.php new file mode 100644 index 0000000..77b7a9c --- /dev/null +++ b/vendor/symfony/translation-contracts/TranslatorInterface.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 Symfony\Contracts\Translation; + +/** + * @author Fabien Potencier + * + * @method string getLocale() Returns the default locale + */ +interface TranslatorInterface +{ + /** + * Translates the given message. + * + * When a number is provided as a parameter named "%count%", the message is parsed for plural + * forms and a translation is chosen according to this number using the following rules: + * + * Given a message with different plural translations separated by a + * pipe (|), this method returns the correct portion of the message based + * on the given number, locale and the pluralization rules in the message + * itself. + * + * The message supports two different types of pluralization rules: + * + * interval: {0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples + * indexed: There is one apple|There are %count% apples + * + * The indexed solution can also contain labels (e.g. one: There is one apple). + * This is purely for making the translations more clear - it does not + * affect the functionality. + * + * The two methods can also be mixed: + * {0} There are no apples|one: There is one apple|more: There are %count% apples + * + * An interval can represent a finite set of numbers: + * {1,2,3,4} + * + * An interval can represent numbers between two numbers: + * [1, +Inf] + * ]-1,2[ + * + * The left delimiter can be [ (inclusive) or ] (exclusive). + * The right delimiter can be [ (exclusive) or ] (inclusive). + * Beside numbers, you can use -Inf and +Inf for the infinite. + * + * @see https://en.wikipedia.org/wiki/ISO_31-11 + * + * @param string $id The message id (may also be an object that can be cast to string) + * @param array $parameters An array of parameters for the message + * @param string|null $domain The domain for the message or null to use the default + * @param string|null $locale The locale or null to use the default + * + * @return string + * + * @throws \InvalidArgumentException If the locale contains invalid characters + */ + public function trans(string $id, array $parameters = [], string $domain = null, string $locale = null); +} diff --git a/vendor/symfony/translation-contracts/TranslatorTrait.php b/vendor/symfony/translation-contracts/TranslatorTrait.php new file mode 100644 index 0000000..405ce8d --- /dev/null +++ b/vendor/symfony/translation-contracts/TranslatorTrait.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 Symfony\Contracts\Translation; + +use Symfony\Component\Translation\Exception\InvalidArgumentException; + +/** + * A trait to help implement TranslatorInterface and LocaleAwareInterface. + * + * @author Fabien Potencier + */ +trait TranslatorTrait +{ + private $locale; + + /** + * {@inheritdoc} + */ + public function setLocale(string $locale) + { + $this->locale = $locale; + } + + /** + * {@inheritdoc} + * + * @return string + */ + public function getLocale() + { + return $this->locale ?: (class_exists(\Locale::class) ? \Locale::getDefault() : 'en'); + } + + /** + * {@inheritdoc} + */ + public function trans(?string $id, array $parameters = [], string $domain = null, string $locale = null): string + { + if (null === $id || '' === $id) { + return ''; + } + + if (!isset($parameters['%count%']) || !is_numeric($parameters['%count%'])) { + return strtr($id, $parameters); + } + + $number = (float) $parameters['%count%']; + $locale = $locale ?: $this->getLocale(); + + $parts = []; + if (preg_match('/^\|++$/', $id)) { + $parts = explode('|', $id); + } elseif (preg_match_all('/(?:\|\||[^\|])++/', $id, $matches)) { + $parts = $matches[0]; + } + + $intervalRegexp = <<<'EOF' +/^(?P + ({\s* + (\-?\d+(\.\d+)?[\s*,\s*\-?\d+(\.\d+)?]*) + \s*}) + + | + + (?P[\[\]]) + \s* + (?P-Inf|\-?\d+(\.\d+)?) + \s*,\s* + (?P\+?Inf|\-?\d+(\.\d+)?) + \s* + (?P[\[\]]) +)\s*(?P.*?)$/xs +EOF; + + $standardRules = []; + foreach ($parts as $part) { + $part = trim(str_replace('||', '|', $part)); + + // try to match an explicit rule, then fallback to the standard ones + if (preg_match($intervalRegexp, $part, $matches)) { + if ($matches[2]) { + foreach (explode(',', $matches[3]) as $n) { + if ($number == $n) { + return strtr($matches['message'], $parameters); + } + } + } else { + $leftNumber = '-Inf' === $matches['left'] ? -\INF : (float) $matches['left']; + $rightNumber = is_numeric($matches['right']) ? (float) $matches['right'] : \INF; + + if (('[' === $matches['left_delimiter'] ? $number >= $leftNumber : $number > $leftNumber) + && (']' === $matches['right_delimiter'] ? $number <= $rightNumber : $number < $rightNumber) + ) { + return strtr($matches['message'], $parameters); + } + } + } elseif (preg_match('/^\w+\:\s*(.*?)$/', $part, $matches)) { + $standardRules[] = $matches[1]; + } else { + $standardRules[] = $part; + } + } + + $position = $this->getPluralizationRule($number, $locale); + + if (!isset($standardRules[$position])) { + // when there's exactly one rule given, and that rule is a standard + // rule, use this rule + if (1 === \count($parts) && isset($standardRules[0])) { + return strtr($standardRules[0], $parameters); + } + + $message = sprintf('Unable to choose a translation for "%s" with locale "%s" for value "%d". Double check that this translation has the correct plural options (e.g. "There is one apple|There are %%count%% apples").', $id, $locale, $number); + + if (class_exists(InvalidArgumentException::class)) { + throw new InvalidArgumentException($message); + } + + throw new \InvalidArgumentException($message); + } + + return strtr($standardRules[$position], $parameters); + } + + /** + * Returns the plural position to use for the given locale and number. + * + * The plural rules are derived from code of the Zend Framework (2010-09-25), + * which is subject to the new BSD license (http://framework.zend.com/license/new-bsd). + * Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) + */ + private function getPluralizationRule(float $number, string $locale): int + { + $number = abs($number); + + switch ('pt_BR' !== $locale && 'en_US_POSIX' !== $locale && \strlen($locale) > 3 ? substr($locale, 0, strrpos($locale, '_')) : $locale) { + case 'af': + case 'bn': + case 'bg': + case 'ca': + case 'da': + case 'de': + case 'el': + case 'en': + case 'en_US_POSIX': + case 'eo': + case 'es': + case 'et': + case 'eu': + case 'fa': + case 'fi': + case 'fo': + case 'fur': + case 'fy': + case 'gl': + case 'gu': + case 'ha': + case 'he': + case 'hu': + case 'is': + case 'it': + case 'ku': + case 'lb': + case 'ml': + case 'mn': + case 'mr': + case 'nah': + case 'nb': + case 'ne': + case 'nl': + case 'nn': + case 'no': + case 'oc': + case 'om': + case 'or': + case 'pa': + case 'pap': + case 'ps': + case 'pt': + case 'so': + case 'sq': + case 'sv': + case 'sw': + case 'ta': + case 'te': + case 'tk': + case 'ur': + case 'zu': + return (1 == $number) ? 0 : 1; + + case 'am': + case 'bh': + case 'fil': + case 'fr': + case 'gun': + case 'hi': + case 'hy': + case 'ln': + case 'mg': + case 'nso': + case 'pt_BR': + case 'ti': + case 'wa': + return ($number < 2) ? 0 : 1; + + case 'be': + case 'bs': + case 'hr': + case 'ru': + case 'sh': + case 'sr': + case 'uk': + return ((1 == $number % 10) && (11 != $number % 100)) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2); + + case 'cs': + case 'sk': + return (1 == $number) ? 0 : ((($number >= 2) && ($number <= 4)) ? 1 : 2); + + case 'ga': + return (1 == $number) ? 0 : ((2 == $number) ? 1 : 2); + + case 'lt': + return ((1 == $number % 10) && (11 != $number % 100)) ? 0 : ((($number % 10 >= 2) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2); + + case 'sl': + return (1 == $number % 100) ? 0 : ((2 == $number % 100) ? 1 : (((3 == $number % 100) || (4 == $number % 100)) ? 2 : 3)); + + case 'mk': + return (1 == $number % 10) ? 0 : 1; + + case 'mt': + return (1 == $number) ? 0 : (((0 == $number) || (($number % 100 > 1) && ($number % 100 < 11))) ? 1 : ((($number % 100 > 10) && ($number % 100 < 20)) ? 2 : 3)); + + case 'lv': + return (0 == $number) ? 0 : (((1 == $number % 10) && (11 != $number % 100)) ? 1 : 2); + + case 'pl': + return (1 == $number) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 12) || ($number % 100 > 14))) ? 1 : 2); + + case 'cy': + return (1 == $number) ? 0 : ((2 == $number) ? 1 : (((8 == $number) || (11 == $number)) ? 2 : 3)); + + case 'ro': + return (1 == $number) ? 0 : (((0 == $number) || (($number % 100 > 0) && ($number % 100 < 20))) ? 1 : 2); + + case 'ar': + return (0 == $number) ? 0 : ((1 == $number) ? 1 : ((2 == $number) ? 2 : ((($number % 100 >= 3) && ($number % 100 <= 10)) ? 3 : ((($number % 100 >= 11) && ($number % 100 <= 99)) ? 4 : 5)))); + + default: + return 0; + } + } +} diff --git a/vendor/symfony/translation/CHANGELOG.md b/vendor/symfony/translation/CHANGELOG.md new file mode 100644 index 0000000..93615dc --- /dev/null +++ b/vendor/symfony/translation/CHANGELOG.md @@ -0,0 +1,183 @@ +CHANGELOG +========= + +5.4.21 +------ + + * [BC BREAK] The following data providers for `ProviderFactoryTestCase` are now static: + `supportsProvider()`, `createProvider()`, `unsupportedSchemeProvider()`and `incompleteDsnProvider()` + * [BC BREAK] `ProviderTestCase::toStringProvider()` is now static + +5.4 +--- + + * Add `github` format & autodetection to render errors as annotations when + running the XLIFF linter command in a Github Actions environment. + * Translation providers are not experimental anymore + +5.3 +--- + + * Add `translation:pull` and `translation:push` commands to manage translations with third-party providers + * Add `TranslatorBagInterface::getCatalogues` method + * Add support to load XLIFF string in `XliffFileLoader` + +5.2.0 +----- + + * added support for calling `trans` with ICU formatted messages + * added `PseudoLocalizationTranslator` + * added `TranslatableMessage` objects that represent a message that can be translated + * added the `t()` function to easily create `TranslatableMessage` objects + * Added support for extracting messages from `TranslatableMessage` objects + +5.1.0 +----- + + * added support for `name` attribute on `unit` element from xliff2 to be used as a translation key instead of always the `source` element + +5.0.0 +----- + + * removed support for using `null` as the locale in `Translator` + * removed `TranslatorInterface` + * removed `MessageSelector` + * removed `ChoiceMessageFormatterInterface` + * removed `PluralizationRule` + * removed `Interval` + * removed `transChoice()` methods, use the trans() method instead with a %count% parameter + * removed `FileDumper::setBackup()` and `TranslationWriter::disableBackup()` + * removed `MessageFormatter::choiceFormat()` + * added argument `$filename` to `PhpExtractor::parseTokens()` + * removed support for implicit STDIN usage in the `lint:xliff` command, use `lint:xliff -` (append a dash) instead to make it explicit. + +4.4.0 +----- + + * deprecated support for using `null` as the locale in `Translator` + * deprecated accepting STDIN implicitly when using the `lint:xliff` command, use `lint:xliff -` (append a dash) instead to make it explicit. + * Marked the `TranslationDataCollector` class as `@final`. + +4.3.0 +----- + + * Improved Xliff 1.2 loader to load the original file's metadata + * Added `TranslatorPathsPass` + +4.2.0 +----- + + * Started using ICU parent locales as fallback locales. + * allow using the ICU message format using domains with the "+intl-icu" suffix + * deprecated `Translator::transChoice()` in favor of using `Translator::trans()` with a `%count%` parameter + * deprecated `TranslatorInterface` in favor of `Symfony\Contracts\Translation\TranslatorInterface` + * deprecated `MessageSelector`, `Interval` and `PluralizationRules`; use `IdentityTranslator` instead + * Added `IntlFormatter` and `IntlFormatterInterface` + * added support for multiple files and directories in `XliffLintCommand` + * Marked `Translator::getFallbackLocales()` and `TranslationDataCollector::getFallbackLocales()` as internal + +4.1.0 +----- + + * The `FileDumper::setBackup()` method is deprecated. + * The `TranslationWriter::disableBackup()` method is deprecated. + * The `XliffFileDumper` will write "name" on the "unit" node when dumping XLIFF 2.0. + +4.0.0 +----- + + * removed the backup feature of the `FileDumper` class + * removed `TranslationWriter::writeTranslations()` method + * removed support for passing `MessageSelector` instances to the constructor of the `Translator` class + +3.4.0 +----- + + * Added `TranslationDumperPass` + * Added `TranslationExtractorPass` + * Added `TranslatorPass` + * Added `TranslationReader` and `TranslationReaderInterface` + * Added `` section to the Xliff 2.0 dumper. + * Improved Xliff 2.0 loader to load `` section. + * Added `TranslationWriterInterface` + * Deprecated `TranslationWriter::writeTranslations` in favor of `TranslationWriter::write` + * added support for adding custom message formatter and decoupling the default one. + * Added `PhpExtractor` + * Added `PhpStringTokenParser` + +3.2.0 +----- + + * Added support for escaping `|` in plural translations with double pipe. + +3.1.0 +----- + + * Deprecated the backup feature of the file dumper classes. + +3.0.0 +----- + + * removed `FileDumper::format()` method. + * Changed the visibility of the locale property in `Translator` from protected to private. + +2.8.0 +----- + + * deprecated FileDumper::format(), overwrite FileDumper::formatCatalogue() instead. + * deprecated Translator::getMessages(), rely on TranslatorBagInterface::getCatalogue() instead. + * added `FileDumper::formatCatalogue` which allows format the catalogue without dumping it into file. + * added option `json_encoding` to JsonFileDumper + * added options `as_tree`, `inline` to YamlFileDumper + * added support for XLIFF 2.0. + * added support for XLIFF target and tool attributes. + * added message parameters to DataCollectorTranslator. + * [DEPRECATION] The `DiffOperation` class has been deprecated and + will be removed in Symfony 3.0, since its operation has nothing to do with 'diff', + so the class name is misleading. The `TargetOperation` class should be used for + this use-case instead. + +2.7.0 +----- + + * added DataCollectorTranslator for collecting the translated messages. + +2.6.0 +----- + + * added possibility to cache catalogues + * added TranslatorBagInterface + * added LoggingTranslator + * added Translator::getMessages() for retrieving the message catalogue as an array + +2.5.0 +----- + + * added relative file path template to the file dumpers + * added optional backup to the file dumpers + * changed IcuResFileDumper to extend FileDumper + +2.3.0 +----- + + * added classes to make operations on catalogues (like making a diff or a merge on 2 catalogues) + * added Translator::getFallbackLocales() + * deprecated Translator::setFallbackLocale() in favor of the new Translator::setFallbackLocales() method + +2.2.0 +----- + + * QtTranslationsLoader class renamed to QtFileLoader. QtTranslationsLoader is deprecated and will be removed in 2.3. + * [BC BREAK] uniformized the exception thrown by the load() method when an error occurs. The load() method now + throws Symfony\Component\Translation\Exception\NotFoundResourceException when a resource cannot be found + and Symfony\Component\Translation\Exception\InvalidResourceException when a resource is invalid. + * changed the exception class thrown by some load() methods from \RuntimeException to \InvalidArgumentException + (IcuDatFileLoader, IcuResFileLoader and QtFileLoader) + +2.1.0 +----- + + * added support for more than one fallback locale + * added support for extracting translation messages from templates (Twig and PHP) + * added dumpers for translation catalogs + * added support for QT, gettext, and ResourceBundles diff --git a/vendor/symfony/translation/Catalogue/AbstractOperation.php b/vendor/symfony/translation/Catalogue/AbstractOperation.php new file mode 100644 index 0000000..98d42e5 --- /dev/null +++ b/vendor/symfony/translation/Catalogue/AbstractOperation.php @@ -0,0 +1,203 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Catalogue; + +use Symfony\Component\Translation\Exception\InvalidArgumentException; +use Symfony\Component\Translation\Exception\LogicException; +use Symfony\Component\Translation\MessageCatalogue; +use Symfony\Component\Translation\MessageCatalogueInterface; + +/** + * Base catalogues binary operation class. + * + * A catalogue binary operation performs operation on + * source (the left argument) and target (the right argument) catalogues. + * + * @author Jean-François Simon + */ +abstract class AbstractOperation implements OperationInterface +{ + public const OBSOLETE_BATCH = 'obsolete'; + public const NEW_BATCH = 'new'; + public const ALL_BATCH = 'all'; + + protected $source; + protected $target; + protected $result; + + /** + * @var array|null The domains affected by this operation + */ + private $domains; + + /** + * This array stores 'all', 'new' and 'obsolete' messages for all valid domains. + * + * The data structure of this array is as follows: + * + * [ + * 'domain 1' => [ + * 'all' => [...], + * 'new' => [...], + * 'obsolete' => [...] + * ], + * 'domain 2' => [ + * 'all' => [...], + * 'new' => [...], + * 'obsolete' => [...] + * ], + * ... + * ] + * + * @var array The array that stores 'all', 'new' and 'obsolete' messages + */ + protected $messages; + + /** + * @throws LogicException + */ + public function __construct(MessageCatalogueInterface $source, MessageCatalogueInterface $target) + { + if ($source->getLocale() !== $target->getLocale()) { + throw new LogicException('Operated catalogues must belong to the same locale.'); + } + + $this->source = $source; + $this->target = $target; + $this->result = new MessageCatalogue($source->getLocale()); + $this->messages = []; + } + + /** + * {@inheritdoc} + */ + public function getDomains() + { + if (null === $this->domains) { + $domains = []; + foreach ([$this->source, $this->target] as $catalogue) { + foreach ($catalogue->getDomains() as $domain) { + $domains[$domain] = $domain; + + if ($catalogue->all($domainIcu = $domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX)) { + $domains[$domainIcu] = $domainIcu; + } + } + } + + $this->domains = array_values($domains); + } + + return $this->domains; + } + + /** + * {@inheritdoc} + */ + public function getMessages(string $domain) + { + if (!\in_array($domain, $this->getDomains())) { + throw new InvalidArgumentException(sprintf('Invalid domain: "%s".', $domain)); + } + + if (!isset($this->messages[$domain][self::ALL_BATCH])) { + $this->processDomain($domain); + } + + return $this->messages[$domain][self::ALL_BATCH]; + } + + /** + * {@inheritdoc} + */ + public function getNewMessages(string $domain) + { + if (!\in_array($domain, $this->getDomains())) { + throw new InvalidArgumentException(sprintf('Invalid domain: "%s".', $domain)); + } + + if (!isset($this->messages[$domain][self::NEW_BATCH])) { + $this->processDomain($domain); + } + + return $this->messages[$domain][self::NEW_BATCH]; + } + + /** + * {@inheritdoc} + */ + public function getObsoleteMessages(string $domain) + { + if (!\in_array($domain, $this->getDomains())) { + throw new InvalidArgumentException(sprintf('Invalid domain: "%s".', $domain)); + } + + if (!isset($this->messages[$domain][self::OBSOLETE_BATCH])) { + $this->processDomain($domain); + } + + return $this->messages[$domain][self::OBSOLETE_BATCH]; + } + + /** + * {@inheritdoc} + */ + public function getResult() + { + foreach ($this->getDomains() as $domain) { + if (!isset($this->messages[$domain])) { + $this->processDomain($domain); + } + } + + return $this->result; + } + + /** + * @param self::*_BATCH $batch + */ + public function moveMessagesToIntlDomainsIfPossible(string $batch = self::ALL_BATCH): void + { + // If MessageFormatter class does not exists, intl domains are not supported. + if (!class_exists(\MessageFormatter::class)) { + return; + } + + foreach ($this->getDomains() as $domain) { + $intlDomain = $domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX; + switch ($batch) { + case self::OBSOLETE_BATCH: $messages = $this->getObsoleteMessages($domain); break; + case self::NEW_BATCH: $messages = $this->getNewMessages($domain); break; + case self::ALL_BATCH: $messages = $this->getMessages($domain); break; + default: throw new \InvalidArgumentException(sprintf('$batch argument must be one of ["%s", "%s", "%s"].', self::ALL_BATCH, self::NEW_BATCH, self::OBSOLETE_BATCH)); + } + + if (!$messages || (!$this->source->all($intlDomain) && $this->source->all($domain))) { + continue; + } + + $result = $this->getResult(); + $allIntlMessages = $result->all($intlDomain); + $currentMessages = array_diff_key($messages, $result->all($domain)); + $result->replace($currentMessages, $domain); + $result->replace($allIntlMessages + $messages, $intlDomain); + } + } + + /** + * Performs operation on source and target catalogues for the given domain and + * stores the results. + * + * @param string $domain The domain which the operation will be performed for + */ + abstract protected function processDomain(string $domain); +} diff --git a/vendor/symfony/translation/Catalogue/MergeOperation.php b/vendor/symfony/translation/Catalogue/MergeOperation.php new file mode 100644 index 0000000..87db2fb --- /dev/null +++ b/vendor/symfony/translation/Catalogue/MergeOperation.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Catalogue; + +use Symfony\Component\Translation\MessageCatalogueInterface; + +/** + * Merge operation between two catalogues as follows: + * all = source ∪ target = {x: x ∈ source ∨ x ∈ target} + * new = all ∖ source = {x: x ∈ target ∧ x ∉ source} + * obsolete = source ∖ all = {x: x ∈ source ∧ x ∉ source ∧ x ∉ target} = ∅ + * Basically, the result contains messages from both catalogues. + * + * @author Jean-François Simon + */ +class MergeOperation extends AbstractOperation +{ + /** + * {@inheritdoc} + */ + protected function processDomain(string $domain) + { + $this->messages[$domain] = [ + 'all' => [], + 'new' => [], + 'obsolete' => [], + ]; + $intlDomain = $domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX; + + foreach ($this->source->all($domain) as $id => $message) { + $this->messages[$domain]['all'][$id] = $message; + $d = $this->source->defines($id, $intlDomain) ? $intlDomain : $domain; + $this->result->add([$id => $message], $d); + if (null !== $keyMetadata = $this->source->getMetadata($id, $d)) { + $this->result->setMetadata($id, $keyMetadata, $d); + } + } + + foreach ($this->target->all($domain) as $id => $message) { + if (!$this->source->has($id, $domain)) { + $this->messages[$domain]['all'][$id] = $message; + $this->messages[$domain]['new'][$id] = $message; + $d = $this->target->defines($id, $intlDomain) ? $intlDomain : $domain; + $this->result->add([$id => $message], $d); + if (null !== $keyMetadata = $this->target->getMetadata($id, $d)) { + $this->result->setMetadata($id, $keyMetadata, $d); + } + } + } + } +} diff --git a/vendor/symfony/translation/Catalogue/OperationInterface.php b/vendor/symfony/translation/Catalogue/OperationInterface.php new file mode 100644 index 0000000..9ffac88 --- /dev/null +++ b/vendor/symfony/translation/Catalogue/OperationInterface.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Catalogue; + +use Symfony\Component\Translation\MessageCatalogueInterface; + +/** + * Represents an operation on catalogue(s). + * + * An instance of this interface performs an operation on one or more catalogues and + * stores intermediate and final results of the operation. + * + * The first catalogue in its argument(s) is called the 'source catalogue' or 'source' and + * the following results are stored: + * + * Messages: also called 'all', are valid messages for the given domain after the operation is performed. + * + * New Messages: also called 'new' (new = all ∖ source = {x: x ∈ all ∧ x ∉ source}). + * + * Obsolete Messages: also called 'obsolete' (obsolete = source ∖ all = {x: x ∈ source ∧ x ∉ all}). + * + * Result: also called 'result', is the resulting catalogue for the given domain that holds the same messages as 'all'. + * + * @author Jean-François Simon + */ +interface OperationInterface +{ + /** + * Returns domains affected by operation. + * + * @return array + */ + public function getDomains(); + + /** + * Returns all valid messages ('all') after operation. + * + * @return array + */ + public function getMessages(string $domain); + + /** + * Returns new messages ('new') after operation. + * + * @return array + */ + public function getNewMessages(string $domain); + + /** + * Returns obsolete messages ('obsolete') after operation. + * + * @return array + */ + public function getObsoleteMessages(string $domain); + + /** + * Returns resulting catalogue ('result'). + * + * @return MessageCatalogueInterface + */ + public function getResult(); +} diff --git a/vendor/symfony/translation/Catalogue/TargetOperation.php b/vendor/symfony/translation/Catalogue/TargetOperation.php new file mode 100644 index 0000000..682b575 --- /dev/null +++ b/vendor/symfony/translation/Catalogue/TargetOperation.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Catalogue; + +use Symfony\Component\Translation\MessageCatalogueInterface; + +/** + * Target operation between two catalogues: + * intersection = source ∩ target = {x: x ∈ source ∧ x ∈ target} + * all = intersection ∪ (target ∖ intersection) = target + * new = all ∖ source = {x: x ∈ target ∧ x ∉ source} + * obsolete = source ∖ all = source ∖ target = {x: x ∈ source ∧ x ∉ target} + * Basically, the result contains messages from the target catalogue. + * + * @author Michael Lee + */ +class TargetOperation extends AbstractOperation +{ + /** + * {@inheritdoc} + */ + protected function processDomain(string $domain) + { + $this->messages[$domain] = [ + 'all' => [], + 'new' => [], + 'obsolete' => [], + ]; + $intlDomain = $domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX; + + // For 'all' messages, the code can't be simplified as ``$this->messages[$domain]['all'] = $target->all($domain);``, + // because doing so will drop messages like {x: x ∈ source ∧ x ∉ target.all ∧ x ∈ target.fallback} + // + // For 'new' messages, the code can't be simplified as ``array_diff_assoc($this->target->all($domain), $this->source->all($domain));`` + // because doing so will not exclude messages like {x: x ∈ target ∧ x ∉ source.all ∧ x ∈ source.fallback} + // + // For 'obsolete' messages, the code can't be simplified as ``array_diff_assoc($this->source->all($domain), $this->target->all($domain))`` + // because doing so will not exclude messages like {x: x ∈ source ∧ x ∉ target.all ∧ x ∈ target.fallback} + + foreach ($this->source->all($domain) as $id => $message) { + if ($this->target->has($id, $domain)) { + $this->messages[$domain]['all'][$id] = $message; + $d = $this->source->defines($id, $intlDomain) ? $intlDomain : $domain; + $this->result->add([$id => $message], $d); + if (null !== $keyMetadata = $this->source->getMetadata($id, $d)) { + $this->result->setMetadata($id, $keyMetadata, $d); + } + } else { + $this->messages[$domain]['obsolete'][$id] = $message; + } + } + + foreach ($this->target->all($domain) as $id => $message) { + if (!$this->source->has($id, $domain)) { + $this->messages[$domain]['all'][$id] = $message; + $this->messages[$domain]['new'][$id] = $message; + $d = $this->target->defines($id, $intlDomain) ? $intlDomain : $domain; + $this->result->add([$id => $message], $d); + if (null !== $keyMetadata = $this->target->getMetadata($id, $d)) { + $this->result->setMetadata($id, $keyMetadata, $d); + } + } + } + } +} diff --git a/vendor/symfony/translation/Command/TranslationPullCommand.php b/vendor/symfony/translation/Command/TranslationPullCommand.php new file mode 100644 index 0000000..e2e7c00 --- /dev/null +++ b/vendor/symfony/translation/Command/TranslationPullCommand.php @@ -0,0 +1,188 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Command; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Translation\Catalogue\TargetOperation; +use Symfony\Component\Translation\MessageCatalogue; +use Symfony\Component\Translation\Provider\TranslationProviderCollection; +use Symfony\Component\Translation\Reader\TranslationReaderInterface; +use Symfony\Component\Translation\Writer\TranslationWriterInterface; + +/** + * @author Mathieu Santostefano + */ +final class TranslationPullCommand extends Command +{ + use TranslationTrait; + + protected static $defaultName = 'translation:pull'; + protected static $defaultDescription = 'Pull translations from a given provider.'; + + private $providerCollection; + private $writer; + private $reader; + private $defaultLocale; + private $transPaths; + private $enabledLocales; + + public function __construct(TranslationProviderCollection $providerCollection, TranslationWriterInterface $writer, TranslationReaderInterface $reader, string $defaultLocale, array $transPaths = [], array $enabledLocales = []) + { + $this->providerCollection = $providerCollection; + $this->writer = $writer; + $this->reader = $reader; + $this->defaultLocale = $defaultLocale; + $this->transPaths = $transPaths; + $this->enabledLocales = $enabledLocales; + + parent::__construct(); + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ($input->mustSuggestArgumentValuesFor('provider')) { + $suggestions->suggestValues($this->providerCollection->keys()); + + return; + } + + if ($input->mustSuggestOptionValuesFor('domains')) { + $provider = $this->providerCollection->get($input->getArgument('provider')); + + if ($provider && method_exists($provider, 'getDomains')) { + $domains = $provider->getDomains(); + $suggestions->suggestValues($domains); + } + + return; + } + + if ($input->mustSuggestOptionValuesFor('locales')) { + $suggestions->suggestValues($this->enabledLocales); + + return; + } + + if ($input->mustSuggestOptionValuesFor('format')) { + $suggestions->suggestValues(['php', 'xlf', 'xlf12', 'xlf20', 'po', 'mo', 'yml', 'yaml', 'ts', 'csv', 'json', 'ini', 'res']); + } + } + + /** + * {@inheritdoc} + */ + protected function configure() + { + $keys = $this->providerCollection->keys(); + $defaultProvider = 1 === \count($keys) ? $keys[0] : null; + + $this + ->setDefinition([ + new InputArgument('provider', null !== $defaultProvider ? InputArgument::OPTIONAL : InputArgument::REQUIRED, 'The provider to pull translations from.', $defaultProvider), + new InputOption('force', null, InputOption::VALUE_NONE, 'Override existing translations with provider ones (it will delete not synchronized messages).'), + new InputOption('intl-icu', null, InputOption::VALUE_NONE, 'Associated to --force option, it will write messages in "%domain%+intl-icu.%locale%.xlf" files.'), + new InputOption('domains', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Specify the domains to pull.'), + new InputOption('locales', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Specify the locales to pull.'), + new InputOption('format', null, InputOption::VALUE_OPTIONAL, 'Override the default output format.', 'xlf12'), + ]) + ->setHelp(<<<'EOF' +The %command.name% command pulls translations from the given provider. Only +new translations are pulled, existing ones are not overwritten. + +You can overwrite existing translations (and remove the missing ones on local side) by using the --force flag: + + php %command.full_name% --force provider + +Full example: + + php %command.full_name% provider --force --domains=messages --domains=validators --locales=en + +This command pulls all translations associated with the messages and validators domains for the en locale. +Local translations for the specified domains and locale are deleted if they're not present on the provider and overwritten if it's the case. +Local translations for others domains and locales are ignored. +EOF + ) + ; + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + + $provider = $this->providerCollection->get($input->getArgument('provider')); + $force = $input->getOption('force'); + $intlIcu = $input->getOption('intl-icu'); + $locales = $input->getOption('locales') ?: $this->enabledLocales; + $domains = $input->getOption('domains'); + $format = $input->getOption('format'); + $xliffVersion = '1.2'; + + if ($intlIcu && !$force) { + $io->note('--intl-icu option only has an effect when used with --force. Here, it will be ignored.'); + } + + switch ($format) { + case 'xlf20': $xliffVersion = '2.0'; + // no break + case 'xlf12': $format = 'xlf'; + } + + $writeOptions = [ + 'path' => end($this->transPaths), + 'xliff_version' => $xliffVersion, + 'default_locale' => $this->defaultLocale, + ]; + + if (!$domains) { + $domains = $provider->getDomains(); + } + + $providerTranslations = $provider->read($domains, $locales); + + if ($force) { + foreach ($providerTranslations->getCatalogues() as $catalogue) { + $operation = new TargetOperation(new MessageCatalogue($catalogue->getLocale()), $catalogue); + if ($intlIcu) { + $operation->moveMessagesToIntlDomainsIfPossible(); + } + $this->writer->write($operation->getResult(), $format, $writeOptions); + } + + $io->success(sprintf('Local translations has been updated from "%s" (for "%s" locale(s), and "%s" domain(s)).', parse_url($provider, \PHP_URL_SCHEME), implode(', ', $locales), implode(', ', $domains))); + + return 0; + } + + $localTranslations = $this->readLocalTranslations($locales, $domains, $this->transPaths); + + // Append pulled translations to local ones. + $localTranslations->addBag($providerTranslations->diff($localTranslations)); + + foreach ($localTranslations->getCatalogues() as $catalogue) { + $this->writer->write($catalogue, $format, $writeOptions); + } + + $io->success(sprintf('New translations from "%s" has been written locally (for "%s" locale(s), and "%s" domain(s)).', parse_url($provider, \PHP_URL_SCHEME), implode(', ', $locales), implode(', ', $domains))); + + return 0; + } +} diff --git a/vendor/symfony/translation/Command/TranslationPushCommand.php b/vendor/symfony/translation/Command/TranslationPushCommand.php new file mode 100644 index 0000000..bf6e8c9 --- /dev/null +++ b/vendor/symfony/translation/Command/TranslationPushCommand.php @@ -0,0 +1,189 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Command; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Translation\Provider\FilteringProvider; +use Symfony\Component\Translation\Provider\TranslationProviderCollection; +use Symfony\Component\Translation\Reader\TranslationReaderInterface; +use Symfony\Component\Translation\TranslatorBag; + +/** + * @author Mathieu Santostefano + */ +final class TranslationPushCommand extends Command +{ + use TranslationTrait; + + protected static $defaultName = 'translation:push'; + protected static $defaultDescription = 'Push translations to a given provider.'; + + private $providers; + private $reader; + private $transPaths; + private $enabledLocales; + + public function __construct(TranslationProviderCollection $providers, TranslationReaderInterface $reader, array $transPaths = [], array $enabledLocales = []) + { + $this->providers = $providers; + $this->reader = $reader; + $this->transPaths = $transPaths; + $this->enabledLocales = $enabledLocales; + + parent::__construct(); + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ($input->mustSuggestArgumentValuesFor('provider')) { + $suggestions->suggestValues($this->providers->keys()); + + return; + } + + if ($input->mustSuggestOptionValuesFor('domains')) { + $provider = $this->providers->get($input->getArgument('provider')); + + if ($provider && method_exists($provider, 'getDomains')) { + $domains = $provider->getDomains(); + $suggestions->suggestValues($domains); + } + + return; + } + + if ($input->mustSuggestOptionValuesFor('locales')) { + $suggestions->suggestValues($this->enabledLocales); + } + } + + /** + * {@inheritdoc} + */ + protected function configure() + { + $keys = $this->providers->keys(); + $defaultProvider = 1 === \count($keys) ? $keys[0] : null; + + $this + ->setDefinition([ + new InputArgument('provider', null !== $defaultProvider ? InputArgument::OPTIONAL : InputArgument::REQUIRED, 'The provider to push translations to.', $defaultProvider), + new InputOption('force', null, InputOption::VALUE_NONE, 'Override existing translations with local ones (it will delete not synchronized messages).'), + new InputOption('delete-missing', null, InputOption::VALUE_NONE, 'Delete translations available on provider but not locally.'), + new InputOption('domains', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Specify the domains to push.'), + new InputOption('locales', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Specify the locales to push.', $this->enabledLocales), + ]) + ->setHelp(<<<'EOF' +The %command.name% command pushes translations to the given provider. Only new +translations are pushed, existing ones are not overwritten. + +You can overwrite existing translations by using the --force flag: + + php %command.full_name% --force provider + +You can delete provider translations which are not present locally by using the --delete-missing flag: + + php %command.full_name% --delete-missing provider + +Full example: + + php %command.full_name% provider --force --delete-missing --domains=messages --domains=validators --locales=en + +This command pushes all translations associated with the messages and validators domains for the en locale. +Provider translations for the specified domains and locale are deleted if they're not present locally and overwritten if it's the case. +Provider translations for others domains and locales are ignored. +EOF + ) + ; + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + $provider = $this->providers->get($input->getArgument('provider')); + + if (!$this->enabledLocales) { + throw new InvalidArgumentException(sprintf('You must define "framework.translator.enabled_locales" or "framework.translator.providers.%s.locales" config key in order to work with translation providers.', parse_url($provider, \PHP_URL_SCHEME))); + } + + $io = new SymfonyStyle($input, $output); + $domains = $input->getOption('domains'); + $locales = $input->getOption('locales'); + $force = $input->getOption('force'); + $deleteMissing = $input->getOption('delete-missing'); + + if (!$domains && $provider instanceof FilteringProvider) { + $domains = $provider->getDomains(); + } + + // Reading local translations must be done after retrieving the domains from the provider + // in order to manage only translations from configured domains + $localTranslations = $this->readLocalTranslations($locales, $domains, $this->transPaths); + + if (!$domains) { + $domains = $this->getDomainsFromTranslatorBag($localTranslations); + } + + if (!$deleteMissing && $force) { + $provider->write($localTranslations); + + $io->success(sprintf('All local translations has been sent to "%s" (for "%s" locale(s), and "%s" domain(s)).', parse_url($provider, \PHP_URL_SCHEME), implode(', ', $locales), implode(', ', $domains))); + + return 0; + } + + $providerTranslations = $provider->read($domains, $locales); + + if ($deleteMissing) { + $provider->delete($providerTranslations->diff($localTranslations)); + + $io->success(sprintf('Missing translations on "%s" has been deleted (for "%s" locale(s), and "%s" domain(s)).', parse_url($provider, \PHP_URL_SCHEME), implode(', ', $locales), implode(', ', $domains))); + + // Read provider translations again, after missing translations deletion, + // to avoid push freshly deleted translations. + $providerTranslations = $provider->read($domains, $locales); + } + + $translationsToWrite = $localTranslations->diff($providerTranslations); + + if ($force) { + $translationsToWrite->addBag($localTranslations->intersect($providerTranslations)); + } + + $provider->write($translationsToWrite); + + $io->success(sprintf('%s local translations has been sent to "%s" (for "%s" locale(s), and "%s" domain(s)).', $force ? 'All' : 'New', parse_url($provider, \PHP_URL_SCHEME), implode(', ', $locales), implode(', ', $domains))); + + return 0; + } + + private function getDomainsFromTranslatorBag(TranslatorBag $translatorBag): array + { + $domains = []; + + foreach ($translatorBag->getCatalogues() as $catalogue) { + $domains += $catalogue->getDomains(); + } + + return array_unique($domains); + } +} diff --git a/vendor/symfony/translation/Command/TranslationTrait.php b/vendor/symfony/translation/Command/TranslationTrait.php new file mode 100644 index 0000000..eafaffd --- /dev/null +++ b/vendor/symfony/translation/Command/TranslationTrait.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Command; + +use Symfony\Component\Translation\MessageCatalogue; +use Symfony\Component\Translation\MessageCatalogueInterface; +use Symfony\Component\Translation\TranslatorBag; + +/** + * @internal + */ +trait TranslationTrait +{ + private function readLocalTranslations(array $locales, array $domains, array $transPaths): TranslatorBag + { + $bag = new TranslatorBag(); + + foreach ($locales as $locale) { + $catalogue = new MessageCatalogue($locale); + foreach ($transPaths as $path) { + $this->reader->read($path, $catalogue); + } + + if ($domains) { + foreach ($domains as $domain) { + $bag->addCatalogue($this->filterCatalogue($catalogue, $domain)); + } + } else { + $bag->addCatalogue($catalogue); + } + } + + return $bag; + } + + private function filterCatalogue(MessageCatalogue $catalogue, string $domain): MessageCatalogue + { + $filteredCatalogue = new MessageCatalogue($catalogue->getLocale()); + + // extract intl-icu messages only + $intlDomain = $domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX; + if ($intlMessages = $catalogue->all($intlDomain)) { + $filteredCatalogue->add($intlMessages, $intlDomain); + } + + // extract all messages and subtract intl-icu messages + if ($messages = array_diff($catalogue->all($domain), $intlMessages)) { + $filteredCatalogue->add($messages, $domain); + } + foreach ($catalogue->getResources() as $resource) { + $filteredCatalogue->addResource($resource); + } + + if ($metadata = $catalogue->getMetadata('', $intlDomain)) { + foreach ($metadata as $k => $v) { + $filteredCatalogue->setMetadata($k, $v, $intlDomain); + } + } + + if ($metadata = $catalogue->getMetadata('', $domain)) { + foreach ($metadata as $k => $v) { + $filteredCatalogue->setMetadata($k, $v, $domain); + } + } + + return $filteredCatalogue; + } +} diff --git a/vendor/symfony/translation/Command/XliffLintCommand.php b/vendor/symfony/translation/Command/XliffLintCommand.php new file mode 100644 index 0000000..fb2b5f3 --- /dev/null +++ b/vendor/symfony/translation/Command/XliffLintCommand.php @@ -0,0 +1,286 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Command; + +use Symfony\Component\Console\CI\GithubActionReporter; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Exception\RuntimeException; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Translation\Exception\InvalidArgumentException; +use Symfony\Component\Translation\Util\XliffUtils; + +/** + * Validates XLIFF files syntax and outputs encountered errors. + * + * @author Grégoire Pineau + * @author Robin Chalas + * @author Javier Eguiluz + */ +class XliffLintCommand extends Command +{ + protected static $defaultName = 'lint:xliff'; + protected static $defaultDescription = 'Lint an XLIFF file and outputs encountered errors'; + + private $format; + private $displayCorrectFiles; + private $directoryIteratorProvider; + private $isReadableProvider; + private $requireStrictFileNames; + + public function __construct(string $name = null, callable $directoryIteratorProvider = null, callable $isReadableProvider = null, bool $requireStrictFileNames = true) + { + parent::__construct($name); + + $this->directoryIteratorProvider = $directoryIteratorProvider; + $this->isReadableProvider = $isReadableProvider; + $this->requireStrictFileNames = $requireStrictFileNames; + } + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setDescription(self::$defaultDescription) + ->addArgument('filename', InputArgument::IS_ARRAY, 'A file, a directory or "-" for reading from STDIN') + ->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format') + ->setHelp(<<%command.name% command lints an XLIFF file and outputs to STDOUT +the first encountered syntax error. + +You can validates XLIFF contents passed from STDIN: + + cat filename | php %command.full_name% - + +You can also validate the syntax of a file: + + php %command.full_name% filename + +Or of a whole directory: + + php %command.full_name% dirname + php %command.full_name% dirname --format=json + +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $io = new SymfonyStyle($input, $output); + $filenames = (array) $input->getArgument('filename'); + $this->format = $input->getOption('format') ?? (GithubActionReporter::isGithubActionEnvironment() ? 'github' : 'txt'); + $this->displayCorrectFiles = $output->isVerbose(); + + if (['-'] === $filenames) { + return $this->display($io, [$this->validate(file_get_contents('php://stdin'))]); + } + + if (!$filenames) { + throw new RuntimeException('Please provide a filename or pipe file content to STDIN.'); + } + + $filesInfo = []; + foreach ($filenames as $filename) { + if (!$this->isReadable($filename)) { + throw new RuntimeException(sprintf('File or directory "%s" is not readable.', $filename)); + } + + foreach ($this->getFiles($filename) as $file) { + $filesInfo[] = $this->validate(file_get_contents($file), $file); + } + } + + return $this->display($io, $filesInfo); + } + + private function validate(string $content, string $file = null): array + { + $errors = []; + + // Avoid: Warning DOMDocument::loadXML(): Empty string supplied as input + if ('' === trim($content)) { + return ['file' => $file, 'valid' => true]; + } + + $internal = libxml_use_internal_errors(true); + + $document = new \DOMDocument(); + $document->loadXML($content); + + if (null !== $targetLanguage = $this->getTargetLanguageFromFile($document)) { + $normalizedLocalePattern = sprintf('(%s|%s)', preg_quote($targetLanguage, '/'), preg_quote(str_replace('-', '_', $targetLanguage), '/')); + // strict file names require translation files to be named '____.locale.xlf' + // otherwise, both '____.locale.xlf' and 'locale.____.xlf' are allowed + // also, the regexp matching must be case-insensitive, as defined for 'target-language' values + // http://docs.oasis-open.org/xliff/v1.2/os/xliff-core.html#target-language + $expectedFilenamePattern = $this->requireStrictFileNames ? sprintf('/^.*\.(?i:%s)\.(?:xlf|xliff)/', $normalizedLocalePattern) : sprintf('/^(?:.*\.(?i:%s)|(?i:%s)\..*)\.(?:xlf|xliff)/', $normalizedLocalePattern, $normalizedLocalePattern); + + if (0 === preg_match($expectedFilenamePattern, basename($file))) { + $errors[] = [ + 'line' => -1, + 'column' => -1, + 'message' => sprintf('There is a mismatch between the language included in the file name ("%s") and the "%s" value used in the "target-language" attribute of the file.', basename($file), $targetLanguage), + ]; + } + } + + foreach (XliffUtils::validateSchema($document) as $xmlError) { + $errors[] = [ + 'line' => $xmlError['line'], + 'column' => $xmlError['column'], + 'message' => $xmlError['message'], + ]; + } + + libxml_clear_errors(); + libxml_use_internal_errors($internal); + + return ['file' => $file, 'valid' => 0 === \count($errors), 'messages' => $errors]; + } + + private function display(SymfonyStyle $io, array $files) + { + switch ($this->format) { + case 'txt': + return $this->displayTxt($io, $files); + case 'json': + return $this->displayJson($io, $files); + case 'github': + return $this->displayTxt($io, $files, true); + default: + throw new InvalidArgumentException(sprintf('The format "%s" is not supported.', $this->format)); + } + } + + private function displayTxt(SymfonyStyle $io, array $filesInfo, bool $errorAsGithubAnnotations = false) + { + $countFiles = \count($filesInfo); + $erroredFiles = 0; + $githubReporter = $errorAsGithubAnnotations ? new GithubActionReporter($io) : null; + + foreach ($filesInfo as $info) { + if ($info['valid'] && $this->displayCorrectFiles) { + $io->comment('OK'.($info['file'] ? sprintf(' in %s', $info['file']) : '')); + } elseif (!$info['valid']) { + ++$erroredFiles; + $io->text(' ERROR '.($info['file'] ? sprintf(' in %s', $info['file']) : '')); + $io->listing(array_map(function ($error) use ($info, $githubReporter) { + // general document errors have a '-1' line number + $line = -1 === $error['line'] ? null : $error['line']; + + if ($githubReporter) { + $githubReporter->error($error['message'], $info['file'], $line, null !== $line ? $error['column'] : null); + } + + return null === $line ? $error['message'] : sprintf('Line %d, Column %d: %s', $line, $error['column'], $error['message']); + }, $info['messages'])); + } + } + + if (0 === $erroredFiles) { + $io->success(sprintf('All %d XLIFF files contain valid syntax.', $countFiles)); + } else { + $io->warning(sprintf('%d XLIFF files have valid syntax and %d contain errors.', $countFiles - $erroredFiles, $erroredFiles)); + } + + return min($erroredFiles, 1); + } + + private function displayJson(SymfonyStyle $io, array $filesInfo) + { + $errors = 0; + + array_walk($filesInfo, function (&$v) use (&$errors) { + $v['file'] = (string) $v['file']; + if (!$v['valid']) { + ++$errors; + } + }); + + $io->writeln(json_encode($filesInfo, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES)); + + return min($errors, 1); + } + + private function getFiles(string $fileOrDirectory) + { + if (is_file($fileOrDirectory)) { + yield new \SplFileInfo($fileOrDirectory); + + return; + } + + foreach ($this->getDirectoryIterator($fileOrDirectory) as $file) { + if (!\in_array($file->getExtension(), ['xlf', 'xliff'])) { + continue; + } + + yield $file; + } + } + + private function getDirectoryIterator(string $directory) + { + $default = function ($directory) { + return new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS), + \RecursiveIteratorIterator::LEAVES_ONLY + ); + }; + + if (null !== $this->directoryIteratorProvider) { + return ($this->directoryIteratorProvider)($directory, $default); + } + + return $default($directory); + } + + private function isReadable(string $fileOrDirectory) + { + $default = function ($fileOrDirectory) { + return is_readable($fileOrDirectory); + }; + + if (null !== $this->isReadableProvider) { + return ($this->isReadableProvider)($fileOrDirectory, $default); + } + + return $default($fileOrDirectory); + } + + private function getTargetLanguageFromFile(\DOMDocument $xliffContents): ?string + { + foreach ($xliffContents->getElementsByTagName('file')[0]->attributes ?? [] as $attribute) { + if ('target-language' === $attribute->nodeName) { + return $attribute->nodeValue; + } + } + + return null; + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ($input->mustSuggestOptionValuesFor('format')) { + $suggestions->suggestValues(['txt', 'json', 'github']); + } + } +} diff --git a/vendor/symfony/translation/DataCollector/TranslationDataCollector.php b/vendor/symfony/translation/DataCollector/TranslationDataCollector.php new file mode 100644 index 0000000..379130a --- /dev/null +++ b/vendor/symfony/translation/DataCollector/TranslationDataCollector.php @@ -0,0 +1,163 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\DataCollector; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\DataCollector\DataCollector; +use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface; +use Symfony\Component\Translation\DataCollectorTranslator; +use Symfony\Component\VarDumper\Cloner\Data; + +/** + * @author Abdellatif Ait boudad + * + * @final + */ +class TranslationDataCollector extends DataCollector implements LateDataCollectorInterface +{ + private $translator; + + public function __construct(DataCollectorTranslator $translator) + { + $this->translator = $translator; + } + + /** + * {@inheritdoc} + */ + public function lateCollect() + { + $messages = $this->sanitizeCollectedMessages($this->translator->getCollectedMessages()); + + $this->data += $this->computeCount($messages); + $this->data['messages'] = $messages; + + $this->data = $this->cloneVar($this->data); + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Throwable $exception = null) + { + $this->data['locale'] = $this->translator->getLocale(); + $this->data['fallback_locales'] = $this->translator->getFallbackLocales(); + } + + /** + * {@inheritdoc} + */ + public function reset() + { + $this->data = []; + } + + /** + * @return array|Data + */ + public function getMessages() + { + return $this->data['messages'] ?? []; + } + + public function getCountMissings(): int + { + return $this->data[DataCollectorTranslator::MESSAGE_MISSING] ?? 0; + } + + public function getCountFallbacks(): int + { + return $this->data[DataCollectorTranslator::MESSAGE_EQUALS_FALLBACK] ?? 0; + } + + public function getCountDefines(): int + { + return $this->data[DataCollectorTranslator::MESSAGE_DEFINED] ?? 0; + } + + public function getLocale() + { + return !empty($this->data['locale']) ? $this->data['locale'] : null; + } + + /** + * @internal + */ + public function getFallbackLocales() + { + return (isset($this->data['fallback_locales']) && \count($this->data['fallback_locales']) > 0) ? $this->data['fallback_locales'] : []; + } + + /** + * {@inheritdoc} + */ + public function getName(): string + { + return 'translation'; + } + + private function sanitizeCollectedMessages(array $messages) + { + $result = []; + foreach ($messages as $key => $message) { + $messageId = $message['locale'].$message['domain'].$message['id']; + + if (!isset($result[$messageId])) { + $message['count'] = 1; + $message['parameters'] = !empty($message['parameters']) ? [$message['parameters']] : []; + $messages[$key]['translation'] = $this->sanitizeString($message['translation']); + $result[$messageId] = $message; + } else { + if (!empty($message['parameters'])) { + $result[$messageId]['parameters'][] = $message['parameters']; + } + + ++$result[$messageId]['count']; + } + + unset($messages[$key]); + } + + return $result; + } + + private function computeCount(array $messages) + { + $count = [ + DataCollectorTranslator::MESSAGE_DEFINED => 0, + DataCollectorTranslator::MESSAGE_MISSING => 0, + DataCollectorTranslator::MESSAGE_EQUALS_FALLBACK => 0, + ]; + + foreach ($messages as $message) { + ++$count[$message['state']]; + } + + return $count; + } + + private function sanitizeString(string $string, int $length = 80) + { + $string = trim(preg_replace('/\s+/', ' ', $string)); + + if (false !== $encoding = mb_detect_encoding($string, null, true)) { + if (mb_strlen($string, $encoding) > $length) { + return mb_substr($string, 0, $length - 3, $encoding).'...'; + } + } elseif (\strlen($string) > $length) { + return substr($string, 0, $length - 3).'...'; + } + + return $string; + } +} diff --git a/vendor/symfony/translation/DataCollectorTranslator.php b/vendor/symfony/translation/DataCollectorTranslator.php new file mode 100644 index 0000000..ea5a2dd --- /dev/null +++ b/vendor/symfony/translation/DataCollectorTranslator.php @@ -0,0 +1,167 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation; + +use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface; +use Symfony\Component\Translation\Exception\InvalidArgumentException; +use Symfony\Contracts\Translation\LocaleAwareInterface; +use Symfony\Contracts\Translation\TranslatorInterface; + +/** + * @author Abdellatif Ait boudad + */ +class DataCollectorTranslator implements TranslatorInterface, TranslatorBagInterface, LocaleAwareInterface, WarmableInterface +{ + public const MESSAGE_DEFINED = 0; + public const MESSAGE_MISSING = 1; + public const MESSAGE_EQUALS_FALLBACK = 2; + + private $translator; + private $messages = []; + + /** + * @param TranslatorInterface&TranslatorBagInterface&LocaleAwareInterface $translator + */ + public function __construct(TranslatorInterface $translator) + { + if (!$translator instanceof TranslatorBagInterface || !$translator instanceof LocaleAwareInterface) { + throw new InvalidArgumentException(sprintf('The Translator "%s" must implement TranslatorInterface, TranslatorBagInterface and LocaleAwareInterface.', get_debug_type($translator))); + } + + $this->translator = $translator; + } + + /** + * {@inheritdoc} + */ + public function trans(?string $id, array $parameters = [], string $domain = null, string $locale = null) + { + $trans = $this->translator->trans($id = (string) $id, $parameters, $domain, $locale); + $this->collectMessage($locale, $domain, $id, $trans, $parameters); + + return $trans; + } + + /** + * {@inheritdoc} + */ + public function setLocale(string $locale) + { + $this->translator->setLocale($locale); + } + + /** + * {@inheritdoc} + */ + public function getLocale() + { + return $this->translator->getLocale(); + } + + /** + * {@inheritdoc} + */ + public function getCatalogue(string $locale = null) + { + return $this->translator->getCatalogue($locale); + } + + /** + * {@inheritdoc} + */ + public function getCatalogues(): array + { + return $this->translator->getCatalogues(); + } + + /** + * {@inheritdoc} + * + * @return string[] + */ + public function warmUp(string $cacheDir) + { + if ($this->translator instanceof WarmableInterface) { + return (array) $this->translator->warmUp($cacheDir); + } + + return []; + } + + /** + * Gets the fallback locales. + * + * @return array + */ + public function getFallbackLocales() + { + if ($this->translator instanceof Translator || method_exists($this->translator, 'getFallbackLocales')) { + return $this->translator->getFallbackLocales(); + } + + return []; + } + + /** + * Passes through all unknown calls onto the translator object. + */ + public function __call(string $method, array $args) + { + return $this->translator->{$method}(...$args); + } + + /** + * @return array + */ + public function getCollectedMessages() + { + return $this->messages; + } + + private function collectMessage(?string $locale, ?string $domain, string $id, string $translation, ?array $parameters = []) + { + if (null === $domain) { + $domain = 'messages'; + } + + $catalogue = $this->translator->getCatalogue($locale); + $locale = $catalogue->getLocale(); + $fallbackLocale = null; + if ($catalogue->defines($id, $domain)) { + $state = self::MESSAGE_DEFINED; + } elseif ($catalogue->has($id, $domain)) { + $state = self::MESSAGE_EQUALS_FALLBACK; + + $fallbackCatalogue = $catalogue->getFallbackCatalogue(); + while ($fallbackCatalogue) { + if ($fallbackCatalogue->defines($id, $domain)) { + $fallbackLocale = $fallbackCatalogue->getLocale(); + break; + } + $fallbackCatalogue = $fallbackCatalogue->getFallbackCatalogue(); + } + } else { + $state = self::MESSAGE_MISSING; + } + + $this->messages[] = [ + 'locale' => $locale, + 'fallbackLocale' => $fallbackLocale, + 'domain' => $domain, + 'id' => $id, + 'translation' => $translation, + 'parameters' => $parameters, + 'state' => $state, + 'transChoiceNumber' => isset($parameters['%count%']) && is_numeric($parameters['%count%']) ? $parameters['%count%'] : null, + ]; + } +} diff --git a/vendor/symfony/translation/DependencyInjection/TranslationDumperPass.php b/vendor/symfony/translation/DependencyInjection/TranslationDumperPass.php new file mode 100644 index 0000000..6d78342 --- /dev/null +++ b/vendor/symfony/translation/DependencyInjection/TranslationDumperPass.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Adds tagged translation.formatter services to translation writer. + */ +class TranslationDumperPass implements CompilerPassInterface +{ + private $writerServiceId; + private $dumperTag; + + public function __construct(string $writerServiceId = 'translation.writer', string $dumperTag = 'translation.dumper') + { + if (1 < \func_num_args()) { + trigger_deprecation('symfony/translation', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); + } + + $this->writerServiceId = $writerServiceId; + $this->dumperTag = $dumperTag; + } + + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition($this->writerServiceId)) { + return; + } + + $definition = $container->getDefinition($this->writerServiceId); + + foreach ($container->findTaggedServiceIds($this->dumperTag, true) as $id => $attributes) { + $definition->addMethodCall('addDumper', [$attributes[0]['alias'], new Reference($id)]); + } + } +} diff --git a/vendor/symfony/translation/DependencyInjection/TranslationExtractorPass.php b/vendor/symfony/translation/DependencyInjection/TranslationExtractorPass.php new file mode 100644 index 0000000..fab6b20 --- /dev/null +++ b/vendor/symfony/translation/DependencyInjection/TranslationExtractorPass.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Adds tagged translation.extractor services to translation extractor. + */ +class TranslationExtractorPass implements CompilerPassInterface +{ + private $extractorServiceId; + private $extractorTag; + + public function __construct(string $extractorServiceId = 'translation.extractor', string $extractorTag = 'translation.extractor') + { + if (0 < \func_num_args()) { + trigger_deprecation('symfony/translation', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); + } + + $this->extractorServiceId = $extractorServiceId; + $this->extractorTag = $extractorTag; + } + + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition($this->extractorServiceId)) { + return; + } + + $definition = $container->getDefinition($this->extractorServiceId); + + foreach ($container->findTaggedServiceIds($this->extractorTag, true) as $id => $attributes) { + if (!isset($attributes[0]['alias'])) { + throw new RuntimeException(sprintf('The alias for the tag "translation.extractor" of service "%s" must be set.', $id)); + } + + $definition->addMethodCall('addExtractor', [$attributes[0]['alias'], new Reference($id)]); + } + } +} diff --git a/vendor/symfony/translation/DependencyInjection/TranslatorPass.php b/vendor/symfony/translation/DependencyInjection/TranslatorPass.php new file mode 100644 index 0000000..e6a4b36 --- /dev/null +++ b/vendor/symfony/translation/DependencyInjection/TranslatorPass.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +class TranslatorPass implements CompilerPassInterface +{ + private $translatorServiceId; + private $readerServiceId; + private $loaderTag; + private $debugCommandServiceId; + private $updateCommandServiceId; + + public function __construct(string $translatorServiceId = 'translator.default', string $readerServiceId = 'translation.reader', string $loaderTag = 'translation.loader', string $debugCommandServiceId = 'console.command.translation_debug', string $updateCommandServiceId = 'console.command.translation_extract') + { + if (0 < \func_num_args()) { + trigger_deprecation('symfony/translation', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); + } + + $this->translatorServiceId = $translatorServiceId; + $this->readerServiceId = $readerServiceId; + $this->loaderTag = $loaderTag; + $this->debugCommandServiceId = $debugCommandServiceId; + $this->updateCommandServiceId = $updateCommandServiceId; + } + + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition($this->translatorServiceId)) { + return; + } + + $loaders = []; + $loaderRefs = []; + foreach ($container->findTaggedServiceIds($this->loaderTag, true) as $id => $attributes) { + $loaderRefs[$id] = new Reference($id); + $loaders[$id][] = $attributes[0]['alias']; + if (isset($attributes[0]['legacy-alias'])) { + $loaders[$id][] = $attributes[0]['legacy-alias']; + } + } + + if ($container->hasDefinition($this->readerServiceId)) { + $definition = $container->getDefinition($this->readerServiceId); + foreach ($loaders as $id => $formats) { + foreach ($formats as $format) { + $definition->addMethodCall('addLoader', [$format, $loaderRefs[$id]]); + } + } + } + + $container + ->findDefinition($this->translatorServiceId) + ->replaceArgument(0, ServiceLocatorTagPass::register($container, $loaderRefs)) + ->replaceArgument(3, $loaders) + ; + + if (!$container->hasParameter('twig.default_path')) { + return; + } + + $paths = array_keys($container->getDefinition('twig.template_iterator')->getArgument(1)); + if ($container->hasDefinition($this->debugCommandServiceId)) { + $definition = $container->getDefinition($this->debugCommandServiceId); + $definition->replaceArgument(4, $container->getParameter('twig.default_path')); + + if (\count($definition->getArguments()) > 6) { + $definition->replaceArgument(6, $paths); + } + } + if ($container->hasDefinition($this->updateCommandServiceId)) { + $definition = $container->getDefinition($this->updateCommandServiceId); + $definition->replaceArgument(5, $container->getParameter('twig.default_path')); + + if (\count($definition->getArguments()) > 7) { + $definition->replaceArgument(7, $paths); + } + } + } +} diff --git a/vendor/symfony/translation/DependencyInjection/TranslatorPathsPass.php b/vendor/symfony/translation/DependencyInjection/TranslatorPathsPass.php new file mode 100644 index 0000000..2ee1364 --- /dev/null +++ b/vendor/symfony/translation/DependencyInjection/TranslatorPathsPass.php @@ -0,0 +1,156 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\AbstractRecursivePass; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\ServiceLocator; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\TraceableValueResolver; + +/** + * @author Yonel Ceruto + */ +class TranslatorPathsPass extends AbstractRecursivePass +{ + private $translatorServiceId; + private $debugCommandServiceId; + private $updateCommandServiceId; + private $resolverServiceId; + private $level = 0; + + /** + * @var array + */ + private $paths = []; + + /** + * @var array + */ + private $definitions = []; + + /** + * @var array> + */ + private $controllers = []; + + public function __construct(string $translatorServiceId = 'translator', string $debugCommandServiceId = 'console.command.translation_debug', string $updateCommandServiceId = 'console.command.translation_extract', string $resolverServiceId = 'argument_resolver.service') + { + if (0 < \func_num_args()) { + trigger_deprecation('symfony/translation', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); + } + + $this->translatorServiceId = $translatorServiceId; + $this->debugCommandServiceId = $debugCommandServiceId; + $this->updateCommandServiceId = $updateCommandServiceId; + $this->resolverServiceId = $resolverServiceId; + } + + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition($this->translatorServiceId)) { + return; + } + + foreach ($this->findControllerArguments($container) as $controller => $argument) { + $id = substr($controller, 0, strpos($controller, ':') ?: \strlen($controller)); + if ($container->hasDefinition($id)) { + [$locatorRef] = $argument->getValues(); + $this->controllers[(string) $locatorRef][$container->getDefinition($id)->getClass()] = true; + } + } + + try { + parent::process($container); + + $paths = []; + foreach ($this->paths as $class => $_) { + if (($r = $container->getReflectionClass($class)) && !$r->isInterface()) { + $paths[] = $r->getFileName(); + foreach ($r->getTraits() as $trait) { + $paths[] = $trait->getFileName(); + } + } + } + if ($paths) { + if ($container->hasDefinition($this->debugCommandServiceId)) { + $definition = $container->getDefinition($this->debugCommandServiceId); + $definition->replaceArgument(6, array_merge($definition->getArgument(6), $paths)); + } + if ($container->hasDefinition($this->updateCommandServiceId)) { + $definition = $container->getDefinition($this->updateCommandServiceId); + $definition->replaceArgument(7, array_merge($definition->getArgument(7), $paths)); + } + } + } finally { + $this->level = 0; + $this->paths = []; + $this->definitions = []; + } + } + + protected function processValue($value, bool $isRoot = false) + { + if ($value instanceof Reference) { + if ((string) $value === $this->translatorServiceId) { + for ($i = $this->level - 1; $i >= 0; --$i) { + $class = $this->definitions[$i]->getClass(); + + if (ServiceLocator::class === $class) { + if (!isset($this->controllers[$this->currentId])) { + continue; + } + foreach ($this->controllers[$this->currentId] as $class => $_) { + $this->paths[$class] = true; + } + } else { + $this->paths[$class] = true; + } + + break; + } + } + + return $value; + } + + if ($value instanceof Definition) { + $this->definitions[$this->level++] = $value; + $value = parent::processValue($value, $isRoot); + unset($this->definitions[--$this->level]); + + return $value; + } + + return parent::processValue($value, $isRoot); + } + + private function findControllerArguments(ContainerBuilder $container): array + { + if (!$container->has($this->resolverServiceId)) { + return []; + } + $resolverDef = $container->findDefinition($this->resolverServiceId); + + if (TraceableValueResolver::class === $resolverDef->getClass()) { + $resolverDef = $container->getDefinition($resolverDef->getArgument(0)); + } + + $argument = $resolverDef->getArgument(0); + if ($argument instanceof Reference) { + $argument = $container->getDefinition($argument); + } + + return $argument->getArgument(0); + } +} diff --git a/vendor/symfony/translation/Dumper/CsvFileDumper.php b/vendor/symfony/translation/Dumper/CsvFileDumper.php new file mode 100644 index 0000000..0c8589a --- /dev/null +++ b/vendor/symfony/translation/Dumper/CsvFileDumper.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Dumper; + +use Symfony\Component\Translation\MessageCatalogue; + +/** + * CsvFileDumper generates a csv formatted string representation of a message catalogue. + * + * @author Stealth35 + */ +class CsvFileDumper extends FileDumper +{ + private $delimiter = ';'; + private $enclosure = '"'; + + /** + * {@inheritdoc} + */ + public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []) + { + $handle = fopen('php://memory', 'r+'); + + foreach ($messages->all($domain) as $source => $target) { + fputcsv($handle, [$source, $target], $this->delimiter, $this->enclosure); + } + + rewind($handle); + $output = stream_get_contents($handle); + fclose($handle); + + return $output; + } + + /** + * Sets the delimiter and escape character for CSV. + */ + public function setCsvControl(string $delimiter = ';', string $enclosure = '"') + { + $this->delimiter = $delimiter; + $this->enclosure = $enclosure; + } + + /** + * {@inheritdoc} + */ + protected function getExtension() + { + return 'csv'; + } +} diff --git a/vendor/symfony/translation/Dumper/DumperInterface.php b/vendor/symfony/translation/Dumper/DumperInterface.php new file mode 100644 index 0000000..7cdaef5 --- /dev/null +++ b/vendor/symfony/translation/Dumper/DumperInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Dumper; + +use Symfony\Component\Translation\MessageCatalogue; + +/** + * DumperInterface is the interface implemented by all translation dumpers. + * There is no common option. + * + * @author Michel Salib + */ +interface DumperInterface +{ + /** + * Dumps the message catalogue. + * + * @param array $options Options that are used by the dumper + */ + public function dump(MessageCatalogue $messages, array $options = []); +} diff --git a/vendor/symfony/translation/Dumper/FileDumper.php b/vendor/symfony/translation/Dumper/FileDumper.php new file mode 100644 index 0000000..0e10845 --- /dev/null +++ b/vendor/symfony/translation/Dumper/FileDumper.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 Symfony\Component\Translation\Dumper; + +use Symfony\Component\Translation\Exception\InvalidArgumentException; +use Symfony\Component\Translation\Exception\RuntimeException; +use Symfony\Component\Translation\MessageCatalogue; + +/** + * FileDumper is an implementation of DumperInterface that dump a message catalogue to file(s). + * + * Options: + * - path (mandatory): the directory where the files should be saved + * + * @author Michel Salib + */ +abstract class FileDumper implements DumperInterface +{ + /** + * A template for the relative paths to files. + * + * @var string + */ + protected $relativePathTemplate = '%domain%.%locale%.%extension%'; + + /** + * Sets the template for the relative paths to files. + * + * @param string $relativePathTemplate A template for the relative paths to files + */ + public function setRelativePathTemplate(string $relativePathTemplate) + { + $this->relativePathTemplate = $relativePathTemplate; + } + + /** + * {@inheritdoc} + */ + public function dump(MessageCatalogue $messages, array $options = []) + { + if (!\array_key_exists('path', $options)) { + throw new InvalidArgumentException('The file dumper needs a path option.'); + } + + // save a file for each domain + foreach ($messages->getDomains() as $domain) { + $fullpath = $options['path'].'/'.$this->getRelativePath($domain, $messages->getLocale()); + if (!file_exists($fullpath)) { + $directory = \dirname($fullpath); + if (!file_exists($directory) && !@mkdir($directory, 0777, true)) { + throw new RuntimeException(sprintf('Unable to create directory "%s".', $directory)); + } + } + + $intlDomain = $domain.MessageCatalogue::INTL_DOMAIN_SUFFIX; + $intlMessages = $messages->all($intlDomain); + + if ($intlMessages) { + $intlPath = $options['path'].'/'.$this->getRelativePath($intlDomain, $messages->getLocale()); + file_put_contents($intlPath, $this->formatCatalogue($messages, $intlDomain, $options)); + + $messages->replace([], $intlDomain); + + try { + if ($messages->all($domain)) { + file_put_contents($fullpath, $this->formatCatalogue($messages, $domain, $options)); + } + continue; + } finally { + $messages->replace($intlMessages, $intlDomain); + } + } + + file_put_contents($fullpath, $this->formatCatalogue($messages, $domain, $options)); + } + } + + /** + * Transforms a domain of a message catalogue to its string representation. + * + * @return string + */ + abstract public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []); + + /** + * Gets the file extension of the dumper. + * + * @return string + */ + abstract protected function getExtension(); + + /** + * Gets the relative file path using the template. + */ + private function getRelativePath(string $domain, string $locale): string + { + return strtr($this->relativePathTemplate, [ + '%domain%' => $domain, + '%locale%' => $locale, + '%extension%' => $this->getExtension(), + ]); + } +} diff --git a/vendor/symfony/translation/Dumper/IcuResFileDumper.php b/vendor/symfony/translation/Dumper/IcuResFileDumper.php new file mode 100644 index 0000000..12a7a8c --- /dev/null +++ b/vendor/symfony/translation/Dumper/IcuResFileDumper.php @@ -0,0 +1,104 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Dumper; + +use Symfony\Component\Translation\MessageCatalogue; + +/** + * IcuResDumper generates an ICU ResourceBundle formatted string representation of a message catalogue. + * + * @author Stealth35 + */ +class IcuResFileDumper extends FileDumper +{ + /** + * {@inheritdoc} + */ + protected $relativePathTemplate = '%domain%/%locale%.%extension%'; + + /** + * {@inheritdoc} + */ + public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []) + { + $data = $indexes = $resources = ''; + + foreach ($messages->all($domain) as $source => $target) { + $indexes .= pack('v', \strlen($data) + 28); + $data .= $source."\0"; + } + + $data .= $this->writePadding($data); + + $keyTop = $this->getPosition($data); + + foreach ($messages->all($domain) as $source => $target) { + $resources .= pack('V', $this->getPosition($data)); + + $data .= pack('V', \strlen($target)) + .mb_convert_encoding($target."\0", 'UTF-16LE', 'UTF-8') + .$this->writePadding($data) + ; + } + + $resOffset = $this->getPosition($data); + + $data .= pack('v', \count($messages->all($domain))) + .$indexes + .$this->writePadding($data) + .$resources + ; + + $bundleTop = $this->getPosition($data); + + $root = pack('V7', + $resOffset + (2 << 28), // Resource Offset + Resource Type + 6, // Index length + $keyTop, // Index keys top + $bundleTop, // Index resources top + $bundleTop, // Index bundle top + \count($messages->all($domain)), // Index max table length + 0 // Index attributes + ); + + $header = pack('vC2v4C12@32', + 32, // Header size + 0xDA, 0x27, // Magic number 1 and 2 + 20, 0, 0, 2, // Rest of the header, ..., Size of a char + 0x52, 0x65, 0x73, 0x42, // Data format identifier + 1, 2, 0, 0, // Data version + 1, 4, 0, 0 // Unicode version + ); + + return $header.$root.$data; + } + + private function writePadding(string $data): ?string + { + $padding = \strlen($data) % 4; + + return $padding ? str_repeat("\xAA", 4 - $padding) : null; + } + + private function getPosition(string $data) + { + return (\strlen($data) + 28) / 4; + } + + /** + * {@inheritdoc} + */ + protected function getExtension() + { + return 'res'; + } +} diff --git a/vendor/symfony/translation/Dumper/IniFileDumper.php b/vendor/symfony/translation/Dumper/IniFileDumper.php new file mode 100644 index 0000000..93c900a --- /dev/null +++ b/vendor/symfony/translation/Dumper/IniFileDumper.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Dumper; + +use Symfony\Component\Translation\MessageCatalogue; + +/** + * IniFileDumper generates an ini formatted string representation of a message catalogue. + * + * @author Stealth35 + */ +class IniFileDumper extends FileDumper +{ + /** + * {@inheritdoc} + */ + public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []) + { + $output = ''; + + foreach ($messages->all($domain) as $source => $target) { + $escapeTarget = str_replace('"', '\"', $target); + $output .= $source.'="'.$escapeTarget."\"\n"; + } + + return $output; + } + + /** + * {@inheritdoc} + */ + protected function getExtension() + { + return 'ini'; + } +} diff --git a/vendor/symfony/translation/Dumper/JsonFileDumper.php b/vendor/symfony/translation/Dumper/JsonFileDumper.php new file mode 100644 index 0000000..34c0b56 --- /dev/null +++ b/vendor/symfony/translation/Dumper/JsonFileDumper.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Dumper; + +use Symfony\Component\Translation\MessageCatalogue; + +/** + * JsonFileDumper generates an json formatted string representation of a message catalogue. + * + * @author singles + */ +class JsonFileDumper extends FileDumper +{ + /** + * {@inheritdoc} + */ + public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []) + { + $flags = $options['json_encoding'] ?? \JSON_PRETTY_PRINT; + + return json_encode($messages->all($domain), $flags); + } + + /** + * {@inheritdoc} + */ + protected function getExtension() + { + return 'json'; + } +} diff --git a/vendor/symfony/translation/Dumper/MoFileDumper.php b/vendor/symfony/translation/Dumper/MoFileDumper.php new file mode 100644 index 0000000..f52206e --- /dev/null +++ b/vendor/symfony/translation/Dumper/MoFileDumper.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Dumper; + +use Symfony\Component\Translation\Loader\MoFileLoader; +use Symfony\Component\Translation\MessageCatalogue; + +/** + * MoFileDumper generates a gettext formatted string representation of a message catalogue. + * + * @author Stealth35 + */ +class MoFileDumper extends FileDumper +{ + /** + * {@inheritdoc} + */ + public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []) + { + $sources = $targets = $sourceOffsets = $targetOffsets = ''; + $offsets = []; + $size = 0; + + foreach ($messages->all($domain) as $source => $target) { + $offsets[] = array_map('strlen', [$sources, $source, $targets, $target]); + $sources .= "\0".$source; + $targets .= "\0".$target; + ++$size; + } + + $header = [ + 'magicNumber' => MoFileLoader::MO_LITTLE_ENDIAN_MAGIC, + 'formatRevision' => 0, + 'count' => $size, + 'offsetId' => MoFileLoader::MO_HEADER_SIZE, + 'offsetTranslated' => MoFileLoader::MO_HEADER_SIZE + (8 * $size), + 'sizeHashes' => 0, + 'offsetHashes' => MoFileLoader::MO_HEADER_SIZE + (16 * $size), + ]; + + $sourcesSize = \strlen($sources); + $sourcesStart = $header['offsetHashes'] + 1; + + foreach ($offsets as $offset) { + $sourceOffsets .= $this->writeLong($offset[1]) + .$this->writeLong($offset[0] + $sourcesStart); + $targetOffsets .= $this->writeLong($offset[3]) + .$this->writeLong($offset[2] + $sourcesStart + $sourcesSize); + } + + $output = implode('', array_map([$this, 'writeLong'], $header)) + .$sourceOffsets + .$targetOffsets + .$sources + .$targets + ; + + return $output; + } + + /** + * {@inheritdoc} + */ + protected function getExtension() + { + return 'mo'; + } + + private function writeLong($str): string + { + return pack('V*', $str); + } +} diff --git a/vendor/symfony/translation/Dumper/PhpFileDumper.php b/vendor/symfony/translation/Dumper/PhpFileDumper.php new file mode 100644 index 0000000..6163b52 --- /dev/null +++ b/vendor/symfony/translation/Dumper/PhpFileDumper.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Dumper; + +use Symfony\Component\Translation\MessageCatalogue; + +/** + * PhpFileDumper generates PHP files from a message catalogue. + * + * @author Michel Salib + */ +class PhpFileDumper extends FileDumper +{ + /** + * {@inheritdoc} + */ + public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []) + { + return "all($domain), true).";\n"; + } + + /** + * {@inheritdoc} + */ + protected function getExtension() + { + return 'php'; + } +} diff --git a/vendor/symfony/translation/Dumper/PoFileDumper.php b/vendor/symfony/translation/Dumper/PoFileDumper.php new file mode 100644 index 0000000..0d82281 --- /dev/null +++ b/vendor/symfony/translation/Dumper/PoFileDumper.php @@ -0,0 +1,137 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Dumper; + +use Symfony\Component\Translation\MessageCatalogue; + +/** + * PoFileDumper generates a gettext formatted string representation of a message catalogue. + * + * @author Stealth35 + */ +class PoFileDumper extends FileDumper +{ + /** + * {@inheritdoc} + */ + public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []) + { + $output = 'msgid ""'."\n"; + $output .= 'msgstr ""'."\n"; + $output .= '"Content-Type: text/plain; charset=UTF-8\n"'."\n"; + $output .= '"Content-Transfer-Encoding: 8bit\n"'."\n"; + $output .= '"Language: '.$messages->getLocale().'\n"'."\n"; + $output .= "\n"; + + $newLine = false; + foreach ($messages->all($domain) as $source => $target) { + if ($newLine) { + $output .= "\n"; + } else { + $newLine = true; + } + $metadata = $messages->getMetadata($source, $domain); + + if (isset($metadata['comments'])) { + $output .= $this->formatComments($metadata['comments']); + } + if (isset($metadata['flags'])) { + $output .= $this->formatComments(implode(',', (array) $metadata['flags']), ','); + } + if (isset($metadata['sources'])) { + $output .= $this->formatComments(implode(' ', (array) $metadata['sources']), ':'); + } + + $sourceRules = $this->getStandardRules($source); + $targetRules = $this->getStandardRules($target); + if (2 == \count($sourceRules) && [] !== $targetRules) { + $output .= sprintf('msgid "%s"'."\n", $this->escape($sourceRules[0])); + $output .= sprintf('msgid_plural "%s"'."\n", $this->escape($sourceRules[1])); + foreach ($targetRules as $i => $targetRule) { + $output .= sprintf('msgstr[%d] "%s"'."\n", $i, $this->escape($targetRule)); + } + } else { + $output .= sprintf('msgid "%s"'."\n", $this->escape($source)); + $output .= sprintf('msgstr "%s"'."\n", $this->escape($target)); + } + } + + return $output; + } + + private function getStandardRules(string $id) + { + // Partly copied from TranslatorTrait::trans. + $parts = []; + if (preg_match('/^\|++$/', $id)) { + $parts = explode('|', $id); + } elseif (preg_match_all('/(?:\|\||[^\|])++/', $id, $matches)) { + $parts = $matches[0]; + } + + $intervalRegexp = <<<'EOF' +/^(?P + ({\s* + (\-?\d+(\.\d+)?[\s*,\s*\-?\d+(\.\d+)?]*) + \s*}) + + | + + (?P[\[\]]) + \s* + (?P-Inf|\-?\d+(\.\d+)?) + \s*,\s* + (?P\+?Inf|\-?\d+(\.\d+)?) + \s* + (?P[\[\]]) +)\s*(?P.*?)$/xs +EOF; + + $standardRules = []; + foreach ($parts as $part) { + $part = trim(str_replace('||', '|', $part)); + + if (preg_match($intervalRegexp, $part)) { + // Explicit rule is not a standard rule. + return []; + } else { + $standardRules[] = $part; + } + } + + return $standardRules; + } + + /** + * {@inheritdoc} + */ + protected function getExtension() + { + return 'po'; + } + + private function escape(string $str): string + { + return addcslashes($str, "\0..\37\42\134"); + } + + private function formatComments($comments, string $prefix = ''): ?string + { + $output = null; + + foreach ((array) $comments as $comment) { + $output .= sprintf('#%s %s'."\n", $prefix, $comment); + } + + return $output; + } +} diff --git a/vendor/symfony/translation/Dumper/QtFileDumper.php b/vendor/symfony/translation/Dumper/QtFileDumper.php new file mode 100644 index 0000000..406e9f0 --- /dev/null +++ b/vendor/symfony/translation/Dumper/QtFileDumper.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Dumper; + +use Symfony\Component\Translation\MessageCatalogue; + +/** + * QtFileDumper generates ts files from a message catalogue. + * + * @author Benjamin Eberlei + */ +class QtFileDumper extends FileDumper +{ + /** + * {@inheritdoc} + */ + public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []) + { + $dom = new \DOMDocument('1.0', 'utf-8'); + $dom->formatOutput = true; + $ts = $dom->appendChild($dom->createElement('TS')); + $context = $ts->appendChild($dom->createElement('context')); + $context->appendChild($dom->createElement('name', $domain)); + + foreach ($messages->all($domain) as $source => $target) { + $message = $context->appendChild($dom->createElement('message')); + $metadata = $messages->getMetadata($source, $domain); + if (isset($metadata['sources'])) { + foreach ((array) $metadata['sources'] as $location) { + $loc = explode(':', $location, 2); + $location = $message->appendChild($dom->createElement('location')); + $location->setAttribute('filename', $loc[0]); + if (isset($loc[1])) { + $location->setAttribute('line', $loc[1]); + } + } + } + $message->appendChild($dom->createElement('source', $source)); + $message->appendChild($dom->createElement('translation', $target)); + } + + return $dom->saveXML(); + } + + /** + * {@inheritdoc} + */ + protected function getExtension() + { + return 'ts'; + } +} diff --git a/vendor/symfony/translation/Dumper/XliffFileDumper.php b/vendor/symfony/translation/Dumper/XliffFileDumper.php new file mode 100644 index 0000000..f7dbdcd --- /dev/null +++ b/vendor/symfony/translation/Dumper/XliffFileDumper.php @@ -0,0 +1,203 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Dumper; + +use Symfony\Component\Translation\Exception\InvalidArgumentException; +use Symfony\Component\Translation\MessageCatalogue; + +/** + * XliffFileDumper generates xliff files from a message catalogue. + * + * @author Michel Salib + */ +class XliffFileDumper extends FileDumper +{ + /** + * {@inheritdoc} + */ + public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []) + { + $xliffVersion = '1.2'; + if (\array_key_exists('xliff_version', $options)) { + $xliffVersion = $options['xliff_version']; + } + + if (\array_key_exists('default_locale', $options)) { + $defaultLocale = $options['default_locale']; + } else { + $defaultLocale = \Locale::getDefault(); + } + + if ('1.2' === $xliffVersion) { + return $this->dumpXliff1($defaultLocale, $messages, $domain, $options); + } + if ('2.0' === $xliffVersion) { + return $this->dumpXliff2($defaultLocale, $messages, $domain); + } + + throw new InvalidArgumentException(sprintf('No support implemented for dumping XLIFF version "%s".', $xliffVersion)); + } + + /** + * {@inheritdoc} + */ + protected function getExtension() + { + return 'xlf'; + } + + private function dumpXliff1(string $defaultLocale, MessageCatalogue $messages, ?string $domain, array $options = []) + { + $toolInfo = ['tool-id' => 'symfony', 'tool-name' => 'Symfony']; + if (\array_key_exists('tool_info', $options)) { + $toolInfo = array_merge($toolInfo, $options['tool_info']); + } + + $dom = new \DOMDocument('1.0', 'utf-8'); + $dom->formatOutput = true; + + $xliff = $dom->appendChild($dom->createElement('xliff')); + $xliff->setAttribute('version', '1.2'); + $xliff->setAttribute('xmlns', 'urn:oasis:names:tc:xliff:document:1.2'); + + $xliffFile = $xliff->appendChild($dom->createElement('file')); + $xliffFile->setAttribute('source-language', str_replace('_', '-', $defaultLocale)); + $xliffFile->setAttribute('target-language', str_replace('_', '-', $messages->getLocale())); + $xliffFile->setAttribute('datatype', 'plaintext'); + $xliffFile->setAttribute('original', 'file.ext'); + + $xliffHead = $xliffFile->appendChild($dom->createElement('header')); + $xliffTool = $xliffHead->appendChild($dom->createElement('tool')); + foreach ($toolInfo as $id => $value) { + $xliffTool->setAttribute($id, $value); + } + + $xliffBody = $xliffFile->appendChild($dom->createElement('body')); + foreach ($messages->all($domain) as $source => $target) { + $translation = $dom->createElement('trans-unit'); + + $translation->setAttribute('id', strtr(substr(base64_encode(hash('sha256', $source, true)), 0, 7), '/+', '._')); + $translation->setAttribute('resname', $source); + + $s = $translation->appendChild($dom->createElement('source')); + $s->appendChild($dom->createTextNode($source)); + + // Does the target contain characters requiring a CDATA section? + $text = 1 === preg_match('/[&<>]/', $target) ? $dom->createCDATASection($target) : $dom->createTextNode($target); + + $targetElement = $dom->createElement('target'); + $metadata = $messages->getMetadata($source, $domain); + if ($this->hasMetadataArrayInfo('target-attributes', $metadata)) { + foreach ($metadata['target-attributes'] as $name => $value) { + $targetElement->setAttribute($name, $value); + } + } + $t = $translation->appendChild($targetElement); + $t->appendChild($text); + + if ($this->hasMetadataArrayInfo('notes', $metadata)) { + foreach ($metadata['notes'] as $note) { + if (!isset($note['content'])) { + continue; + } + + $n = $translation->appendChild($dom->createElement('note')); + $n->appendChild($dom->createTextNode($note['content'])); + + if (isset($note['priority'])) { + $n->setAttribute('priority', $note['priority']); + } + + if (isset($note['from'])) { + $n->setAttribute('from', $note['from']); + } + } + } + + $xliffBody->appendChild($translation); + } + + return $dom->saveXML(); + } + + private function dumpXliff2(string $defaultLocale, MessageCatalogue $messages, ?string $domain) + { + $dom = new \DOMDocument('1.0', 'utf-8'); + $dom->formatOutput = true; + + $xliff = $dom->appendChild($dom->createElement('xliff')); + $xliff->setAttribute('xmlns', 'urn:oasis:names:tc:xliff:document:2.0'); + $xliff->setAttribute('version', '2.0'); + $xliff->setAttribute('srcLang', str_replace('_', '-', $defaultLocale)); + $xliff->setAttribute('trgLang', str_replace('_', '-', $messages->getLocale())); + + $xliffFile = $xliff->appendChild($dom->createElement('file')); + if (str_ends_with($domain, MessageCatalogue::INTL_DOMAIN_SUFFIX)) { + $xliffFile->setAttribute('id', substr($domain, 0, -\strlen(MessageCatalogue::INTL_DOMAIN_SUFFIX)).'.'.$messages->getLocale()); + } else { + $xliffFile->setAttribute('id', $domain.'.'.$messages->getLocale()); + } + + foreach ($messages->all($domain) as $source => $target) { + $translation = $dom->createElement('unit'); + $translation->setAttribute('id', strtr(substr(base64_encode(hash('sha256', $source, true)), 0, 7), '/+', '._')); + + if (\strlen($source) <= 80) { + $translation->setAttribute('name', $source); + } + + $metadata = $messages->getMetadata($source, $domain); + + // Add notes section + if ($this->hasMetadataArrayInfo('notes', $metadata)) { + $notesElement = $dom->createElement('notes'); + foreach ($metadata['notes'] as $note) { + $n = $dom->createElement('note'); + $n->appendChild($dom->createTextNode($note['content'] ?? '')); + unset($note['content']); + + foreach ($note as $name => $value) { + $n->setAttribute($name, $value); + } + $notesElement->appendChild($n); + } + $translation->appendChild($notesElement); + } + + $segment = $translation->appendChild($dom->createElement('segment')); + + $s = $segment->appendChild($dom->createElement('source')); + $s->appendChild($dom->createTextNode($source)); + + // Does the target contain characters requiring a CDATA section? + $text = 1 === preg_match('/[&<>]/', $target) ? $dom->createCDATASection($target) : $dom->createTextNode($target); + + $targetElement = $dom->createElement('target'); + if ($this->hasMetadataArrayInfo('target-attributes', $metadata)) { + foreach ($metadata['target-attributes'] as $name => $value) { + $targetElement->setAttribute($name, $value); + } + } + $t = $segment->appendChild($targetElement); + $t->appendChild($text); + + $xliffFile->appendChild($translation); + } + + return $dom->saveXML(); + } + + private function hasMetadataArrayInfo(string $key, array $metadata = null): bool + { + return is_iterable($metadata[$key] ?? null); + } +} diff --git a/vendor/symfony/translation/Dumper/YamlFileDumper.php b/vendor/symfony/translation/Dumper/YamlFileDumper.php new file mode 100644 index 0000000..0b21e8c --- /dev/null +++ b/vendor/symfony/translation/Dumper/YamlFileDumper.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Dumper; + +use Symfony\Component\Translation\Exception\LogicException; +use Symfony\Component\Translation\MessageCatalogue; +use Symfony\Component\Translation\Util\ArrayConverter; +use Symfony\Component\Yaml\Yaml; + +/** + * YamlFileDumper generates yaml files from a message catalogue. + * + * @author Michel Salib + */ +class YamlFileDumper extends FileDumper +{ + private $extension; + + public function __construct(string $extension = 'yml') + { + $this->extension = $extension; + } + + /** + * {@inheritdoc} + */ + public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []) + { + if (!class_exists(Yaml::class)) { + throw new LogicException('Dumping translations in the YAML format requires the Symfony Yaml component.'); + } + + $data = $messages->all($domain); + + if (isset($options['as_tree']) && $options['as_tree']) { + $data = ArrayConverter::expandToTree($data); + } + + if (isset($options['inline']) && ($inline = (int) $options['inline']) > 0) { + return Yaml::dump($data, $inline); + } + + return Yaml::dump($data); + } + + /** + * {@inheritdoc} + */ + protected function getExtension() + { + return $this->extension; + } +} diff --git a/vendor/symfony/translation/Exception/ExceptionInterface.php b/vendor/symfony/translation/Exception/ExceptionInterface.php new file mode 100644 index 0000000..8f9c54e --- /dev/null +++ b/vendor/symfony/translation/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Exception; + +/** + * Exception interface for all exceptions thrown by the component. + * + * @author Fabien Potencier + */ +interface ExceptionInterface extends \Throwable +{ +} diff --git a/vendor/symfony/translation/Exception/IncompleteDsnException.php b/vendor/symfony/translation/Exception/IncompleteDsnException.php new file mode 100644 index 0000000..cb0ce02 --- /dev/null +++ b/vendor/symfony/translation/Exception/IncompleteDsnException.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Exception; + +class IncompleteDsnException extends InvalidArgumentException +{ + public function __construct(string $message, string $dsn = null, \Throwable $previous = null) + { + if ($dsn) { + $message = sprintf('Invalid "%s" provider DSN: ', $dsn).$message; + } + + parent::__construct($message, 0, $previous); + } +} diff --git a/vendor/symfony/translation/Exception/InvalidArgumentException.php b/vendor/symfony/translation/Exception/InvalidArgumentException.php new file mode 100644 index 0000000..90d0669 --- /dev/null +++ b/vendor/symfony/translation/Exception/InvalidArgumentException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Exception; + +/** + * Base InvalidArgumentException for the Translation component. + * + * @author Abdellatif Ait boudad + */ +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/translation/Exception/InvalidResourceException.php b/vendor/symfony/translation/Exception/InvalidResourceException.php new file mode 100644 index 0000000..cf07943 --- /dev/null +++ b/vendor/symfony/translation/Exception/InvalidResourceException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Exception; + +/** + * Thrown when a resource cannot be loaded. + * + * @author Fabien Potencier + */ +class InvalidResourceException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/translation/Exception/LogicException.php b/vendor/symfony/translation/Exception/LogicException.php new file mode 100644 index 0000000..9019c7e --- /dev/null +++ b/vendor/symfony/translation/Exception/LogicException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Exception; + +/** + * Base LogicException for Translation component. + * + * @author Abdellatif Ait boudad + */ +class LogicException extends \LogicException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/translation/Exception/MissingRequiredOptionException.php b/vendor/symfony/translation/Exception/MissingRequiredOptionException.php new file mode 100644 index 0000000..2b5f808 --- /dev/null +++ b/vendor/symfony/translation/Exception/MissingRequiredOptionException.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 Symfony\Component\Translation\Exception; + +/** + * @author Oskar Stark + */ +class MissingRequiredOptionException extends IncompleteDsnException +{ + public function __construct(string $option, string $dsn = null, \Throwable $previous = null) + { + $message = sprintf('The option "%s" is required but missing.', $option); + + parent::__construct($message, $dsn, $previous); + } +} diff --git a/vendor/symfony/translation/Exception/NotFoundResourceException.php b/vendor/symfony/translation/Exception/NotFoundResourceException.php new file mode 100644 index 0000000..cff73ae --- /dev/null +++ b/vendor/symfony/translation/Exception/NotFoundResourceException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Exception; + +/** + * Thrown when a resource does not exist. + * + * @author Fabien Potencier + */ +class NotFoundResourceException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/translation/Exception/ProviderException.php b/vendor/symfony/translation/Exception/ProviderException.php new file mode 100644 index 0000000..571920d --- /dev/null +++ b/vendor/symfony/translation/Exception/ProviderException.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Exception; + +use Symfony\Contracts\HttpClient\ResponseInterface; + +/** + * @author Fabien Potencier + */ +class ProviderException extends RuntimeException implements ProviderExceptionInterface +{ + private $response; + private $debug; + + public function __construct(string $message, ResponseInterface $response, int $code = 0, \Exception $previous = null) + { + $this->response = $response; + $this->debug = $response->getInfo('debug') ?? ''; + + parent::__construct($message, $code, $previous); + } + + public function getResponse(): ResponseInterface + { + return $this->response; + } + + public function getDebug(): string + { + return $this->debug; + } +} diff --git a/vendor/symfony/translation/Exception/ProviderExceptionInterface.php b/vendor/symfony/translation/Exception/ProviderExceptionInterface.php new file mode 100644 index 0000000..922e827 --- /dev/null +++ b/vendor/symfony/translation/Exception/ProviderExceptionInterface.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Exception; + +/** + * @author Fabien Potencier + */ +interface ProviderExceptionInterface extends ExceptionInterface +{ + /* + * Returns debug info coming from the Symfony\Contracts\HttpClient\ResponseInterface + */ + public function getDebug(): string; +} diff --git a/vendor/symfony/translation/Exception/RuntimeException.php b/vendor/symfony/translation/Exception/RuntimeException.php new file mode 100644 index 0000000..dcd7940 --- /dev/null +++ b/vendor/symfony/translation/Exception/RuntimeException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Exception; + +/** + * Base RuntimeException for the Translation component. + * + * @author Abdellatif Ait boudad + */ +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/translation/Exception/UnsupportedSchemeException.php b/vendor/symfony/translation/Exception/UnsupportedSchemeException.php new file mode 100644 index 0000000..7fbaa8f --- /dev/null +++ b/vendor/symfony/translation/Exception/UnsupportedSchemeException.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Exception; + +use Symfony\Component\Translation\Bridge; +use Symfony\Component\Translation\Provider\Dsn; + +class UnsupportedSchemeException extends LogicException +{ + private const SCHEME_TO_PACKAGE_MAP = [ + 'crowdin' => [ + 'class' => Bridge\Crowdin\CrowdinProviderFactory::class, + 'package' => 'symfony/crowdin-translation-provider', + ], + 'loco' => [ + 'class' => Bridge\Loco\LocoProviderFactory::class, + 'package' => 'symfony/loco-translation-provider', + ], + 'lokalise' => [ + 'class' => Bridge\Lokalise\LokaliseProviderFactory::class, + 'package' => 'symfony/lokalise-translation-provider', + ], + ]; + + public function __construct(Dsn $dsn, string $name = null, array $supported = []) + { + $provider = $dsn->getScheme(); + if (false !== $pos = strpos($provider, '+')) { + $provider = substr($provider, 0, $pos); + } + $package = self::SCHEME_TO_PACKAGE_MAP[$provider] ?? null; + if ($package && !class_exists($package['class'])) { + parent::__construct(sprintf('Unable to synchronize translations via "%s" as the provider is not installed; try running "composer require %s".', $provider, $package['package'])); + + return; + } + + $message = sprintf('The "%s" scheme is not supported', $dsn->getScheme()); + if ($name && $supported) { + $message .= sprintf('; supported schemes for translation provider "%s" are: "%s"', $name, implode('", "', $supported)); + } + + parent::__construct($message.'.'); + } +} diff --git a/vendor/symfony/translation/Extractor/AbstractFileExtractor.php b/vendor/symfony/translation/Extractor/AbstractFileExtractor.php new file mode 100644 index 0000000..3bc8783 --- /dev/null +++ b/vendor/symfony/translation/Extractor/AbstractFileExtractor.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Extractor; + +use Symfony\Component\Translation\Exception\InvalidArgumentException; + +/** + * Base class used by classes that extract translation messages from files. + * + * @author Marcos D. Sánchez + */ +abstract class AbstractFileExtractor +{ + /** + * @param string|iterable $resource Files, a file or a directory + * + * @return iterable + */ + protected function extractFiles($resource) + { + if (is_iterable($resource)) { + $files = []; + foreach ($resource as $file) { + if ($this->canBeExtracted($file)) { + $files[] = $this->toSplFileInfo($file); + } + } + } elseif (is_file($resource)) { + $files = $this->canBeExtracted($resource) ? [$this->toSplFileInfo($resource)] : []; + } else { + $files = $this->extractFromDirectory($resource); + } + + return $files; + } + + private function toSplFileInfo(string $file): \SplFileInfo + { + return new \SplFileInfo($file); + } + + /** + * @return bool + * + * @throws InvalidArgumentException + */ + protected function isFile(string $file) + { + if (!is_file($file)) { + throw new InvalidArgumentException(sprintf('The "%s" file does not exist.', $file)); + } + + return true; + } + + /** + * @return bool + */ + abstract protected function canBeExtracted(string $file); + + /** + * @param string|array $resource Files, a file or a directory + * + * @return iterable + */ + abstract protected function extractFromDirectory($resource); +} diff --git a/vendor/symfony/translation/Extractor/ChainExtractor.php b/vendor/symfony/translation/Extractor/ChainExtractor.php new file mode 100644 index 0000000..95dcf15 --- /dev/null +++ b/vendor/symfony/translation/Extractor/ChainExtractor.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Extractor; + +use Symfony\Component\Translation\MessageCatalogue; + +/** + * ChainExtractor extracts translation messages from template files. + * + * @author Michel Salib + */ +class ChainExtractor implements ExtractorInterface +{ + /** + * The extractors. + * + * @var ExtractorInterface[] + */ + private $extractors = []; + + /** + * Adds a loader to the translation extractor. + */ + public function addExtractor(string $format, ExtractorInterface $extractor) + { + $this->extractors[$format] = $extractor; + } + + /** + * {@inheritdoc} + */ + public function setPrefix(string $prefix) + { + foreach ($this->extractors as $extractor) { + $extractor->setPrefix($prefix); + } + } + + /** + * {@inheritdoc} + */ + public function extract($directory, MessageCatalogue $catalogue) + { + foreach ($this->extractors as $extractor) { + $extractor->extract($directory, $catalogue); + } + } +} diff --git a/vendor/symfony/translation/Extractor/ExtractorInterface.php b/vendor/symfony/translation/Extractor/ExtractorInterface.php new file mode 100644 index 0000000..e1db8a9 --- /dev/null +++ b/vendor/symfony/translation/Extractor/ExtractorInterface.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Extractor; + +use Symfony\Component\Translation\MessageCatalogue; + +/** + * Extracts translation messages from a directory or files to the catalogue. + * New found messages are injected to the catalogue using the prefix. + * + * @author Michel Salib + */ +interface ExtractorInterface +{ + /** + * Extracts translation messages from files, a file or a directory to the catalogue. + * + * @param string|iterable $resource Files, a file or a directory + */ + public function extract($resource, MessageCatalogue $catalogue); + + /** + * Sets the prefix that should be used for new found messages. + */ + public function setPrefix(string $prefix); +} diff --git a/vendor/symfony/translation/Extractor/PhpExtractor.php b/vendor/symfony/translation/Extractor/PhpExtractor.php new file mode 100644 index 0000000..38c08d5 --- /dev/null +++ b/vendor/symfony/translation/Extractor/PhpExtractor.php @@ -0,0 +1,336 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Extractor; + +use Symfony\Component\Finder\Finder; +use Symfony\Component\Translation\MessageCatalogue; + +/** + * PhpExtractor extracts translation messages from a PHP template. + * + * @author Michel Salib + */ +class PhpExtractor extends AbstractFileExtractor implements ExtractorInterface +{ + public const MESSAGE_TOKEN = 300; + public const METHOD_ARGUMENTS_TOKEN = 1000; + public const DOMAIN_TOKEN = 1001; + + /** + * Prefix for new found message. + */ + private $prefix = ''; + + /** + * The sequence that captures translation messages. + */ + protected $sequences = [ + [ + '->', + 'trans', + '(', + self::MESSAGE_TOKEN, + ',', + self::METHOD_ARGUMENTS_TOKEN, + ',', + self::DOMAIN_TOKEN, + ], + [ + '->', + 'trans', + '(', + self::MESSAGE_TOKEN, + ], + [ + 'new', + 'TranslatableMessage', + '(', + self::MESSAGE_TOKEN, + ',', + self::METHOD_ARGUMENTS_TOKEN, + ',', + self::DOMAIN_TOKEN, + ], + [ + 'new', + 'TranslatableMessage', + '(', + self::MESSAGE_TOKEN, + ], + [ + 'new', + '\\', + 'Symfony', + '\\', + 'Component', + '\\', + 'Translation', + '\\', + 'TranslatableMessage', + '(', + self::MESSAGE_TOKEN, + ',', + self::METHOD_ARGUMENTS_TOKEN, + ',', + self::DOMAIN_TOKEN, + ], + [ + 'new', + '\Symfony\Component\Translation\TranslatableMessage', + '(', + self::MESSAGE_TOKEN, + ',', + self::METHOD_ARGUMENTS_TOKEN, + ',', + self::DOMAIN_TOKEN, + ], + [ + 'new', + '\\', + 'Symfony', + '\\', + 'Component', + '\\', + 'Translation', + '\\', + 'TranslatableMessage', + '(', + self::MESSAGE_TOKEN, + ], + [ + 'new', + '\Symfony\Component\Translation\TranslatableMessage', + '(', + self::MESSAGE_TOKEN, + ], + [ + 't', + '(', + self::MESSAGE_TOKEN, + ',', + self::METHOD_ARGUMENTS_TOKEN, + ',', + self::DOMAIN_TOKEN, + ], + [ + 't', + '(', + self::MESSAGE_TOKEN, + ], + ]; + + /** + * {@inheritdoc} + */ + public function extract($resource, MessageCatalogue $catalog) + { + $files = $this->extractFiles($resource); + foreach ($files as $file) { + $this->parseTokens(token_get_all(file_get_contents($file)), $catalog, $file); + + gc_mem_caches(); + } + } + + /** + * {@inheritdoc} + */ + public function setPrefix(string $prefix) + { + $this->prefix = $prefix; + } + + /** + * Normalizes a token. + * + * @param mixed $token + * + * @return string|null + */ + protected function normalizeToken($token) + { + if (isset($token[1]) && 'b"' !== $token) { + return $token[1]; + } + + return $token; + } + + /** + * Seeks to a non-whitespace token. + */ + private function seekToNextRelevantToken(\Iterator $tokenIterator) + { + for (; $tokenIterator->valid(); $tokenIterator->next()) { + $t = $tokenIterator->current(); + if (\T_WHITESPACE !== $t[0]) { + break; + } + } + } + + private function skipMethodArgument(\Iterator $tokenIterator) + { + $openBraces = 0; + + for (; $tokenIterator->valid(); $tokenIterator->next()) { + $t = $tokenIterator->current(); + + if ('[' === $t[0] || '(' === $t[0]) { + ++$openBraces; + } + + if (']' === $t[0] || ')' === $t[0]) { + --$openBraces; + } + + if ((0 === $openBraces && ',' === $t[0]) || (-1 === $openBraces && ')' === $t[0])) { + break; + } + } + } + + /** + * Extracts the message from the iterator while the tokens + * match allowed message tokens. + */ + private function getValue(\Iterator $tokenIterator) + { + $message = ''; + $docToken = ''; + $docPart = ''; + + for (; $tokenIterator->valid(); $tokenIterator->next()) { + $t = $tokenIterator->current(); + if ('.' === $t) { + // Concatenate with next token + continue; + } + if (!isset($t[1])) { + break; + } + + switch ($t[0]) { + case \T_START_HEREDOC: + $docToken = $t[1]; + break; + case \T_ENCAPSED_AND_WHITESPACE: + case \T_CONSTANT_ENCAPSED_STRING: + if ('' === $docToken) { + $message .= PhpStringTokenParser::parse($t[1]); + } else { + $docPart = $t[1]; + } + break; + case \T_END_HEREDOC: + if ($indentation = strspn($t[1], ' ')) { + $docPartWithLineBreaks = $docPart; + $docPart = ''; + + foreach (preg_split('~(\r\n|\n|\r)~', $docPartWithLineBreaks, -1, \PREG_SPLIT_DELIM_CAPTURE) as $str) { + if (\in_array($str, ["\r\n", "\n", "\r"], true)) { + $docPart .= $str; + } else { + $docPart .= substr($str, $indentation); + } + } + } + + $message .= PhpStringTokenParser::parseDocString($docToken, $docPart); + $docToken = ''; + $docPart = ''; + break; + case \T_WHITESPACE: + break; + default: + break 2; + } + } + + return $message; + } + + /** + * Extracts trans message from PHP tokens. + */ + protected function parseTokens(array $tokens, MessageCatalogue $catalog, string $filename) + { + $tokenIterator = new \ArrayIterator($tokens); + + for ($key = 0; $key < $tokenIterator->count(); ++$key) { + foreach ($this->sequences as $sequence) { + $message = ''; + $domain = 'messages'; + $tokenIterator->seek($key); + + foreach ($sequence as $sequenceKey => $item) { + $this->seekToNextRelevantToken($tokenIterator); + + if ($this->normalizeToken($tokenIterator->current()) === $item) { + $tokenIterator->next(); + continue; + } elseif (self::MESSAGE_TOKEN === $item) { + $message = $this->getValue($tokenIterator); + + if (\count($sequence) === ($sequenceKey + 1)) { + break; + } + } elseif (self::METHOD_ARGUMENTS_TOKEN === $item) { + $this->skipMethodArgument($tokenIterator); + } elseif (self::DOMAIN_TOKEN === $item) { + $domainToken = $this->getValue($tokenIterator); + if ('' !== $domainToken) { + $domain = $domainToken; + } + + break; + } else { + break; + } + } + + if ($message) { + $catalog->set($message, $this->prefix.$message, $domain); + $metadata = $catalog->getMetadata($message, $domain) ?? []; + $normalizedFilename = preg_replace('{[\\\\/]+}', '/', $filename); + $metadata['sources'][] = $normalizedFilename.':'.$tokens[$key][2]; + $catalog->setMetadata($message, $metadata, $domain); + break; + } + } + } + } + + /** + * @return bool + * + * @throws \InvalidArgumentException + */ + protected function canBeExtracted(string $file) + { + return $this->isFile($file) && 'php' === pathinfo($file, \PATHINFO_EXTENSION); + } + + /** + * {@inheritdoc} + */ + protected function extractFromDirectory($directory) + { + if (!class_exists(Finder::class)) { + throw new \LogicException(sprintf('You cannot use "%s" as the "symfony/finder" package is not installed. Try running "composer require symfony/finder".', static::class)); + } + + $finder = new Finder(); + + return $finder->files()->name('*.php')->in($directory); + } +} diff --git a/vendor/symfony/translation/Extractor/PhpStringTokenParser.php b/vendor/symfony/translation/Extractor/PhpStringTokenParser.php new file mode 100644 index 0000000..d114cc7 --- /dev/null +++ b/vendor/symfony/translation/Extractor/PhpStringTokenParser.php @@ -0,0 +1,142 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Extractor; + +/* + * The following is derived from code at http://github.com/nikic/PHP-Parser + * + * 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. + */ + +class PhpStringTokenParser +{ + protected static $replacements = [ + '\\' => '\\', + '$' => '$', + 'n' => "\n", + 'r' => "\r", + 't' => "\t", + 'f' => "\f", + 'v' => "\v", + 'e' => "\x1B", + ]; + + /** + * Parses a string token. + * + * @param string $str String token content + * + * @return string + */ + public static function parse(string $str) + { + $bLength = 0; + if ('b' === $str[0]) { + $bLength = 1; + } + + if ('\'' === $str[$bLength]) { + return str_replace( + ['\\\\', '\\\''], + ['\\', '\''], + substr($str, $bLength + 1, -1) + ); + } else { + return self::parseEscapeSequences(substr($str, $bLength + 1, -1), '"'); + } + } + + /** + * Parses escape sequences in strings (all string types apart from single quoted). + * + * @param string $str String without quotes + * @param string|null $quote Quote type + * + * @return string + */ + public static function parseEscapeSequences(string $str, string $quote = null) + { + if (null !== $quote) { + $str = str_replace('\\'.$quote, $quote, $str); + } + + return preg_replace_callback( + '~\\\\([\\\\$nrtfve]|[xX][0-9a-fA-F]{1,2}|[0-7]{1,3})~', + [__CLASS__, 'parseCallback'], + $str + ); + } + + private static function parseCallback(array $matches): string + { + $str = $matches[1]; + + if (isset(self::$replacements[$str])) { + return self::$replacements[$str]; + } elseif ('x' === $str[0] || 'X' === $str[0]) { + return \chr(hexdec($str)); + } else { + return \chr(octdec($str)); + } + } + + /** + * Parses a constant doc string. + * + * @param string $startToken Doc string start token content (<< + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Formatter; + +use Symfony\Component\Translation\Exception\InvalidArgumentException; +use Symfony\Component\Translation\Exception\LogicException; + +/** + * @author Guilherme Blanco + * @author Abdellatif Ait boudad + */ +class IntlFormatter implements IntlFormatterInterface +{ + private $hasMessageFormatter; + private $cache = []; + + /** + * {@inheritdoc} + */ + public function formatIntl(string $message, string $locale, array $parameters = []): string + { + // MessageFormatter constructor throws an exception if the message is empty + if ('' === $message) { + return ''; + } + + if (!$formatter = $this->cache[$locale][$message] ?? null) { + if (!($this->hasMessageFormatter ?? $this->hasMessageFormatter = class_exists(\MessageFormatter::class))) { + throw new LogicException('Cannot parse message translation: please install the "intl" PHP extension or the "symfony/polyfill-intl-messageformatter" package.'); + } + try { + $this->cache[$locale][$message] = $formatter = new \MessageFormatter($locale, $message); + } catch (\IntlException $e) { + throw new InvalidArgumentException(sprintf('Invalid message format (error #%d): ', intl_get_error_code()).intl_get_error_message(), 0, $e); + } + } + + foreach ($parameters as $key => $value) { + if (\in_array($key[0] ?? null, ['%', '{'], true)) { + unset($parameters[$key]); + $parameters[trim($key, '%{ }')] = $value; + } + } + + if (false === $message = $formatter->format($parameters)) { + throw new InvalidArgumentException(sprintf('Unable to format message (error #%s): ', $formatter->getErrorCode()).$formatter->getErrorMessage()); + } + + return $message; + } +} diff --git a/vendor/symfony/translation/Formatter/IntlFormatterInterface.php b/vendor/symfony/translation/Formatter/IntlFormatterInterface.php new file mode 100644 index 0000000..02fc6ac --- /dev/null +++ b/vendor/symfony/translation/Formatter/IntlFormatterInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Formatter; + +/** + * Formats ICU message patterns. + * + * @author Nicolas Grekas + */ +interface IntlFormatterInterface +{ + /** + * Formats a localized message using rules defined by ICU MessageFormat. + * + * @see http://icu-project.org/apiref/icu4c/classMessageFormat.html#details + */ + public function formatIntl(string $message, string $locale, array $parameters = []): string; +} diff --git a/vendor/symfony/translation/Formatter/MessageFormatter.php b/vendor/symfony/translation/Formatter/MessageFormatter.php new file mode 100644 index 0000000..0407964 --- /dev/null +++ b/vendor/symfony/translation/Formatter/MessageFormatter.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Formatter; + +use Symfony\Component\Translation\IdentityTranslator; +use Symfony\Contracts\Translation\TranslatorInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(IntlFormatter::class); + +/** + * @author Abdellatif Ait boudad + */ +class MessageFormatter implements MessageFormatterInterface, IntlFormatterInterface +{ + private $translator; + private $intlFormatter; + + /** + * @param TranslatorInterface|null $translator An identity translator to use as selector for pluralization + */ + public function __construct(TranslatorInterface $translator = null, IntlFormatterInterface $intlFormatter = null) + { + $this->translator = $translator ?? new IdentityTranslator(); + $this->intlFormatter = $intlFormatter ?? new IntlFormatter(); + } + + /** + * {@inheritdoc} + */ + public function format(string $message, string $locale, array $parameters = []) + { + if ($this->translator instanceof TranslatorInterface) { + return $this->translator->trans($message, $parameters, null, $locale); + } + + return strtr($message, $parameters); + } + + /** + * {@inheritdoc} + */ + public function formatIntl(string $message, string $locale, array $parameters = []): string + { + return $this->intlFormatter->formatIntl($message, $locale, $parameters); + } +} diff --git a/vendor/symfony/translation/Formatter/MessageFormatterInterface.php b/vendor/symfony/translation/Formatter/MessageFormatterInterface.php new file mode 100644 index 0000000..b85dbfd --- /dev/null +++ b/vendor/symfony/translation/Formatter/MessageFormatterInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Formatter; + +/** + * @author Guilherme Blanco + * @author Abdellatif Ait boudad + */ +interface MessageFormatterInterface +{ + /** + * Formats a localized message pattern with given arguments. + * + * @param string $message The message (may also be an object that can be cast to string) + * @param string $locale The message locale + * @param array $parameters An array of parameters for the message + * + * @return string + */ + public function format(string $message, string $locale, array $parameters = []); +} diff --git a/vendor/symfony/translation/IdentityTranslator.php b/vendor/symfony/translation/IdentityTranslator.php new file mode 100644 index 0000000..46875ed --- /dev/null +++ b/vendor/symfony/translation/IdentityTranslator.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation; + +use Symfony\Contracts\Translation\LocaleAwareInterface; +use Symfony\Contracts\Translation\TranslatorInterface; +use Symfony\Contracts\Translation\TranslatorTrait; + +/** + * IdentityTranslator does not translate anything. + * + * @author Fabien Potencier + */ +class IdentityTranslator implements TranslatorInterface, LocaleAwareInterface +{ + use TranslatorTrait; +} diff --git a/vendor/symfony/translation/LICENSE b/vendor/symfony/translation/LICENSE new file mode 100644 index 0000000..0138f8f --- /dev/null +++ b/vendor/symfony/translation/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-present Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/translation/Loader/ArrayLoader.php b/vendor/symfony/translation/Loader/ArrayLoader.php new file mode 100644 index 0000000..feed0de --- /dev/null +++ b/vendor/symfony/translation/Loader/ArrayLoader.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Loader; + +use Symfony\Component\Translation\MessageCatalogue; + +/** + * ArrayLoader loads translations from a PHP array. + * + * @author Fabien Potencier + */ +class ArrayLoader implements LoaderInterface +{ + /** + * {@inheritdoc} + */ + public function load($resource, string $locale, string $domain = 'messages') + { + $resource = $this->flatten($resource); + $catalogue = new MessageCatalogue($locale); + $catalogue->add($resource, $domain); + + return $catalogue; + } + + /** + * Flattens an nested array of translations. + * + * The scheme used is: + * 'key' => ['key2' => ['key3' => 'value']] + * Becomes: + * 'key.key2.key3' => 'value' + */ + private function flatten(array $messages): array + { + $result = []; + foreach ($messages as $key => $value) { + if (\is_array($value)) { + foreach ($this->flatten($value) as $k => $v) { + if (null !== $v) { + $result[$key.'.'.$k] = $v; + } + } + } elseif (null !== $value) { + $result[$key] = $value; + } + } + + return $result; + } +} diff --git a/vendor/symfony/translation/Loader/CsvFileLoader.php b/vendor/symfony/translation/Loader/CsvFileLoader.php new file mode 100644 index 0000000..8d5d4db --- /dev/null +++ b/vendor/symfony/translation/Loader/CsvFileLoader.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Loader; + +use Symfony\Component\Translation\Exception\NotFoundResourceException; + +/** + * CsvFileLoader loads translations from CSV files. + * + * @author Saša Stamenković + */ +class CsvFileLoader extends FileLoader +{ + private $delimiter = ';'; + private $enclosure = '"'; + private $escape = '\\'; + + /** + * {@inheritdoc} + */ + protected function loadResource(string $resource) + { + $messages = []; + + try { + $file = new \SplFileObject($resource, 'rb'); + } catch (\RuntimeException $e) { + throw new NotFoundResourceException(sprintf('Error opening file "%s".', $resource), 0, $e); + } + + $file->setFlags(\SplFileObject::READ_CSV | \SplFileObject::SKIP_EMPTY); + $file->setCsvControl($this->delimiter, $this->enclosure, $this->escape); + + foreach ($file as $data) { + if (false === $data) { + continue; + } + + if ('#' !== substr($data[0], 0, 1) && isset($data[1]) && 2 === \count($data)) { + $messages[$data[0]] = $data[1]; + } + } + + return $messages; + } + + /** + * Sets the delimiter, enclosure, and escape character for CSV. + */ + public function setCsvControl(string $delimiter = ';', string $enclosure = '"', string $escape = '\\') + { + $this->delimiter = $delimiter; + $this->enclosure = $enclosure; + $this->escape = $escape; + } +} diff --git a/vendor/symfony/translation/Loader/FileLoader.php b/vendor/symfony/translation/Loader/FileLoader.php new file mode 100644 index 0000000..4725ea6 --- /dev/null +++ b/vendor/symfony/translation/Loader/FileLoader.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Loader; + +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Translation\Exception\InvalidResourceException; +use Symfony\Component\Translation\Exception\NotFoundResourceException; + +/** + * @author Abdellatif Ait boudad + */ +abstract class FileLoader extends ArrayLoader +{ + /** + * {@inheritdoc} + */ + public function load($resource, string $locale, string $domain = 'messages') + { + if (!stream_is_local($resource)) { + throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource)); + } + + if (!file_exists($resource)) { + throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource)); + } + + $messages = $this->loadResource($resource); + + // empty resource + if (null === $messages) { + $messages = []; + } + + // not an array + if (!\is_array($messages)) { + throw new InvalidResourceException(sprintf('Unable to load file "%s".', $resource)); + } + + $catalogue = parent::load($messages, $locale, $domain); + + if (class_exists(FileResource::class)) { + $catalogue->addResource(new FileResource($resource)); + } + + return $catalogue; + } + + /** + * @return array + * + * @throws InvalidResourceException if stream content has an invalid format + */ + abstract protected function loadResource(string $resource); +} diff --git a/vendor/symfony/translation/Loader/IcuDatFileLoader.php b/vendor/symfony/translation/Loader/IcuDatFileLoader.php new file mode 100644 index 0000000..2a1aecc --- /dev/null +++ b/vendor/symfony/translation/Loader/IcuDatFileLoader.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Loader; + +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Translation\Exception\InvalidResourceException; +use Symfony\Component\Translation\Exception\NotFoundResourceException; +use Symfony\Component\Translation\MessageCatalogue; + +/** + * IcuResFileLoader loads translations from a resource bundle. + * + * @author stealth35 + */ +class IcuDatFileLoader extends IcuResFileLoader +{ + /** + * {@inheritdoc} + */ + public function load($resource, string $locale, string $domain = 'messages') + { + if (!stream_is_local($resource.'.dat')) { + throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource)); + } + + if (!file_exists($resource.'.dat')) { + throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource)); + } + + try { + $rb = new \ResourceBundle($locale, $resource); + } catch (\Exception $e) { + $rb = null; + } + + if (!$rb) { + throw new InvalidResourceException(sprintf('Cannot load resource "%s".', $resource)); + } elseif (intl_is_failure($rb->getErrorCode())) { + throw new InvalidResourceException($rb->getErrorMessage(), $rb->getErrorCode()); + } + + $messages = $this->flatten($rb); + $catalogue = new MessageCatalogue($locale); + $catalogue->add($messages, $domain); + + if (class_exists(FileResource::class)) { + $catalogue->addResource(new FileResource($resource.'.dat')); + } + + return $catalogue; + } +} diff --git a/vendor/symfony/translation/Loader/IcuResFileLoader.php b/vendor/symfony/translation/Loader/IcuResFileLoader.php new file mode 100644 index 0000000..6b7834e --- /dev/null +++ b/vendor/symfony/translation/Loader/IcuResFileLoader.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Loader; + +use Symfony\Component\Config\Resource\DirectoryResource; +use Symfony\Component\Translation\Exception\InvalidResourceException; +use Symfony\Component\Translation\Exception\NotFoundResourceException; +use Symfony\Component\Translation\MessageCatalogue; + +/** + * IcuResFileLoader loads translations from a resource bundle. + * + * @author stealth35 + */ +class IcuResFileLoader implements LoaderInterface +{ + /** + * {@inheritdoc} + */ + public function load($resource, string $locale, string $domain = 'messages') + { + if (!stream_is_local($resource)) { + throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource)); + } + + if (!is_dir($resource)) { + throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource)); + } + + try { + $rb = new \ResourceBundle($locale, $resource); + } catch (\Exception $e) { + $rb = null; + } + + if (!$rb) { + throw new InvalidResourceException(sprintf('Cannot load resource "%s".', $resource)); + } elseif (intl_is_failure($rb->getErrorCode())) { + throw new InvalidResourceException($rb->getErrorMessage(), $rb->getErrorCode()); + } + + $messages = $this->flatten($rb); + $catalogue = new MessageCatalogue($locale); + $catalogue->add($messages, $domain); + + if (class_exists(DirectoryResource::class)) { + $catalogue->addResource(new DirectoryResource($resource)); + } + + return $catalogue; + } + + /** + * Flattens an ResourceBundle. + * + * The scheme used is: + * key { key2 { key3 { "value" } } } + * Becomes: + * 'key.key2.key3' => 'value' + * + * This function takes an array by reference and will modify it + * + * @param \ResourceBundle $rb The ResourceBundle that will be flattened + * @param array $messages Used internally for recursive calls + * @param string|null $path Current path being parsed, used internally for recursive calls + * + * @return array + */ + protected function flatten(\ResourceBundle $rb, array &$messages = [], string $path = null) + { + foreach ($rb as $key => $value) { + $nodePath = $path ? $path.'.'.$key : $key; + if ($value instanceof \ResourceBundle) { + $this->flatten($value, $messages, $nodePath); + } else { + $messages[$nodePath] = $value; + } + } + + return $messages; + } +} diff --git a/vendor/symfony/translation/Loader/IniFileLoader.php b/vendor/symfony/translation/Loader/IniFileLoader.php new file mode 100644 index 0000000..7398f77 --- /dev/null +++ b/vendor/symfony/translation/Loader/IniFileLoader.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Loader; + +/** + * IniFileLoader loads translations from an ini file. + * + * @author stealth35 + */ +class IniFileLoader extends FileLoader +{ + /** + * {@inheritdoc} + */ + protected function loadResource(string $resource) + { + return parse_ini_file($resource, true); + } +} diff --git a/vendor/symfony/translation/Loader/JsonFileLoader.php b/vendor/symfony/translation/Loader/JsonFileLoader.php new file mode 100644 index 0000000..5aefba0 --- /dev/null +++ b/vendor/symfony/translation/Loader/JsonFileLoader.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Loader; + +use Symfony\Component\Translation\Exception\InvalidResourceException; + +/** + * JsonFileLoader loads translations from an json file. + * + * @author singles + */ +class JsonFileLoader extends FileLoader +{ + /** + * {@inheritdoc} + */ + protected function loadResource(string $resource) + { + $messages = []; + if ($data = file_get_contents($resource)) { + $messages = json_decode($data, true); + + if (0 < $errorCode = json_last_error()) { + throw new InvalidResourceException('Error parsing JSON: '.$this->getJSONErrorMessage($errorCode)); + } + } + + return $messages; + } + + /** + * Translates JSON_ERROR_* constant into meaningful message. + */ + private function getJSONErrorMessage(int $errorCode): string + { + switch ($errorCode) { + case \JSON_ERROR_DEPTH: + return 'Maximum stack depth exceeded'; + case \JSON_ERROR_STATE_MISMATCH: + return 'Underflow or the modes mismatch'; + case \JSON_ERROR_CTRL_CHAR: + return 'Unexpected control character found'; + case \JSON_ERROR_SYNTAX: + return 'Syntax error, malformed JSON'; + case \JSON_ERROR_UTF8: + return 'Malformed UTF-8 characters, possibly incorrectly encoded'; + default: + return 'Unknown error'; + } + } +} diff --git a/vendor/symfony/translation/Loader/LoaderInterface.php b/vendor/symfony/translation/Loader/LoaderInterface.php new file mode 100644 index 0000000..96b0c0d --- /dev/null +++ b/vendor/symfony/translation/Loader/LoaderInterface.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Loader; + +use Symfony\Component\Translation\Exception\InvalidResourceException; +use Symfony\Component\Translation\Exception\NotFoundResourceException; +use Symfony\Component\Translation\MessageCatalogue; + +/** + * LoaderInterface is the interface implemented by all translation loaders. + * + * @author Fabien Potencier + */ +interface LoaderInterface +{ + /** + * Loads a locale. + * + * @param mixed $resource A resource + * @param string $locale A locale + * @param string $domain The domain + * + * @return MessageCatalogue + * + * @throws NotFoundResourceException when the resource cannot be found + * @throws InvalidResourceException when the resource cannot be loaded + */ + public function load($resource, string $locale, string $domain = 'messages'); +} diff --git a/vendor/symfony/translation/Loader/MoFileLoader.php b/vendor/symfony/translation/Loader/MoFileLoader.php new file mode 100644 index 0000000..7fa2f2d --- /dev/null +++ b/vendor/symfony/translation/Loader/MoFileLoader.php @@ -0,0 +1,140 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Loader; + +use Symfony\Component\Translation\Exception\InvalidResourceException; + +/** + * @copyright Copyright (c) 2010, Union of RAD http://union-of-rad.org (http://lithify.me/) + */ +class MoFileLoader extends FileLoader +{ + /** + * Magic used for validating the format of an MO file as well as + * detecting if the machine used to create that file was little endian. + */ + public const MO_LITTLE_ENDIAN_MAGIC = 0x950412DE; + + /** + * Magic used for validating the format of an MO file as well as + * detecting if the machine used to create that file was big endian. + */ + public const MO_BIG_ENDIAN_MAGIC = 0xDE120495; + + /** + * The size of the header of an MO file in bytes. + */ + public const MO_HEADER_SIZE = 28; + + /** + * Parses machine object (MO) format, independent of the machine's endian it + * was created on. Both 32bit and 64bit systems are supported. + * + * {@inheritdoc} + */ + protected function loadResource(string $resource) + { + $stream = fopen($resource, 'r'); + + $stat = fstat($stream); + + if ($stat['size'] < self::MO_HEADER_SIZE) { + throw new InvalidResourceException('MO stream content has an invalid format.'); + } + $magic = unpack('V1', fread($stream, 4)); + $magic = hexdec(substr(dechex(current($magic)), -8)); + + if (self::MO_LITTLE_ENDIAN_MAGIC == $magic) { + $isBigEndian = false; + } elseif (self::MO_BIG_ENDIAN_MAGIC == $magic) { + $isBigEndian = true; + } else { + throw new InvalidResourceException('MO stream content has an invalid format.'); + } + + // formatRevision + $this->readLong($stream, $isBigEndian); + $count = $this->readLong($stream, $isBigEndian); + $offsetId = $this->readLong($stream, $isBigEndian); + $offsetTranslated = $this->readLong($stream, $isBigEndian); + // sizeHashes + $this->readLong($stream, $isBigEndian); + // offsetHashes + $this->readLong($stream, $isBigEndian); + + $messages = []; + + for ($i = 0; $i < $count; ++$i) { + $pluralId = null; + $translated = null; + + fseek($stream, $offsetId + $i * 8); + + $length = $this->readLong($stream, $isBigEndian); + $offset = $this->readLong($stream, $isBigEndian); + + if ($length < 1) { + continue; + } + + fseek($stream, $offset); + $singularId = fread($stream, $length); + + if (str_contains($singularId, "\000")) { + [$singularId, $pluralId] = explode("\000", $singularId); + } + + fseek($stream, $offsetTranslated + $i * 8); + $length = $this->readLong($stream, $isBigEndian); + $offset = $this->readLong($stream, $isBigEndian); + + if ($length < 1) { + continue; + } + + fseek($stream, $offset); + $translated = fread($stream, $length); + + if (str_contains($translated, "\000")) { + $translated = explode("\000", $translated); + } + + $ids = ['singular' => $singularId, 'plural' => $pluralId]; + $item = compact('ids', 'translated'); + + if (!empty($item['ids']['singular'])) { + $id = $item['ids']['singular']; + if (isset($item['ids']['plural'])) { + $id .= '|'.$item['ids']['plural']; + } + $messages[$id] = stripcslashes(implode('|', (array) $item['translated'])); + } + } + + fclose($stream); + + return array_filter($messages); + } + + /** + * Reads an unsigned long from stream respecting endianness. + * + * @param resource $stream + */ + private function readLong($stream, bool $isBigEndian): int + { + $result = unpack($isBigEndian ? 'N1' : 'V1', fread($stream, 4)); + $result = current($result); + + return (int) substr($result, -8); + } +} diff --git a/vendor/symfony/translation/Loader/PhpFileLoader.php b/vendor/symfony/translation/Loader/PhpFileLoader.php new file mode 100644 index 0000000..40f64db --- /dev/null +++ b/vendor/symfony/translation/Loader/PhpFileLoader.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 Symfony\Component\Translation\Loader; + +/** + * PhpFileLoader loads translations from PHP files returning an array of translations. + * + * @author Fabien Potencier + */ +class PhpFileLoader extends FileLoader +{ + private static $cache = []; + + /** + * {@inheritdoc} + */ + protected function loadResource(string $resource) + { + if ([] === self::$cache && \function_exists('opcache_invalidate') && filter_var(\ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN) && (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) || filter_var(\ini_get('opcache.enable_cli'), \FILTER_VALIDATE_BOOLEAN))) { + self::$cache = null; + } + + if (null === self::$cache) { + return require $resource; + } + + if (isset(self::$cache[$resource])) { + return self::$cache[$resource]; + } + + return self::$cache[$resource] = require $resource; + } +} diff --git a/vendor/symfony/translation/Loader/PoFileLoader.php b/vendor/symfony/translation/Loader/PoFileLoader.php new file mode 100644 index 0000000..ee143e2 --- /dev/null +++ b/vendor/symfony/translation/Loader/PoFileLoader.php @@ -0,0 +1,149 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Loader; + +/** + * @copyright Copyright (c) 2010, Union of RAD https://github.com/UnionOfRAD/lithium + * @copyright Copyright (c) 2012, Clemens Tolboom + */ +class PoFileLoader extends FileLoader +{ + /** + * Parses portable object (PO) format. + * + * From https://www.gnu.org/software/gettext/manual/gettext.html#PO-Files + * we should be able to parse files having: + * + * white-space + * # translator-comments + * #. extracted-comments + * #: reference... + * #, flag... + * #| msgid previous-untranslated-string + * msgid untranslated-string + * msgstr translated-string + * + * extra or different lines are: + * + * #| msgctxt previous-context + * #| msgid previous-untranslated-string + * msgctxt context + * + * #| msgid previous-untranslated-string-singular + * #| msgid_plural previous-untranslated-string-plural + * msgid untranslated-string-singular + * msgid_plural untranslated-string-plural + * msgstr[0] translated-string-case-0 + * ... + * msgstr[N] translated-string-case-n + * + * The definition states: + * - white-space and comments are optional. + * - msgid "" that an empty singleline defines a header. + * + * This parser sacrifices some features of the reference implementation the + * differences to that implementation are as follows. + * - No support for comments spanning multiple lines. + * - Translator and extracted comments are treated as being the same type. + * - Message IDs are allowed to have other encodings as just US-ASCII. + * + * Items with an empty id are ignored. + * + * {@inheritdoc} + */ + protected function loadResource(string $resource) + { + $stream = fopen($resource, 'r'); + + $defaults = [ + 'ids' => [], + 'translated' => null, + ]; + + $messages = []; + $item = $defaults; + $flags = []; + + while ($line = fgets($stream)) { + $line = trim($line); + + if ('' === $line) { + // Whitespace indicated current item is done + if (!\in_array('fuzzy', $flags)) { + $this->addMessage($messages, $item); + } + $item = $defaults; + $flags = []; + } elseif ('#,' === substr($line, 0, 2)) { + $flags = array_map('trim', explode(',', substr($line, 2))); + } elseif ('msgid "' === substr($line, 0, 7)) { + // We start a new msg so save previous + // TODO: this fails when comments or contexts are added + $this->addMessage($messages, $item); + $item = $defaults; + $item['ids']['singular'] = substr($line, 7, -1); + } elseif ('msgstr "' === substr($line, 0, 8)) { + $item['translated'] = substr($line, 8, -1); + } elseif ('"' === $line[0]) { + $continues = isset($item['translated']) ? 'translated' : 'ids'; + + if (\is_array($item[$continues])) { + end($item[$continues]); + $item[$continues][key($item[$continues])] .= substr($line, 1, -1); + } else { + $item[$continues] .= substr($line, 1, -1); + } + } elseif ('msgid_plural "' === substr($line, 0, 14)) { + $item['ids']['plural'] = substr($line, 14, -1); + } elseif ('msgstr[' === substr($line, 0, 7)) { + $size = strpos($line, ']'); + $item['translated'][(int) substr($line, 7, 1)] = substr($line, $size + 3, -1); + } + } + // save last item + if (!\in_array('fuzzy', $flags)) { + $this->addMessage($messages, $item); + } + fclose($stream); + + return $messages; + } + + /** + * Save a translation item to the messages. + * + * A .po file could contain by error missing plural indexes. We need to + * fix these before saving them. + */ + private function addMessage(array &$messages, array $item) + { + if (!empty($item['ids']['singular'])) { + $id = stripcslashes($item['ids']['singular']); + if (isset($item['ids']['plural'])) { + $id .= '|'.stripcslashes($item['ids']['plural']); + } + + $translated = (array) $item['translated']; + // PO are by definition indexed so sort by index. + ksort($translated); + // Make sure every index is filled. + end($translated); + $count = key($translated); + // Fill missing spots with '-'. + $empties = array_fill(0, $count + 1, '-'); + $translated += $empties; + ksort($translated); + + $messages[$id] = stripcslashes(implode('|', $translated)); + } + } +} diff --git a/vendor/symfony/translation/Loader/QtFileLoader.php b/vendor/symfony/translation/Loader/QtFileLoader.php new file mode 100644 index 0000000..9cf2fe9 --- /dev/null +++ b/vendor/symfony/translation/Loader/QtFileLoader.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Loader; + +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Config\Util\XmlUtils; +use Symfony\Component\Translation\Exception\InvalidResourceException; +use Symfony\Component\Translation\Exception\NotFoundResourceException; +use Symfony\Component\Translation\Exception\RuntimeException; +use Symfony\Component\Translation\MessageCatalogue; + +/** + * QtFileLoader loads translations from QT Translations XML files. + * + * @author Benjamin Eberlei + */ +class QtFileLoader implements LoaderInterface +{ + /** + * {@inheritdoc} + */ + public function load($resource, string $locale, string $domain = 'messages') + { + if (!class_exists(XmlUtils::class)) { + throw new RuntimeException('Loading translations from the QT format requires the Symfony Config component.'); + } + + if (!stream_is_local($resource)) { + throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource)); + } + + if (!file_exists($resource)) { + throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource)); + } + + try { + $dom = XmlUtils::loadFile($resource); + } catch (\InvalidArgumentException $e) { + throw new InvalidResourceException(sprintf('Unable to load "%s".', $resource), $e->getCode(), $e); + } + + $internalErrors = libxml_use_internal_errors(true); + libxml_clear_errors(); + + $xpath = new \DOMXPath($dom); + $nodes = $xpath->evaluate('//TS/context/name[text()="'.$domain.'"]'); + + $catalogue = new MessageCatalogue($locale); + if (1 == $nodes->length) { + $translations = $nodes->item(0)->nextSibling->parentNode->parentNode->getElementsByTagName('message'); + foreach ($translations as $translation) { + $translationValue = (string) $translation->getElementsByTagName('translation')->item(0)->nodeValue; + + if (!empty($translationValue)) { + $catalogue->set( + (string) $translation->getElementsByTagName('source')->item(0)->nodeValue, + $translationValue, + $domain + ); + } + $translation = $translation->nextSibling; + } + + if (class_exists(FileResource::class)) { + $catalogue->addResource(new FileResource($resource)); + } + } + + libxml_use_internal_errors($internalErrors); + + return $catalogue; + } +} diff --git a/vendor/symfony/translation/Loader/XliffFileLoader.php b/vendor/symfony/translation/Loader/XliffFileLoader.php new file mode 100644 index 0000000..5c9794a --- /dev/null +++ b/vendor/symfony/translation/Loader/XliffFileLoader.php @@ -0,0 +1,232 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Loader; + +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Config\Util\Exception\InvalidXmlException; +use Symfony\Component\Config\Util\Exception\XmlParsingException; +use Symfony\Component\Config\Util\XmlUtils; +use Symfony\Component\Translation\Exception\InvalidResourceException; +use Symfony\Component\Translation\Exception\NotFoundResourceException; +use Symfony\Component\Translation\Exception\RuntimeException; +use Symfony\Component\Translation\MessageCatalogue; +use Symfony\Component\Translation\Util\XliffUtils; + +/** + * XliffFileLoader loads translations from XLIFF files. + * + * @author Fabien Potencier + */ +class XliffFileLoader implements LoaderInterface +{ + /** + * {@inheritdoc} + */ + public function load($resource, string $locale, string $domain = 'messages') + { + if (!class_exists(XmlUtils::class)) { + throw new RuntimeException('Loading translations from the Xliff format requires the Symfony Config component.'); + } + + if (!$this->isXmlString($resource)) { + if (!stream_is_local($resource)) { + throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource)); + } + + if (!file_exists($resource)) { + throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource)); + } + + if (!is_file($resource)) { + throw new InvalidResourceException(sprintf('This is neither a file nor an XLIFF string "%s".', $resource)); + } + } + + try { + if ($this->isXmlString($resource)) { + $dom = XmlUtils::parse($resource); + } else { + $dom = XmlUtils::loadFile($resource); + } + } catch (\InvalidArgumentException|XmlParsingException|InvalidXmlException $e) { + throw new InvalidResourceException(sprintf('Unable to load "%s": ', $resource).$e->getMessage(), $e->getCode(), $e); + } + + if ($errors = XliffUtils::validateSchema($dom)) { + throw new InvalidResourceException(sprintf('Invalid resource provided: "%s"; Errors: ', $resource).XliffUtils::getErrorsAsString($errors)); + } + + $catalogue = new MessageCatalogue($locale); + $this->extract($dom, $catalogue, $domain); + + if (is_file($resource) && class_exists(FileResource::class)) { + $catalogue->addResource(new FileResource($resource)); + } + + return $catalogue; + } + + private function extract(\DOMDocument $dom, MessageCatalogue $catalogue, string $domain) + { + $xliffVersion = XliffUtils::getVersionNumber($dom); + + if ('1.2' === $xliffVersion) { + $this->extractXliff1($dom, $catalogue, $domain); + } + + if ('2.0' === $xliffVersion) { + $this->extractXliff2($dom, $catalogue, $domain); + } + } + + /** + * Extract messages and metadata from DOMDocument into a MessageCatalogue. + */ + private function extractXliff1(\DOMDocument $dom, MessageCatalogue $catalogue, string $domain) + { + $xml = simplexml_import_dom($dom); + $encoding = $dom->encoding ? strtoupper($dom->encoding) : null; + + $namespace = 'urn:oasis:names:tc:xliff:document:1.2'; + $xml->registerXPathNamespace('xliff', $namespace); + + foreach ($xml->xpath('//xliff:file') as $file) { + $fileAttributes = $file->attributes(); + + $file->registerXPathNamespace('xliff', $namespace); + + foreach ($file->xpath('.//xliff:trans-unit') as $translation) { + $attributes = $translation->attributes(); + + if (!(isset($attributes['resname']) || isset($translation->source))) { + continue; + } + + $source = isset($attributes['resname']) && $attributes['resname'] ? $attributes['resname'] : $translation->source; + // If the xlf file has another encoding specified, try to convert it because + // simple_xml will always return utf-8 encoded values + $target = $this->utf8ToCharset((string) ($translation->target ?? $translation->source), $encoding); + + $catalogue->set((string) $source, $target, $domain); + + $metadata = [ + 'source' => (string) $translation->source, + 'file' => [ + 'original' => (string) $fileAttributes['original'], + ], + ]; + if ($notes = $this->parseNotesMetadata($translation->note, $encoding)) { + $metadata['notes'] = $notes; + } + + if (isset($translation->target) && $translation->target->attributes()) { + $metadata['target-attributes'] = []; + foreach ($translation->target->attributes() as $key => $value) { + $metadata['target-attributes'][$key] = (string) $value; + } + } + + if (isset($attributes['id'])) { + $metadata['id'] = (string) $attributes['id']; + } + + $catalogue->setMetadata((string) $source, $metadata, $domain); + } + } + } + + private function extractXliff2(\DOMDocument $dom, MessageCatalogue $catalogue, string $domain) + { + $xml = simplexml_import_dom($dom); + $encoding = $dom->encoding ? strtoupper($dom->encoding) : null; + + $xml->registerXPathNamespace('xliff', 'urn:oasis:names:tc:xliff:document:2.0'); + + foreach ($xml->xpath('//xliff:unit') as $unit) { + foreach ($unit->segment as $segment) { + $attributes = $unit->attributes(); + $source = $attributes['name'] ?? $segment->source; + + // If the xlf file has another encoding specified, try to convert it because + // simple_xml will always return utf-8 encoded values + $target = $this->utf8ToCharset((string) ($segment->target ?? $segment->source), $encoding); + + $catalogue->set((string) $source, $target, $domain); + + $metadata = []; + if (isset($segment->target) && $segment->target->attributes()) { + $metadata['target-attributes'] = []; + foreach ($segment->target->attributes() as $key => $value) { + $metadata['target-attributes'][$key] = (string) $value; + } + } + + if (isset($unit->notes)) { + $metadata['notes'] = []; + foreach ($unit->notes->note as $noteNode) { + $note = []; + foreach ($noteNode->attributes() as $key => $value) { + $note[$key] = (string) $value; + } + $note['content'] = (string) $noteNode; + $metadata['notes'][] = $note; + } + } + + $catalogue->setMetadata((string) $source, $metadata, $domain); + } + } + } + + /** + * Convert a UTF8 string to the specified encoding. + */ + private function utf8ToCharset(string $content, string $encoding = null): string + { + if ('UTF-8' !== $encoding && !empty($encoding)) { + return mb_convert_encoding($content, $encoding, 'UTF-8'); + } + + return $content; + } + + private function parseNotesMetadata(\SimpleXMLElement $noteElement = null, string $encoding = null): array + { + $notes = []; + + if (null === $noteElement) { + return $notes; + } + + /** @var \SimpleXMLElement $xmlNote */ + foreach ($noteElement as $xmlNote) { + $noteAttributes = $xmlNote->attributes(); + $note = ['content' => $this->utf8ToCharset((string) $xmlNote, $encoding)]; + if (isset($noteAttributes['priority'])) { + $note['priority'] = (int) $noteAttributes['priority']; + } + + if (isset($noteAttributes['from'])) { + $note['from'] = (string) $noteAttributes['from']; + } + + $notes[] = $note; + } + + return $notes; + } + + private function isXmlString(string $resource): bool + { + return 0 === strpos($resource, ' + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Loader; + +use Symfony\Component\Translation\Exception\InvalidResourceException; +use Symfony\Component\Translation\Exception\LogicException; +use Symfony\Component\Yaml\Exception\ParseException; +use Symfony\Component\Yaml\Parser as YamlParser; +use Symfony\Component\Yaml\Yaml; + +/** + * YamlFileLoader loads translations from Yaml files. + * + * @author Fabien Potencier + */ +class YamlFileLoader extends FileLoader +{ + private $yamlParser; + + /** + * {@inheritdoc} + */ + protected function loadResource(string $resource) + { + if (null === $this->yamlParser) { + if (!class_exists(\Symfony\Component\Yaml\Parser::class)) { + throw new LogicException('Loading translations from the YAML format requires the Symfony Yaml component.'); + } + + $this->yamlParser = new YamlParser(); + } + + try { + $messages = $this->yamlParser->parseFile($resource, Yaml::PARSE_CONSTANT); + } catch (ParseException $e) { + throw new InvalidResourceException(sprintf('The file "%s" does not contain valid YAML: ', $resource).$e->getMessage(), 0, $e); + } + + if (null !== $messages && !\is_array($messages)) { + throw new InvalidResourceException(sprintf('Unable to load file "%s".', $resource)); + } + + return $messages ?: []; + } +} diff --git a/vendor/symfony/translation/LoggingTranslator.php b/vendor/symfony/translation/LoggingTranslator.php new file mode 100644 index 0000000..6ccd482 --- /dev/null +++ b/vendor/symfony/translation/LoggingTranslator.php @@ -0,0 +1,131 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation; + +use Psr\Log\LoggerInterface; +use Symfony\Component\Translation\Exception\InvalidArgumentException; +use Symfony\Contracts\Translation\LocaleAwareInterface; +use Symfony\Contracts\Translation\TranslatorInterface; + +/** + * @author Abdellatif Ait boudad + */ +class LoggingTranslator implements TranslatorInterface, TranslatorBagInterface, LocaleAwareInterface +{ + private $translator; + private $logger; + + /** + * @param TranslatorInterface&TranslatorBagInterface&LocaleAwareInterface $translator The translator must implement TranslatorBagInterface + */ + public function __construct(TranslatorInterface $translator, LoggerInterface $logger) + { + if (!$translator instanceof TranslatorBagInterface || !$translator instanceof LocaleAwareInterface) { + throw new InvalidArgumentException(sprintf('The Translator "%s" must implement TranslatorInterface, TranslatorBagInterface and LocaleAwareInterface.', get_debug_type($translator))); + } + + $this->translator = $translator; + $this->logger = $logger; + } + + /** + * {@inheritdoc} + */ + public function trans(?string $id, array $parameters = [], string $domain = null, string $locale = null) + { + $trans = $this->translator->trans($id = (string) $id, $parameters, $domain, $locale); + $this->log($id, $domain, $locale); + + return $trans; + } + + /** + * {@inheritdoc} + */ + public function setLocale(string $locale) + { + $prev = $this->translator->getLocale(); + $this->translator->setLocale($locale); + if ($prev === $locale) { + return; + } + + $this->logger->debug(sprintf('The locale of the translator has changed from "%s" to "%s".', $prev, $locale)); + } + + /** + * {@inheritdoc} + */ + public function getLocale() + { + return $this->translator->getLocale(); + } + + /** + * {@inheritdoc} + */ + public function getCatalogue(string $locale = null) + { + return $this->translator->getCatalogue($locale); + } + + /** + * {@inheritdoc} + */ + public function getCatalogues(): array + { + return $this->translator->getCatalogues(); + } + + /** + * Gets the fallback locales. + * + * @return array + */ + public function getFallbackLocales() + { + if ($this->translator instanceof Translator || method_exists($this->translator, 'getFallbackLocales')) { + return $this->translator->getFallbackLocales(); + } + + return []; + } + + /** + * Passes through all unknown calls onto the translator object. + */ + public function __call(string $method, array $args) + { + return $this->translator->{$method}(...$args); + } + + /** + * Logs for missing translations. + */ + private function log(string $id, ?string $domain, ?string $locale) + { + if (null === $domain) { + $domain = 'messages'; + } + + $catalogue = $this->translator->getCatalogue($locale); + if ($catalogue->defines($id, $domain)) { + return; + } + + if ($catalogue->has($id, $domain)) { + $this->logger->debug('Translation use fallback catalogue.', ['id' => $id, 'domain' => $domain, 'locale' => $catalogue->getLocale()]); + } else { + $this->logger->warning('Translation not found.', ['id' => $id, 'domain' => $domain, 'locale' => $catalogue->getLocale()]); + } + } +} diff --git a/vendor/symfony/translation/MessageCatalogue.php b/vendor/symfony/translation/MessageCatalogue.php new file mode 100644 index 0000000..9da3b7f --- /dev/null +++ b/vendor/symfony/translation/MessageCatalogue.php @@ -0,0 +1,309 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation; + +use Symfony\Component\Config\Resource\ResourceInterface; +use Symfony\Component\Translation\Exception\LogicException; + +/** + * @author Fabien Potencier + */ +class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterface +{ + private $messages = []; + private $metadata = []; + private $resources = []; + private $locale; + private $fallbackCatalogue; + private $parent; + + /** + * @param array $messages An array of messages classified by domain + */ + public function __construct(string $locale, array $messages = []) + { + $this->locale = $locale; + $this->messages = $messages; + } + + /** + * {@inheritdoc} + */ + public function getLocale() + { + return $this->locale; + } + + /** + * {@inheritdoc} + */ + public function getDomains() + { + $domains = []; + + foreach ($this->messages as $domain => $messages) { + if (str_ends_with($domain, self::INTL_DOMAIN_SUFFIX)) { + $domain = substr($domain, 0, -\strlen(self::INTL_DOMAIN_SUFFIX)); + } + $domains[$domain] = $domain; + } + + return array_values($domains); + } + + /** + * {@inheritdoc} + */ + public function all(string $domain = null) + { + if (null !== $domain) { + // skip messages merge if intl-icu requested explicitly + if (str_ends_with($domain, self::INTL_DOMAIN_SUFFIX)) { + return $this->messages[$domain] ?? []; + } + + return ($this->messages[$domain.self::INTL_DOMAIN_SUFFIX] ?? []) + ($this->messages[$domain] ?? []); + } + + $allMessages = []; + + foreach ($this->messages as $domain => $messages) { + if (str_ends_with($domain, self::INTL_DOMAIN_SUFFIX)) { + $domain = substr($domain, 0, -\strlen(self::INTL_DOMAIN_SUFFIX)); + $allMessages[$domain] = $messages + ($allMessages[$domain] ?? []); + } else { + $allMessages[$domain] = ($allMessages[$domain] ?? []) + $messages; + } + } + + return $allMessages; + } + + /** + * {@inheritdoc} + */ + public function set(string $id, string $translation, string $domain = 'messages') + { + $this->add([$id => $translation], $domain); + } + + /** + * {@inheritdoc} + */ + public function has(string $id, string $domain = 'messages') + { + if (isset($this->messages[$domain][$id]) || isset($this->messages[$domain.self::INTL_DOMAIN_SUFFIX][$id])) { + return true; + } + + if (null !== $this->fallbackCatalogue) { + return $this->fallbackCatalogue->has($id, $domain); + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function defines(string $id, string $domain = 'messages') + { + return isset($this->messages[$domain][$id]) || isset($this->messages[$domain.self::INTL_DOMAIN_SUFFIX][$id]); + } + + /** + * {@inheritdoc} + */ + public function get(string $id, string $domain = 'messages') + { + if (isset($this->messages[$domain.self::INTL_DOMAIN_SUFFIX][$id])) { + return $this->messages[$domain.self::INTL_DOMAIN_SUFFIX][$id]; + } + + if (isset($this->messages[$domain][$id])) { + return $this->messages[$domain][$id]; + } + + if (null !== $this->fallbackCatalogue) { + return $this->fallbackCatalogue->get($id, $domain); + } + + return $id; + } + + /** + * {@inheritdoc} + */ + public function replace(array $messages, string $domain = 'messages') + { + unset($this->messages[$domain], $this->messages[$domain.self::INTL_DOMAIN_SUFFIX]); + + $this->add($messages, $domain); + } + + /** + * {@inheritdoc} + */ + public function add(array $messages, string $domain = 'messages') + { + $altDomain = str_ends_with($domain, self::INTL_DOMAIN_SUFFIX) ? substr($domain, 0, -\strlen(self::INTL_DOMAIN_SUFFIX)) : $domain.self::INTL_DOMAIN_SUFFIX; + foreach ($messages as $id => $message) { + unset($this->messages[$altDomain][$id]); + $this->messages[$domain][$id] = $message; + } + + if ([] === ($this->messages[$altDomain] ?? null)) { + unset($this->messages[$altDomain]); + } + } + + /** + * {@inheritdoc} + */ + public function addCatalogue(MessageCatalogueInterface $catalogue) + { + if ($catalogue->getLocale() !== $this->locale) { + throw new LogicException(sprintf('Cannot add a catalogue for locale "%s" as the current locale for this catalogue is "%s".', $catalogue->getLocale(), $this->locale)); + } + + foreach ($catalogue->all() as $domain => $messages) { + if ($intlMessages = $catalogue->all($domain.self::INTL_DOMAIN_SUFFIX)) { + $this->add($intlMessages, $domain.self::INTL_DOMAIN_SUFFIX); + $messages = array_diff_key($messages, $intlMessages); + } + $this->add($messages, $domain); + } + + foreach ($catalogue->getResources() as $resource) { + $this->addResource($resource); + } + + if ($catalogue instanceof MetadataAwareInterface) { + $metadata = $catalogue->getMetadata('', ''); + $this->addMetadata($metadata); + } + } + + /** + * {@inheritdoc} + */ + public function addFallbackCatalogue(MessageCatalogueInterface $catalogue) + { + // detect circular references + $c = $catalogue; + while ($c = $c->getFallbackCatalogue()) { + if ($c->getLocale() === $this->getLocale()) { + throw new LogicException(sprintf('Circular reference detected when adding a fallback catalogue for locale "%s".', $catalogue->getLocale())); + } + } + + $c = $this; + do { + if ($c->getLocale() === $catalogue->getLocale()) { + throw new LogicException(sprintf('Circular reference detected when adding a fallback catalogue for locale "%s".', $catalogue->getLocale())); + } + + foreach ($catalogue->getResources() as $resource) { + $c->addResource($resource); + } + } while ($c = $c->parent); + + $catalogue->parent = $this; + $this->fallbackCatalogue = $catalogue; + + foreach ($catalogue->getResources() as $resource) { + $this->addResource($resource); + } + } + + /** + * {@inheritdoc} + */ + public function getFallbackCatalogue() + { + return $this->fallbackCatalogue; + } + + /** + * {@inheritdoc} + */ + public function getResources() + { + return array_values($this->resources); + } + + /** + * {@inheritdoc} + */ + public function addResource(ResourceInterface $resource) + { + $this->resources[$resource->__toString()] = $resource; + } + + /** + * {@inheritdoc} + */ + public function getMetadata(string $key = '', string $domain = 'messages') + { + if ('' == $domain) { + return $this->metadata; + } + + if (isset($this->metadata[$domain])) { + if ('' == $key) { + return $this->metadata[$domain]; + } + + if (isset($this->metadata[$domain][$key])) { + return $this->metadata[$domain][$key]; + } + } + + return null; + } + + /** + * {@inheritdoc} + */ + public function setMetadata(string $key, $value, string $domain = 'messages') + { + $this->metadata[$domain][$key] = $value; + } + + /** + * {@inheritdoc} + */ + public function deleteMetadata(string $key = '', string $domain = 'messages') + { + if ('' == $domain) { + $this->metadata = []; + } elseif ('' == $key) { + unset($this->metadata[$domain]); + } else { + unset($this->metadata[$domain][$key]); + } + } + + /** + * Adds current values with the new values. + * + * @param array $values Values to add + */ + private function addMetadata(array $values) + { + foreach ($values as $domain => $keys) { + foreach ($keys as $key => $value) { + $this->setMetadata($key, $value, $domain); + } + } + } +} diff --git a/vendor/symfony/translation/MessageCatalogueInterface.php b/vendor/symfony/translation/MessageCatalogueInterface.php new file mode 100644 index 0000000..965bf00 --- /dev/null +++ b/vendor/symfony/translation/MessageCatalogueInterface.php @@ -0,0 +1,138 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation; + +use Symfony\Component\Config\Resource\ResourceInterface; + +/** + * MessageCatalogueInterface. + * + * @author Fabien Potencier + */ +interface MessageCatalogueInterface +{ + public const INTL_DOMAIN_SUFFIX = '+intl-icu'; + + /** + * Gets the catalogue locale. + * + * @return string + */ + public function getLocale(); + + /** + * Gets the domains. + * + * @return array + */ + public function getDomains(); + + /** + * Gets the messages within a given domain. + * + * If $domain is null, it returns all messages. + * + * @param string|null $domain The domain name + * + * @return array + */ + public function all(string $domain = null); + + /** + * Sets a message translation. + * + * @param string $id The message id + * @param string $translation The messages translation + * @param string $domain The domain name + */ + public function set(string $id, string $translation, string $domain = 'messages'); + + /** + * Checks if a message has a translation. + * + * @param string $id The message id + * @param string $domain The domain name + * + * @return bool + */ + public function has(string $id, string $domain = 'messages'); + + /** + * Checks if a message has a translation (it does not take into account the fallback mechanism). + * + * @param string $id The message id + * @param string $domain The domain name + * + * @return bool + */ + public function defines(string $id, string $domain = 'messages'); + + /** + * Gets a message translation. + * + * @param string $id The message id + * @param string $domain The domain name + * + * @return string + */ + public function get(string $id, string $domain = 'messages'); + + /** + * Sets translations for a given domain. + * + * @param array $messages An array of translations + * @param string $domain The domain name + */ + public function replace(array $messages, string $domain = 'messages'); + + /** + * Adds translations for a given domain. + * + * @param array $messages An array of translations + * @param string $domain The domain name + */ + public function add(array $messages, string $domain = 'messages'); + + /** + * Merges translations from the given Catalogue into the current one. + * + * The two catalogues must have the same locale. + */ + public function addCatalogue(self $catalogue); + + /** + * Merges translations from the given Catalogue into the current one + * only when the translation does not exist. + * + * This is used to provide default translations when they do not exist for the current locale. + */ + public function addFallbackCatalogue(self $catalogue); + + /** + * Gets the fallback catalogue. + * + * @return self|null + */ + public function getFallbackCatalogue(); + + /** + * Returns an array of resources loaded to build this collection. + * + * @return ResourceInterface[] + */ + public function getResources(); + + /** + * Adds a resource for this collection. + */ + public function addResource(ResourceInterface $resource); +} diff --git a/vendor/symfony/translation/MetadataAwareInterface.php b/vendor/symfony/translation/MetadataAwareInterface.php new file mode 100644 index 0000000..2216eed --- /dev/null +++ b/vendor/symfony/translation/MetadataAwareInterface.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation; + +/** + * MetadataAwareInterface. + * + * @author Fabien Potencier + */ +interface MetadataAwareInterface +{ + /** + * Gets metadata for the given domain and key. + * + * Passing an empty domain will return an array with all metadata indexed by + * domain and then by key. Passing an empty key will return an array with all + * metadata for the given domain. + * + * @return mixed The value that was set or an array with the domains/keys or null + */ + public function getMetadata(string $key = '', string $domain = 'messages'); + + /** + * Adds metadata to a message domain. + * + * @param mixed $value + */ + public function setMetadata(string $key, $value, string $domain = 'messages'); + + /** + * Deletes metadata for the given key and domain. + * + * Passing an empty domain will delete all metadata. Passing an empty key will + * delete all metadata for the given domain. + */ + public function deleteMetadata(string $key = '', string $domain = 'messages'); +} diff --git a/vendor/symfony/translation/Provider/AbstractProviderFactory.php b/vendor/symfony/translation/Provider/AbstractProviderFactory.php new file mode 100644 index 0000000..17442fd --- /dev/null +++ b/vendor/symfony/translation/Provider/AbstractProviderFactory.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Provider; + +use Symfony\Component\Translation\Exception\IncompleteDsnException; + +abstract class AbstractProviderFactory implements ProviderFactoryInterface +{ + public function supports(Dsn $dsn): bool + { + return \in_array($dsn->getScheme(), $this->getSupportedSchemes(), true); + } + + /** + * @return string[] + */ + abstract protected function getSupportedSchemes(): array; + + protected function getUser(Dsn $dsn): string + { + if (null === $user = $dsn->getUser()) { + throw new IncompleteDsnException('User is not set.', $dsn->getOriginalDsn()); + } + + return $user; + } + + protected function getPassword(Dsn $dsn): string + { + if (null === $password = $dsn->getPassword()) { + throw new IncompleteDsnException('Password is not set.', $dsn->getOriginalDsn()); + } + + return $password; + } +} diff --git a/vendor/symfony/translation/Provider/Dsn.php b/vendor/symfony/translation/Provider/Dsn.php new file mode 100644 index 0000000..820cabf --- /dev/null +++ b/vendor/symfony/translation/Provider/Dsn.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 Symfony\Component\Translation\Provider; + +use Symfony\Component\Translation\Exception\InvalidArgumentException; +use Symfony\Component\Translation\Exception\MissingRequiredOptionException; + +/** + * @author Fabien Potencier + * @author Oskar Stark + */ +final class Dsn +{ + private $scheme; + private $host; + private $user; + private $password; + private $port; + private $path; + private $options; + private $originalDsn; + + public function __construct(string $dsn) + { + $this->originalDsn = $dsn; + + if (false === $parsedDsn = parse_url($dsn)) { + throw new InvalidArgumentException(sprintf('The "%s" translation provider DSN is invalid.', $dsn)); + } + + if (!isset($parsedDsn['scheme'])) { + throw new InvalidArgumentException(sprintf('The "%s" translation provider DSN must contain a scheme.', $dsn)); + } + $this->scheme = $parsedDsn['scheme']; + + if (!isset($parsedDsn['host'])) { + throw new InvalidArgumentException(sprintf('The "%s" translation provider DSN must contain a host (use "default" by default).', $dsn)); + } + $this->host = $parsedDsn['host']; + + $this->user = '' !== ($parsedDsn['user'] ?? '') ? urldecode($parsedDsn['user']) : null; + $this->password = '' !== ($parsedDsn['pass'] ?? '') ? urldecode($parsedDsn['pass']) : null; + $this->port = $parsedDsn['port'] ?? null; + $this->path = $parsedDsn['path'] ?? null; + parse_str($parsedDsn['query'] ?? '', $this->options); + } + + public function getScheme(): string + { + return $this->scheme; + } + + public function getHost(): string + { + return $this->host; + } + + public function getUser(): ?string + { + return $this->user; + } + + public function getPassword(): ?string + { + return $this->password; + } + + public function getPort(int $default = null): ?int + { + return $this->port ?? $default; + } + + public function getOption(string $key, $default = null) + { + return $this->options[$key] ?? $default; + } + + public function getRequiredOption(string $key) + { + if (!\array_key_exists($key, $this->options) || '' === trim($this->options[$key])) { + throw new MissingRequiredOptionException($key); + } + + return $this->options[$key]; + } + + public function getOptions(): array + { + return $this->options; + } + + public function getPath(): ?string + { + return $this->path; + } + + public function getOriginalDsn(): string + { + return $this->originalDsn; + } +} diff --git a/vendor/symfony/translation/Provider/FilteringProvider.php b/vendor/symfony/translation/Provider/FilteringProvider.php new file mode 100644 index 0000000..5f970a2 --- /dev/null +++ b/vendor/symfony/translation/Provider/FilteringProvider.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Provider; + +use Symfony\Component\Translation\TranslatorBag; +use Symfony\Component\Translation\TranslatorBagInterface; + +/** + * Filters domains and locales between the Translator config values and those specific to each provider. + * + * @author Mathieu Santostefano + */ +class FilteringProvider implements ProviderInterface +{ + private $provider; + private $locales; + private $domains; + + public function __construct(ProviderInterface $provider, array $locales, array $domains = []) + { + $this->provider = $provider; + $this->locales = $locales; + $this->domains = $domains; + } + + public function __toString(): string + { + return (string) $this->provider; + } + + /** + * {@inheritdoc} + */ + public function write(TranslatorBagInterface $translatorBag): void + { + $this->provider->write($translatorBag); + } + + public function read(array $domains, array $locales): TranslatorBag + { + $domains = !$this->domains ? $domains : array_intersect($this->domains, $domains); + $locales = array_intersect($this->locales, $locales); + + return $this->provider->read($domains, $locales); + } + + public function delete(TranslatorBagInterface $translatorBag): void + { + $this->provider->delete($translatorBag); + } + + public function getDomains(): array + { + return $this->domains; + } +} diff --git a/vendor/symfony/translation/Provider/NullProvider.php b/vendor/symfony/translation/Provider/NullProvider.php new file mode 100644 index 0000000..f00392e --- /dev/null +++ b/vendor/symfony/translation/Provider/NullProvider.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Provider; + +use Symfony\Component\Translation\TranslatorBag; +use Symfony\Component\Translation\TranslatorBagInterface; + +/** + * @author Mathieu Santostefano + */ +class NullProvider implements ProviderInterface +{ + public function __toString(): string + { + return 'null'; + } + + public function write(TranslatorBagInterface $translatorBag, bool $override = false): void + { + } + + public function read(array $domains, array $locales): TranslatorBag + { + return new TranslatorBag(); + } + + public function delete(TranslatorBagInterface $translatorBag): void + { + } +} diff --git a/vendor/symfony/translation/Provider/NullProviderFactory.php b/vendor/symfony/translation/Provider/NullProviderFactory.php new file mode 100644 index 0000000..f350f16 --- /dev/null +++ b/vendor/symfony/translation/Provider/NullProviderFactory.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Provider; + +use Symfony\Component\Translation\Exception\UnsupportedSchemeException; + +/** + * @author Mathieu Santostefano + */ +final class NullProviderFactory extends AbstractProviderFactory +{ + public function create(Dsn $dsn): ProviderInterface + { + if ('null' === $dsn->getScheme()) { + return new NullProvider(); + } + + throw new UnsupportedSchemeException($dsn, 'null', $this->getSupportedSchemes()); + } + + protected function getSupportedSchemes(): array + { + return ['null']; + } +} diff --git a/vendor/symfony/translation/Provider/ProviderFactoryInterface.php b/vendor/symfony/translation/Provider/ProviderFactoryInterface.php new file mode 100644 index 0000000..3fd4494 --- /dev/null +++ b/vendor/symfony/translation/Provider/ProviderFactoryInterface.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Provider; + +use Symfony\Component\Translation\Exception\IncompleteDsnException; +use Symfony\Component\Translation\Exception\UnsupportedSchemeException; + +interface ProviderFactoryInterface +{ + /** + * @throws UnsupportedSchemeException + * @throws IncompleteDsnException + */ + public function create(Dsn $dsn): ProviderInterface; + + public function supports(Dsn $dsn): bool; +} diff --git a/vendor/symfony/translation/Provider/ProviderInterface.php b/vendor/symfony/translation/Provider/ProviderInterface.php new file mode 100644 index 0000000..a32193f --- /dev/null +++ b/vendor/symfony/translation/Provider/ProviderInterface.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Provider; + +use Symfony\Component\Translation\TranslatorBag; +use Symfony\Component\Translation\TranslatorBagInterface; + +interface ProviderInterface +{ + public function __toString(): string; + + /** + * Translations available in the TranslatorBag only must be created. + * Translations available in both the TranslatorBag and on the provider + * must be overwritten. + * Translations available on the provider only must be kept. + */ + public function write(TranslatorBagInterface $translatorBag): void; + + public function read(array $domains, array $locales): TranslatorBag; + + public function delete(TranslatorBagInterface $translatorBag): void; +} diff --git a/vendor/symfony/translation/Provider/TranslationProviderCollection.php b/vendor/symfony/translation/Provider/TranslationProviderCollection.php new file mode 100644 index 0000000..61ac641 --- /dev/null +++ b/vendor/symfony/translation/Provider/TranslationProviderCollection.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Provider; + +use Symfony\Component\Translation\Exception\InvalidArgumentException; + +/** + * @author Mathieu Santostefano + */ +final class TranslationProviderCollection +{ + /** + * @var array + */ + private $providers; + + /** + * @param array $providers + */ + public function __construct(iterable $providers) + { + $this->providers = \is_array($providers) ? $providers : iterator_to_array($providers); + } + + public function __toString(): string + { + return '['.implode(',', array_keys($this->providers)).']'; + } + + public function has(string $name): bool + { + return isset($this->providers[$name]); + } + + public function get(string $name): ProviderInterface + { + if (!$this->has($name)) { + throw new InvalidArgumentException(sprintf('Provider "%s" not found. Available: "%s".', $name, (string) $this)); + } + + return $this->providers[$name]; + } + + public function keys(): array + { + return array_keys($this->providers); + } +} diff --git a/vendor/symfony/translation/Provider/TranslationProviderCollectionFactory.php b/vendor/symfony/translation/Provider/TranslationProviderCollectionFactory.php new file mode 100644 index 0000000..81db3a5 --- /dev/null +++ b/vendor/symfony/translation/Provider/TranslationProviderCollectionFactory.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Provider; + +use Symfony\Component\Translation\Exception\UnsupportedSchemeException; + +/** + * @author Mathieu Santostefano + */ +class TranslationProviderCollectionFactory +{ + private $factories; + private $enabledLocales; + + /** + * @param iterable $factories + */ + public function __construct(iterable $factories, array $enabledLocales) + { + $this->factories = $factories; + $this->enabledLocales = $enabledLocales; + } + + public function fromConfig(array $config): TranslationProviderCollection + { + $providers = []; + foreach ($config as $name => $currentConfig) { + $providers[$name] = $this->fromDsnObject( + new Dsn($currentConfig['dsn']), + !$currentConfig['locales'] ? $this->enabledLocales : $currentConfig['locales'], + !$currentConfig['domains'] ? [] : $currentConfig['domains'] + ); + } + + return new TranslationProviderCollection($providers); + } + + public function fromDsnObject(Dsn $dsn, array $locales, array $domains = []): ProviderInterface + { + foreach ($this->factories as $factory) { + if ($factory->supports($dsn)) { + return new FilteringProvider($factory->create($dsn), $locales, $domains); + } + } + + throw new UnsupportedSchemeException($dsn); + } +} diff --git a/vendor/symfony/translation/PseudoLocalizationTranslator.php b/vendor/symfony/translation/PseudoLocalizationTranslator.php new file mode 100644 index 0000000..c769bda --- /dev/null +++ b/vendor/symfony/translation/PseudoLocalizationTranslator.php @@ -0,0 +1,368 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation; + +use Symfony\Contracts\Translation\TranslatorInterface; + +/** + * This translator should only be used in a development environment. + */ +final class PseudoLocalizationTranslator implements TranslatorInterface +{ + private const EXPANSION_CHARACTER = '~'; + + private $translator; + private $accents; + private $expansionFactor; + private $brackets; + private $parseHTML; + + /** + * @var string[] + */ + private $localizableHTMLAttributes; + + /** + * Available options: + * * accents: + * type: boolean + * default: true + * description: replace ASCII characters of the translated string with accented versions or similar characters + * example: if true, "foo" => "ƒöö". + * + * * expansion_factor: + * type: float + * default: 1 + * validation: it must be greater than or equal to 1 + * description: expand the translated string by the given factor with spaces and tildes + * example: if 2, "foo" => "~foo ~" + * + * * brackets: + * type: boolean + * default: true + * description: wrap the translated string with brackets + * example: if true, "foo" => "[foo]" + * + * * parse_html: + * type: boolean + * default: false + * description: parse the translated string as HTML - looking for HTML tags has a performance impact but allows to preserve them from alterations - it also allows to compute the visible translated string length which is useful to correctly expand ot when it contains HTML + * warning: unclosed tags are unsupported, they will be fixed (closed) by the parser - eg, "foo
bar" => "foo
bar
" + * + * * localizable_html_attributes: + * type: string[] + * default: [] + * description: the list of HTML attributes whose values can be altered - it is only useful when the "parse_html" option is set to true + * example: if ["title"], and with the "accents" option set to true, "Profile" => "Þŕöƒîļé" - if "title" was not in the "localizable_html_attributes" list, the title attribute data would be left unchanged. + */ + public function __construct(TranslatorInterface $translator, array $options = []) + { + $this->translator = $translator; + $this->accents = $options['accents'] ?? true; + + if (1.0 > ($this->expansionFactor = $options['expansion_factor'] ?? 1.0)) { + throw new \InvalidArgumentException('The expansion factor must be greater than or equal to 1.'); + } + + $this->brackets = $options['brackets'] ?? true; + + $this->parseHTML = $options['parse_html'] ?? false; + if ($this->parseHTML && !$this->accents && 1.0 === $this->expansionFactor) { + $this->parseHTML = false; + } + + $this->localizableHTMLAttributes = $options['localizable_html_attributes'] ?? []; + } + + /** + * {@inheritdoc} + */ + public function trans(string $id, array $parameters = [], string $domain = null, string $locale = null): string + { + $trans = ''; + $visibleText = ''; + + foreach ($this->getParts($this->translator->trans($id, $parameters, $domain, $locale)) as [$visible, $localizable, $text]) { + if ($visible) { + $visibleText .= $text; + } + + if (!$localizable) { + $trans .= $text; + + continue; + } + + $this->addAccents($trans, $text); + } + + $this->expand($trans, $visibleText); + + $this->addBrackets($trans); + + return $trans; + } + + public function getLocale(): string + { + return $this->translator->getLocale(); + } + + private function getParts(string $originalTrans): array + { + if (!$this->parseHTML) { + return [[true, true, $originalTrans]]; + } + + $html = mb_encode_numericentity($originalTrans, [0x80, 0xFFFF, 0, 0xFFFF], mb_detect_encoding($originalTrans, null, true) ?: 'UTF-8'); + + $useInternalErrors = libxml_use_internal_errors(true); + + $dom = new \DOMDocument(); + $dom->loadHTML(''.$html.''); + + libxml_clear_errors(); + libxml_use_internal_errors($useInternalErrors); + + return $this->parseNode($dom->childNodes->item(1)->childNodes->item(0)->childNodes->item(0)); + } + + private function parseNode(\DOMNode $node): array + { + $parts = []; + + foreach ($node->childNodes as $childNode) { + if (!$childNode instanceof \DOMElement) { + $parts[] = [true, true, $childNode->nodeValue]; + + continue; + } + + $parts[] = [false, false, '<'.$childNode->tagName]; + + /** @var \DOMAttr $attribute */ + foreach ($childNode->attributes as $attribute) { + $parts[] = [false, false, ' '.$attribute->nodeName.'="']; + + $localizableAttribute = \in_array($attribute->nodeName, $this->localizableHTMLAttributes, true); + foreach (preg_split('/(&(?:amp|quot|#039|lt|gt);+)/', htmlspecialchars($attribute->nodeValue, \ENT_QUOTES, 'UTF-8'), -1, \PREG_SPLIT_DELIM_CAPTURE) as $i => $match) { + if ('' === $match) { + continue; + } + + $parts[] = [false, $localizableAttribute && 0 === $i % 2, $match]; + } + + $parts[] = [false, false, '"']; + } + + $parts[] = [false, false, '>']; + + $parts = array_merge($parts, $this->parseNode($childNode, $parts)); + + $parts[] = [false, false, 'tagName.'>']; + } + + return $parts; + } + + private function addAccents(string &$trans, string $text): void + { + $trans .= $this->accents ? strtr($text, [ + ' ' => ' ', + '!' => '¡', + '"' => '″', + '#' => '♯', + '$' => '€', + '%' => '‰', + '&' => '⅋', + '\'' => '´', + '(' => '{', + ')' => '}', + '*' => '⁎', + '+' => '⁺', + ',' => '،', + '-' => '‐', + '.' => '·', + '/' => '⁄', + '0' => '⓪', + '1' => '①', + '2' => '②', + '3' => '③', + '4' => '④', + '5' => '⑤', + '6' => '⑥', + '7' => '⑦', + '8' => '⑧', + '9' => '⑨', + ':' => '∶', + ';' => '⁏', + '<' => '≤', + '=' => '≂', + '>' => '≥', + '?' => '¿', + '@' => '՞', + 'A' => 'Å', + 'B' => 'Ɓ', + 'C' => 'Ç', + 'D' => 'Ð', + 'E' => 'É', + 'F' => 'Ƒ', + 'G' => 'Ĝ', + 'H' => 'Ĥ', + 'I' => 'Î', + 'J' => 'Ĵ', + 'K' => 'Ķ', + 'L' => 'Ļ', + 'M' => 'Ṁ', + 'N' => 'Ñ', + 'O' => 'Ö', + 'P' => 'Þ', + 'Q' => 'Ǫ', + 'R' => 'Ŕ', + 'S' => 'Š', + 'T' => 'Ţ', + 'U' => 'Û', + 'V' => 'Ṽ', + 'W' => 'Ŵ', + 'X' => 'Ẋ', + 'Y' => 'Ý', + 'Z' => 'Ž', + '[' => '⁅', + '\\' => '∖', + ']' => '⁆', + '^' => '˄', + '_' => '‿', + '`' => '‵', + 'a' => 'å', + 'b' => 'ƀ', + 'c' => 'ç', + 'd' => 'ð', + 'e' => 'é', + 'f' => 'ƒ', + 'g' => 'ĝ', + 'h' => 'ĥ', + 'i' => 'î', + 'j' => 'ĵ', + 'k' => 'ķ', + 'l' => 'ļ', + 'm' => 'ɱ', + 'n' => 'ñ', + 'o' => 'ö', + 'p' => 'þ', + 'q' => 'ǫ', + 'r' => 'ŕ', + 's' => 'š', + 't' => 'ţ', + 'u' => 'û', + 'v' => 'ṽ', + 'w' => 'ŵ', + 'x' => 'ẋ', + 'y' => 'ý', + 'z' => 'ž', + '{' => '(', + '|' => '¦', + '}' => ')', + '~' => '˞', + ]) : $text; + } + + private function expand(string &$trans, string $visibleText): void + { + if (1.0 >= $this->expansionFactor) { + return; + } + + $visibleLength = $this->strlen($visibleText); + $missingLength = (int) ceil($visibleLength * $this->expansionFactor) - $visibleLength; + if ($this->brackets) { + $missingLength -= 2; + } + + if (0 >= $missingLength) { + return; + } + + $words = []; + $wordsCount = 0; + foreach (preg_split('/ +/', $visibleText, -1, \PREG_SPLIT_NO_EMPTY) as $word) { + $wordLength = $this->strlen($word); + + if ($wordLength >= $missingLength) { + continue; + } + + if (!isset($words[$wordLength])) { + $words[$wordLength] = 0; + } + + ++$words[$wordLength]; + ++$wordsCount; + } + + if (!$words) { + $trans .= 1 === $missingLength ? self::EXPANSION_CHARACTER : ' '.str_repeat(self::EXPANSION_CHARACTER, $missingLength - 1); + + return; + } + + arsort($words, \SORT_NUMERIC); + + $longestWordLength = max(array_keys($words)); + + while (true) { + $r = mt_rand(1, $wordsCount); + + foreach ($words as $length => $count) { + $r -= $count; + if ($r <= 0) { + break; + } + } + + $trans .= ' '.str_repeat(self::EXPANSION_CHARACTER, $length); + + $missingLength -= $length + 1; + + if (0 === $missingLength) { + return; + } + + while ($longestWordLength >= $missingLength) { + $wordsCount -= $words[$longestWordLength]; + unset($words[$longestWordLength]); + + if (!$words) { + $trans .= 1 === $missingLength ? self::EXPANSION_CHARACTER : ' '.str_repeat(self::EXPANSION_CHARACTER, $missingLength - 1); + + return; + } + + $longestWordLength = max(array_keys($words)); + } + } + } + + private function addBrackets(string &$trans): void + { + if (!$this->brackets) { + return; + } + + $trans = '['.$trans.']'; + } + + private function strlen(string $s): int + { + return false === ($encoding = mb_detect_encoding($s, null, true)) ? \strlen($s) : mb_strlen($s, $encoding); + } +} diff --git a/vendor/symfony/translation/README.md b/vendor/symfony/translation/README.md new file mode 100644 index 0000000..adda9a5 --- /dev/null +++ b/vendor/symfony/translation/README.md @@ -0,0 +1,48 @@ +Translation Component +===================== + +The Translation component provides tools to internationalize your application. + +Getting Started +--------------- + +``` +$ composer require symfony/translation +``` + +```php +use Symfony\Component\Translation\Translator; +use Symfony\Component\Translation\Loader\ArrayLoader; + +$translator = new Translator('fr_FR'); +$translator->addLoader('array', new ArrayLoader()); +$translator->addResource('array', [ + 'Hello World!' => 'Bonjour !', +], 'fr_FR'); + +echo $translator->trans('Hello World!'); // outputs « Bonjour ! » +``` + +Sponsor +------- + +The Translation component for Symfony 5.4/6.0 is [backed][1] by: + + * [Crowdin][2], a cloud-based localization management software helping teams to go global and stay agile. + * [Lokalise][3], a continuous localization and translation management platform that integrates into your development workflow so you can ship localized products, faster. + +Help Symfony by [sponsoring][4] its development! + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/translation.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) + +[1]: https://symfony.com/backers +[2]: https://crowdin.com +[3]: https://lokalise.com +[4]: https://symfony.com/sponsor diff --git a/vendor/symfony/translation/Reader/TranslationReader.php b/vendor/symfony/translation/Reader/TranslationReader.php new file mode 100644 index 0000000..e8e8638 --- /dev/null +++ b/vendor/symfony/translation/Reader/TranslationReader.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Reader; + +use Symfony\Component\Finder\Finder; +use Symfony\Component\Translation\Loader\LoaderInterface; +use Symfony\Component\Translation\MessageCatalogue; + +/** + * TranslationReader reads translation messages from translation files. + * + * @author Michel Salib + */ +class TranslationReader implements TranslationReaderInterface +{ + /** + * Loaders used for import. + * + * @var array + */ + private $loaders = []; + + /** + * Adds a loader to the translation extractor. + * + * @param string $format The format of the loader + */ + public function addLoader(string $format, LoaderInterface $loader) + { + $this->loaders[$format] = $loader; + } + + /** + * {@inheritdoc} + */ + public function read(string $directory, MessageCatalogue $catalogue) + { + if (!is_dir($directory)) { + return; + } + + foreach ($this->loaders as $format => $loader) { + // load any existing translation files + $finder = new Finder(); + $extension = $catalogue->getLocale().'.'.$format; + $files = $finder->files()->name('*.'.$extension)->in($directory); + foreach ($files as $file) { + $domain = substr($file->getFilename(), 0, -1 * \strlen($extension) - 1); + $catalogue->addCatalogue($loader->load($file->getPathname(), $catalogue->getLocale(), $domain)); + } + } + } +} diff --git a/vendor/symfony/translation/Reader/TranslationReaderInterface.php b/vendor/symfony/translation/Reader/TranslationReaderInterface.php new file mode 100644 index 0000000..bc37204 --- /dev/null +++ b/vendor/symfony/translation/Reader/TranslationReaderInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Reader; + +use Symfony\Component\Translation\MessageCatalogue; + +/** + * TranslationReader reads translation messages from translation files. + * + * @author Tobias Nyholm + */ +interface TranslationReaderInterface +{ + /** + * Reads translation messages from a directory to the catalogue. + */ + public function read(string $directory, MessageCatalogue $catalogue); +} diff --git a/vendor/symfony/translation/Resources/bin/translation-status.php b/vendor/symfony/translation/Resources/bin/translation-status.php new file mode 100644 index 0000000..53e642c --- /dev/null +++ b/vendor/symfony/translation/Resources/bin/translation-status.php @@ -0,0 +1,278 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if ('cli' !== \PHP_SAPI) { + throw new Exception('This script must be run from the command line.'); +} + +$usageInstructions = << false, + // NULL = analyze all locales + 'locale_to_analyze' => null, + // append --incomplete to only show incomplete languages + 'include_completed_languages' => true, + // the reference files all the other translations are compared to + 'original_files' => [ + 'src/Symfony/Component/Form/Resources/translations/validators.en.xlf', + 'src/Symfony/Component/Security/Core/Resources/translations/security.en.xlf', + 'src/Symfony/Component/Validator/Resources/translations/validators.en.xlf', + ], +]; + +$argc = $_SERVER['argc']; +$argv = $_SERVER['argv']; + +if ($argc > 4) { + echo str_replace('translation-status.php', $argv[0], $usageInstructions); + exit(1); +} + +foreach (array_slice($argv, 1) as $argumentOrOption) { + if ('--incomplete' === $argumentOrOption) { + $config['include_completed_languages'] = false; + continue; + } + + if (0 === strpos($argumentOrOption, '-')) { + $config['verbose_output'] = true; + } else { + $config['locale_to_analyze'] = $argumentOrOption; + } +} + +foreach ($config['original_files'] as $originalFilePath) { + if (!file_exists($originalFilePath)) { + echo sprintf('The following file does not exist. Make sure that you execute this command at the root dir of the Symfony code repository.%s %s', \PHP_EOL, $originalFilePath); + exit(1); + } +} + +$totalMissingTranslations = 0; +$totalTranslationMismatches = 0; + +foreach ($config['original_files'] as $originalFilePath) { + $translationFilePaths = findTranslationFiles($originalFilePath, $config['locale_to_analyze']); + $translationStatus = calculateTranslationStatus($originalFilePath, $translationFilePaths); + + $totalMissingTranslations += array_sum(array_map(function ($translation) { + return count($translation['missingKeys']); + }, array_values($translationStatus))); + $totalTranslationMismatches += array_sum(array_map(function ($translation) { + return count($translation['mismatches']); + }, array_values($translationStatus))); + + printTranslationStatus($originalFilePath, $translationStatus, $config['verbose_output'], $config['include_completed_languages']); +} + +exit($totalTranslationMismatches > 0 ? 1 : 0); + +function findTranslationFiles($originalFilePath, $localeToAnalyze) +{ + $translations = []; + + $translationsDir = dirname($originalFilePath); + $originalFileName = basename($originalFilePath); + $translationFileNamePattern = str_replace('.en.', '.*.', $originalFileName); + + $translationFiles = glob($translationsDir.'/'.$translationFileNamePattern, \GLOB_NOSORT); + sort($translationFiles); + foreach ($translationFiles as $filePath) { + $locale = extractLocaleFromFilePath($filePath); + + if (null !== $localeToAnalyze && $locale !== $localeToAnalyze) { + continue; + } + + $translations[$locale] = $filePath; + } + + return $translations; +} + +function calculateTranslationStatus($originalFilePath, $translationFilePaths) +{ + $translationStatus = []; + $allTranslationKeys = extractTranslationKeys($originalFilePath); + + foreach ($translationFilePaths as $locale => $translationPath) { + $translatedKeys = extractTranslationKeys($translationPath); + $missingKeys = array_diff_key($allTranslationKeys, $translatedKeys); + $mismatches = findTransUnitMismatches($allTranslationKeys, $translatedKeys); + + $translationStatus[$locale] = [ + 'total' => count($allTranslationKeys), + 'translated' => count($translatedKeys), + 'missingKeys' => $missingKeys, + 'mismatches' => $mismatches, + ]; + $translationStatus[$locale]['is_completed'] = isTranslationCompleted($translationStatus[$locale]); + } + + return $translationStatus; +} + +function isTranslationCompleted(array $translationStatus): bool +{ + return $translationStatus['total'] === $translationStatus['translated'] && 0 === count($translationStatus['mismatches']); +} + +function printTranslationStatus($originalFilePath, $translationStatus, $verboseOutput, $includeCompletedLanguages) +{ + printTitle($originalFilePath); + printTable($translationStatus, $verboseOutput, $includeCompletedLanguages); + echo \PHP_EOL.\PHP_EOL; +} + +function extractLocaleFromFilePath($filePath) +{ + $parts = explode('.', $filePath); + + return $parts[count($parts) - 2]; +} + +function extractTranslationKeys($filePath) +{ + $translationKeys = []; + $contents = new \SimpleXMLElement(file_get_contents($filePath)); + + foreach ($contents->file->body->{'trans-unit'} as $translationKey) { + $translationId = (string) $translationKey['id']; + $translationKey = (string) $translationKey->source; + + $translationKeys[$translationId] = $translationKey; + } + + return $translationKeys; +} + +/** + * Check whether the trans-unit id and source match with the base translation. + */ +function findTransUnitMismatches(array $baseTranslationKeys, array $translatedKeys): array +{ + $mismatches = []; + + foreach ($baseTranslationKeys as $translationId => $translationKey) { + if (!isset($translatedKeys[$translationId])) { + continue; + } + if ($translatedKeys[$translationId] !== $translationKey) { + $mismatches[$translationId] = [ + 'found' => $translatedKeys[$translationId], + 'expected' => $translationKey, + ]; + } + } + + return $mismatches; +} + +function printTitle($title) +{ + echo $title.\PHP_EOL; + echo str_repeat('=', strlen($title)).\PHP_EOL.\PHP_EOL; +} + +function printTable($translations, $verboseOutput, bool $includeCompletedLanguages) +{ + if (0 === count($translations)) { + echo 'No translations found'; + + return; + } + $longestLocaleNameLength = max(array_map('strlen', array_keys($translations))); + + foreach ($translations as $locale => $translation) { + if (!$includeCompletedLanguages && $translation['is_completed']) { + continue; + } + + if ($translation['translated'] > $translation['total']) { + textColorRed(); + } elseif (count($translation['mismatches']) > 0) { + textColorRed(); + } elseif ($translation['is_completed']) { + textColorGreen(); + } + + echo sprintf( + '| Locale: %-'.$longestLocaleNameLength.'s | Translated: %2d/%2d | Mismatches: %d |', + $locale, + $translation['translated'], + $translation['total'], + count($translation['mismatches']) + ).\PHP_EOL; + + textColorNormal(); + + $shouldBeClosed = false; + if (true === $verboseOutput && count($translation['missingKeys']) > 0) { + echo '| Missing Translations:'.\PHP_EOL; + + foreach ($translation['missingKeys'] as $id => $content) { + echo sprintf('| (id=%s) %s', $id, $content).\PHP_EOL; + } + $shouldBeClosed = true; + } + if (true === $verboseOutput && count($translation['mismatches']) > 0) { + echo '| Mismatches between trans-unit id and source:'.\PHP_EOL; + + foreach ($translation['mismatches'] as $id => $content) { + echo sprintf('| (id=%s) Expected: %s', $id, $content['expected']).\PHP_EOL; + echo sprintf('| Found: %s', $content['found']).\PHP_EOL; + } + $shouldBeClosed = true; + } + if ($shouldBeClosed) { + echo str_repeat('-', 80).\PHP_EOL; + } + } +} + +function textColorGreen() +{ + echo "\033[32m"; +} + +function textColorRed() +{ + echo "\033[31m"; +} + +function textColorNormal() +{ + echo "\033[0m"; +} diff --git a/vendor/symfony/translation/Resources/data/parents.json b/vendor/symfony/translation/Resources/data/parents.json new file mode 100644 index 0000000..32a33cd --- /dev/null +++ b/vendor/symfony/translation/Resources/data/parents.json @@ -0,0 +1,141 @@ +{ + "az_Cyrl": "root", + "bs_Cyrl": "root", + "en_150": "en_001", + "en_AG": "en_001", + "en_AI": "en_001", + "en_AT": "en_150", + "en_AU": "en_001", + "en_BB": "en_001", + "en_BE": "en_150", + "en_BM": "en_001", + "en_BS": "en_001", + "en_BW": "en_001", + "en_BZ": "en_001", + "en_CC": "en_001", + "en_CH": "en_150", + "en_CK": "en_001", + "en_CM": "en_001", + "en_CX": "en_001", + "en_CY": "en_001", + "en_DE": "en_150", + "en_DG": "en_001", + "en_DK": "en_150", + "en_DM": "en_001", + "en_ER": "en_001", + "en_FI": "en_150", + "en_FJ": "en_001", + "en_FK": "en_001", + "en_FM": "en_001", + "en_GB": "en_001", + "en_GD": "en_001", + "en_GG": "en_001", + "en_GH": "en_001", + "en_GI": "en_001", + "en_GM": "en_001", + "en_GY": "en_001", + "en_HK": "en_001", + "en_IE": "en_001", + "en_IL": "en_001", + "en_IM": "en_001", + "en_IN": "en_001", + "en_IO": "en_001", + "en_JE": "en_001", + "en_JM": "en_001", + "en_KE": "en_001", + "en_KI": "en_001", + "en_KN": "en_001", + "en_KY": "en_001", + "en_LC": "en_001", + "en_LR": "en_001", + "en_LS": "en_001", + "en_MG": "en_001", + "en_MO": "en_001", + "en_MS": "en_001", + "en_MT": "en_001", + "en_MU": "en_001", + "en_MV": "en_001", + "en_MW": "en_001", + "en_MY": "en_001", + "en_NA": "en_001", + "en_NF": "en_001", + "en_NG": "en_001", + "en_NL": "en_150", + "en_NR": "en_001", + "en_NU": "en_001", + "en_NZ": "en_001", + "en_PG": "en_001", + "en_PK": "en_001", + "en_PN": "en_001", + "en_PW": "en_001", + "en_RW": "en_001", + "en_SB": "en_001", + "en_SC": "en_001", + "en_SD": "en_001", + "en_SE": "en_150", + "en_SG": "en_001", + "en_SH": "en_001", + "en_SI": "en_150", + "en_SL": "en_001", + "en_SS": "en_001", + "en_SX": "en_001", + "en_SZ": "en_001", + "en_TC": "en_001", + "en_TK": "en_001", + "en_TO": "en_001", + "en_TT": "en_001", + "en_TV": "en_001", + "en_TZ": "en_001", + "en_UG": "en_001", + "en_VC": "en_001", + "en_VG": "en_001", + "en_VU": "en_001", + "en_WS": "en_001", + "en_ZA": "en_001", + "en_ZM": "en_001", + "en_ZW": "en_001", + "es_AR": "es_419", + "es_BO": "es_419", + "es_BR": "es_419", + "es_BZ": "es_419", + "es_CL": "es_419", + "es_CO": "es_419", + "es_CR": "es_419", + "es_CU": "es_419", + "es_DO": "es_419", + "es_EC": "es_419", + "es_GT": "es_419", + "es_HN": "es_419", + "es_MX": "es_419", + "es_NI": "es_419", + "es_PA": "es_419", + "es_PE": "es_419", + "es_PR": "es_419", + "es_PY": "es_419", + "es_SV": "es_419", + "es_US": "es_419", + "es_UY": "es_419", + "es_VE": "es_419", + "ff_Adlm": "root", + "hi_Latn": "en_IN", + "ks_Deva": "root", + "nb": "no", + "nn": "no", + "pa_Arab": "root", + "pt_AO": "pt_PT", + "pt_CH": "pt_PT", + "pt_CV": "pt_PT", + "pt_GQ": "pt_PT", + "pt_GW": "pt_PT", + "pt_LU": "pt_PT", + "pt_MO": "pt_PT", + "pt_MZ": "pt_PT", + "pt_ST": "pt_PT", + "pt_TL": "pt_PT", + "sd_Deva": "root", + "sr_Latn": "root", + "uz_Arab": "root", + "uz_Cyrl": "root", + "zh_Hant": "root", + "zh_Hant_MO": "zh_Hant_HK" +} diff --git a/vendor/symfony/translation/Resources/functions.php b/vendor/symfony/translation/Resources/functions.php new file mode 100644 index 0000000..901d2f8 --- /dev/null +++ b/vendor/symfony/translation/Resources/functions.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation; + +if (!\function_exists(t::class)) { + /** + * @author Nate Wiebe + */ + function t(string $message, array $parameters = [], string $domain = null): TranslatableMessage + { + return new TranslatableMessage($message, $parameters, $domain); + } +} diff --git a/vendor/symfony/translation/Resources/schemas/xliff-core-1.2-strict.xsd b/vendor/symfony/translation/Resources/schemas/xliff-core-1.2-strict.xsd new file mode 100644 index 0000000..dface62 --- /dev/null +++ b/vendor/symfony/translation/Resources/schemas/xliff-core-1.2-strict.xsd @@ -0,0 +1,2223 @@ + + + + + + + + + + + + + + + Values for the attribute 'context-type'. + + + + + Indicates a database content. + + + + + Indicates the content of an element within an XML document. + + + + + Indicates the name of an element within an XML document. + + + + + Indicates the line number from the sourcefile (see context-type="sourcefile") where the <source> is found. + + + + + Indicates a the number of parameters contained within the <source>. + + + + + Indicates notes pertaining to the parameters in the <source>. + + + + + Indicates the content of a record within a database. + + + + + Indicates the name of a record within a database. + + + + + Indicates the original source file in the case that multiple files are merged to form the original file from which the XLIFF file is created. This differs from the original <file> attribute in that this sourcefile is one of many that make up that file. + + + + + + + Values for the attribute 'count-type'. + + + + + Indicates the count units are items that are used X times in a certain context; example: this is a reusable text unit which is used 42 times in other texts. + + + + + Indicates the count units are translation units existing already in the same document. + + + + + Indicates a total count. + + + + + + + Values for the attribute 'ctype' when used other elements than <ph> or <x>. + + + + + Indicates a run of bolded text. + + + + + Indicates a run of text in italics. + + + + + Indicates a run of underlined text. + + + + + Indicates a run of hyper-text. + + + + + + + Values for the attribute 'ctype' when used with <ph> or <x>. + + + + + Indicates a inline image. + + + + + Indicates a page break. + + + + + Indicates a line break. + + + + + + + + + + + + Values for the attribute 'datatype'. + + + + + Indicates Active Server Page data. + + + + + Indicates C source file data. + + + + + Indicates Channel Definition Format (CDF) data. + + + + + Indicates ColdFusion data. + + + + + Indicates C++ source file data. + + + + + Indicates C-Sharp data. + + + + + Indicates strings from C, ASM, and driver files data. + + + + + Indicates comma-separated values data. + + + + + Indicates database data. + + + + + Indicates portions of document that follows data and contains metadata. + + + + + Indicates portions of document that precedes data and contains metadata. + + + + + Indicates data from standard UI file operations dialogs (e.g., Open, Save, Save As, Export, Import). + + + + + Indicates standard user input screen data. + + + + + Indicates HyperText Markup Language (HTML) data - document instance. + + + + + Indicates content within an HTML document’s <body> element. + + + + + Indicates Windows INI file data. + + + + + Indicates Interleaf data. + + + + + Indicates Java source file data (extension '.java'). + + + + + Indicates Java property resource bundle data. + + + + + Indicates Java list resource bundle data. + + + + + Indicates JavaScript source file data. + + + + + Indicates JScript source file data. + + + + + Indicates information relating to formatting. + + + + + Indicates LISP source file data. + + + + + Indicates information relating to margin formats. + + + + + Indicates a file containing menu. + + + + + Indicates numerically identified string table. + + + + + Indicates Maker Interchange Format (MIF) data. + + + + + Indicates that the datatype attribute value is a MIME Type value and is defined in the mime-type attribute. + + + + + Indicates GNU Machine Object data. + + + + + Indicates Message Librarian strings created by Novell's Message Librarian Tool. + + + + + Indicates information to be displayed at the bottom of each page of a document. + + + + + Indicates information to be displayed at the top of each page of a document. + + + + + Indicates a list of property values (e.g., settings within INI files or preferences dialog). + + + + + Indicates Pascal source file data. + + + + + Indicates Hypertext Preprocessor data. + + + + + Indicates plain text file (no formatting other than, possibly, wrapping). + + + + + Indicates GNU Portable Object file. + + + + + Indicates dynamically generated user defined document. e.g. Oracle Report, Crystal Report, etc. + + + + + Indicates Windows .NET binary resources. + + + + + Indicates Windows .NET Resources. + + + + + Indicates Rich Text Format (RTF) data. + + + + + Indicates Standard Generalized Markup Language (SGML) data - document instance. + + + + + Indicates Standard Generalized Markup Language (SGML) data - Document Type Definition (DTD). + + + + + Indicates Scalable Vector Graphic (SVG) data. + + + + + Indicates VisualBasic Script source file. + + + + + Indicates warning message. + + + + + Indicates Windows (Win32) resources (i.e. resources extracted from an RC script, a message file, or a compiled file). + + + + + Indicates Extensible HyperText Markup Language (XHTML) data - document instance. + + + + + Indicates Extensible Markup Language (XML) data - document instance. + + + + + Indicates Extensible Markup Language (XML) data - Document Type Definition (DTD). + + + + + Indicates Extensible Stylesheet Language (XSL) data. + + + + + Indicates XUL elements. + + + + + + + Values for the attribute 'mtype'. + + + + + Indicates the marked text is an abbreviation. + + + + + ISO-12620 2.1.8: A term resulting from the omission of any part of the full term while designating the same concept. + + + + + ISO-12620 2.1.8.1: An abbreviated form of a simple term resulting from the omission of some of its letters (e.g. 'adj.' for 'adjective'). + + + + + ISO-12620 2.1.8.4: An abbreviated form of a term made up of letters from the full form of a multiword term strung together into a sequence pronounced only syllabically (e.g. 'radar' for 'radio detecting and ranging'). + + + + + ISO-12620: A proper-name term, such as the name of an agency or other proper entity. + + + + + ISO-12620 2.1.18.1: A recurrent word combination characterized by cohesion in that the components of the collocation must co-occur within an utterance or series of utterances, even though they do not necessarily have to maintain immediate proximity to one another. + + + + + ISO-12620 2.1.5: A synonym for an international scientific term that is used in general discourse in a given language. + + + + + Indicates the marked text is a date and/or time. + + + + + ISO-12620 2.1.15: An expression used to represent a concept based on a statement that two mathematical expressions are, for instance, equal as identified by the equal sign (=), or assigned to one another by a similar sign. + + + + + ISO-12620 2.1.7: The complete representation of a term for which there is an abbreviated form. + + + + + ISO-12620 2.1.14: Figures, symbols or the like used to express a concept briefly, such as a mathematical or chemical formula. + + + + + ISO-12620 2.1.1: The concept designation that has been chosen to head a terminological record. + + + + + ISO-12620 2.1.8.3: An abbreviated form of a term consisting of some of the initial letters of the words making up a multiword term or the term elements making up a compound term when these letters are pronounced individually (e.g. 'BSE' for 'bovine spongiform encephalopathy'). + + + + + ISO-12620 2.1.4: A term that is part of an international scientific nomenclature as adopted by an appropriate scientific body. + + + + + ISO-12620 2.1.6: A term that has the same or nearly identical orthographic or phonemic form in many languages. + + + + + ISO-12620 2.1.16: An expression used to represent a concept based on mathematical or logical relations, such as statements of inequality, set relationships, Boolean operations, and the like. + + + + + ISO-12620 2.1.17: A unit to track object. + + + + + Indicates the marked text is a name. + + + + + ISO-12620 2.1.3: A term that represents the same or a very similar concept as another term in the same language, but for which interchangeability is limited to some contexts and inapplicable in others. + + + + + ISO-12620 2.1.17.2: A unique alphanumeric designation assigned to an object in a manufacturing system. + + + + + Indicates the marked text is a phrase. + + + + + ISO-12620 2.1.18: Any group of two or more words that form a unit, the meaning of which frequently cannot be deduced based on the combined sense of the words making up the phrase. + + + + + Indicates the marked text should not be translated. + + + + + ISO-12620 2.1.12: A form of a term resulting from an operation whereby non-Latin writing systems are converted to the Latin alphabet. + + + + + Indicates that the marked text represents a segment. + + + + + ISO-12620 2.1.18.2: A fixed, lexicalized phrase. + + + + + ISO-12620 2.1.8.2: A variant of a multiword term that includes fewer words than the full form of the term (e.g. 'Group of Twenty-four' for 'Intergovernmental Group of Twenty-four on International Monetary Affairs'). + + + + + ISO-12620 2.1.17.1: Stock keeping unit, an inventory item identified by a unique alphanumeric designation assigned to an object in an inventory control system. + + + + + ISO-12620 2.1.19: A fixed chunk of recurring text. + + + + + ISO-12620 2.1.13: A designation of a concept by letters, numerals, pictograms or any combination thereof. + + + + + ISO-12620 2.1.2: Any term that represents the same or a very similar concept as the main entry term in a term entry. + + + + + ISO-12620 2.1.18.3: Phraseological unit in a language that expresses the same semantic content as another phrase in that same language. + + + + + Indicates the marked text is a term. + + + + + ISO-12620 2.1.11: A form of a term resulting from an operation whereby the characters of one writing system are represented by characters from another writing system, taking into account the pronunciation of the characters converted. + + + + + ISO-12620 2.1.10: A form of a term resulting from an operation whereby the characters of an alphabetic writing system are represented by characters from another alphabetic writing system. + + + + + ISO-12620 2.1.8.5: An abbreviated form of a term resulting from the omission of one or more term elements or syllables (e.g. 'flu' for 'influenza'). + + + + + ISO-12620 2.1.9: One of the alternate forms of a term. + + + + + + + Values for the attribute 'restype'. + + + + + Indicates a Windows RC AUTO3STATE control. + + + + + Indicates a Windows RC AUTOCHECKBOX control. + + + + + Indicates a Windows RC AUTORADIOBUTTON control. + + + + + Indicates a Windows RC BEDIT control. + + + + + Indicates a bitmap, for example a BITMAP resource in Windows. + + + + + Indicates a button object, for example a BUTTON control Windows. + + + + + Indicates a caption, such as the caption of a dialog box. + + + + + Indicates the cell in a table, for example the content of the <td> element in HTML. + + + + + Indicates check box object, for example a CHECKBOX control in Windows. + + + + + Indicates a menu item with an associated checkbox. + + + + + Indicates a list box, but with a check-box for each item. + + + + + Indicates a color selection dialog. + + + + + Indicates a combination of edit box and listbox object, for example a COMBOBOX control in Windows. + + + + + Indicates an initialization entry of an extended combobox DLGINIT resource block. (code 0x1234). + + + + + Indicates an initialization entry of a combobox DLGINIT resource block (code 0x0403). + + + + + Indicates a UI base class element that cannot be represented by any other element. + + + + + Indicates a context menu. + + + + + Indicates a Windows RC CTEXT control. + + + + + Indicates a cursor, for example a CURSOR resource in Windows. + + + + + Indicates a date/time picker. + + + + + Indicates a Windows RC DEFPUSHBUTTON control. + + + + + Indicates a dialog box. + + + + + Indicates a Windows RC DLGINIT resource block. + + + + + Indicates an edit box object, for example an EDIT control in Windows. + + + + + Indicates a filename. + + + + + Indicates a file dialog. + + + + + Indicates a footnote. + + + + + Indicates a font name. + + + + + Indicates a footer. + + + + + Indicates a frame object. + + + + + Indicates a XUL grid element. + + + + + Indicates a groupbox object, for example a GROUPBOX control in Windows. + + + + + Indicates a header item. + + + + + Indicates a heading, such has the content of <h1>, <h2>, etc. in HTML. + + + + + Indicates a Windows RC HEDIT control. + + + + + Indicates a horizontal scrollbar. + + + + + Indicates an icon, for example an ICON resource in Windows. + + + + + Indicates a Windows RC IEDIT control. + + + + + Indicates keyword list, such as the content of the Keywords meta-data in HTML, or a K footnote in WinHelp RTF. + + + + + Indicates a label object. + + + + + Indicates a label that is also a HTML link (not necessarily a URL). + + + + + Indicates a list (a group of list-items, for example an <ol> or <ul> element in HTML). + + + + + Indicates a listbox object, for example an LISTBOX control in Windows. + + + + + Indicates an list item (an entry in a list). + + + + + Indicates a Windows RC LTEXT control. + + + + + Indicates a menu (a group of menu-items). + + + + + Indicates a toolbar containing one or more tope level menus. + + + + + Indicates a menu item (an entry in a menu). + + + + + Indicates a XUL menuseparator element. + + + + + Indicates a message, for example an entry in a MESSAGETABLE resource in Windows. + + + + + Indicates a calendar control. + + + + + Indicates an edit box beside a spin control. + + + + + Indicates a catch all for rectangular areas. + + + + + Indicates a standalone menu not necessarily associated with a menubar. + + + + + Indicates a pushbox object, for example a PUSHBOX control in Windows. + + + + + Indicates a Windows RC PUSHBUTTON control. + + + + + Indicates a radio button object. + + + + + Indicates a menuitem with associated radio button. + + + + + Indicates raw data resources for an application. + + + + + Indicates a row in a table. + + + + + Indicates a Windows RC RTEXT control. + + + + + Indicates a user navigable container used to show a portion of a document. + + + + + Indicates a generic divider object (e.g. menu group separator). + + + + + Windows accelerators, shortcuts in resource or property files. + + + + + Indicates a UI control to indicate process activity but not progress. + + + + + Indicates a splitter bar. + + + + + Indicates a Windows RC STATE3 control. + + + + + Indicates a window for providing feedback to the users, like 'read-only', etc. + + + + + Indicates a string, for example an entry in a STRINGTABLE resource in Windows. + + + + + Indicates a layers of controls with a tab to select layers. + + + + + Indicates a display and edits regular two-dimensional tables of cells. + + + + + Indicates a XUL textbox element. + + + + + Indicates a UI button that can be toggled to on or off state. + + + + + Indicates an array of controls, usually buttons. + + + + + Indicates a pop up tool tip text. + + + + + Indicates a bar with a pointer indicating a position within a certain range. + + + + + Indicates a control that displays a set of hierarchical data. + + + + + Indicates a URI (URN or URL). + + + + + Indicates a Windows RC USERBUTTON control. + + + + + Indicates a user-defined control like CONTROL control in Windows. + + + + + Indicates the text of a variable. + + + + + Indicates version information about a resource like VERSIONINFO in Windows. + + + + + Indicates a vertical scrollbar. + + + + + Indicates a graphical window. + + + + + + + Values for the attribute 'size-unit'. + + + + + Indicates a size in 8-bit bytes. + + + + + Indicates a size in Unicode characters. + + + + + Indicates a size in columns. Used for HTML text area. + + + + + Indicates a size in centimeters. + + + + + Indicates a size in dialog units, as defined in Windows resources. + + + + + Indicates a size in 'font-size' units (as defined in CSS). + + + + + Indicates a size in 'x-height' units (as defined in CSS). + + + + + Indicates a size in glyphs. A glyph is considered to be one or more combined Unicode characters that represent a single displayable text character. Sometimes referred to as a 'grapheme cluster' + + + + + Indicates a size in inches. + + + + + Indicates a size in millimeters. + + + + + Indicates a size in percentage. + + + + + Indicates a size in pixels. + + + + + Indicates a size in point. + + + + + Indicates a size in rows. Used for HTML text area. + + + + + + + Values for the attribute 'state'. + + + + + Indicates the terminating state. + + + + + Indicates only non-textual information needs adaptation. + + + + + Indicates both text and non-textual information needs adaptation. + + + + + Indicates only non-textual information needs review. + + + + + Indicates both text and non-textual information needs review. + + + + + Indicates that only the text of the item needs to be reviewed. + + + + + Indicates that the item needs to be translated. + + + + + Indicates that the item is new. For example, translation units that were not in a previous version of the document. + + + + + Indicates that changes are reviewed and approved. + + + + + Indicates that the item has been translated. + + + + + + + Values for the attribute 'state-qualifier'. + + + + + Indicates an exact match. An exact match occurs when a source text of a segment is exactly the same as the source text of a segment that was translated previously. + + + + + Indicates a fuzzy match. A fuzzy match occurs when a source text of a segment is very similar to the source text of a segment that was translated previously (e.g. when the difference is casing, a few changed words, white-space discripancy, etc.). + + + + + Indicates a match based on matching IDs (in addition to matching text). + + + + + Indicates a translation derived from a glossary. + + + + + Indicates a translation derived from existing translation. + + + + + Indicates a translation derived from machine translation. + + + + + Indicates a translation derived from a translation repository. + + + + + Indicates a translation derived from a translation memory. + + + + + Indicates the translation is suggested by machine translation. + + + + + Indicates that the item has been rejected because of incorrect grammar. + + + + + Indicates that the item has been rejected because it is incorrect. + + + + + Indicates that the item has been rejected because it is too long or too short. + + + + + Indicates that the item has been rejected because of incorrect spelling. + + + + + Indicates the translation is suggested by translation memory. + + + + + + + Values for the attribute 'unit'. + + + + + Refers to words. + + + + + Refers to pages. + + + + + Refers to <trans-unit> elements. + + + + + Refers to <bin-unit> elements. + + + + + Refers to glyphs. + + + + + Refers to <trans-unit> and/or <bin-unit> elements. + + + + + Refers to the occurrences of instances defined by the count-type value. + + + + + Refers to characters. + + + + + Refers to lines. + + + + + Refers to sentences. + + + + + Refers to paragraphs. + + + + + Refers to segments. + + + + + Refers to placeables (inline elements). + + + + + + + Values for the attribute 'priority'. + + + + + Highest priority. + + + + + High priority. + + + + + High priority, but not as important as 2. + + + + + High priority, but not as important as 3. + + + + + Medium priority, but more important than 6. + + + + + Medium priority, but less important than 5. + + + + + Low priority, but more important than 8. + + + + + Low priority, but more important than 9. + + + + + Low priority. + + + + + Lowest priority. + + + + + + + + + This value indicates that all properties can be reformatted. This value must be used alone. + + + + + This value indicates that no properties should be reformatted. This value must be used alone. + + + + + + + + + + + + + This value indicates that all information in the coord attribute can be modified. + + + + + This value indicates that the x information in the coord attribute can be modified. + + + + + This value indicates that the y information in the coord attribute can be modified. + + + + + This value indicates that the cx information in the coord attribute can be modified. + + + + + This value indicates that the cy information in the coord attribute can be modified. + + + + + This value indicates that all the information in the font attribute can be modified. + + + + + This value indicates that the name information in the font attribute can be modified. + + + + + This value indicates that the size information in the font attribute can be modified. + + + + + This value indicates that the weight information in the font attribute can be modified. + + + + + This value indicates that the information in the css-style attribute can be modified. + + + + + This value indicates that the information in the style attribute can be modified. + + + + + This value indicates that the information in the exstyle attribute can be modified. + + + + + + + + + + + + + Indicates that the context is informational in nature, specifying for example, how a term should be translated. Thus, should be displayed to anyone editing the XLIFF document. + + + + + Indicates that the context-group is used to specify where the term was found in the translatable source. Thus, it is not displayed. + + + + + Indicates that the context information should be used during translation memory lookups. Thus, it is not displayed. + + + + + + + + + Represents a translation proposal from a translation memory or other resource. + + + + + Represents a previous version of the target element. + + + + + Represents a rejected version of the target element. + + + + + Represents a translation to be used for reference purposes only, for example from a related product or a different language. + + + + + Represents a proposed translation that was used for the translation of the trans-unit, possibly modified. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Values for the attribute 'coord'. + + + + + + + + Version values: 1.0 and 1.1 are allowed for backward compatibility. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/symfony/translation/Resources/schemas/xliff-core-2.0.xsd b/vendor/symfony/translation/Resources/schemas/xliff-core-2.0.xsd new file mode 100644 index 0000000..963232f --- /dev/null +++ b/vendor/symfony/translation/Resources/schemas/xliff-core-2.0.xsd @@ -0,0 +1,411 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/symfony/translation/Resources/schemas/xml.xsd b/vendor/symfony/translation/Resources/schemas/xml.xsd new file mode 100644 index 0000000..a46162a --- /dev/null +++ b/vendor/symfony/translation/Resources/schemas/xml.xsd @@ -0,0 +1,309 @@ + + + + + + +
+

About the XML namespace

+ +
+

+ + This schema document describes the XML namespace, in a form + suitable for import by other schema documents. +

+

+ See + http://www.w3.org/XML/1998/namespace.html and + + http://www.w3.org/TR/REC-xml for information + about this namespace. +

+ +

+ Note that local names in this namespace are intended to be + defined only by the World Wide Web Consortium or its subgroups. + The names currently defined in this namespace are listed below. + They should not be used with conflicting semantics by any Working + Group, specification, or document instance. +

+

+ See further below in this document for more information about how to refer to this schema document from your own + XSD schema documents and about the + namespace-versioning policy governing this schema document. +

+
+
+ +
+
+ + + + +
+ +

lang (as an attribute name)

+

+ + denotes an attribute whose value + is a language code for the natural language of the content of + any element; its value is inherited. This name is reserved + by virtue of its definition in the XML specification.

+ +
+
+

Notes

+

+ Attempting to install the relevant ISO 2- and 3-letter + codes as the enumerated possible values is probably never + going to be a realistic possibility. +

+

+ + See BCP 47 at + http://www.rfc-editor.org/rfc/bcp/bcp47.txt + and the IANA language subtag registry at + + http://www.iana.org/assignments/language-subtag-registry + for further information. +

+

+ + The union allows for the 'un-declaration' of xml:lang with + the empty string. +

+
+
+
+ + + + + + + + + + +
+ + + + + +
+ +

space (as an attribute name)

+

+ denotes an attribute whose + value is a keyword indicating what whitespace processing + discipline is intended for the content of the element; its + value is inherited. This name is reserved by virtue of its + definition in the XML specification.

+ +
+
+
+ + + + + + + +
+ + + + +
+ +

base (as an attribute name)

+

+ denotes an attribute whose value + provides a URI to be used as the base for interpreting any + relative URIs in the scope of the element on which it + appears; its value is inherited. This name is reserved + by virtue of its definition in the XML Base specification.

+ +

+ See http://www.w3.org/TR/xmlbase/ + for information about this attribute. +

+ +
+
+
+
+ + + + +
+ +

id (as an attribute name)

+

+ + denotes an attribute whose value + should be interpreted as if declared to be of type ID. + This name is reserved by virtue of its definition in the + xml:id specification.

+ +

+ See http://www.w3.org/TR/xml-id/ + for information about this attribute. +

+
+
+
+ +
+ + + + + + + + + + + +
+ +

Father (in any context at all)

+ +
+

+ denotes Jon Bosak, the chair of + the original XML Working Group. This name is reserved by + the following decision of the W3C XML Plenary and + XML Coordination groups: +

+
+

+ + In appreciation for his vision, leadership and + dedication the W3C XML Plenary on this 10th day of + February, 2000, reserves for Jon Bosak in perpetuity + the XML name "xml:Father". +

+
+
+
+
+
+ + + + +
+

About this schema document

+ +
+

+ This schema defines attributes and an attribute group suitable + for use by schemas wishing to allow xml:base, + xml:lang, xml:space or + xml:id attributes on elements they define. +

+ +

+ To enable this, such a schema must import this schema for + the XML namespace, e.g. as follows: +

+
+          <schema.. .>
+          .. .
+           <import namespace="http://www.w3.org/XML/1998/namespace"
+                      schemaLocation="http://www.w3.org/2001/xml.xsd"/>
+     
+

+ or +

+
+
+           <import namespace="http://www.w3.org/XML/1998/namespace"
+                      schemaLocation="http://www.w3.org/2009/01/xml.xsd"/>
+     
+

+ Subsequently, qualified reference to any of the attributes or the + group defined below will have the desired effect, e.g. +

+
+          <type.. .>
+          .. .
+           <attributeGroup ref="xml:specialAttrs"/>
+     
+

+ will define a type which will schema-validate an instance element + with any of those attributes. +

+ +
+
+
+
+ + + +
+

Versioning policy for this schema document

+ +
+

+ In keeping with the XML Schema WG's standard versioning + policy, this schema document will persist at + + http://www.w3.org/2009/01/xml.xsd. +

+

+ At the date of issue it can also be found at + + http://www.w3.org/2001/xml.xsd. +

+ +

+ The schema document at that URI may however change in the future, + in order to remain compatible with the latest version of XML + Schema itself, or with the XML namespace itself. In other words, + if the XML Schema or XML namespaces change, the version of this + document at + http://www.w3.org/2001/xml.xsd + + will change accordingly; the version at + + http://www.w3.org/2009/01/xml.xsd + + will not change. +

+

+ + Previous dated (and unchanging) versions of this schema + document are at: +

+ +
+
+
+
+ +
diff --git a/vendor/symfony/translation/Test/ProviderFactoryTestCase.php b/vendor/symfony/translation/Test/ProviderFactoryTestCase.php new file mode 100644 index 0000000..5df82eb --- /dev/null +++ b/vendor/symfony/translation/Test/ProviderFactoryTestCase.php @@ -0,0 +1,147 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Test; + +use PHPUnit\Framework\TestCase; +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpClient\MockHttpClient; +use Symfony\Component\Translation\Dumper\XliffFileDumper; +use Symfony\Component\Translation\Exception\IncompleteDsnException; +use Symfony\Component\Translation\Exception\UnsupportedSchemeException; +use Symfony\Component\Translation\Loader\LoaderInterface; +use Symfony\Component\Translation\Provider\Dsn; +use Symfony\Component\Translation\Provider\ProviderFactoryInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +/** + * A test case to ease testing a translation provider factory. + * + * @author Mathieu Santostefano + * + * @internal + */ +abstract class ProviderFactoryTestCase extends TestCase +{ + protected $client; + protected $logger; + protected $defaultLocale; + protected $loader; + protected $xliffFileDumper; + + abstract public function createFactory(): ProviderFactoryInterface; + + /** + * @return iterable + */ + abstract public static function supportsProvider(): iterable; + + /** + * @return iterable + */ + abstract public static function createProvider(): iterable; + + /** + * @return iterable + */ + public static function unsupportedSchemeProvider(): iterable + { + return []; + } + + /** + * @return iterable + */ + public static function incompleteDsnProvider(): iterable + { + return []; + } + + /** + * @dataProvider supportsProvider + */ + public function testSupports(bool $expected, string $dsn) + { + $factory = $this->createFactory(); + + $this->assertSame($expected, $factory->supports(new Dsn($dsn))); + } + + /** + * @dataProvider createProvider + */ + public function testCreate(string $expected, string $dsn) + { + $factory = $this->createFactory(); + $provider = $factory->create(new Dsn($dsn)); + + $this->assertSame($expected, (string) $provider); + } + + /** + * @dataProvider unsupportedSchemeProvider + */ + public function testUnsupportedSchemeException(string $dsn, string $message = null) + { + $factory = $this->createFactory(); + + $dsn = new Dsn($dsn); + + $this->expectException(UnsupportedSchemeException::class); + if (null !== $message) { + $this->expectExceptionMessage($message); + } + + $factory->create($dsn); + } + + /** + * @dataProvider incompleteDsnProvider + */ + public function testIncompleteDsnException(string $dsn, string $message = null) + { + $factory = $this->createFactory(); + + $dsn = new Dsn($dsn); + + $this->expectException(IncompleteDsnException::class); + if (null !== $message) { + $this->expectExceptionMessage($message); + } + + $factory->create($dsn); + } + + protected function getClient(): HttpClientInterface + { + return $this->client ?? $this->client = new MockHttpClient(); + } + + protected function getLogger(): LoggerInterface + { + return $this->logger ?? $this->logger = $this->createMock(LoggerInterface::class); + } + + protected function getDefaultLocale(): string + { + return $this->defaultLocale ?? $this->defaultLocale = 'en'; + } + + protected function getLoader(): LoaderInterface + { + return $this->loader ?? $this->loader = $this->createMock(LoaderInterface::class); + } + + protected function getXliffFileDumper(): XliffFileDumper + { + return $this->xliffFileDumper ?? $this->xliffFileDumper = $this->createMock(XliffFileDumper::class); + } +} diff --git a/vendor/symfony/translation/Test/ProviderTestCase.php b/vendor/symfony/translation/Test/ProviderTestCase.php new file mode 100644 index 0000000..cb8a03f --- /dev/null +++ b/vendor/symfony/translation/Test/ProviderTestCase.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Test; + +use PHPUnit\Framework\TestCase; +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpClient\MockHttpClient; +use Symfony\Component\Translation\Dumper\XliffFileDumper; +use Symfony\Component\Translation\Loader\LoaderInterface; +use Symfony\Component\Translation\Provider\ProviderInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +/** + * A test case to ease testing a translation provider. + * + * @author Mathieu Santostefano + * + * @internal + */ +abstract class ProviderTestCase extends TestCase +{ + protected $client; + protected $logger; + protected $defaultLocale; + protected $loader; + protected $xliffFileDumper; + + abstract public static function createProvider(HttpClientInterface $client, LoaderInterface $loader, LoggerInterface $logger, string $defaultLocale, string $endpoint): ProviderInterface; + + /** + * @return iterable + */ + abstract public static function toStringProvider(): iterable; + + /** + * @dataProvider toStringProvider + */ + public function testToString(ProviderInterface $provider, string $expected) + { + $this->assertSame($expected, (string) $provider); + } + + protected function getClient(): MockHttpClient + { + return $this->client ?? $this->client = new MockHttpClient(); + } + + protected function getLoader(): LoaderInterface + { + return $this->loader ?? $this->loader = $this->createMock(LoaderInterface::class); + } + + protected function getLogger(): LoggerInterface + { + return $this->logger ?? $this->logger = $this->createMock(LoggerInterface::class); + } + + protected function getDefaultLocale(): string + { + return $this->defaultLocale ?? $this->defaultLocale = 'en'; + } + + protected function getXliffFileDumper(): XliffFileDumper + { + return $this->xliffFileDumper ?? $this->xliffFileDumper = $this->createMock(XliffFileDumper::class); + } +} diff --git a/vendor/symfony/translation/TranslatableMessage.php b/vendor/symfony/translation/TranslatableMessage.php new file mode 100644 index 0000000..282d289 --- /dev/null +++ b/vendor/symfony/translation/TranslatableMessage.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation; + +use Symfony\Contracts\Translation\TranslatableInterface; +use Symfony\Contracts\Translation\TranslatorInterface; + +/** + * @author Nate Wiebe + */ +class TranslatableMessage implements TranslatableInterface +{ + private $message; + private $parameters; + private $domain; + + public function __construct(string $message, array $parameters = [], string $domain = null) + { + $this->message = $message; + $this->parameters = $parameters; + $this->domain = $domain; + } + + public function __toString(): string + { + return $this->getMessage(); + } + + public function getMessage(): string + { + return $this->message; + } + + public function getParameters(): array + { + return $this->parameters; + } + + public function getDomain(): ?string + { + return $this->domain; + } + + public function trans(TranslatorInterface $translator, string $locale = null): string + { + return $translator->trans($this->getMessage(), array_map( + static function ($parameter) use ($translator, $locale) { + return $parameter instanceof TranslatableInterface ? $parameter->trans($translator, $locale) : $parameter; + }, + $this->getParameters() + ), $this->getDomain(), $locale); + } +} diff --git a/vendor/symfony/translation/Translator.php b/vendor/symfony/translation/Translator.php new file mode 100644 index 0000000..dc06260 --- /dev/null +++ b/vendor/symfony/translation/Translator.php @@ -0,0 +1,494 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation; + +use Symfony\Component\Config\ConfigCacheFactory; +use Symfony\Component\Config\ConfigCacheFactoryInterface; +use Symfony\Component\Config\ConfigCacheInterface; +use Symfony\Component\Translation\Exception\InvalidArgumentException; +use Symfony\Component\Translation\Exception\NotFoundResourceException; +use Symfony\Component\Translation\Exception\RuntimeException; +use Symfony\Component\Translation\Formatter\IntlFormatterInterface; +use Symfony\Component\Translation\Formatter\MessageFormatter; +use Symfony\Component\Translation\Formatter\MessageFormatterInterface; +use Symfony\Component\Translation\Loader\LoaderInterface; +use Symfony\Contracts\Translation\LocaleAwareInterface; +use Symfony\Contracts\Translation\TranslatorInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(MessageCatalogue::class); + +/** + * @author Fabien Potencier + */ +class Translator implements TranslatorInterface, TranslatorBagInterface, LocaleAwareInterface +{ + /** + * @var MessageCatalogueInterface[] + */ + protected $catalogues = []; + + /** + * @var string + */ + private $locale; + + /** + * @var string[] + */ + private $fallbackLocales = []; + + /** + * @var LoaderInterface[] + */ + private $loaders = []; + + /** + * @var array + */ + private $resources = []; + + /** + * @var MessageFormatterInterface + */ + private $formatter; + + /** + * @var string + */ + private $cacheDir; + + /** + * @var bool + */ + private $debug; + + private $cacheVary; + + /** + * @var ConfigCacheFactoryInterface|null + */ + private $configCacheFactory; + + /** + * @var array|null + */ + private $parentLocales; + + private $hasIntlFormatter; + + /** + * @throws InvalidArgumentException If a locale contains invalid characters + */ + public function __construct(string $locale, MessageFormatterInterface $formatter = null, string $cacheDir = null, bool $debug = false, array $cacheVary = []) + { + $this->setLocale($locale); + + if (null === $formatter) { + $formatter = new MessageFormatter(); + } + + $this->formatter = $formatter; + $this->cacheDir = $cacheDir; + $this->debug = $debug; + $this->cacheVary = $cacheVary; + $this->hasIntlFormatter = $formatter instanceof IntlFormatterInterface; + } + + public function setConfigCacheFactory(ConfigCacheFactoryInterface $configCacheFactory) + { + $this->configCacheFactory = $configCacheFactory; + } + + /** + * Adds a Loader. + * + * @param string $format The name of the loader (@see addResource()) + */ + public function addLoader(string $format, LoaderInterface $loader) + { + $this->loaders[$format] = $loader; + } + + /** + * Adds a Resource. + * + * @param string $format The name of the loader (@see addLoader()) + * @param mixed $resource The resource name + * + * @throws InvalidArgumentException If the locale contains invalid characters + */ + public function addResource(string $format, $resource, string $locale, string $domain = null) + { + if (null === $domain) { + $domain = 'messages'; + } + + $this->assertValidLocale($locale); + $locale ?: $locale = class_exists(\Locale::class) ? \Locale::getDefault() : 'en'; + + $this->resources[$locale][] = [$format, $resource, $domain]; + + if (\in_array($locale, $this->fallbackLocales)) { + $this->catalogues = []; + } else { + unset($this->catalogues[$locale]); + } + } + + /** + * {@inheritdoc} + */ + public function setLocale(string $locale) + { + $this->assertValidLocale($locale); + $this->locale = $locale; + } + + /** + * {@inheritdoc} + */ + public function getLocale() + { + return $this->locale ?: (class_exists(\Locale::class) ? \Locale::getDefault() : 'en'); + } + + /** + * Sets the fallback locales. + * + * @param string[] $locales + * + * @throws InvalidArgumentException If a locale contains invalid characters + */ + public function setFallbackLocales(array $locales) + { + // needed as the fallback locales are linked to the already loaded catalogues + $this->catalogues = []; + + foreach ($locales as $locale) { + $this->assertValidLocale($locale); + } + + $this->fallbackLocales = $this->cacheVary['fallback_locales'] = $locales; + } + + /** + * Gets the fallback locales. + * + * @internal + */ + public function getFallbackLocales(): array + { + return $this->fallbackLocales; + } + + /** + * {@inheritdoc} + */ + public function trans(?string $id, array $parameters = [], string $domain = null, string $locale = null) + { + if (null === $id || '' === $id) { + return ''; + } + + if (null === $domain) { + $domain = 'messages'; + } + + $catalogue = $this->getCatalogue($locale); + $locale = $catalogue->getLocale(); + while (!$catalogue->defines($id, $domain)) { + if ($cat = $catalogue->getFallbackCatalogue()) { + $catalogue = $cat; + $locale = $catalogue->getLocale(); + } else { + break; + } + } + + $len = \strlen(MessageCatalogue::INTL_DOMAIN_SUFFIX); + if ($this->hasIntlFormatter + && ($catalogue->defines($id, $domain.MessageCatalogue::INTL_DOMAIN_SUFFIX) + || (\strlen($domain) > $len && 0 === substr_compare($domain, MessageCatalogue::INTL_DOMAIN_SUFFIX, -$len, $len))) + ) { + return $this->formatter->formatIntl($catalogue->get($id, $domain), $locale, $parameters); + } + + return $this->formatter->format($catalogue->get($id, $domain), $locale, $parameters); + } + + /** + * {@inheritdoc} + */ + public function getCatalogue(string $locale = null) + { + if (!$locale) { + $locale = $this->getLocale(); + } else { + $this->assertValidLocale($locale); + } + + if (!isset($this->catalogues[$locale])) { + $this->loadCatalogue($locale); + } + + return $this->catalogues[$locale]; + } + + /** + * {@inheritdoc} + */ + public function getCatalogues(): array + { + return array_values($this->catalogues); + } + + /** + * Gets the loaders. + * + * @return LoaderInterface[] + */ + protected function getLoaders() + { + return $this->loaders; + } + + protected function loadCatalogue(string $locale) + { + if (null === $this->cacheDir) { + $this->initializeCatalogue($locale); + } else { + $this->initializeCacheCatalogue($locale); + } + } + + protected function initializeCatalogue(string $locale) + { + $this->assertValidLocale($locale); + + try { + $this->doLoadCatalogue($locale); + } catch (NotFoundResourceException $e) { + if (!$this->computeFallbackLocales($locale)) { + throw $e; + } + } + $this->loadFallbackCatalogues($locale); + } + + private function initializeCacheCatalogue(string $locale): void + { + if (isset($this->catalogues[$locale])) { + /* Catalogue already initialized. */ + return; + } + + $this->assertValidLocale($locale); + $cache = $this->getConfigCacheFactory()->cache($this->getCatalogueCachePath($locale), + function (ConfigCacheInterface $cache) use ($locale) { + $this->dumpCatalogue($locale, $cache); + } + ); + + if (isset($this->catalogues[$locale])) { + /* Catalogue has been initialized as it was written out to cache. */ + return; + } + + /* Read catalogue from cache. */ + $this->catalogues[$locale] = include $cache->getPath(); + } + + private function dumpCatalogue(string $locale, ConfigCacheInterface $cache): void + { + $this->initializeCatalogue($locale); + $fallbackContent = $this->getFallbackContent($this->catalogues[$locale]); + + $content = sprintf(<<getAllMessages($this->catalogues[$locale]), true), + $fallbackContent + ); + + $cache->write($content, $this->catalogues[$locale]->getResources()); + } + + private function getFallbackContent(MessageCatalogue $catalogue): string + { + $fallbackContent = ''; + $current = ''; + $replacementPattern = '/[^a-z0-9_]/i'; + $fallbackCatalogue = $catalogue->getFallbackCatalogue(); + while ($fallbackCatalogue) { + $fallback = $fallbackCatalogue->getLocale(); + $fallbackSuffix = ucfirst(preg_replace($replacementPattern, '_', $fallback)); + $currentSuffix = ucfirst(preg_replace($replacementPattern, '_', $current)); + + $fallbackContent .= sprintf(<<<'EOF' +$catalogue%s = new MessageCatalogue('%s', %s); +$catalogue%s->addFallbackCatalogue($catalogue%s); + +EOF + , + $fallbackSuffix, + $fallback, + var_export($this->getAllMessages($fallbackCatalogue), true), + $currentSuffix, + $fallbackSuffix + ); + $current = $fallbackCatalogue->getLocale(); + $fallbackCatalogue = $fallbackCatalogue->getFallbackCatalogue(); + } + + return $fallbackContent; + } + + private function getCatalogueCachePath(string $locale): string + { + return $this->cacheDir.'/catalogue.'.$locale.'.'.strtr(substr(base64_encode(hash('sha256', serialize($this->cacheVary), true)), 0, 7), '/', '_').'.php'; + } + + /** + * @internal + */ + protected function doLoadCatalogue(string $locale): void + { + $this->catalogues[$locale] = new MessageCatalogue($locale); + + if (isset($this->resources[$locale])) { + foreach ($this->resources[$locale] as $resource) { + if (!isset($this->loaders[$resource[0]])) { + if (\is_string($resource[1])) { + throw new RuntimeException(sprintf('No loader is registered for the "%s" format when loading the "%s" resource.', $resource[0], $resource[1])); + } + + throw new RuntimeException(sprintf('No loader is registered for the "%s" format.', $resource[0])); + } + $this->catalogues[$locale]->addCatalogue($this->loaders[$resource[0]]->load($resource[1], $locale, $resource[2])); + } + } + } + + private function loadFallbackCatalogues(string $locale): void + { + $current = $this->catalogues[$locale]; + + foreach ($this->computeFallbackLocales($locale) as $fallback) { + if (!isset($this->catalogues[$fallback])) { + $this->initializeCatalogue($fallback); + } + + $fallbackCatalogue = new MessageCatalogue($fallback, $this->getAllMessages($this->catalogues[$fallback])); + foreach ($this->catalogues[$fallback]->getResources() as $resource) { + $fallbackCatalogue->addResource($resource); + } + $current->addFallbackCatalogue($fallbackCatalogue); + $current = $fallbackCatalogue; + } + } + + protected function computeFallbackLocales(string $locale) + { + if (null === $this->parentLocales) { + $this->parentLocales = json_decode(file_get_contents(__DIR__.'/Resources/data/parents.json'), true); + } + + $originLocale = $locale; + $locales = []; + + while ($locale) { + $parent = $this->parentLocales[$locale] ?? null; + + if ($parent) { + $locale = 'root' !== $parent ? $parent : null; + } elseif (\function_exists('locale_parse')) { + $localeSubTags = locale_parse($locale); + $locale = null; + if (1 < \count($localeSubTags)) { + array_pop($localeSubTags); + $locale = locale_compose($localeSubTags) ?: null; + } + } elseif ($i = strrpos($locale, '_') ?: strrpos($locale, '-')) { + $locale = substr($locale, 0, $i); + } else { + $locale = null; + } + + if (null !== $locale) { + $locales[] = $locale; + } + } + + foreach ($this->fallbackLocales as $fallback) { + if ($fallback === $originLocale) { + continue; + } + + $locales[] = $fallback; + } + + return array_unique($locales); + } + + /** + * Asserts that the locale is valid, throws an Exception if not. + * + * @throws InvalidArgumentException If the locale contains invalid characters + */ + protected function assertValidLocale(string $locale) + { + if (!preg_match('/^[a-z0-9@_\\.\\-]*$/i', $locale)) { + throw new InvalidArgumentException(sprintf('Invalid "%s" locale.', $locale)); + } + } + + /** + * Provides the ConfigCache factory implementation, falling back to a + * default implementation if necessary. + */ + private function getConfigCacheFactory(): ConfigCacheFactoryInterface + { + if (!$this->configCacheFactory) { + $this->configCacheFactory = new ConfigCacheFactory($this->debug); + } + + return $this->configCacheFactory; + } + + private function getAllMessages(MessageCatalogueInterface $catalogue): array + { + $allMessages = []; + + foreach ($catalogue->all() as $domain => $messages) { + if ($intlMessages = $catalogue->all($domain.MessageCatalogue::INTL_DOMAIN_SUFFIX)) { + $allMessages[$domain.MessageCatalogue::INTL_DOMAIN_SUFFIX] = $intlMessages; + $messages = array_diff_key($messages, $intlMessages); + } + if ($messages) { + $allMessages[$domain] = $messages; + } + } + + return $allMessages; + } +} diff --git a/vendor/symfony/translation/TranslatorBag.php b/vendor/symfony/translation/TranslatorBag.php new file mode 100644 index 0000000..6a4df3c --- /dev/null +++ b/vendor/symfony/translation/TranslatorBag.php @@ -0,0 +1,108 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation; + +use Symfony\Component\Translation\Catalogue\AbstractOperation; +use Symfony\Component\Translation\Catalogue\TargetOperation; + +final class TranslatorBag implements TranslatorBagInterface +{ + /** @var MessageCatalogue[] */ + private $catalogues = []; + + public function addCatalogue(MessageCatalogue $catalogue): void + { + if (null !== $existingCatalogue = $this->getCatalogue($catalogue->getLocale())) { + $catalogue->addCatalogue($existingCatalogue); + } + + $this->catalogues[$catalogue->getLocale()] = $catalogue; + } + + public function addBag(TranslatorBagInterface $bag): void + { + foreach ($bag->getCatalogues() as $catalogue) { + $this->addCatalogue($catalogue); + } + } + + /** + * {@inheritdoc} + */ + public function getCatalogue(string $locale = null): MessageCatalogueInterface + { + if (null === $locale || !isset($this->catalogues[$locale])) { + $this->catalogues[$locale] = new MessageCatalogue($locale); + } + + return $this->catalogues[$locale]; + } + + /** + * {@inheritdoc} + */ + public function getCatalogues(): array + { + return array_values($this->catalogues); + } + + public function diff(TranslatorBagInterface $diffBag): self + { + $diff = new self(); + + foreach ($this->catalogues as $locale => $catalogue) { + if (null === $diffCatalogue = $diffBag->getCatalogue($locale)) { + $diff->addCatalogue($catalogue); + + continue; + } + + $operation = new TargetOperation($diffCatalogue, $catalogue); + $operation->moveMessagesToIntlDomainsIfPossible(AbstractOperation::NEW_BATCH); + $newCatalogue = new MessageCatalogue($locale); + + foreach ($catalogue->getDomains() as $domain) { + $newCatalogue->add($operation->getNewMessages($domain), $domain); + } + + $diff->addCatalogue($newCatalogue); + } + + return $diff; + } + + public function intersect(TranslatorBagInterface $intersectBag): self + { + $diff = new self(); + + foreach ($this->catalogues as $locale => $catalogue) { + if (null === $intersectCatalogue = $intersectBag->getCatalogue($locale)) { + continue; + } + + $operation = new TargetOperation($catalogue, $intersectCatalogue); + $operation->moveMessagesToIntlDomainsIfPossible(AbstractOperation::OBSOLETE_BATCH); + $obsoleteCatalogue = new MessageCatalogue($locale); + + foreach ($operation->getDomains() as $domain) { + $obsoleteCatalogue->add( + array_diff($operation->getMessages($domain), $operation->getNewMessages($domain)), + $domain + ); + } + + $diff->addCatalogue($obsoleteCatalogue); + } + + return $diff; + } +} diff --git a/vendor/symfony/translation/TranslatorBagInterface.php b/vendor/symfony/translation/TranslatorBagInterface.php new file mode 100644 index 0000000..4228977 --- /dev/null +++ b/vendor/symfony/translation/TranslatorBagInterface.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation; + +use Symfony\Component\Translation\Exception\InvalidArgumentException; + +/** + * TranslatorBagInterface. + * + * @method MessageCatalogueInterface[] getCatalogues() Returns all catalogues of the instance + * + * @author Abdellatif Ait boudad + */ +interface TranslatorBagInterface +{ + /** + * Gets the catalogue by locale. + * + * @param string|null $locale The locale or null to use the default + * + * @return MessageCatalogueInterface + * + * @throws InvalidArgumentException If the locale contains invalid characters + */ + public function getCatalogue(string $locale = null); +} diff --git a/vendor/symfony/translation/Util/ArrayConverter.php b/vendor/symfony/translation/Util/ArrayConverter.php new file mode 100644 index 0000000..f69c2e3 --- /dev/null +++ b/vendor/symfony/translation/Util/ArrayConverter.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Util; + +/** + * ArrayConverter generates tree like structure from a message catalogue. + * e.g. this + * 'foo.bar1' => 'test1', + * 'foo.bar2' => 'test2' + * converts to follows: + * foo: + * bar1: test1 + * bar2: test2. + * + * @author Gennady Telegin + */ +class ArrayConverter +{ + /** + * Converts linear messages array to tree-like array. + * For example this array('foo.bar' => 'value') will be converted to ['foo' => ['bar' => 'value']]. + * + * @param array $messages Linear messages array + * + * @return array + */ + public static function expandToTree(array $messages) + { + $tree = []; + + foreach ($messages as $id => $value) { + $referenceToElement = &self::getElementByPath($tree, explode('.', $id)); + + $referenceToElement = $value; + + unset($referenceToElement); + } + + return $tree; + } + + private static function &getElementByPath(array &$tree, array $parts) + { + $elem = &$tree; + $parentOfElem = null; + + foreach ($parts as $i => $part) { + if (isset($elem[$part]) && \is_string($elem[$part])) { + /* Process next case: + * 'foo': 'test1', + * 'foo.bar': 'test2' + * + * $tree['foo'] was string before we found array {bar: test2}. + * Treat new element as string too, e.g. add $tree['foo.bar'] = 'test2'; + */ + $elem = &$elem[implode('.', \array_slice($parts, $i))]; + break; + } + $parentOfElem = &$elem; + $elem = &$elem[$part]; + } + + if ($elem && \is_array($elem) && $parentOfElem) { + /* Process next case: + * 'foo.bar': 'test1' + * 'foo': 'test2' + * + * $tree['foo'] was array = {bar: 'test1'} before we found string constant `foo`. + * Cancel treating $tree['foo'] as array and cancel back it expansion, + * e.g. make it $tree['foo.bar'] = 'test1' again. + */ + self::cancelExpand($parentOfElem, $part, $elem); + } + + return $elem; + } + + private static function cancelExpand(array &$tree, string $prefix, array $node) + { + $prefix .= '.'; + + foreach ($node as $id => $value) { + if (\is_string($value)) { + $tree[$prefix.$id] = $value; + } else { + self::cancelExpand($tree, $prefix.$id, $value); + } + } + } +} diff --git a/vendor/symfony/translation/Util/XliffUtils.php b/vendor/symfony/translation/Util/XliffUtils.php new file mode 100644 index 0000000..e4373a7 --- /dev/null +++ b/vendor/symfony/translation/Util/XliffUtils.php @@ -0,0 +1,196 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Util; + +use Symfony\Component\Translation\Exception\InvalidArgumentException; +use Symfony\Component\Translation\Exception\InvalidResourceException; + +/** + * Provides some utility methods for XLIFF translation files, such as validating + * their contents according to the XSD schema. + * + * @author Fabien Potencier + */ +class XliffUtils +{ + /** + * Gets xliff file version based on the root "version" attribute. + * + * Defaults to 1.2 for backwards compatibility. + * + * @throws InvalidArgumentException + */ + public static function getVersionNumber(\DOMDocument $dom): string + { + /** @var \DOMNode $xliff */ + foreach ($dom->getElementsByTagName('xliff') as $xliff) { + $version = $xliff->attributes->getNamedItem('version'); + if ($version) { + return $version->nodeValue; + } + + $namespace = $xliff->attributes->getNamedItem('xmlns'); + if ($namespace) { + if (0 !== substr_compare('urn:oasis:names:tc:xliff:document:', $namespace->nodeValue, 0, 34)) { + throw new InvalidArgumentException(sprintf('Not a valid XLIFF namespace "%s".', $namespace)); + } + + return substr($namespace, 34); + } + } + + // Falls back to v1.2 + return '1.2'; + } + + /** + * Validates and parses the given file into a DOMDocument. + * + * @throws InvalidResourceException + */ + public static function validateSchema(\DOMDocument $dom): array + { + $xliffVersion = static::getVersionNumber($dom); + $internalErrors = libxml_use_internal_errors(true); + if ($shouldEnable = self::shouldEnableEntityLoader()) { + $disableEntities = libxml_disable_entity_loader(false); + } + try { + $isValid = @$dom->schemaValidateSource(self::getSchema($xliffVersion)); + if (!$isValid) { + return self::getXmlErrors($internalErrors); + } + } finally { + if ($shouldEnable) { + libxml_disable_entity_loader($disableEntities); + } + } + + $dom->normalizeDocument(); + + libxml_clear_errors(); + libxml_use_internal_errors($internalErrors); + + return []; + } + + private static function shouldEnableEntityLoader(): bool + { + // Version prior to 8.0 can be enabled without deprecation + if (\PHP_VERSION_ID < 80000) { + return true; + } + + static $dom, $schema; + if (null === $dom) { + $dom = new \DOMDocument(); + $dom->loadXML(''); + + $tmpfile = tempnam(sys_get_temp_dir(), 'symfony'); + register_shutdown_function(static function () use ($tmpfile) { + @unlink($tmpfile); + }); + $schema = ' + + +'; + file_put_contents($tmpfile, ' + + + +'); + } + + return !@$dom->schemaValidateSource($schema); + } + + public static function getErrorsAsString(array $xmlErrors): string + { + $errorsAsString = ''; + + foreach ($xmlErrors as $error) { + $errorsAsString .= sprintf("[%s %s] %s (in %s - line %d, column %d)\n", + \LIBXML_ERR_WARNING === $error['level'] ? 'WARNING' : 'ERROR', + $error['code'], + $error['message'], + $error['file'], + $error['line'], + $error['column'] + ); + } + + return $errorsAsString; + } + + private static function getSchema(string $xliffVersion): string + { + if ('1.2' === $xliffVersion) { + $schemaSource = file_get_contents(__DIR__.'/../Resources/schemas/xliff-core-1.2-strict.xsd'); + $xmlUri = 'http://www.w3.org/2001/xml.xsd'; + } elseif ('2.0' === $xliffVersion) { + $schemaSource = file_get_contents(__DIR__.'/../Resources/schemas/xliff-core-2.0.xsd'); + $xmlUri = 'informativeCopiesOf3rdPartySchemas/w3c/xml.xsd'; + } else { + throw new InvalidArgumentException(sprintf('No support implemented for loading XLIFF version "%s".', $xliffVersion)); + } + + return self::fixXmlLocation($schemaSource, $xmlUri); + } + + /** + * Internally changes the URI of a dependent xsd to be loaded locally. + */ + private static function fixXmlLocation(string $schemaSource, string $xmlUri): string + { + $newPath = str_replace('\\', '/', __DIR__).'/../Resources/schemas/xml.xsd'; + $parts = explode('/', $newPath); + $locationstart = 'file:///'; + if (0 === stripos($newPath, 'phar://')) { + $tmpfile = tempnam(sys_get_temp_dir(), 'symfony'); + if ($tmpfile) { + copy($newPath, $tmpfile); + $parts = explode('/', str_replace('\\', '/', $tmpfile)); + } else { + array_shift($parts); + $locationstart = 'phar:///'; + } + } + + $drive = '\\' === \DIRECTORY_SEPARATOR ? array_shift($parts).'/' : ''; + $newPath = $locationstart.$drive.implode('/', array_map('rawurlencode', $parts)); + + return str_replace($xmlUri, $newPath, $schemaSource); + } + + /** + * Returns the XML errors of the internal XML parser. + */ + private static function getXmlErrors(bool $internalErrors): array + { + $errors = []; + foreach (libxml_get_errors() as $error) { + $errors[] = [ + 'level' => \LIBXML_ERR_WARNING == $error->level ? 'WARNING' : 'ERROR', + 'code' => $error->code, + 'message' => trim($error->message), + 'file' => $error->file ?: 'n/a', + 'line' => $error->line, + 'column' => $error->column, + ]; + } + + libxml_clear_errors(); + libxml_use_internal_errors($internalErrors); + + return $errors; + } +} diff --git a/vendor/symfony/translation/Writer/TranslationWriter.php b/vendor/symfony/translation/Writer/TranslationWriter.php new file mode 100644 index 0000000..96d608f --- /dev/null +++ b/vendor/symfony/translation/Writer/TranslationWriter.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Writer; + +use Symfony\Component\Translation\Dumper\DumperInterface; +use Symfony\Component\Translation\Exception\InvalidArgumentException; +use Symfony\Component\Translation\Exception\RuntimeException; +use Symfony\Component\Translation\MessageCatalogue; + +/** + * TranslationWriter writes translation messages. + * + * @author Michel Salib + */ +class TranslationWriter implements TranslationWriterInterface +{ + /** + * @var array + */ + private $dumpers = []; + + /** + * Adds a dumper to the writer. + */ + public function addDumper(string $format, DumperInterface $dumper) + { + $this->dumpers[$format] = $dumper; + } + + /** + * Obtains the list of supported formats. + * + * @return array + */ + public function getFormats() + { + return array_keys($this->dumpers); + } + + /** + * Writes translation from the catalogue according to the selected format. + * + * @param string $format The format to use to dump the messages + * @param array $options Options that are passed to the dumper + * + * @throws InvalidArgumentException + */ + public function write(MessageCatalogue $catalogue, string $format, array $options = []) + { + if (!isset($this->dumpers[$format])) { + throw new InvalidArgumentException(sprintf('There is no dumper associated with format "%s".', $format)); + } + + // get the right dumper + $dumper = $this->dumpers[$format]; + + if (isset($options['path']) && !is_dir($options['path']) && !@mkdir($options['path'], 0777, true) && !is_dir($options['path'])) { + throw new RuntimeException(sprintf('Translation Writer was not able to create directory "%s".', $options['path'])); + } + + // save + $dumper->dump($catalogue, $options); + } +} diff --git a/vendor/symfony/translation/Writer/TranslationWriterInterface.php b/vendor/symfony/translation/Writer/TranslationWriterInterface.php new file mode 100644 index 0000000..4321309 --- /dev/null +++ b/vendor/symfony/translation/Writer/TranslationWriterInterface.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Writer; + +use Symfony\Component\Translation\Exception\InvalidArgumentException; +use Symfony\Component\Translation\MessageCatalogue; + +/** + * TranslationWriter writes translation messages. + * + * @author Michel Salib + */ +interface TranslationWriterInterface +{ + /** + * Writes translation from the catalogue according to the selected format. + * + * @param string $format The format to use to dump the messages + * @param array $options Options that are passed to the dumper + * + * @throws InvalidArgumentException + */ + public function write(MessageCatalogue $catalogue, string $format, array $options = []); +} diff --git a/vendor/topthink/think-helper/.github/workflows/ci.yml b/vendor/topthink/think-helper/.github/workflows/ci.yml new file mode 100644 index 0000000..c63b5d4 --- /dev/null +++ b/vendor/topthink/think-helper/.github/workflows/ci.yml @@ -0,0 +1,36 @@ +name: CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + + +jobs: + phpcs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Setup PHP environment + uses: shivammathur/setup-php@v2 + - name: Install dependencies + run: composer install + - name: PHPCSFixer check + run: composer check-style + phpunit: + strategy: + matrix: + php_version: [7.3, 7.4, 8.0] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Setup PHP environment + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php_version }} + coverage: xdebug + - name: Install dependencies + run: composer install + - name: PHPUnit check + run: ./vendor/bin/phpunit --coverage-text diff --git a/vendor/topthink/think-helper/.github/workflows/php.yml b/vendor/topthink/think-helper/.github/workflows/php.yml new file mode 100644 index 0000000..01d5b63 --- /dev/null +++ b/vendor/topthink/think-helper/.github/workflows/php.yml @@ -0,0 +1,36 @@ +name: PHP Composer + +on: + push: + branches: [ 3.0 ] + pull_request: + branches: [ 3.0 ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Validate composer.json and composer.lock + run: composer validate --strict + + - name: Cache Composer packages + id: composer-cache + uses: actions/cache@v2 + with: + path: vendor + key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-php- + + - name: Install dependencies + run: composer install --prefer-dist --no-progress + + # Add a test script to composer.json, for instance: "test": "vendor/bin/phpunit" + # Docs: https://getcomposer.org/doc/articles/scripts.md + + - name: Run test suite + run: composer test diff --git a/vendor/topthink/think-helper/.gitignore b/vendor/topthink/think-helper/.gitignore new file mode 100644 index 0000000..5ea11e9 --- /dev/null +++ b/vendor/topthink/think-helper/.gitignore @@ -0,0 +1,4 @@ +/vendor/ +/.idea/ +composer.lock +.phpunit.result.cache \ No newline at end of file diff --git a/vendor/topthink/think-helper/LICENSE b/vendor/topthink/think-helper/LICENSE new file mode 100644 index 0000000..8dada3e --- /dev/null +++ b/vendor/topthink/think-helper/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/topthink/think-helper/README.md b/vendor/topthink/think-helper/README.md new file mode 100644 index 0000000..f8f226d --- /dev/null +++ b/vendor/topthink/think-helper/README.md @@ -0,0 +1,35 @@ +# thinkphp6 常用的一些扩展类库 + +基于PHP7.1+ + +[![PHP Composer](https://github.com/larvatecn/think-helper/actions/workflows/php.yml/badge.svg)](https://github.com/larvatecn/think-helper/actions/workflows/php.yml) + +> 以下类库都在`\\think\\helper`命名空间下 + +## Str + +> 字符串操作 + +``` +// 检查字符串中是否包含某些字符串 +Str::contains($haystack, $needles) + +// 检查字符串是否以某些字符串结尾 +Str::endsWith($haystack, $needles) + +// 获取指定长度的随机字母数字组合的字符串 +Str::random($length = 16) + +// 字符串转小写 +Str::lower($value) + +// 字符串转大写 +Str::upper($value) + +// 获取字符串的长度 +Str::length($value) + +// 截取字符串 +Str::substr($string, $start, $length = null) + +``` \ No newline at end of file diff --git a/vendor/topthink/think-helper/phpunit.xml.dist b/vendor/topthink/think-helper/phpunit.xml.dist new file mode 100644 index 0000000..c083f14 --- /dev/null +++ b/vendor/topthink/think-helper/phpunit.xml.dist @@ -0,0 +1,17 @@ + + + + + ./tests/ + + + + + ./src + + + diff --git a/vendor/topthink/think-helper/src/Collection.php b/vendor/topthink/think-helper/src/Collection.php new file mode 100644 index 0000000..5b19ffc --- /dev/null +++ b/vendor/topthink/think-helper/src/Collection.php @@ -0,0 +1,662 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use ArrayAccess; +use ArrayIterator; +use Countable; +use IteratorAggregate; +use JsonSerializable; +use think\contract\Arrayable; +use think\contract\Jsonable; +use think\helper\Arr; +use Traversable; + +/** + * 数据集管理类 + */ +class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable, Arrayable, Jsonable +{ + /** + * 数据集数据 + * @var array + */ + protected $items = []; + + public function __construct($items = []) + { + $this->items = $this->convertToArray($items); + } + + public static function make($items = []) + { + return new static($items); + } + + /** + * 是否为空 + * @access public + * @return bool + */ + public function isEmpty(): bool + { + return empty($this->items); + } + + public function toArray(): array + { + return array_map(function ($value) { + return $value instanceof Arrayable ? $value->toArray() : $value; + }, $this->items); + } + + public function all(): array + { + return $this->items; + } + + /** + * 合并数组 + * + * @access public + * @param mixed $items 数据 + * @return static + */ + public function merge($items) + { + return new static(array_merge($this->items, $this->convertToArray($items))); + } + + /** + * 按指定键整理数据 + * + * @access public + * @param mixed $items 数据 + * @param string $indexKey 键名 + * @return array + */ + public function dictionary($items = null, string &$indexKey = null) + { + if ($items instanceof self) { + $items = $items->all(); + } + + $items = is_null($items) ? $this->items : $items; + + if ($items && empty($indexKey)) { + $indexKey = is_array($items[0]) ? 'id' : $items[0]->getPk(); + } + + if (isset($indexKey) && is_string($indexKey)) { + return array_column($items, null, $indexKey); + } + + return $items; + } + + /** + * 比较数组,返回差集 + * + * @access public + * @param mixed $items 数据 + * @param string $indexKey 指定比较的键名 + * @return static + */ + public function diff($items, string $indexKey = null) + { + if ($this->isEmpty() || is_scalar($this->items[0])) { + return new static(array_diff($this->items, $this->convertToArray($items))); + } + + $diff = []; + $dictionary = $this->dictionary($items, $indexKey); + + if (is_string($indexKey)) { + foreach ($this->items as $item) { + if (!isset($dictionary[$item[$indexKey]])) { + $diff[] = $item; + } + } + } + + return new static($diff); + } + + /** + * 比较数组,返回交集 + * + * @access public + * @param mixed $items 数据 + * @param string $indexKey 指定比较的键名 + * @return static + */ + public function intersect($items, string $indexKey = null) + { + if ($this->isEmpty() || is_scalar($this->items[0])) { + return new static(array_intersect($this->items, $this->convertToArray($items))); + } + + $intersect = []; + $dictionary = $this->dictionary($items, $indexKey); + + if (is_string($indexKey)) { + foreach ($this->items as $item) { + if (isset($dictionary[$item[$indexKey]])) { + $intersect[] = $item; + } + } + } + + return new static($intersect); + } + + /** + * 交换数组中的键和值 + * + * @access public + * @return static + */ + public function flip() + { + return new static(array_flip($this->items)); + } + + /** + * 返回数组中所有的键名 + * + * @access public + * @return static + */ + public function keys() + { + return new static(array_keys($this->items)); + } + + /** + * 返回数组中所有的值组成的新 Collection 实例 + * @access public + * @return static + */ + public function values() + { + return new static(array_values($this->items)); + } + + /** + * 删除数组的最后一个元素(出栈) + * + * @access public + * @return mixed + */ + public function pop() + { + return array_pop($this->items); + } + + /** + * 通过使用用户自定义函数,以字符串返回数组 + * + * @access public + * @param callable $callback 调用方法 + * @param mixed $initial + * @return mixed + */ + public function reduce(callable $callback, $initial = null) + { + return array_reduce($this->items, $callback, $initial); + } + + /** + * 以相反的顺序返回数组。 + * + * @access public + * @return static + */ + public function reverse() + { + return new static(array_reverse($this->items)); + } + + /** + * 删除数组中首个元素,并返回被删除元素的值 + * + * @access public + * @return mixed + */ + public function shift() + { + return array_shift($this->items); + } + + /** + * 在数组结尾插入一个元素 + * @access public + * @param mixed $value 元素 + * @param string $key KEY + * @return $this + */ + public function push($value, string $key = null) + { + if (is_null($key)) { + $this->items[] = $value; + } else { + $this->items[$key] = $value; + } + + return $this; + } + + /** + * 把一个数组分割为新的数组块. + * + * @access public + * @param int $size 块大小 + * @param bool $preserveKeys + * @return static + */ + public function chunk(int $size, bool $preserveKeys = false) + { + $chunks = []; + + foreach (array_chunk($this->items, $size, $preserveKeys) as $chunk) { + $chunks[] = new static($chunk); + } + + return new static($chunks); + } + + /** + * 在数组开头插入一个元素 + * @access public + * @param mixed $value 元素 + * @param string $key KEY + * @return $this + */ + public function unshift($value, string $key = null) + { + if (is_null($key)) { + array_unshift($this->items, $value); + } else { + $this->items = [$key => $value] + $this->items; + } + + return $this; + } + + /** + * 给每个元素执行个回调 + * + * @access public + * @param callable $callback 回调 + * @return $this + */ + public function each(callable $callback) + { + foreach ($this->items as $key => $item) { + $result = $callback($item, $key); + + if (false === $result) { + break; + } elseif (!is_object($item)) { + $this->items[$key] = $result; + } + } + + return $this; + } + + /** + * 用回调函数处理数组中的元素 + * @access public + * @param callable|null $callback 回调 + * @return static + */ + public function map(callable $callback) + { + return new static(array_map($callback, $this->items)); + } + + /** + * 用回调函数过滤数组中的元素 + * @access public + * @param callable|null $callback 回调 + * @return static + */ + public function filter(callable $callback = null) + { + if ($callback) { + return new static(array_filter($this->items, $callback)); + } + + return new static(array_filter($this->items)); + } + + /** + * 根据字段条件过滤数组中的元素 + * @access public + * @param string $field 字段名 + * @param mixed $operator 操作符 + * @param mixed $value 数据 + * @return static + */ + public function where(string $field, $operator, $value = null) + { + if (is_null($value)) { + $value = $operator; + $operator = '='; + } + + return $this->filter(function ($data) use ($field, $operator, $value) { + if (strpos($field, '.')) { + [$field, $relation] = explode('.', $field); + + $result = $data[$field][$relation] ?? null; + } else { + $result = $data[$field] ?? null; + } + + switch (strtolower($operator)) { + case '===': + return $result === $value; + case '!==': + return $result !== $value; + case '!=': + case '<>': + return $result != $value; + case '>': + return $result > $value; + case '>=': + return $result >= $value; + case '<': + return $result < $value; + case '<=': + return $result <= $value; + case 'like': + return is_string($result) && false !== strpos($result, $value); + case 'not like': + return is_string($result) && false === strpos($result, $value); + case 'in': + return is_scalar($result) && in_array($result, $value, true); + case 'not in': + return is_scalar($result) && !in_array($result, $value, true); + case 'between': + [$min, $max] = is_string($value) ? explode(',', $value) : $value; + return is_scalar($result) && $result >= $min && $result <= $max; + case 'not between': + [$min, $max] = is_string($value) ? explode(',', $value) : $value; + return is_scalar($result) && $result > $max || $result < $min; + case '==': + case '=': + default: + return $result == $value; + } + }); + } + + /** + * LIKE过滤 + * @access public + * @param string $field 字段名 + * @param string $value 数据 + * @return static + */ + public function whereLike(string $field, string $value) + { + return $this->where($field, 'like', $value); + } + + /** + * NOT LIKE过滤 + * @access public + * @param string $field 字段名 + * @param string $value 数据 + * @return static + */ + public function whereNotLike(string $field, string $value) + { + return $this->where($field, 'not like', $value); + } + + /** + * IN过滤 + * @access public + * @param string $field 字段名 + * @param array $value 数据 + * @return static + */ + public function whereIn(string $field, array $value) + { + return $this->where($field, 'in', $value); + } + + /** + * NOT IN过滤 + * @access public + * @param string $field 字段名 + * @param array $value 数据 + * @return static + */ + public function whereNotIn(string $field, array $value) + { + return $this->where($field, 'not in', $value); + } + + /** + * BETWEEN 过滤 + * @access public + * @param string $field 字段名 + * @param mixed $value 数据 + * @return static + */ + public function whereBetween(string $field, $value) + { + return $this->where($field, 'between', $value); + } + + /** + * NOT BETWEEN 过滤 + * @access public + * @param string $field 字段名 + * @param mixed $value 数据 + * @return static + */ + public function whereNotBetween(string $field, $value) + { + return $this->where($field, 'not between', $value); + } + + /** + * 返回数据中指定的一列 + * @access public + * @param string|null $columnKey 键名 + * @param string|null $indexKey 作为索引值的列 + * @return array + */ + public function column( ? string $columnKey, string $indexKey = null) + { + return array_column($this->items, $columnKey, $indexKey); + } + + /** + * 对数组排序 + * + * @access public + * @param callable|null $callback 回调 + * @return static + */ + public function sort(callable $callback = null) + { + $items = $this->items; + + $callback = $callback ?: function ($a, $b) { + return $a == $b ? 0 : (($a < $b) ? -1 : 1); + }; + + uasort($items, $callback); + + return new static($items); + } + + /** + * 指定字段排序 + * @access public + * @param string $field 排序字段 + * @param string $order 排序 + * @return $this + */ + public function order(string $field, string $order = 'asc') + { + return $this->sort(function ($a, $b) use ($field, $order) { + $fieldA = $a[$field] ?? null; + $fieldB = $b[$field] ?? null; + + return 'desc' == strtolower($order) ? intval($fieldB > $fieldA) : intval($fieldA > $fieldB); + }); + } + + /** + * 将数组打乱 + * + * @access public + * @return static + */ + public function shuffle() + { + $items = $this->items; + + shuffle($items); + + return new static($items); + } + + /** + * 获取第一个单元数据 + * + * @access public + * @param callable|null $callback + * @param null $default + * @return mixed + */ + public function first(callable $callback = null, $default = null) + { + return Arr::first($this->items, $callback, $default); + } + + /** + * 获取最后一个单元数据 + * + * @access public + * @param callable|null $callback + * @param null $default + * @return mixed + */ + public function last(callable $callback = null, $default = null) + { + return Arr::last($this->items, $callback, $default); + } + + /** + * 截取数组 + * + * @access public + * @param int $offset 起始位置 + * @param int $length 截取长度 + * @param bool $preserveKeys preserveKeys + * @return static + */ + public function slice(int $offset, int $length = null, bool $preserveKeys = false) + { + return new static(array_slice($this->items, $offset, $length, $preserveKeys)); + } + + // ArrayAccess + #[\ReturnTypeWillChange] + public function offsetExists($offset) : bool + { + return array_key_exists($offset, $this->items); + } + + #[\ReturnTypeWillChange] + public function offsetGet($offset) + { + return $this->items[$offset]; + } + + #[\ReturnTypeWillChange] + public function offsetSet($offset, $value) + { + if (is_null($offset)) { + $this->items[] = $value; + } else { + $this->items[$offset] = $value; + } + } + + #[\ReturnTypeWillChange] + public function offsetUnset($offset) + { + unset($this->items[$offset]); + } + + //Countable + public function count(): int + { + return count($this->items); + } + + //IteratorAggregate + #[\ReturnTypeWillChange] + public function getIterator(): Traversable + { + return new ArrayIterator($this->items); + } + + //JsonSerializable + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return $this->toArray(); + } + + /** + * 转换当前数据集为JSON字符串 + * @access public + * @param integer $options json参数 + * @return string + */ + public function toJson(int $options = JSON_UNESCAPED_UNICODE): string + { + return json_encode($this->toArray(), $options); + } + + public function __toString() + { + return $this->toJson(); + } + + /** + * 转换成数组 + * + * @access public + * @param mixed $items 数据 + * @return array + */ + protected function convertToArray($items): array + { + if ($items instanceof self) { + return $items->all(); + } + + return (array) $items; + } +} diff --git a/vendor/topthink/think-helper/src/contract/Arrayable.php b/vendor/topthink/think-helper/src/contract/Arrayable.php new file mode 100644 index 0000000..7c6b992 --- /dev/null +++ b/vendor/topthink/think-helper/src/contract/Arrayable.php @@ -0,0 +1,8 @@ + +// +---------------------------------------------------------------------- + +use think\Collection; +use think\helper\Arr; + +if (!function_exists('throw_if')) { + /** + * 按条件抛异常 + * + * @param mixed $condition + * @param Throwable|string $exception + * @param array ...$parameters + * @return mixed + * + * @throws Throwable + */ + function throw_if($condition, $exception, ...$parameters) + { + if ($condition) { + throw (is_string($exception) ? new $exception(...$parameters) : $exception); + } + + return $condition; + } +} + +if (!function_exists('throw_unless')) { + /** + * 按条件抛异常 + * + * @param mixed $condition + * @param Throwable|string $exception + * @param array ...$parameters + * @return mixed + * @throws Throwable + */ + function throw_unless($condition, $exception, ...$parameters) + { + if (!$condition) { + throw (is_string($exception) ? new $exception(...$parameters) : $exception); + } + + return $condition; + } +} + +if (!function_exists('tap')) { + /** + * 对一个值调用给定的闭包,然后返回该值 + * + * @param mixed $value + * @param callable|null $callback + * @return mixed + */ + function tap($value, $callback = null) + { + if (is_null($callback)) { + return $value; + } + + $callback($value); + + return $value; + } +} + +if (!function_exists('value')) { + /** + * Return the default value of the given value. + * + * @param mixed $value + * @return mixed + */ + function value($value) + { + return $value instanceof Closure ? $value() : $value; + } +} + +if (!function_exists('collect')) { + /** + * Create a collection from the given value. + * + * @param mixed $value + * @return Collection + */ + function collect($value = null) + { + return new Collection($value); + } +} + +if (!function_exists('data_fill')) { + /** + * Fill in data where it's missing. + * + * @param mixed $target + * @param string|array $key + * @param mixed $value + * @return mixed + */ + function data_fill(&$target, $key, $value) + { + return data_set($target, $key, $value, false); + } +} + +if (!function_exists('data_get')) { + /** + * Get an item from an array or object using "dot" notation. + * + * @param mixed $target + * @param string|array|int $key + * @param mixed $default + * @return mixed + */ + function data_get($target, $key, $default = null) + { + if (is_null($key)) { + return $target; + } + + $key = is_array($key) ? $key : explode('.', $key); + + while (!is_null($segment = array_shift($key))) { + if ('*' === $segment) { + if ($target instanceof Collection) { + $target = $target->all(); + } elseif (!is_array($target)) { + return value($default); + } + + $result = []; + + foreach ($target as $item) { + $result[] = data_get($item, $key); + } + + return in_array('*', $key) ? Arr::collapse($result) : $result; + } + + if (Arr::accessible($target) && Arr::exists($target, $segment)) { + $target = $target[$segment]; + } elseif (is_object($target) && isset($target->{$segment})) { + $target = $target->{$segment}; + } else { + return value($default); + } + } + + return $target; + } +} + +if (!function_exists('data_set')) { + /** + * Set an item on an array or object using dot notation. + * + * @param mixed $target + * @param string|array $key + * @param mixed $value + * @param bool $overwrite + * @return mixed + */ + function data_set(&$target, $key, $value, $overwrite = true) + { + $segments = is_array($key) ? $key : explode('.', $key); + + if (($segment = array_shift($segments)) === '*') { + if (!Arr::accessible($target)) { + $target = []; + } + + if ($segments) { + foreach ($target as &$inner) { + data_set($inner, $segments, $value, $overwrite); + } + } elseif ($overwrite) { + foreach ($target as &$inner) { + $inner = $value; + } + } + } elseif (Arr::accessible($target)) { + if ($segments) { + if (!Arr::exists($target, $segment)) { + $target[$segment] = []; + } + + data_set($target[$segment], $segments, $value, $overwrite); + } elseif ($overwrite || !Arr::exists($target, $segment)) { + $target[$segment] = $value; + } + } elseif (is_object($target)) { + if ($segments) { + if (!isset($target->{$segment})) { + $target->{$segment} = []; + } + + data_set($target->{$segment}, $segments, $value, $overwrite); + } elseif ($overwrite || !isset($target->{$segment})) { + $target->{$segment} = $value; + } + } else { + $target = []; + + if ($segments) { + data_set($target[$segment], $segments, $value, $overwrite); + } elseif ($overwrite) { + $target[$segment] = $value; + } + } + + return $target; + } +} + +if (!function_exists('trait_uses_recursive')) { + /** + * 获取一个trait里所有引用到的trait + * + * @param string $trait Trait + * @return array + */ + function trait_uses_recursive(string $trait): array + { + $traits = class_uses($trait); + foreach ($traits as $trait) { + $traits += trait_uses_recursive($trait); + } + + return $traits; + } +} + +if (!function_exists('class_basename')) { + /** + * 获取类名(不包含命名空间) + * + * @param mixed $class 类名 + * @return string + */ + function class_basename($class): string + { + $class = is_object($class) ? get_class($class) : $class; + return basename(str_replace('\\', '/', $class)); + } +} + +if (!function_exists('class_uses_recursive')) { + /** + *获取一个类里所有用到的trait,包括父类的 + * + * @param mixed $class 类名 + * @return array + */ + function class_uses_recursive($class): array + { + if (is_object($class)) { + $class = get_class($class); + } + + $results = []; + $classes = array_merge([$class => $class], class_parents($class)); + foreach ($classes as $class) { + $results += trait_uses_recursive($class); + } + + return array_unique($results); + } +} diff --git a/vendor/topthink/think-helper/src/helper/Arr.php b/vendor/topthink/think-helper/src/helper/Arr.php new file mode 100644 index 0000000..ed4d6a9 --- /dev/null +++ b/vendor/topthink/think-helper/src/helper/Arr.php @@ -0,0 +1,634 @@ + +// +---------------------------------------------------------------------- + +namespace think\helper; + +use ArrayAccess; +use InvalidArgumentException; +use think\Collection; + +class Arr +{ + + /** + * Determine whether the given value is array accessible. + * + * @param mixed $value + * @return bool + */ + public static function accessible($value) + { + return is_array($value) || $value instanceof ArrayAccess; + } + + /** + * Add an element to an array using "dot" notation if it doesn't exist. + * + * @param array $array + * @param string $key + * @param mixed $value + * @return array + */ + public static function add($array, $key, $value) + { + if (is_null(static::get($array, $key))) { + static::set($array, $key, $value); + } + + return $array; + } + + /** + * Collapse an array of arrays into a single array. + * + * @param array $array + * @return array + */ + public static function collapse($array) + { + $results = []; + + foreach ($array as $values) { + if ($values instanceof Collection) { + $values = $values->all(); + } elseif (!is_array($values)) { + continue; + } + + $results = array_merge($results, $values); + } + + return $results; + } + + /** + * Cross join the given arrays, returning all possible permutations. + * + * @param array ...$arrays + * @return array + */ + public static function crossJoin(...$arrays) + { + $results = [[]]; + + foreach ($arrays as $index => $array) { + $append = []; + + foreach ($results as $product) { + foreach ($array as $item) { + $product[$index] = $item; + + $append[] = $product; + } + } + + $results = $append; + } + + return $results; + } + + /** + * Divide an array into two arrays. One with keys and the other with values. + * + * @param array $array + * @return array + */ + public static function divide($array) + { + return [array_keys($array), array_values($array)]; + } + + /** + * Flatten a multi-dimensional associative array with dots. + * + * @param array $array + * @param string $prepend + * @return array + */ + public static function dot($array, $prepend = '') + { + $results = []; + + foreach ($array as $key => $value) { + if (is_array($value) && !empty($value)) { + $results = array_merge($results, static::dot($value, $prepend . $key . '.')); + } else { + $results[$prepend . $key] = $value; + } + } + + return $results; + } + + /** + * Get all of the given array except for a specified array of keys. + * + * @param array $array + * @param array|string $keys + * @return array + */ + public static function except($array, $keys) + { + static::forget($array, $keys); + + return $array; + } + + /** + * Determine if the given key exists in the provided array. + * + * @param \ArrayAccess|array $array + * @param string|int $key + * @return bool + */ + public static function exists($array, $key) + { + if ($array instanceof ArrayAccess) { + return $array->offsetExists($key); + } + + return array_key_exists($key, $array); + } + + /** + * Return the first element in an array passing a given truth test. + * + * @param array $array + * @param callable|null $callback + * @param mixed $default + * @return mixed + */ + public static function first($array, callable $callback = null, $default = null) + { + if (is_null($callback)) { + if (empty($array)) { + return value($default); + } + + foreach ($array as $item) { + return $item; + } + } + + foreach ($array as $key => $value) { + if (call_user_func($callback, $value, $key)) { + return $value; + } + } + + return value($default); + } + + /** + * Return the last element in an array passing a given truth test. + * + * @param array $array + * @param callable|null $callback + * @param mixed $default + * @return mixed + */ + public static function last($array, callable $callback = null, $default = null) + { + if (is_null($callback)) { + return empty($array) ? value($default) : end($array); + } + + return static::first(array_reverse($array, true), $callback, $default); + } + + /** + * Flatten a multi-dimensional array into a single level. + * + * @param array $array + * @param int $depth + * @return array + */ + public static function flatten($array, $depth = INF) + { + $result = []; + + foreach ($array as $item) { + $item = $item instanceof Collection ? $item->all() : $item; + + if (!is_array($item)) { + $result[] = $item; + } elseif ($depth === 1) { + $result = array_merge($result, array_values($item)); + } else { + $result = array_merge($result, static::flatten($item, $depth - 1)); + } + } + + return $result; + } + + /** + * Remove one or many array items from a given array using "dot" notation. + * + * @param array $array + * @param array|string $keys + * @return void + */ + public static function forget(&$array, $keys) + { + $original = &$array; + + $keys = (array) $keys; + + if (count($keys) === 0) { + return; + } + + foreach ($keys as $key) { + // if the exact key exists in the top-level, remove it + if (static::exists($array, $key)) { + unset($array[$key]); + + continue; + } + + $parts = explode('.', $key); + + // clean up before each pass + $array = &$original; + + while (count($parts) > 1) { + $part = array_shift($parts); + + if (isset($array[$part]) && is_array($array[$part])) { + $array = &$array[$part]; + } else { + continue 2; + } + } + + unset($array[array_shift($parts)]); + } + } + + /** + * Get an item from an array using "dot" notation. + * + * @param \ArrayAccess|array $array + * @param string $key + * @param mixed $default + * @return mixed + */ + public static function get($array, $key, $default = null) + { + if (!static::accessible($array)) { + return value($default); + } + + if (is_null($key)) { + return $array; + } + + if (static::exists($array, $key)) { + return $array[$key]; + } + + if (strpos($key, '.') === false) { + return $array[$key] ?? value($default); + } + + foreach (explode('.', $key) as $segment) { + if (static::accessible($array) && static::exists($array, $segment)) { + $array = $array[$segment]; + } else { + return value($default); + } + } + + return $array; + } + + /** + * Check if an item or items exist in an array using "dot" notation. + * + * @param \ArrayAccess|array $array + * @param string|array $keys + * @return bool + */ + public static function has($array, $keys) + { + $keys = (array) $keys; + + if (!$array || $keys === []) { + return false; + } + + foreach ($keys as $key) { + $subKeyArray = $array; + + if (static::exists($array, $key)) { + continue; + } + + foreach (explode('.', $key) as $segment) { + if (static::accessible($subKeyArray) && static::exists($subKeyArray, $segment)) { + $subKeyArray = $subKeyArray[$segment]; + } else { + return false; + } + } + } + + return true; + } + + /** + * Determines if an array is associative. + * + * An array is "associative" if it doesn't have sequential numerical keys beginning with zero. + * + * @param array $array + * @return bool + */ + public static function isAssoc(array $array) + { + $keys = array_keys($array); + + return array_keys($keys) !== $keys; + } + + /** + * Get a subset of the items from the given array. + * + * @param array $array + * @param array|string $keys + * @return array + */ + public static function only($array, $keys) + { + return array_intersect_key($array, array_flip((array) $keys)); + } + + /** + * Pluck an array of values from an array. + * + * @param array $array + * @param string|array $value + * @param string|array|null $key + * @return array + */ + public static function pluck($array, $value, $key = null) + { + $results = []; + + [$value, $key] = static::explodePluckParameters($value, $key); + + foreach ($array as $item) { + $itemValue = data_get($item, $value); + + // If the key is "null", we will just append the value to the array and keep + // looping. Otherwise we will key the array using the value of the key we + // received from the developer. Then we'll return the final array form. + if (is_null($key)) { + $results[] = $itemValue; + } else { + $itemKey = data_get($item, $key); + + if (is_object($itemKey) && method_exists($itemKey, '__toString')) { + $itemKey = (string) $itemKey; + } + + $results[$itemKey] = $itemValue; + } + } + + return $results; + } + + /** + * Explode the "value" and "key" arguments passed to "pluck". + * + * @param string|array $value + * @param string|array|null $key + * @return array + */ + protected static function explodePluckParameters($value, $key) + { + $value = is_string($value) ? explode('.', $value) : $value; + + $key = is_null($key) || is_array($key) ? $key : explode('.', $key); + + return [$value, $key]; + } + + /** + * Push an item onto the beginning of an array. + * + * @param array $array + * @param mixed $value + * @param mixed $key + * @return array + */ + public static function prepend($array, $value, $key = null) + { + if (is_null($key)) { + array_unshift($array, $value); + } else { + $array = [$key => $value] + $array; + } + + return $array; + } + + /** + * Get a value from the array, and remove it. + * + * @param array $array + * @param string $key + * @param mixed $default + * @return mixed + */ + public static function pull(&$array, $key, $default = null) + { + $value = static::get($array, $key, $default); + + static::forget($array, $key); + + return $value; + } + + /** + * Get one or a specified number of random values from an array. + * + * @param array $array + * @param int|null $number + * @return mixed + * + * @throws \InvalidArgumentException + */ + public static function random($array, $number = null) + { + $requested = is_null($number) ? 1 : $number; + + $count = count($array); + + if ($requested > $count) { + throw new InvalidArgumentException( + "You requested {$requested} items, but there are only {$count} items available." + ); + } + + if (is_null($number)) { + return $array[array_rand($array)]; + } + + if ((int) $number === 0) { + return []; + } + + $keys = array_rand($array, $number); + + $results = []; + + foreach ((array) $keys as $key) { + $results[] = $array[$key]; + } + + return $results; + } + + /** + * Set an array item to a given value using "dot" notation. + * + * If no key is given to the method, the entire array will be replaced. + * + * @param array $array + * @param string $key + * @param mixed $value + * @return array + */ + public static function set(&$array, $key, $value) + { + if (is_null($key)) { + return $array = $value; + } + + $keys = explode('.', $key); + + while (count($keys) > 1) { + $key = array_shift($keys); + + // If the key doesn't exist at this depth, we will just create an empty array + // to hold the next value, allowing us to create the arrays to hold final + // values at the correct depth. Then we'll keep digging into the array. + if (!isset($array[$key]) || !is_array($array[$key])) { + $array[$key] = []; + } + + $array = &$array[$key]; + } + + $array[array_shift($keys)] = $value; + + return $array; + } + + /** + * Shuffle the given array and return the result. + * + * @param array $array + * @param int|null $seed + * @return array + */ + public static function shuffle($array, $seed = null) + { + if (is_null($seed)) { + shuffle($array); + } else { + srand($seed); + + usort($array, function () { + return rand(-1, 1); + }); + } + + return $array; + } + + /** + * Sort the array using the given callback or "dot" notation. + * + * @param array $array + * @param callable|string|null $callback + * @return array + */ + public static function sort($array, $callback = null) + { + return Collection::make($array)->sort($callback)->all(); + } + + /** + * Recursively sort an array by keys and values. + * + * @param array $array + * @return array + */ + public static function sortRecursive($array) + { + foreach ($array as &$value) { + if (is_array($value)) { + $value = static::sortRecursive($value); + } + } + + if (static::isAssoc($array)) { + ksort($array); + } else { + sort($array); + } + + return $array; + } + + /** + * Convert the array into a query string. + * + * @param array $array + * @return string + */ + public static function query($array) + { + return http_build_query($array, null, '&', PHP_QUERY_RFC3986); + } + + /** + * Filter the array using the given callback. + * + * @param array $array + * @param callable $callback + * @return array + */ + public static function where($array, callable $callback) + { + return array_filter($array, $callback, ARRAY_FILTER_USE_BOTH); + } + + /** + * If the given value is not an array and not null, wrap it in one. + * + * @param mixed $value + * @return array + */ + public static function wrap($value) + { + if (is_null($value)) { + return []; + } + + return is_array($value) ? $value : [$value]; + } +} \ No newline at end of file diff --git a/vendor/topthink/think-helper/src/helper/Str.php b/vendor/topthink/think-helper/src/helper/Str.php new file mode 100644 index 0000000..664dba2 --- /dev/null +++ b/vendor/topthink/think-helper/src/helper/Str.php @@ -0,0 +1,234 @@ + +// +---------------------------------------------------------------------- +namespace think\helper; + +class Str +{ + + protected static $snakeCache = []; + + protected static $camelCache = []; + + protected static $studlyCache = []; + + /** + * 检查字符串中是否包含某些字符串 + * @param string $haystack + * @param string|array $needles + * @return bool + */ + public static function contains(string $haystack, $needles): bool + { + foreach ((array) $needles as $needle) { + if ('' != $needle && mb_strpos($haystack, $needle) !== false) { + return true; + } + } + + return false; + } + + /** + * 检查字符串是否以某些字符串结尾 + * + * @param string $haystack + * @param string|array $needles + * @return bool + */ + public static function endsWith(string $haystack, $needles): bool + { + foreach ((array) $needles as $needle) { + if ((string) $needle === static::substr($haystack, -static::length($needle))) { + return true; + } + } + + return false; + } + + /** + * 检查字符串是否以某些字符串开头 + * + * @param string $haystack + * @param string|array $needles + * @return bool + */ + public static function startsWith(string $haystack, $needles): bool + { + foreach ((array) $needles as $needle) { + if ('' != $needle && mb_strpos($haystack, $needle) === 0) { + return true; + } + } + + return false; + } + + /** + * 获取指定长度的随机字母数字组合的字符串 + * + * @param int $length + * @param int $type + * @param string $addChars + * @return string + */ + public static function random(int $length = 6, int $type = null, string $addChars = ''): string + { + $str = ''; + switch ($type) { + case 0: + $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' . $addChars; + break; + case 1: + $chars = str_repeat('0123456789', 3); + break; + case 2: + $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' . $addChars; + break; + case 3: + $chars = 'abcdefghijklmnopqrstuvwxyz' . $addChars; + break; + case 4: + $chars = "们以我到他会作时要动国产的一是工就年阶义发成部民可出能方进在了不和有大这主中人上为来分生对于学下级地个用同行面说种过命度革而多子后自社加小机也经力线本电高量长党得实家定深法表着水理化争现所二起政三好十战无农使性前等反体合斗路图把结第里正新开论之物从当两些还天资事队批点育重其思与间内去因件日利相由压员气业代全组数果期导平各基或月毛然如应形想制心样干都向变关问比展那它最及外没看治提五解系林者米群头意只明四道马认次文通但条较克又公孔领军流入接席位情运器并飞原油放立题质指建区验活众很教决特此常石强极土少已根共直团统式转别造切九你取西持总料连任志观调七么山程百报更见必真保热委手改管处己将修支识病象几先老光专什六型具示复安带每东增则完风回南广劳轮科北打积车计给节做务被整联步类集号列温装即毫知轴研单色坚据速防史拉世设达尔场织历花受求传口断况采精金界品判参层止边清至万确究书" . $addChars; + break; + default: + $chars = 'ABCDEFGHIJKMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789' . $addChars; + break; + } + if ($length > 10) { + $chars = $type == 1 ? str_repeat($chars, $length) : str_repeat($chars, 5); + } + if ($type != 4) { + $chars = str_shuffle($chars); + $str = substr($chars, 0, $length); + } else { + for ($i = 0; $i < $length; $i++) { + $str .= mb_substr($chars, floor(mt_rand(0, mb_strlen($chars, 'utf-8') - 1)), 1); + } + } + return $str; + } + + /** + * 字符串转小写 + * + * @param string $value + * @return string + */ + public static function lower(string $value): string + { + return mb_strtolower($value, 'UTF-8'); + } + + /** + * 字符串转大写 + * + * @param string $value + * @return string + */ + public static function upper(string $value): string + { + return mb_strtoupper($value, 'UTF-8'); + } + + /** + * 获取字符串的长度 + * + * @param string $value + * @return int + */ + public static function length(string $value): int + { + return mb_strlen($value); + } + + /** + * 截取字符串 + * + * @param string $string + * @param int $start + * @param int|null $length + * @return string + */ + public static function substr(string $string, int $start, int $length = null): string + { + return mb_substr($string, $start, $length, 'UTF-8'); + } + + /** + * 驼峰转下划线 + * + * @param string $value + * @param string $delimiter + * @return string + */ + public static function snake(string $value, string $delimiter = '_'): string + { + $key = $value; + + if (isset(static::$snakeCache[$key][$delimiter])) { + return static::$snakeCache[$key][$delimiter]; + } + + if (!ctype_lower($value)) { + $value = preg_replace('/\s+/u', '', ucwords($value)); + + $value = static::lower(preg_replace('/(.)(?=[A-Z])/u', '$1' . $delimiter, $value)); + } + + return static::$snakeCache[$key][$delimiter] = $value; + } + + /** + * 下划线转驼峰(首字母小写) + * + * @param string $value + * @return string + */ + public static function camel(string $value): string + { + if (isset(static::$camelCache[$value])) { + return static::$camelCache[$value]; + } + + return static::$camelCache[$value] = lcfirst(static::studly($value)); + } + + /** + * 下划线转驼峰(首字母大写) + * + * @param string $value + * @return string + */ + public static function studly(string $value): string + { + $key = $value; + + if (isset(static::$studlyCache[$key])) { + return static::$studlyCache[$key]; + } + + $value = ucwords(str_replace(['-', '_'], ' ', $value)); + + return static::$studlyCache[$key] = str_replace(' ', '', $value); + } + + /** + * 转为首字母大写的标题格式 + * + * @param string $value + * @return string + */ + public static function title(string $value): string + { + return mb_convert_case($value, MB_CASE_TITLE, 'UTF-8'); + } +} diff --git a/vendor/topthink/think-helper/tests/ArrTest.php b/vendor/topthink/think-helper/tests/ArrTest.php new file mode 100644 index 0000000..a8effe7 --- /dev/null +++ b/vendor/topthink/think-helper/tests/ArrTest.php @@ -0,0 +1,342 @@ + 'ThinkPHP'], 'price', 100); + $this->assertSame(['name' => 'ThinkPHP', 'price' => 100], $array); + } + + public function testCrossJoin() + { + // Single dimension + $this->assertSame( + [[1, 'a'], [1, 'b'], [1, 'c']], + Arr::crossJoin([1], ['a', 'b', 'c']) + ); + // Square matrix + $this->assertSame( + [[1, 'a'], [1, 'b'], [2, 'a'], [2, 'b']], + Arr::crossJoin([1, 2], ['a', 'b']) + ); + // Rectangular matrix + $this->assertSame( + [[1, 'a'], [1, 'b'], [1, 'c'], [2, 'a'], [2, 'b'], [2, 'c']], + Arr::crossJoin([1, 2], ['a', 'b', 'c']) + ); + // 3D matrix + $this->assertSame( + [ + [1, 'a', 'I'], [1, 'a', 'II'], [1, 'a', 'III'], + [1, 'b', 'I'], [1, 'b', 'II'], [1, 'b', 'III'], + [2, 'a', 'I'], [2, 'a', 'II'], [2, 'a', 'III'], + [2, 'b', 'I'], [2, 'b', 'II'], [2, 'b', 'III'], + ], + Arr::crossJoin([1, 2], ['a', 'b'], ['I', 'II', 'III']) + ); + // With 1 empty dimension + $this->assertSame([], Arr::crossJoin([], ['a', 'b'], ['I', 'II', 'III'])); + $this->assertSame([], Arr::crossJoin([1, 2], [], ['I', 'II', 'III'])); + $this->assertSame([], Arr::crossJoin([1, 2], ['a', 'b'], [])); + // With empty arrays + $this->assertSame([], Arr::crossJoin([], [], [])); + $this->assertSame([], Arr::crossJoin([], [])); + $this->assertSame([], Arr::crossJoin([])); + // Not really a proper usage, still, test for preserving BC + $this->assertSame([[]], Arr::crossJoin()); + } + + public function testDivide() + { + list($keys, $values) = Arr::divide(['name' => 'ThinkPHP']); + $this->assertSame(['name'], $keys); + $this->assertSame(['ThinkPHP'], $values); + } + + public function testDot() + { + $array = Arr::dot(['foo' => ['bar' => 'baz']]); + $this->assertSame(['foo.bar' => 'baz'], $array); + $array = Arr::dot([]); + $this->assertSame([], $array); + $array = Arr::dot(['foo' => []]); + $this->assertSame(['foo' => []], $array); + $array = Arr::dot(['foo' => ['bar' => []]]); + $this->assertSame(['foo.bar' => []], $array); + } + + public function testExcept() + { + $array = ['name' => 'ThinkPHP', 'price' => 100]; + $array = Arr::except($array, ['price']); + $this->assertSame(['name' => 'ThinkPHP'], $array); + } + + public function testExists() + { + $this->assertTrue(Arr::exists([1], 0)); + $this->assertTrue(Arr::exists([null], 0)); + $this->assertTrue(Arr::exists(['a' => 1], 'a')); + $this->assertTrue(Arr::exists(['a' => null], 'a')); + $this->assertFalse(Arr::exists([1], 1)); + $this->assertFalse(Arr::exists([null], 1)); + $this->assertFalse(Arr::exists(['a' => 1], 0)); + } + + public function testFirst() + { + $array = [100, 200, 300]; + $value = Arr::first($array, function ($value) { + return $value >= 150; + }); + $this->assertSame(200, $value); + $this->assertSame(100, Arr::first($array)); + + $this->assertSame('default', Arr::first([], null, 'default')); + + $this->assertSame('default', Arr::first([], function () { + return false; + }, 'default')); + } + + public function testLast() + { + $array = [100, 200, 300]; + $last = Arr::last($array, function ($value) { + return $value < 250; + }); + $this->assertSame(200, $last); + $last = Arr::last($array, function ($value, $key) { + return $key < 2; + }); + $this->assertSame(200, $last); + $this->assertSame(300, Arr::last($array)); + } + + public function testFlatten() + { + // Flat arrays are unaffected + $array = ['#foo', '#bar', '#baz']; + $this->assertSame(['#foo', '#bar', '#baz'], Arr::flatten(['#foo', '#bar', '#baz'])); + // Nested arrays are flattened with existing flat items + $array = [['#foo', '#bar'], '#baz']; + $this->assertSame(['#foo', '#bar', '#baz'], Arr::flatten($array)); + // Flattened array includes "null" items + $array = [['#foo', null], '#baz', null]; + $this->assertSame(['#foo', null, '#baz', null], Arr::flatten($array)); + // Sets of nested arrays are flattened + $array = [['#foo', '#bar'], ['#baz']]; + $this->assertSame(['#foo', '#bar', '#baz'], Arr::flatten($array)); + // Deeply nested arrays are flattened + $array = [['#foo', ['#bar']], ['#baz']]; + $this->assertSame(['#foo', '#bar', '#baz'], Arr::flatten($array)); + // Nested arrays are flattened alongside arrays + $array = [new Collection(['#foo', '#bar']), ['#baz']]; + $this->assertSame(['#foo', '#bar', '#baz'], Arr::flatten($array)); + // Nested arrays containing plain arrays are flattened + $array = [new Collection(['#foo', ['#bar']]), ['#baz']]; + $this->assertSame(['#foo', '#bar', '#baz'], Arr::flatten($array)); + // Nested arrays containing arrays are flattened + $array = [['#foo', new Collection(['#bar'])], ['#baz']]; + $this->assertSame(['#foo', '#bar', '#baz'], Arr::flatten($array)); + // Nested arrays containing arrays containing arrays are flattened + $array = [['#foo', new Collection(['#bar', ['#zap']])], ['#baz']]; + $this->assertSame(['#foo', '#bar', '#zap', '#baz'], Arr::flatten($array)); + } + + public function testFlattenWithDepth() + { + // No depth flattens recursively + $array = [['#foo', ['#bar', ['#baz']]], '#zap']; + $this->assertSame(['#foo', '#bar', '#baz', '#zap'], Arr::flatten($array)); + // Specifying a depth only flattens to that depth + $array = [['#foo', ['#bar', ['#baz']]], '#zap']; + $this->assertSame(['#foo', ['#bar', ['#baz']], '#zap'], Arr::flatten($array, 1)); + $array = [['#foo', ['#bar', ['#baz']]], '#zap']; + $this->assertSame(['#foo', '#bar', ['#baz'], '#zap'], Arr::flatten($array, 2)); + } + + public function testGet() + { + $array = ['products.item' => ['price' => 100]]; + $this->assertSame(['price' => 100], Arr::get($array, 'products.item')); + $array = ['products' => ['item' => ['price' => 100]]]; + $value = Arr::get($array, 'products.item'); + $this->assertSame(['price' => 100], $value); + // Test null array values + $array = ['foo' => null, 'bar' => ['baz' => null]]; + $this->assertNull(Arr::get($array, 'foo', 'default')); + $this->assertNull(Arr::get($array, 'bar.baz', 'default')); + // Test null key returns the whole array + $array = ['foo', 'bar']; + $this->assertSame($array, Arr::get($array, null)); + // Test $array is empty and key is null + $this->assertSame([], Arr::get([], null)); + $this->assertSame([], Arr::get([], null, 'default')); + } + + public function testHas() + { + $array = ['products.item' => ['price' => 100]]; + $this->assertTrue(Arr::has($array, 'products.item')); + $array = ['products' => ['item' => ['price' => 100]]]; + $this->assertTrue(Arr::has($array, 'products.item')); + $this->assertTrue(Arr::has($array, 'products.item.price')); + $this->assertFalse(Arr::has($array, 'products.foo')); + $this->assertFalse(Arr::has($array, 'products.item.foo')); + $array = ['foo' => null, 'bar' => ['baz' => null]]; + $this->assertTrue(Arr::has($array, 'foo')); + $this->assertTrue(Arr::has($array, 'bar.baz')); + $array = ['foo', 'bar']; + $this->assertFalse(Arr::has($array, null)); + $this->assertFalse(Arr::has([], null)); + $array = ['products' => ['item' => ['price' => 100]]]; + $this->assertTrue(Arr::has($array, ['products.item'])); + $this->assertTrue(Arr::has($array, ['products.item', 'products.item.price'])); + $this->assertTrue(Arr::has($array, ['products', 'products'])); + $this->assertFalse(Arr::has($array, ['foo'])); + $this->assertFalse(Arr::has($array, [])); + $this->assertFalse(Arr::has($array, ['products.item', 'products.price'])); + $this->assertFalse(Arr::has([], [null])); + } + + public function testIsAssoc() + { + $this->assertTrue(Arr::isAssoc(['a' => 'a', 0 => 'b'])); + $this->assertTrue(Arr::isAssoc([1 => 'a', 0 => 'b'])); + $this->assertTrue(Arr::isAssoc([1 => 'a', 2 => 'b'])); + $this->assertFalse(Arr::isAssoc([0 => 'a', 1 => 'b'])); + $this->assertFalse(Arr::isAssoc(['a', 'b'])); + } + + public function testOnly() + { + $array = ['name' => 'ThinkPHP', 'price' => 100, 'orders' => 10]; + $array = Arr::only($array, ['name', 'price']); + $this->assertSame(['name' => 'ThinkPHP', 'price' => 100], $array); + } + + public function testPrepend() + { + $array = Arr::prepend(['one', 'two', 'three', 'four'], 'zero'); + $this->assertSame(['zero', 'one', 'two', 'three', 'four'], $array); + $array = Arr::prepend(['one' => 1, 'two' => 2], 0, 'zero'); + $this->assertSame(['zero' => 0, 'one' => 1, 'two' => 2], $array); + } + + public function testPull() + { + $array = ['name' => 'ThinkPHP', 'price' => 100]; + $name = Arr::pull($array, 'name'); + $this->assertSame('ThinkPHP', $name); + $this->assertSame(['price' => 100], $array); + // Only works on first level keys + $array = ['i@example.com' => 'Joe', 'jack@localhost' => 'Jane']; + $name = Arr::pull($array, 'i@example.com'); + $this->assertSame('Joe', $name); + $this->assertSame(['jack@localhost' => 'Jane'], $array); + // Does not work for nested keys + $array = ['emails' => ['i@example.com' => 'Joe', 'jack@localhost' => 'Jane']]; + $name = Arr::pull($array, 'emails.i@example.com'); + $this->assertNull($name); + $this->assertSame(['emails' => ['i@example.com' => 'Joe', 'jack@localhost' => 'Jane']], $array); + } + + public function testRandom() + { + $randomValue = Arr::random(['foo', 'bar', 'baz']); + $this->assertContains($randomValue, ['foo', 'bar', 'baz']); + $randomValues = Arr::random(['foo', 'bar', 'baz'], 1); + $this->assertIsArray($randomValues); + $this->assertCount(1, $randomValues); + $this->assertContains($randomValues[0], ['foo', 'bar', 'baz']); + $randomValues = Arr::random(['foo', 'bar', 'baz'], 2); + $this->assertIsArray($randomValues); + $this->assertCount(2, $randomValues); + $this->assertContains($randomValues[0], ['foo', 'bar', 'baz']); + $this->assertContains($randomValues[1], ['foo', 'bar', 'baz']); + } + + public function testSet() + { + $array = ['products' => ['item' => ['price' => 100]]]; + Arr::set($array, 'products.item.price', 200); + Arr::set($array, 'goods.item.price', 200); + $this->assertSame(['products' => ['item' => ['price' => 200]], 'goods' => ['item' => ['price' => 200]]], $array); + } + + public function testWhere() + { + $array = [100, '200', 300, '400', 500]; + $array = Arr::where($array, function ($value, $key) { + return is_string($value); + }); + $this->assertSame([1 => '200', 3 => '400'], $array); + } + + public function testWhereKey() + { + $array = ['10' => 1, 'foo' => 3, 20 => 2]; + $array = Arr::where($array, function ($value, $key) { + return is_numeric($key); + }); + $this->assertSame(['10' => 1, 20 => 2], $array); + } + + public function testForget() + { + $array = ['products' => ['item' => ['price' => 100]]]; + Arr::forget($array, null); + $this->assertSame(['products' => ['item' => ['price' => 100]]], $array); + $array = ['products' => ['item' => ['price' => 100]]]; + Arr::forget($array, []); + $this->assertSame(['products' => ['item' => ['price' => 100]]], $array); + $array = ['products' => ['item' => ['price' => 100]]]; + Arr::forget($array, 'products.item'); + $this->assertSame(['products' => []], $array); + $array = ['products' => ['item' => ['price' => 100]]]; + Arr::forget($array, 'products.item.price'); + $this->assertSame(['products' => ['item' => []]], $array); + $array = ['products' => ['item' => ['price' => 100]]]; + Arr::forget($array, 'products.final.price'); + $this->assertSame(['products' => ['item' => ['price' => 100]]], $array); + $array = ['shop' => ['cart' => [150 => 0]]]; + Arr::forget($array, 'shop.final.cart'); + $this->assertSame(['shop' => ['cart' => [150 => 0]]], $array); + $array = ['products' => ['item' => ['price' => ['original' => 50, 'taxes' => 60]]]]; + Arr::forget($array, 'products.item.price.taxes'); + $this->assertSame(['products' => ['item' => ['price' => ['original' => 50]]]], $array); + $array = ['products' => ['item' => ['price' => ['original' => 50, 'taxes' => 60]]]]; + Arr::forget($array, 'products.item.final.taxes'); + $this->assertSame(['products' => ['item' => ['price' => ['original' => 50, 'taxes' => 60]]]], $array); + $array = ['products' => ['item' => ['price' => 50], null => 'something']]; + Arr::forget($array, ['products.amount.all', 'products.item.price']); + $this->assertSame(['products' => ['item' => [], null => 'something']], $array); + // Only works on first level keys + $array = ['i@example.com' => 'Joe', 'i@thinkphp.com' => 'Jane']; + Arr::forget($array, 'i@example.com'); + $this->assertSame(['i@thinkphp.com' => 'Jane'], $array); + // Does not work for nested keys + $array = ['emails' => ['i@example.com' => ['name' => 'Joe'], 'jack@localhost' => ['name' => 'Jane']]]; + Arr::forget($array, ['emails.i@example.com', 'emails.jack@localhost']); + $this->assertSame(['emails' => ['i@example.com' => ['name' => 'Joe']]], $array); + } + + public function testWrap() + { + $string = 'a'; + $array = ['a']; + $object = new stdClass(); + $object->value = 'a'; + $this->assertSame(['a'], Arr::wrap($string)); + $this->assertSame($array, Arr::wrap($array)); + $this->assertSame([$object], Arr::wrap($object)); + } +} diff --git a/vendor/topthink/think-helper/tests/CollectionTest.php b/vendor/topthink/think-helper/tests/CollectionTest.php new file mode 100644 index 0000000..b69c175 --- /dev/null +++ b/vendor/topthink/think-helper/tests/CollectionTest.php @@ -0,0 +1,70 @@ + 'Hello']); + $this->assertSame(['name' => 'Hello', 'id' => 1], $c->merge(['id' => 1])->all()); + } + + public function testFirst() + { + $c = new Collection(['name' => 'Hello', 'age' => 25]); + + $this->assertSame('Hello', $c->first()); + } + + public function testLast() + { + $c = new Collection(['name' => 'Hello', 'age' => 25]); + + $this->assertSame(25, $c->last()); + } + + public function testToArray() + { + $c = new Collection(['name' => 'Hello', 'age' => 25]); + + $this->assertSame(['name' => 'Hello', 'age' => 25], $c->toArray()); + } + + public function testToJson() + { + $c = new Collection(['name' => 'Hello', 'age' => 25]); + + $this->assertSame(json_encode(['name' => 'Hello', 'age' => 25]), $c->toJson()); + $this->assertSame(json_encode(['name' => 'Hello', 'age' => 25]), (string) $c); + $this->assertSame(json_encode(['name' => 'Hello', 'age' => 25]), json_encode($c)); + } + + public function testSerialize() + { + $c = new Collection(['name' => 'Hello', 'age' => 25]); + + $sc = serialize($c); + $c = unserialize($sc); + + $this->assertSame(['name' => 'Hello', 'age' => 25], $c->all()); + } + + public function testGetIterator() + { + $c = new Collection(['name' => 'Hello', 'age' => 25]); + + $this->assertInstanceOf(\ArrayIterator::class, $c->getIterator()); + + $this->assertSame(['name' => 'Hello', 'age' => 25], $c->getIterator()->getArrayCopy()); + } + + public function testCount() + { + $c = new Collection(['name' => 'Hello', 'age' => 25]); + + $this->assertCount(2, $c); + } +} diff --git a/vendor/topthink/think-helper/tests/StrTest.php b/vendor/topthink/think-helper/tests/StrTest.php new file mode 100644 index 0000000..813ad4c --- /dev/null +++ b/vendor/topthink/think-helper/tests/StrTest.php @@ -0,0 +1,59 @@ +assertSame('fooBar', Str::camel('FooBar')); + $this->assertSame('fooBar', Str::camel('FooBar')); + $this->assertSame('fooBar', Str::camel('foo_bar')); + $this->assertSame('fooBar', Str::camel('_foo_bar')); + $this->assertSame('fooBar', Str::camel('_foo_bar_')); + } + + public function testStudly() + { + $this->assertSame('FooBar', Str::studly('fooBar')); + $this->assertSame('FooBar', Str::studly('_foo_bar')); + $this->assertSame('FooBar', Str::studly('_foo_bar_')); + $this->assertSame('FooBar', Str::studly('_foo_bar_')); + } + + public function testSnake() + { + $this->assertSame('think_p_h_p_framework', Str::snake('ThinkPHPFramework')); + $this->assertSame('think_php_framework', Str::snake('ThinkPhpFramework')); + $this->assertSame('think php framework', Str::snake('ThinkPhpFramework', ' ')); + $this->assertSame('think_php_framework', Str::snake('Think Php Framework')); + $this->assertSame('think_php_framework', Str::snake('Think Php Framework ')); + // ensure cache keys don't overlap + $this->assertSame('think__php__framework', Str::snake('ThinkPhpFramework', '__')); + $this->assertSame('think_php_framework_', Str::snake('ThinkPhpFramework_', '_')); + $this->assertSame('think_php_framework', Str::snake('think php Framework')); + $this->assertSame('think_php_frame_work', Str::snake('think php FrameWork')); + // prevent breaking changes + $this->assertSame('foo-bar', Str::snake('foo-bar')); + $this->assertSame('foo-_bar', Str::snake('Foo-Bar')); + $this->assertSame('foo__bar', Str::snake('Foo_Bar')); + $this->assertSame('żółtałódka', Str::snake('ŻółtaŁódka')); + } + + public function testTitle() + { + $this->assertSame('Welcome Back', Str::title('welcome back')); + } + + public function testRandom() + { + $this->assertIsString(Str::random(10)); + } + + public function testUpper() + { + $this->assertSame('USERNAME', Str::upper('username')); + $this->assertSame('USERNAME', Str::upper('userNaMe')); + } +} diff --git a/vendor/topthink/think-helper/tests/TestCase.php b/vendor/topthink/think-helper/tests/TestCase.php new file mode 100644 index 0000000..87f6cb3 --- /dev/null +++ b/vendor/topthink/think-helper/tests/TestCase.php @@ -0,0 +1,13 @@ + + */ +class TestCase extends BaseTestCase +{ +} diff --git a/vendor/topthink/think-orm/.gitattributes b/vendor/topthink/think-orm/.gitattributes new file mode 100644 index 0000000..36c9cd1 --- /dev/null +++ b/vendor/topthink/think-orm/.gitattributes @@ -0,0 +1,3 @@ +/.github export-ignore +/tests export-ignore +/phpunit.xml export-ignore \ No newline at end of file diff --git a/vendor/topthink/think-orm/.gitignore b/vendor/topthink/think-orm/.gitignore new file mode 100644 index 0000000..82cfc4e --- /dev/null +++ b/vendor/topthink/think-orm/.gitignore @@ -0,0 +1,3 @@ +.idea +composer.lock +vendor diff --git a/vendor/topthink/think-orm/LICENSE b/vendor/topthink/think-orm/LICENSE new file mode 100644 index 0000000..8dada3e --- /dev/null +++ b/vendor/topthink/think-orm/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/topthink/think-orm/README.md b/vendor/topthink/think-orm/README.md new file mode 100644 index 0000000..c65afe9 --- /dev/null +++ b/vendor/topthink/think-orm/README.md @@ -0,0 +1,27 @@ +# ThinkORM + +基于PHP7.1+ 和PDO实现的ORM,支持多数据库,2.0版本主要特性包括: + +* 基于PDO和PHP强类型实现 +* 支持原生查询和查询构造器 +* 自动参数绑定和预查询 +* 简洁易用的查询功能 +* 强大灵活的模型用法 +* 支持预载入关联查询和延迟关联查询 +* 支持多数据库及动态切换 +* 支持`MongoDb` +* 支持分布式及事务 +* 支持断点重连 +* 支持`JSON`查询 +* 支持数据库日志 +* 支持`PSR-16`缓存及`PSR-3`日志规范 + + +## 安装 +~~~ +composer require topthink/think-orm +~~~ + +## 文档 + +详细参考 [ThinkORM开发指南](https://www.kancloud.cn/manual/think-orm/content) diff --git a/vendor/topthink/think-orm/src/DbManager.php b/vendor/topthink/think-orm/src/DbManager.php new file mode 100644 index 0000000..1c1314e --- /dev/null +++ b/vendor/topthink/think-orm/src/DbManager.php @@ -0,0 +1,386 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use InvalidArgumentException; +use Psr\Log\LoggerInterface; +use Psr\SimpleCache\CacheInterface; +use think\db\BaseQuery; +use think\db\ConnectionInterface; +use think\db\Query; +use think\db\Raw; + +/** + * Class DbManager + * @package think + * @mixin BaseQuery + * @mixin Query + */ +class DbManager +{ + /** + * 数据库连接实例 + * @var array + */ + protected $instance = []; + + /** + * 数据库配置 + * @var array + */ + protected $config = []; + + /** + * Event对象或者数组 + * @var array|object + */ + protected $event; + + /** + * SQL监听 + * @var array + */ + protected $listen = []; + + /** + * SQL日志 + * @var array + */ + protected $dbLog = []; + + /** + * 查询次数 + * @var int + */ + protected $queryTimes = 0; + + /** + * 查询缓存对象 + * @var CacheInterface + */ + protected $cache; + + /** + * 查询日志对象 + * @var LoggerInterface + */ + protected $log; + + /** + * 架构函数 + * @access public + */ + public function __construct() + { + $this->modelMaker(); + } + + /** + * 注入模型对象 + * @access public + * @return void + */ + protected function modelMaker() + { + Model::setDb($this); + + if (is_object($this->event)) { + Model::setEvent($this->event); + } + + Model::maker(function (Model $model) { + $isAutoWriteTimestamp = $model->getAutoWriteTimestamp(); + + if (is_null($isAutoWriteTimestamp)) { + // 自动写入时间戳 + $model->isAutoWriteTimestamp($this->getConfig('auto_timestamp', true)); + } + + $dateFormat = $model->getDateFormat(); + + if (is_null($dateFormat)) { + // 设置时间戳格式 + $model->setDateFormat($this->getConfig('datetime_format', 'Y-m-d H:i:s')); + } + }); + } + + /** + * 监听SQL + * @access protected + * @return void + */ + public function triggerSql(): void + {} + + /** + * 初始化配置参数 + * @access public + * @param array $config 连接配置 + * @return void + */ + public function setConfig($config): void + { + $this->config = $config; + } + + /** + * 设置缓存对象 + * @access public + * @param CacheInterface $cache 缓存对象 + * @return void + */ + public function setCache(CacheInterface $cache): void + { + $this->cache = $cache; + } + + /** + * 设置日志对象 + * @access public + * @param LoggerInterface $log 日志对象 + * @return void + */ + public function setLog(LoggerInterface $log): void + { + $this->log = $log; + } + + /** + * 记录SQL日志 + * @access protected + * @param string $log SQL日志信息 + * @param string $type 日志类型 + * @return void + */ + public function log(string $log, string $type = 'sql') + { + if ($this->log) { + $this->log->log($type, $log); + } else { + $this->dbLog[$type][] = $log; + } + } + + /** + * 获得查询日志(没有设置日志对象使用) + * @access public + * @param bool $clear 是否清空 + * @return array + */ + public function getDbLog(bool $clear = false): array + { + $logs = $this->dbLog; + if ($clear) { + $this->dbLog = []; + } + + return $logs; + } + + /** + * 获取配置参数 + * @access public + * @param string $name 配置参数 + * @param mixed $default 默认值 + * @return mixed + */ + public function getConfig(string $name = '', $default = null) + { + if ('' === $name) { + return $this->config; + } + + return $this->config[$name] ?? $default; + } + + /** + * 创建/切换数据库连接查询 + * @access public + * @param string|null $name 连接配置标识 + * @param bool $force 强制重新连接 + * @return ConnectionInterface + */ + public function connect(string $name = null, bool $force = false) + { + return $this->instance($name, $force); + } + + /** + * 创建数据库连接实例 + * @access protected + * @param string|null $name 连接标识 + * @param bool $force 强制重新连接 + * @return ConnectionInterface + */ + protected function instance(string $name = null, bool $force = false): ConnectionInterface + { + if (empty($name)) { + $name = $this->getConfig('default', 'mysql'); + } + + if ($force || !isset($this->instance[$name])) { + $this->instance[$name] = $this->createConnection($name); + } + + return $this->instance[$name]; + } + + /** + * 获取连接配置 + * @param string $name + * @return array + */ + protected function getConnectionConfig(string $name): array + { + $connections = $this->getConfig('connections'); + if (!isset($connections[$name])) { + throw new InvalidArgumentException('Undefined db config:' . $name); + } + + return $connections[$name]; + } + + /** + * 创建连接 + * @param $name + * @return ConnectionInterface + */ + protected function createConnection(string $name): ConnectionInterface + { + $config = $this->getConnectionConfig($name); + + $type = !empty($config['type']) ? $config['type'] : 'mysql'; + + if (false !== strpos($type, '\\')) { + $class = $type; + } else { + $class = '\\think\\db\\connector\\' . ucfirst($type); + } + + /** @var ConnectionInterface $connection */ + $connection = new $class($config); + $connection->setDb($this); + + if ($this->cache) { + $connection->setCache($this->cache); + } + + return $connection; + } + + /** + * 使用表达式设置数据 + * @access public + * @param string $value 表达式 + * @return Raw + */ + public function raw(string $value): Raw + { + return new Raw($value); + } + + /** + * 更新查询次数 + * @access public + * @return void + */ + public function updateQueryTimes(): void + { + $this->queryTimes++; + } + + /** + * 重置查询次数 + * @access public + * @return void + */ + public function clearQueryTimes(): void + { + $this->queryTimes = 0; + } + + /** + * 获得查询次数 + * @access public + * @return integer + */ + public function getQueryTimes(): int + { + return $this->queryTimes; + } + + /** + * 监听SQL执行 + * @access public + * @param callable $callback 回调方法 + * @return void + */ + public function listen(callable $callback): void + { + $this->listen[] = $callback; + } + + /** + * 获取监听SQL执行 + * @access public + * @return array + */ + public function getListen(): array + { + return $this->listen; + } + + /** + * 获取所有连接实列 + * @access public + * @return array + */ + public function getInstance(): array + { + return $this->instance; + } + + /** + * 注册回调方法 + * @access public + * @param string $event 事件名 + * @param callable $callback 回调方法 + * @return void + */ + public function event(string $event, callable $callback): void + { + $this->event[$event][] = $callback; + } + + /** + * 触发事件 + * @access public + * @param string $event 事件名 + * @param mixed $params 传入参数 + * @return mixed + */ + public function trigger(string $event, $params = null) + { + if (isset($this->event[$event])) { + foreach ($this->event[$event] as $callback) { + call_user_func_array($callback, [$params]); + } + } + } + + public function __call($method, $args) + { + return call_user_func_array([$this->connect(), $method], $args); + } +} diff --git a/vendor/topthink/think-orm/src/Model.php b/vendor/topthink/think-orm/src/Model.php new file mode 100644 index 0000000..a8332e9 --- /dev/null +++ b/vendor/topthink/think-orm/src/Model.php @@ -0,0 +1,1073 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use ArrayAccess; +use Closure; +use JsonSerializable; +use think\contract\Arrayable; +use think\contract\Jsonable; +use think\db\BaseQuery as Query; + +/** + * Class Model + * @package think + * @mixin Query + * @method void onAfterRead(Model $model) static after_read事件定义 + * @method mixed onBeforeInsert(Model $model) static before_insert事件定义 + * @method void onAfterInsert(Model $model) static after_insert事件定义 + * @method mixed onBeforeUpdate(Model $model) static before_update事件定义 + * @method void onAfterUpdate(Model $model) static after_update事件定义 + * @method mixed onBeforeWrite(Model $model) static before_write事件定义 + * @method void onAfterWrite(Model $model) static after_write事件定义 + * @method mixed onBeforeDelete(Model $model) static before_write事件定义 + * @method void onAfterDelete(Model $model) static after_delete事件定义 + * @method void onBeforeRestore(Model $model) static before_restore事件定义 + * @method void onAfterRestore(Model $model) static after_restore事件定义 + */ +abstract class Model implements JsonSerializable, ArrayAccess, Arrayable, Jsonable +{ + use model\concern\Attribute; + use model\concern\RelationShip; + use model\concern\ModelEvent; + use model\concern\TimeStamp; + use model\concern\Conversion; + + /** + * 数据是否存在 + * @var bool + */ + private $exists = false; + + /** + * 是否强制更新所有数据 + * @var bool + */ + private $force = false; + + /** + * 是否Replace + * @var bool + */ + private $replace = false; + + /** + * 数据表后缀 + * @var string + */ + protected $suffix; + + /** + * 更新条件 + * @var array + */ + private $updateWhere; + + /** + * 数据库配置 + * @var string + */ + protected $connection; + + /** + * 模型名称 + * @var string + */ + protected $name; + + /** + * 主键值 + * @var string + */ + protected $key; + + /** + * 数据表名称 + * @var string + */ + protected $table; + + /** + * 初始化过的模型. + * @var array + */ + protected static $initialized = []; + + /** + * 软删除字段默认值 + * @var mixed + */ + protected $defaultSoftDelete; + + /** + * 全局查询范围 + * @var array + */ + protected $globalScope = []; + + /** + * 延迟保存信息 + * @var bool + */ + private $lazySave = false; + + /** + * Db对象 + * @var DbManager + */ + protected static $db; + + /** + * 容器对象的依赖注入方法 + * @var callable + */ + protected static $invoker; + + /** + * 服务注入 + * @var Closure[] + */ + protected static $maker = []; + + /** + * 方法注入 + * @var Closure[][] + */ + protected static $macro = []; + + /** + * 设置服务注入 + * @access public + * @param Closure $maker + * @return void + */ + public static function maker(Closure $maker) + { + static::$maker[] = $maker; + } + + /** + * 设置方法注入 + * @access public + * @param string $method + * @param Closure $closure + * @return void + */ + public static function macro(string $method, Closure $closure) + { + if (!isset(static::$macro[static::class])) { + static::$macro[static::class] = []; + } + static::$macro[static::class][$method] = $closure; + } + + /** + * 设置Db对象 + * @access public + * @param DbManager $db Db对象 + * @return void + */ + public static function setDb(DbManager $db) + { + self::$db = $db; + } + + /** + * 设置容器对象的依赖注入方法 + * @access public + * @param callable $callable 依赖注入方法 + * @return void + */ + public static function setInvoker(callable $callable): void + { + self::$invoker = $callable; + } + + /** + * 调用反射执行模型方法 支持参数绑定 + * @access public + * @param mixed $method + * @param array $vars 参数 + * @return mixed + */ + public function invoke($method, array $vars = []) + { + if (self::$invoker) { + $call = self::$invoker; + return $call($method instanceof Closure ? $method : Closure::fromCallable([$this, $method]), $vars); + } + + return call_user_func_array($method instanceof Closure ? $method : [$this, $method], $vars); + } + + /** + * 架构函数 + * @access public + * @param array $data 数据 + */ + public function __construct(array $data = []) + { + $this->data = $data; + + if (!empty($this->data)) { + // 废弃字段 + foreach ((array) $this->disuse as $key) { + if (array_key_exists($key, $this->data)) { + unset($this->data[$key]); + } + } + } + + // 记录原始数据 + $this->origin = $this->data; + + if (empty($this->name)) { + // 当前模型名 + $name = str_replace('\\', '/', static::class); + $this->name = basename($name); + } + + if (!empty(static::$maker)) { + foreach (static::$maker as $maker) { + call_user_func($maker, $this); + } + } + + // 执行初始化操作 + $this->initialize(); + } + + /** + * 获取当前模型名称 + * @access public + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * 创建新的模型实例 + * @access public + * @param array $data 数据 + * @param mixed $where 更新条件 + * @param array $options 参数 + * @return Model + */ + public function newInstance(array $data = [], $where = null, array $options = []): Model + { + $model = new static($data); + + if ($this->connection) { + $model->setConnection($this->connection); + } + + if ($this->suffix) { + $model->setSuffix($this->suffix); + } + + if (empty($data)) { + return $model; + } + + $model->exists(true); + + $model->setUpdateWhere($where); + + $model->trigger('AfterRead'); + + return $model; + } + + /** + * 设置模型的更新条件 + * @access protected + * @param mixed $where 更新条件 + * @return void + */ + protected function setUpdateWhere($where): void + { + $this->updateWhere = $where; + } + + /** + * 设置当前模型的数据库连接 + * @access public + * @param string $connection 数据表连接标识 + * @return $this + */ + public function setConnection(string $connection) + { + $this->connection = $connection; + return $this; + } + + /** + * 获取当前模型的数据库连接标识 + * @access public + * @return string + */ + public function getConnection(): string + { + return $this->connection ?: ''; + } + + /** + * 设置当前模型数据表的后缀 + * @access public + * @param string $suffix 数据表后缀 + * @return $this + */ + public function setSuffix(string $suffix) + { + $this->suffix = $suffix; + return $this; + } + + /** + * 获取当前模型的数据表后缀 + * @access public + * @return string + */ + public function getSuffix(): string + { + return $this->suffix ?: ''; + } + + /** + * 获取当前模型的数据库查询对象 + * @access public + * @param array $scope 设置不使用的全局查询范围 + * @return Query + */ + public function db($scope = []): Query + { + /** @var Query $query */ + $query = self::$db->connect($this->connection) + ->name($this->name . $this->suffix) + ->pk($this->pk); + + if (!empty($this->table)) { + $query->table($this->table . $this->suffix); + } + + $query->model($this) + ->json($this->json, $this->jsonAssoc) + ->setFieldType(array_merge($this->schema, $this->jsonType)); + + // 软删除 + if (property_exists($this, 'withTrashed') && !$this->withTrashed) { + $this->withNoTrashed($query); + } + + // 全局作用域 + if (is_array($scope)) { + $globalScope = array_diff($this->globalScope, $scope); + $query->scope($globalScope); + } + + // 返回当前模型的数据库查询对象 + return $query; + } + + /** + * 初始化模型 + * @access private + * @return void + */ + private function initialize(): void + { + if (!isset(static::$initialized[static::class])) { + static::$initialized[static::class] = true; + static::init(); + } + } + + /** + * 初始化处理 + * @access protected + * @return void + */ + protected static function init() + { + } + + protected function checkData(): void + { + } + + protected function checkResult($result): void + { + } + + /** + * 更新是否强制写入数据 而不做比较(亦可用于软删除的强制删除) + * @access public + * @param bool $force + * @return $this + */ + public function force(bool $force = true) + { + $this->force = $force; + return $this; + } + + /** + * 判断force + * @access public + * @return bool + */ + public function isForce(): bool + { + return $this->force; + } + + /** + * 新增数据是否使用Replace + * @access public + * @param bool $replace + * @return $this + */ + public function replace(bool $replace = true) + { + $this->replace = $replace; + return $this; + } + + /** + * 刷新模型数据 + * @access public + * @param bool $relation 是否刷新关联数据 + * @return $this + */ + public function refresh(bool $relation = false) + { + if ($this->exists) { + $this->data = $this->db()->find($this->getKey())->getData(); + $this->origin = $this->data; + $this->get = []; + + if ($relation) { + $this->relation = []; + } + } + + return $this; + } + + /** + * 设置数据是否存在 + * @access public + * @param bool $exists + * @return $this + */ + public function exists(bool $exists = true) + { + $this->exists = $exists; + return $this; + } + + /** + * 判断数据是否存在数据库 + * @access public + * @return bool + */ + public function isExists(): bool + { + return $this->exists; + } + + /** + * 判断模型是否为空 + * @access public + * @return bool + */ + public function isEmpty(): bool + { + return empty($this->data); + } + + /** + * 延迟保存当前数据对象 + * @access public + * @param array|bool $data 数据 + * @return void + */ + public function lazySave($data = []): void + { + if (false === $data) { + $this->lazySave = false; + } else { + if (is_array($data)) { + $this->setAttrs($data); + } + + $this->lazySave = true; + } + } + + /** + * 保存当前数据对象 + * @access public + * @param array $data 数据 + * @param string $sequence 自增序列名 + * @return bool + */ + public function save(array $data = [], string $sequence = null): bool + { + // 数据对象赋值 + $this->setAttrs($data); + + if ($this->isEmpty() || false === $this->trigger('BeforeWrite')) { + return false; + } + + $result = $this->exists ? $this->updateData() : $this->insertData($sequence); + + if (false === $result) { + return false; + } + + // 写入回调 + $this->trigger('AfterWrite'); + + // 重新记录原始数据 + $this->origin = $this->data; + $this->get = []; + $this->lazySave = false; + + return true; + } + + /** + * 检查数据是否允许写入 + * @access protected + * @return array + */ + protected function checkAllowFields(): array + { + // 检测字段 + if (empty($this->field)) { + if (!empty($this->schema)) { + $this->field = array_keys(array_merge($this->schema, $this->jsonType)); + } else { + $query = $this->db(); + $table = $this->table ? $this->table . $this->suffix : $query->getTable(); + + $this->field = $query->getConnection()->getTableFields($table); + } + + return $this->field; + } + + $field = $this->field; + + if ($this->autoWriteTimestamp) { + array_push($field, $this->createTime, $this->updateTime); + } + + if (!empty($this->disuse)) { + // 废弃字段 + $field = array_diff($field, $this->disuse); + } + + return $field; + } + + /** + * 保存写入数据 + * @access protected + * @return bool + */ + protected function updateData(): bool + { + // 事件回调 + if (false === $this->trigger('BeforeUpdate')) { + return false; + } + + $this->checkData(); + + // 获取有更新的数据 + $data = $this->getChangedData(); + + if (empty($data)) { + // 关联更新 + if (!empty($this->relationWrite)) { + $this->autoRelationUpdate(); + } + + return true; + } + + if ($this->autoWriteTimestamp && $this->updateTime) { + // 自动写入更新时间 + $data[$this->updateTime] = $this->autoWriteTimestamp(); + $this->data[$this->updateTime] = $data[$this->updateTime]; + } + + // 检查允许字段 + $allowFields = $this->checkAllowFields(); + + foreach ($this->relationWrite as $name => $val) { + if (!is_array($val)) { + continue; + } + + foreach ($val as $key) { + if (isset($data[$key])) { + unset($data[$key]); + } + } + } + + // 模型更新 + $db = $this->db(); + + $db->transaction(function () use ($data, $allowFields, $db) { + $this->key = null; + $where = $this->getWhere(); + + $result = $db->where($where) + ->strict(false) + ->cache(true) + ->setOption('key', $this->key) + ->field($allowFields) + ->update($data); + + $this->checkResult($result); + + // 关联更新 + if (!empty($this->relationWrite)) { + $this->autoRelationUpdate(); + } + }); + + // 更新回调 + $this->trigger('AfterUpdate'); + + return true; + } + + /** + * 新增写入数据 + * @access protected + * @param string $sequence 自增名 + * @return bool + */ + protected function insertData(string $sequence = null): bool + { + if (false === $this->trigger('BeforeInsert')) { + return false; + } + + $this->checkData(); + $data = $this->data; + + // 时间戳自动写入 + if ($this->autoWriteTimestamp) { + if ($this->createTime && !isset($data[$this->createTime])) { + $data[$this->createTime] = $this->autoWriteTimestamp(); + $this->data[$this->createTime] = $data[$this->createTime]; + } + + if ($this->updateTime && !isset($data[$this->updateTime])) { + $data[$this->updateTime] = $this->autoWriteTimestamp(); + $this->data[$this->updateTime] = $data[$this->updateTime]; + } + } + + // 检查允许字段 + $allowFields = $this->checkAllowFields(); + + $db = $this->db(); + + $db->transaction(function () use ($data, $sequence, $allowFields, $db) { + $result = $db->strict(false) + ->field($allowFields) + ->replace($this->replace) + ->sequence($sequence) + ->insert($data, true); + + // 获取自动增长主键 + if ($result) { + $pk = $this->getPk(); + + if (is_string($pk) && (!isset($this->data[$pk]) || '' == $this->data[$pk])) { + unset($this->get[$pk]); + $this->data[$pk] = $result; + } + } + + // 关联写入 + if (!empty($this->relationWrite)) { + $this->autoRelationInsert(); + } + }); + + // 标记数据已经存在 + $this->exists = true; + $this->origin = $this->data; + + // 新增回调 + $this->trigger('AfterInsert'); + + return true; + } + + /** + * 获取当前的更新条件 + * @access public + * @return mixed + */ + public function getWhere() + { + $pk = $this->getPk(); + + if (is_string($pk) && isset($this->origin[$pk])) { + $where = [[$pk, '=', $this->origin[$pk]]]; + $this->key = $this->origin[$pk]; + } elseif (is_array($pk)) { + foreach ($pk as $field) { + if (isset($this->origin[$field])) { + $where[] = [$field, '=', $this->origin[$field]]; + } + } + } + + if (empty($where)) { + $where = empty($this->updateWhere) ? null : $this->updateWhere; + } + + return $where; + } + + /** + * 保存多个数据到当前数据对象 + * @access public + * @param iterable $dataSet 数据 + * @param boolean $replace 是否自动识别更新和写入 + * @return Collection + * @throws \Exception + */ + public function saveAll(iterable $dataSet, bool $replace = true): Collection + { + $db = $this->db(); + + $result = $db->transaction(function () use ($replace, $dataSet) { + + $pk = $this->getPk(); + + $result = []; + $suffix = $this->getSuffix(); + + foreach ($dataSet as $key => $data) { + if ($replace) { + $exists = true; + foreach ((array) $pk as $field) { + if (!isset($data[$field])) { + $exists = false; + } + } + } + + if ($replace && !empty($exists)) { + $result[$key] = static::update($data, [], [], $suffix); + } else { + $result[$key] = static::create($data, $this->field, $this->replace, $suffix); + } + } + + return $result; + }); + + return $this->toCollection($result); + } + + /** + * 删除当前的记录 + * @access public + * @return bool + */ + public function delete(): bool + { + if (!$this->exists || $this->isEmpty() || false === $this->trigger('BeforeDelete')) { + return false; + } + + // 读取更新条件 + $where = $this->getWhere(); + + $db = $this->db(); + + $db->transaction(function () use ($where, $db) { + // 删除当前模型数据 + $db->where($where)->delete(); + + // 关联删除 + if (!empty($this->relationWrite)) { + $this->autoRelationDelete(); + } + }); + + $this->trigger('AfterDelete'); + + $this->exists = false; + $this->lazySave = false; + + return true; + } + + /** + * 写入数据 + * @access public + * @param array $data 数据数组 + * @param array $allowField 允许字段 + * @param bool $replace 使用Replace + * @param string $suffix 数据表后缀 + * @return static + */ + public static function create(array $data, array $allowField = [], bool $replace = false, string $suffix = ''): Model + { + $model = new static(); + + if (!empty($allowField)) { + $model->allowField($allowField); + } + + if (!empty($suffix)) { + $model->setSuffix($suffix); + } + + $model->replace($replace)->save($data); + + return $model; + } + + /** + * 更新数据 + * @access public + * @param array $data 数据数组 + * @param mixed $where 更新条件 + * @param array $allowField 允许字段 + * @param string $suffix 数据表后缀 + * @return static + */ + public static function update(array $data, $where = [], array $allowField = [], string $suffix = '') + { + $model = new static(); + + if (!empty($allowField)) { + $model->allowField($allowField); + } + + if (!empty($where)) { + $model->setUpdateWhere($where); + } + + if (!empty($suffix)) { + $model->setSuffix($suffix); + } + + $model->exists(true)->save($data); + + return $model; + } + + /** + * 删除记录 + * @access public + * @param mixed $data 主键列表 支持闭包查询条件 + * @param bool $force 是否强制删除 + * @return bool + */ + public static function destroy($data, bool $force = false): bool + { + if (empty($data) && 0 !== $data) { + return false; + } + + $model = new static(); + + $query = $model->db(); + + if (is_array($data) && key($data) !== 0) { + $query->where($data); + $data = null; + } elseif ($data instanceof \Closure) { + $data($query); + $data = null; + } + + $resultSet = $query->select($data); + + foreach ($resultSet as $result) { + $result->force($force)->delete(); + } + + return true; + } + + /** + * 解序列化后处理 + */ + public function __wakeup() + { + $this->initialize(); + } + + /** + * 修改器 设置数据对象的值 + * @access public + * @param string $name 名称 + * @param mixed $value 值 + * @return void + */ + public function __set(string $name, $value): void + { + $this->setAttr($name, $value); + } + + /** + * 获取器 获取数据对象的值 + * @access public + * @param string $name 名称 + * @return mixed + */ + public function __get(string $name) + { + return $this->getAttr($name); + } + + /** + * 检测数据对象的值 + * @access public + * @param string $name 名称 + * @return bool + */ + public function __isset(string $name): bool + { + return !is_null($this->getAttr($name)); + } + + /** + * 销毁数据对象的值 + * @access public + * @param string $name 名称 + * @return void + */ + public function __unset(string $name): void + { + unset($this->data[$name], + $this->get[$name], + $this->relation[$name]); + } + + // ArrayAccess + #[\ReturnTypeWillChange] + public function offsetSet($name, $value) + { + $this->setAttr($name, $value); + } + + #[\ReturnTypeWillChange] + public function offsetExists($name): bool + { + return $this->__isset($name); + } + + #[\ReturnTypeWillChange] + public function offsetUnset($name) + { + $this->__unset($name); + } + + #[\ReturnTypeWillChange] + public function offsetGet($name) + { + return $this->getAttr($name); + } + + /** + * 设置不使用的全局查询范围 + * @access public + * @param array $scope 不启用的全局查询范围 + * @return Query + */ + public static function withoutGlobalScope(array $scope = null) + { + $model = new static(); + + return $model->db($scope); + } + + /** + * 切换后缀进行查询 + * @access public + * @param string $suffix 切换的表后缀 + * @return Model + */ + public static function suffix(string $suffix) + { + $model = new static(); + $model->setSuffix($suffix); + + return $model; + } + + /** + * 切换数据库连接进行查询 + * @access public + * @param string $connection 数据库连接标识 + * @return Model + */ + public static function connect(string $connection) + { + $model = new static(); + $model->setConnection($connection); + + return $model; + } + + public function __call($method, $args) + { + if (isset(static::$macro[static::class][$method])) { + return call_user_func_array(static::$macro[static::class][$method]->bindTo($this, static::class), $args); + } + + return call_user_func_array([$this->db(), $method], $args); + } + + public static function __callStatic($method, $args) + { + if (isset(static::$macro[static::class][$method])) { + return call_user_func_array(static::$macro[static::class][$method]->bindTo(null, static::class), $args); + } + + $model = new static(); + + return call_user_func_array([$model->db(), $method], $args); + } + + /** + * 析构方法 + * @access public + */ + public function __destruct() + { + if ($this->lazySave) { + $this->save(); + } + } +} diff --git a/vendor/topthink/think-orm/src/Paginator.php b/vendor/topthink/think-orm/src/Paginator.php new file mode 100644 index 0000000..eace6ba --- /dev/null +++ b/vendor/topthink/think-orm/src/Paginator.php @@ -0,0 +1,537 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use ArrayAccess; +use ArrayIterator; +use Closure; +use Countable; +use DomainException; +use IteratorAggregate; +use JsonSerializable; +use think\paginator\driver\Bootstrap; +use Traversable; + +/** + * 分页基础类 + * @mixin Collection + */ +abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable +{ + /** + * 是否简洁模式 + * @var bool + */ + protected $simple = false; + + /** + * 数据集 + * @var Collection + */ + protected $items; + + /** + * 当前页 + * @var int + */ + protected $currentPage; + + /** + * 最后一页 + * @var int + */ + protected $lastPage; + + /** + * 数据总数 + * @var integer|null + */ + protected $total; + + /** + * 每页数量 + * @var int + */ + protected $listRows; + + /** + * 是否有下一页 + * @var bool + */ + protected $hasMore; + + /** + * 分页配置 + * @var array + */ + protected $options = [ + 'var_page' => 'page', + 'path' => '/', + 'query' => [], + 'fragment' => '', + ]; + + /** + * 获取当前页码 + * @var Closure + */ + protected static $currentPageResolver; + + /** + * 获取当前路径 + * @var Closure + */ + protected static $currentPathResolver; + + /** + * @var Closure + */ + protected static $maker; + + public function __construct($items, int $listRows, int $currentPage = 1, int $total = null, bool $simple = false, array $options = []) + { + $this->options = array_merge($this->options, $options); + + $this->options['path'] = '/' != $this->options['path'] ? rtrim($this->options['path'], '/') : $this->options['path']; + + $this->simple = $simple; + $this->listRows = $listRows; + + if (!$items instanceof Collection) { + $items = Collection::make($items); + } + + if ($simple) { + $this->currentPage = $this->setCurrentPage($currentPage); + $this->hasMore = count($items) > ($this->listRows); + $items = $items->slice(0, $this->listRows); + } else { + $this->total = $total; + $this->lastPage = (int) ceil($total / $listRows); + $this->currentPage = $this->setCurrentPage($currentPage); + $this->hasMore = $this->currentPage < $this->lastPage; + } + $this->items = $items; + } + + /** + * @access public + * @param mixed $items + * @param int $listRows + * @param int $currentPage + * @param int $total + * @param bool $simple + * @param array $options + * @return Paginator + */ + public static function make($items, int $listRows, int $currentPage = 1, int $total = null, bool $simple = false, array $options = []) + { + if (isset(static::$maker)) { + return call_user_func(static::$maker, $items, $listRows, $currentPage, $total, $simple, $options); + } + + return new Bootstrap($items, $listRows, $currentPage, $total, $simple, $options); + } + + public static function maker(Closure $resolver) + { + static::$maker = $resolver; + } + + protected function setCurrentPage(int $currentPage): int + { + if (!$this->simple && $currentPage > $this->lastPage) { + return $this->lastPage > 0 ? $this->lastPage : 1; + } + + return $currentPage; + } + + /** + * 获取页码对应的链接 + * + * @access protected + * @param int $page + * @return string + */ + protected function url(int $page): string + { + if ($page <= 0) { + $page = 1; + } + + if (strpos($this->options['path'], '[PAGE]') === false) { + $parameters = [$this->options['var_page'] => $page]; + $path = $this->options['path']; + } else { + $parameters = []; + $path = str_replace('[PAGE]', (string) $page, $this->options['path']); + } + + if (count($this->options['query']) > 0) { + $parameters = array_merge($this->options['query'], $parameters); + } + + $url = $path; + if (!empty($parameters)) { + $url .= '?' . http_build_query($parameters, '', '&'); + } + + return $url . $this->buildFragment(); + } + + /** + * 自动获取当前页码 + * @access public + * @param string $varPage + * @param int $default + * @return int + */ + public static function getCurrentPage(string $varPage = 'page', int $default = 1): int + { + if (isset(static::$currentPageResolver)) { + return call_user_func(static::$currentPageResolver, $varPage); + } + + return $default; + } + + /** + * 设置获取当前页码闭包 + * @param Closure $resolver + */ + public static function currentPageResolver(Closure $resolver) + { + static::$currentPageResolver = $resolver; + } + + /** + * 自动获取当前的path + * @access public + * @param string $default + * @return string + */ + public static function getCurrentPath($default = '/'): string + { + if (isset(static::$currentPathResolver)) { + return call_user_func(static::$currentPathResolver); + } + + return $default; + } + + /** + * 设置获取当前路径闭包 + * @param Closure $resolver + */ + public static function currentPathResolver(Closure $resolver) + { + static::$currentPathResolver = $resolver; + } + + /** + * 获取数据总条数 + * @return int + */ + public function total(): int + { + if ($this->simple) { + throw new DomainException('not support total'); + } + + return $this->total; + } + + /** + * 获取每页数量 + * @return int + */ + public function listRows(): int + { + return $this->listRows; + } + + /** + * 获取当前页页码 + * @return int + */ + public function currentPage(): int + { + return $this->currentPage; + } + + /** + * 获取最后一页页码 + * @return int + */ + public function lastPage(): int + { + if ($this->simple) { + throw new DomainException('not support last'); + } + + return $this->lastPage; + } + + /** + * 数据是否足够分页 + * @access public + * @return bool + */ + public function hasPages(): bool + { + return !(1 == $this->currentPage && !$this->hasMore); + } + + /** + * 创建一组分页链接 + * + * @access public + * @param int $start + * @param int $end + * @return array + */ + public function getUrlRange(int $start, int $end): array + { + $urls = []; + + for ($page = $start; $page <= $end; $page++) { + $urls[$page] = $this->url($page); + } + + return $urls; + } + + /** + * 设置URL锚点 + * + * @access public + * @param string|null $fragment + * @return $this + */ + public function fragment(string $fragment = null) + { + $this->options['fragment'] = $fragment; + + return $this; + } + + /** + * 添加URL参数 + * + * @access public + * @param array $append + * @return $this + */ + public function appends(array $append) + { + foreach ($append as $k => $v) { + if ($k !== $this->options['var_page']) { + $this->options['query'][$k] = $v; + } + } + + return $this; + } + + /** + * 构造锚点字符串 + * + * @access public + * @return string + */ + protected function buildFragment(): string + { + return $this->options['fragment'] ? '#' . $this->options['fragment'] : ''; + } + + /** + * 渲染分页html + * @access public + * @return mixed + */ + abstract public function render(); + + public function items() + { + return $this->items->all(); + } + + /** + * 获取数据集 + * + * @return Collection|\think\model\Collection + */ + public function getCollection() + { + return $this->items; + } + + /** + * 设置数据集 + * + * @param Collection $items + * @return $this + */ + public function setCollection(Collection $items) + { + $this->items = $items; + + return $this; + } + + public function isEmpty(): bool + { + return $this->items->isEmpty(); + } + + /** + * 给每个元素执行个回调 + * + * @access public + * @param callable $callback + * @return $this + */ + public function each(callable $callback) + { + foreach ($this->items as $key => $item) { + $result = $callback($item, $key); + + if (false === $result) { + break; + } elseif (!is_object($item)) { + $this->items[$key] = $result; + } + } + + return $this; + } + + /** + * Retrieve an external iterator + * @access public + * @return Traversable An instance of an object implementing Iterator or + * Traversable + */ + #[\ReturnTypeWillChange] + public function getIterator(): Traversable + { + return new ArrayIterator($this->items->all()); + } + + /** + * Whether a offset exists + * @access public + * @param mixed $offset + * @return bool + */ + #[\ReturnTypeWillChange] + public function offsetExists($offset): bool + { + return $this->items->offsetExists($offset); + } + + /** + * Offset to retrieve + * @access public + * @param mixed $offset + * @return mixed + */ + #[\ReturnTypeWillChange] + public function offsetGet($offset) + { + return $this->items->offsetGet($offset); + } + + /** + * Offset to set + * @access public + * @param mixed $offset + * @param mixed $value + */ + #[\ReturnTypeWillChange] + public function offsetSet($offset, $value) + { + $this->items->offsetSet($offset, $value); + } + + /** + * Offset to unset + * @access public + * @param mixed $offset + * @return void + * @since 5.0.0 + */ + #[\ReturnTypeWillChange] + public function offsetUnset($offset) + { + $this->items->offsetUnset($offset); + } + + /** + * 统计数据集条数 + * @return int + */ + public function count(): int + { + return $this->items->count(); + } + + public function __toString() + { + return (string) $this->render(); + } + + /** + * 转换为数组 + * @return array + */ + public function toArray(): array + { + try { + $total = $this->total(); + } catch (DomainException $e) { + $total = null; + } + + return [ + 'total' => $total, + 'per_page' => $this->listRows(), + 'current_page' => $this->currentPage(), + 'last_page' => $this->lastPage, + 'data' => $this->items->toArray(), + ]; + } + + /** + * Specify data which should be serialized to JSON + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return $this->toArray(); + } + + public function __call($name, $arguments) + { + $result = call_user_func_array([$this->items, $name], $arguments); + + if ($result instanceof Collection) { + $this->items = $result; + return $this; + } + + return $result; + } + +} diff --git a/vendor/topthink/think-orm/src/db/BaseQuery.php b/vendor/topthink/think-orm/src/db/BaseQuery.php new file mode 100644 index 0000000..60aa21e --- /dev/null +++ b/vendor/topthink/think-orm/src/db/BaseQuery.php @@ -0,0 +1,1344 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db; + +use think\Collection; +use think\db\exception\DataNotFoundException; +use think\db\exception\DbException as Exception; +use think\db\exception\ModelNotFoundException; +use think\helper\Str; +use think\Model; +use think\Paginator; + +/** + * 数据查询基础类 + */ +abstract class BaseQuery +{ + use concern\TimeFieldQuery; + use concern\AggregateQuery; + use concern\ModelRelationQuery; + use concern\ResultOperation; + use concern\Transaction; + use concern\WhereQuery; + + /** + * 当前数据库连接对象 + * @var Connection + */ + protected $connection; + + /** + * 当前数据表名称(不含前缀) + * @var string + */ + protected $name = ''; + + /** + * 当前数据表主键 + * @var string|array + */ + protected $pk; + + /** + * 当前数据表自增主键 + * @var string + */ + protected $autoinc; + + /** + * 当前数据表前缀 + * @var string + */ + protected $prefix = ''; + + /** + * 当前查询参数 + * @var array + */ + protected $options = []; + + /** + * 架构函数 + * @access public + * @param ConnectionInterface $connection 数据库连接对象 + */ + public function __construct(ConnectionInterface $connection) + { + $this->connection = $connection; + + $this->prefix = $this->connection->getConfig('prefix'); + } + + /** + * 利用__call方法实现一些特殊的Model方法 + * @access public + * @param string $method 方法名称 + * @param array $args 调用参数 + * @return mixed + * @throws Exception + */ + public function __call(string $method, array $args) + { + if (strtolower(substr($method, 0, 5)) == 'getby') { + // 根据某个字段获取记录 + $field = Str::snake(substr($method, 5)); + return $this->where($field, '=', $args[0])->find(); + } elseif (strtolower(substr($method, 0, 10)) == 'getfieldby') { + // 根据某个字段获取记录的某个值 + $name = Str::snake(substr($method, 10)); + return $this->where($name, '=', $args[0])->value($args[1]); + } elseif (strtolower(substr($method, 0, 7)) == 'whereor') { + $name = Str::snake(substr($method, 7)); + array_unshift($args, $name); + return call_user_func_array([$this, 'whereOr'], $args); + } elseif (strtolower(substr($method, 0, 5)) == 'where') { + $name = Str::snake(substr($method, 5)); + array_unshift($args, $name); + return call_user_func_array([$this, 'where'], $args); + } elseif ($this->model && method_exists($this->model, 'scope' . $method)) { + // 动态调用命名范围 + $method = 'scope' . $method; + array_unshift($args, $this); + + call_user_func_array([$this->model, $method], $args); + return $this; + } else { + throw new Exception('method not exist:' . static::class . '->' . $method); + } + } + + /** + * 创建一个新的查询对象 + * @access public + * @return BaseQuery + */ + public function newQuery(): BaseQuery + { + $query = new static($this->connection); + + if ($this->model) { + $query->model($this->model); + } + + if (isset($this->options['table'])) { + $query->table($this->options['table']); + } else { + $query->name($this->name); + } + + if (!empty($this->options['json'])) { + $query->json($this->options['json'], $this->options['json_assoc']); + } + + if (isset($this->options['field_type'])) { + $query->setFieldType($this->options['field_type']); + } + + return $query; + } + + /** + * 获取当前的数据库Connection对象 + * @access public + * @return ConnectionInterface + */ + public function getConnection() + { + return $this->connection; + } + + /** + * 指定当前数据表名(不含前缀) + * @access public + * @param string $name 不含前缀的数据表名字 + * @return $this + */ + public function name(string $name) + { + $this->name = $name; + return $this; + } + + /** + * 获取当前的数据表名称 + * @access public + * @return string + */ + public function getName(): string + { + return $this->name ?: $this->model->getName(); + } + + /** + * 获取数据库的配置参数 + * @access public + * @param string $name 参数名称 + * @return mixed + */ + public function getConfig(string $name = '') + { + return $this->connection->getConfig($name); + } + + /** + * 得到当前或者指定名称的数据表 + * @access public + * @param string $name 不含前缀的数据表名字 + * @return mixed + */ + public function getTable(string $name = '') + { + if (empty($name) && isset($this->options['table'])) { + return $this->options['table']; + } + + $name = $name ?: $this->name; + + return $this->prefix . Str::snake($name); + } + + /** + * 设置字段类型信息 + * @access public + * @param array $type 字段类型信息 + * @return $this + */ + public function setFieldType(array $type) + { + $this->options['field_type'] = $type; + return $this; + } + + /** + * 获取最近一次查询的sql语句 + * @access public + * @return string + */ + public function getLastSql(): string + { + return $this->connection->getLastSql(); + } + + /** + * 获取返回或者影响的记录数 + * @access public + * @return integer + */ + public function getNumRows(): int + { + return $this->connection->getNumRows(); + } + + /** + * 获取最近插入的ID + * @access public + * @param string $sequence 自增序列名 + * @return mixed + */ + public function getLastInsID(string $sequence = null) + { + return $this->connection->getLastInsID($this, $sequence); + } + + /** + * 得到某个字段的值 + * @access public + * @param string $field 字段名 + * @param mixed $default 默认值 + * @return mixed + */ + public function value(string $field, $default = null) + { + $result = $this->connection->value($this, $field, $default); + + $array[$field] = $result; + $this->result($array); + + return $array[$field]; + } + + /** + * 得到某个列的数组 + * @access public + * @param string|array $field 字段名 多个字段用逗号分隔 + * @param string $key 索引 + * @return array + */ + public function column($field, string $key = ''): array + { + $result = $this->connection->column($this, $field, $key); + + if (count($result) != count($result, 1)) { + $this->resultSet($result, false); + } + + return $result; + } + + /** + * 查询SQL组装 union + * @access public + * @param mixed $union UNION + * @param boolean $all 是否适用UNION ALL + * @return $this + */ + public function union($union, bool $all = false) + { + $this->options['union']['type'] = $all ? 'UNION ALL' : 'UNION'; + + if (is_array($union)) { + $this->options['union'] = array_merge($this->options['union'], $union); + } else { + $this->options['union'][] = $union; + } + + return $this; + } + + /** + * 查询SQL组装 union all + * @access public + * @param mixed $union UNION数据 + * @return $this + */ + public function unionAll($union) + { + return $this->union($union, true); + } + + /** + * 指定查询字段 + * @access public + * @param mixed $field 字段信息 + * @return $this + */ + public function field($field) + { + if (empty($field)) { + return $this; + } elseif ($field instanceof Raw) { + $this->options['field'][] = $field; + return $this; + } + + if (is_string($field)) { + if (preg_match('/[\<\'\"\(]/', $field)) { + return $this->fieldRaw($field); + } + + $field = array_map('trim', explode(',', $field)); + } + + if (true === $field) { + // 获取全部字段 + $fields = $this->getTableFields(); + $field = $fields ?: ['*']; + } + + if (isset($this->options['field'])) { + $field = array_merge((array) $this->options['field'], $field); + } + + $this->options['field'] = array_unique($field, SORT_REGULAR); + + return $this; + } + + /** + * 指定要排除的查询字段 + * @access public + * @param array|string $field 要排除的字段 + * @return $this + */ + public function withoutField($field) + { + if (empty($field)) { + return $this; + } + + if (is_string($field)) { + $field = array_map('trim', explode(',', $field)); + } + + // 字段排除 + $fields = $this->getTableFields(); + $field = $fields ? array_diff($fields, $field) : $field; + + if (isset($this->options['field'])) { + $field = array_merge((array) $this->options['field'], $field); + } + + $this->options['field'] = array_unique($field, SORT_REGULAR); + + return $this; + } + + /** + * 指定其它数据表的查询字段 + * @access public + * @param mixed $field 字段信息 + * @param string $tableName 数据表名 + * @param string $prefix 字段前缀 + * @param string $alias 别名前缀 + * @return $this + */ + public function tableField($field, string $tableName, string $prefix = '', string $alias = '') + { + if (empty($field)) { + return $this; + } + + if (is_string($field)) { + $field = array_map('trim', explode(',', $field)); + } + + if (true === $field) { + // 获取全部字段 + $fields = $this->getTableFields($tableName); + $field = $fields ?: ['*']; + } + + // 添加统一的前缀 + $prefix = $prefix ?: $tableName; + foreach ($field as $key => &$val) { + if (is_numeric($key) && $alias) { + $field[$prefix . '.' . $val] = $alias . $val; + unset($field[$key]); + } elseif (is_numeric($key)) { + $val = $prefix . '.' . $val; + } + } + + if (isset($this->options['field'])) { + $field = array_merge((array) $this->options['field'], $field); + } + + $this->options['field'] = array_unique($field, SORT_REGULAR); + + return $this; + } + + /** + * 设置数据 + * @access public + * @param array $data 数据 + * @return $this + */ + public function data(array $data) + { + $this->options['data'] = $data; + + return $this; + } + + /** + * 去除查询参数 + * @access public + * @param string $option 参数名 留空去除所有参数 + * @return $this + */ + public function removeOption(string $option = '') + { + if ('' === $option) { + $this->options = []; + $this->bind = []; + } elseif (isset($this->options[$option])) { + unset($this->options[$option]); + } + + return $this; + } + + /** + * 指定查询数量 + * @access public + * @param int $offset 起始位置 + * @param int $length 查询数量 + * @return $this + */ + public function limit(int $offset, int $length = null) + { + $this->options['limit'] = $offset . ($length ? ',' . $length : ''); + + return $this; + } + + /** + * 指定分页 + * @access public + * @param int $page 页数 + * @param int $listRows 每页数量 + * @return $this + */ + public function page(int $page, int $listRows = null) + { + $this->options['page'] = [$page, $listRows]; + + return $this; + } + + /** + * 指定当前操作的数据表 + * @access public + * @param mixed $table 表名 + * @return $this + */ + public function table($table) + { + if (is_string($table)) { + if (strpos($table, ')')) { + // 子查询 + } elseif (false === strpos($table, ',')) { + if (strpos($table, ' ')) { + [$item, $alias] = explode(' ', $table); + $table = []; + $this->alias([$item => $alias]); + $table[$item] = $alias; + } + } else { + $tables = explode(',', $table); + $table = []; + + foreach ($tables as $item) { + $item = trim($item); + if (strpos($item, ' ')) { + [$item, $alias] = explode(' ', $item); + $this->alias([$item => $alias]); + $table[$item] = $alias; + } else { + $table[] = $item; + } + } + } + } elseif (is_array($table)) { + $tables = $table; + $table = []; + + foreach ($tables as $key => $val) { + if (is_numeric($key)) { + $table[] = $val; + } else { + $this->alias([$key => $val]); + $table[$key] = $val; + } + } + } + + $this->options['table'] = $table; + + return $this; + } + + /** + * 指定排序 order('id','desc') 或者 order(['id'=>'desc','create_time'=>'desc']) + * @access public + * @param string|array|Raw $field 排序字段 + * @param string $order 排序 + * @return $this + */ + public function order($field, string $order = '') + { + if (empty($field)) { + return $this; + } elseif ($field instanceof Raw) { + $this->options['order'][] = $field; + return $this; + } + + if (is_string($field)) { + if (!empty($this->options['via'])) { + $field = $this->options['via'] . '.' . $field; + } + if (strpos($field, ',')) { + $field = array_map('trim', explode(',', $field)); + } else { + $field = empty($order) ? $field : [$field => $order]; + } + } elseif (!empty($this->options['via'])) { + foreach ($field as $key => $val) { + if (is_numeric($key)) { + $field[$key] = $this->options['via'] . '.' . $val; + } else { + $field[$this->options['via'] . '.' . $key] = $val; + unset($field[$key]); + } + } + } + + if (!isset($this->options['order'])) { + $this->options['order'] = []; + } + + if (is_array($field)) { + $this->options['order'] = array_merge($this->options['order'], $field); + } else { + $this->options['order'][] = $field; + } + + return $this; + } + + /** + * 分页查询 + * @access public + * @param int|array $listRows 每页数量 数组表示配置参数 + * @param int|bool $simple 是否简洁模式或者总记录数 + * @return Paginator + * @throws Exception + */ + public function paginate($listRows = null, $simple = false): Paginator + { + if (is_int($simple)) { + $total = $simple; + $simple = false; + } + + $defaultConfig = [ + 'query' => [], //url额外参数 + 'fragment' => '', //url锚点 + 'var_page' => 'page', //分页变量 + 'list_rows' => 15, //每页数量 + ]; + + if (is_array($listRows)) { + $config = array_merge($defaultConfig, $listRows); + $listRows = intval($config['list_rows']); + } else { + $config = $defaultConfig; + $listRows = intval($listRows ?: $config['list_rows']); + } + + $page = isset($config['page']) ? (int) $config['page'] : Paginator::getCurrentPage($config['var_page']); + + $page = $page < 1 ? 1 : $page; + + $config['path'] = $config['path'] ?? Paginator::getCurrentPath(); + + if (!isset($total) && !$simple) { + $options = $this->getOptions(); + + unset($this->options['order'], $this->options['cache'], $this->options['limit'], $this->options['page'], $this->options['field']); + + $bind = $this->bind; + $total = $this->count(); + if ($total > 0) { + $results = $this->options($options)->bind($bind)->page($page, $listRows)->select(); + } else { + if (!empty($this->model)) { + $results = new \think\model\Collection([]); + } else { + $results = new \think\Collection([]); + } + } + } elseif ($simple) { + $results = $this->limit(($page - 1) * $listRows, $listRows + 1)->select(); + $total = null; + } else { + $results = $this->page($page, $listRows)->select(); + } + + $this->removeOption('limit'); + $this->removeOption('page'); + + return Paginator::make($results, $listRows, $page, $total, $simple, $config); + } + + /** + * 根据数字类型字段进行分页查询(大数据) + * @access public + * @param int|array $listRows 每页数量或者分页配置 + * @param string $key 分页索引键 + * @param string $sort 索引键排序 asc|desc + * @return Paginator + * @throws Exception + */ + public function paginateX($listRows = null, string $key = null, string $sort = null): Paginator + { + $defaultConfig = [ + 'query' => [], //url额外参数 + 'fragment' => '', //url锚点 + 'var_page' => 'page', //分页变量 + 'list_rows' => 15, //每页数量 + ]; + + $config = is_array($listRows) ? array_merge($defaultConfig, $listRows) : $defaultConfig; + $listRows = is_int($listRows) ? $listRows : (int) $config['list_rows']; + $page = isset($config['page']) ? (int) $config['page'] : Paginator::getCurrentPage($config['var_page']); + $page = $page < 1 ? 1 : $page; + + $config['path'] = $config['path'] ?? Paginator::getCurrentPath(); + + $key = $key ?: $this->getPk(); + $options = $this->getOptions(); + + if (is_null($sort)) { + $order = $options['order'] ?? ''; + if (!empty($order)) { + $sort = $order[$key] ?? 'desc'; + } else { + $this->order($key, 'desc'); + $sort = 'desc'; + } + } else { + $this->order($key, $sort); + } + + $newOption = $options; + unset($newOption['field'], $newOption['page']); + + $data = $this->newQuery() + ->options($newOption) + ->field($key) + ->where(true) + ->order($key, $sort) + ->limit(1) + ->find(); + + $result = $data[$key] ?? 0; + + if (is_numeric($result)) { + $lastId = 'asc' == $sort ? ($result - 1) + ($page - 1) * $listRows : ($result + 1) - ($page - 1) * $listRows; + } else { + throw new Exception('not support type'); + } + + $results = $this->when($lastId, function ($query) use ($key, $sort, $lastId) { + $query->where($key, 'asc' == $sort ? '>' : '<', $lastId); + }) + ->limit($listRows) + ->select(); + + $this->options($options); + + return Paginator::make($results, $listRows, $page, null, true, $config); + } + + /** + * 根据最后ID查询更多N个数据 + * @access public + * @param int $limit LIMIT + * @param int|string $lastId LastId + * @param string $key 分页索引键 默认为主键 + * @param string $sort 索引键排序 asc|desc + * @return array + * @throws Exception + */ + public function more(int $limit, $lastId = null, string $key = null, string $sort = null): array + { + $key = $key ?: $this->getPk(); + + if (is_null($sort)) { + $order = $this->getOptions('order'); + if (!empty($order)) { + $sort = $order[$key] ?? 'desc'; + } else { + $this->order($key, 'desc'); + $sort = 'desc'; + } + } else { + $this->order($key, $sort); + } + + $result = $this->when($lastId, function ($query) use ($key, $sort, $lastId) { + $query->where($key, 'asc' == $sort ? '>' : '<', $lastId); + })->limit($limit)->select(); + + $last = $result->last(); + + $result->first(); + + return [ + 'data' => $result, + 'lastId' => $last ? $last[$key] : null, + ]; + } + + /** + * 查询缓存 数据为空不缓存 + * @access public + * @param mixed $key 缓存key + * @param integer|\DateTime $expire 缓存有效期 + * @param string|array $tag 缓存标签 + * @return $this + */ + public function cache($key = true, $expire = null, $tag = null) + { + if (false === $key || !$this->getConnection()->getCache()) { + return $this; + } + + if ($key instanceof \DateTimeInterface || $key instanceof \DateInterval || (is_int($key) && is_null($expire))) { + $expire = $key; + $key = true; + } + + $this->options['cache'] = [$key, $expire, $tag ?: $this->getTable()]; + + return $this; + } + + /** + * 查询缓存 允许缓存空数据 + * @access public + * @param mixed $key 缓存key + * @param integer|\DateTime $expire 缓存有效期 + * @param string|array $tag 缓存标签 + * @return $this + */ + public function cacheAlways($key = true, $expire = null, $tag = null) + { + $this->options['cache_always'] = true; + return $this->cache($key, $expire, $tag); + } + + /** + * 强制更新缓存 + * + * @param mixed $key 缓存key + * @param int|\DateTime $expire 缓存有效期 + * @param string|array $tag 缓存标签 + * + * @return $this + */ + public function cacheForce($key = true, $expire = null, $tag = null) + { + $this->options['force_cache'] = true; + return $this->cache($key, $expire, $tag); + } + + /** + * 指定查询lock + * @access public + * @param bool|string $lock 是否lock + * @return $this + */ + public function lock($lock = false) + { + $this->options['lock'] = $lock; + + if ($lock) { + $this->options['master'] = true; + } + + return $this; + } + + /** + * 指定数据表别名 + * @access public + * @param array|string $alias 数据表别名 + * @return $this + */ + public function alias($alias) + { + if (is_array($alias)) { + $this->options['alias'] = $alias; + } else { + $table = $this->getTable(); + + $this->options['alias'][$table] = $alias; + } + + return $this; + } + + /** + * 设置从主服务器读取数据 + * @access public + * @param bool $readMaster 是否从主服务器读取 + * @return $this + */ + public function master(bool $readMaster = true) + { + $this->options['master'] = $readMaster; + return $this; + } + + /** + * 设置是否严格检查字段名 + * @access public + * @param bool $strict 是否严格检查字段 + * @return $this + */ + public function strict(bool $strict = true) + { + $this->options['strict'] = $strict; + return $this; + } + + /** + * 设置自增序列名 + * @access public + * @param string $sequence 自增序列名 + * @return $this + */ + public function sequence(string $sequence = null) + { + $this->options['sequence'] = $sequence; + return $this; + } + + /** + * 设置JSON字段信息 + * @access public + * @param array $json JSON字段 + * @param bool $assoc 是否取出数组 + * @return $this + */ + public function json(array $json = [], bool $assoc = false) + { + $this->options['json'] = $json; + $this->options['json_assoc'] = $assoc; + + return $this; + } + + /** + * 指定数据表主键 + * @access public + * @param string|array $pk 主键 + * @return $this + */ + public function pk($pk) + { + $this->pk = $pk; + return $this; + } + + /** + * 查询参数批量赋值 + * @access protected + * @param array $options 表达式参数 + * @return $this + */ + protected function options(array $options) + { + $this->options = $options; + return $this; + } + + /** + * 获取当前的查询参数 + * @access public + * @param string $name 参数名 + * @return mixed + */ + public function getOptions(string $name = '') + { + if ('' === $name) { + return $this->options; + } + + return $this->options[$name] ?? null; + } + + /** + * 设置当前的查询参数 + * @access public + * @param string $option 参数名 + * @param mixed $value 参数值 + * @return $this + */ + public function setOption(string $option, $value) + { + $this->options[$option] = $value; + return $this; + } + + /** + * 设置当前字段添加的表别名 + * @access public + * @param string $via 临时表别名 + * @return $this + */ + public function via(string $via = '') + { + $this->options['via'] = $via; + + return $this; + } + + /** + * 保存记录 自动判断insert或者update + * @access public + * @param array $data 数据 + * @param bool $forceInsert 是否强制insert + * @return integer + */ + public function save(array $data = [], bool $forceInsert = false) + { + if ($forceInsert) { + return $this->insert($data); + } + + $this->options['data'] = array_merge($this->options['data'] ?? [], $data); + + if (!empty($this->options['where'])) { + $isUpdate = true; + } else { + $isUpdate = $this->parseUpdateData($this->options['data']); + } + + return $isUpdate ? $this->update() : $this->insert(); + } + + /** + * 插入记录 + * @access public + * @param array $data 数据 + * @param boolean $getLastInsID 返回自增主键 + * @return integer|string + */ + public function insert(array $data = [], bool $getLastInsID = false) + { + if (!empty($data)) { + $this->options['data'] = $data; + } + + return $this->connection->insert($this, $getLastInsID); + } + + /** + * 插入记录并获取自增ID + * @access public + * @param array $data 数据 + * @return integer|string + */ + public function insertGetId(array $data) + { + return $this->insert($data, true); + } + + /** + * 批量插入记录 + * @access public + * @param array $dataSet 数据集 + * @param integer $limit 每次写入数据限制 + * @return integer + */ + public function insertAll(array $dataSet = [], int $limit = 0): int + { + if (empty($dataSet)) { + $dataSet = $this->options['data'] ?? []; + } + + if (empty($limit) && !empty($this->options['limit']) && is_numeric($this->options['limit'])) { + $limit = (int) $this->options['limit']; + } + + return $this->connection->insertAll($this, $dataSet, $limit); + } + + /** + * 批量插入记录 + * @access public + * @param array $keys 键值 + * @param array $values 数据 + * @param integer $limit 每次写入数据限制 + * @return integer + */ + public function insertAllByKeys(array $keys, array $values, int $limit = 0): int + { + if (empty($limit) && !empty($this->options['limit']) && is_numeric($this->options['limit'])) { + $limit = (int) $this->options['limit']; + } + + return $this->connection->insertAllByKeys($this, $keys, $values, $limit); + } + + /** + * 通过Select方式插入记录 + * @access public + * @param array $fields 要插入的数据表字段名 + * @param string $table 要插入的数据表名 + * @return integer + */ + public function selectInsert(array $fields, string $table): int + { + return $this->connection->selectInsert($this, $fields, $table); + } + + /** + * 更新记录 + * @access public + * @param mixed $data 数据 + * @return integer + * @throws Exception + */ + public function update(array $data = []): int + { + if (!empty($data)) { + $this->options['data'] = array_merge($this->options['data'] ?? [], $data); + } + + if (empty($this->options['where'])) { + $this->parseUpdateData($this->options['data']); + } + + if (empty($this->options['where']) && $this->model) { + $this->where($this->model->getWhere()); + } + + if (empty($this->options['where'])) { + // 如果没有任何更新条件则不执行 + throw new Exception('miss update condition'); + } + + return $this->connection->update($this); + } + + /** + * 删除记录 + * @access public + * @param mixed $data 表达式 true 表示强制删除 + * @return int + * @throws Exception + */ + public function delete($data = null): int + { + if (!is_null($data) && true !== $data) { + // AR模式分析主键条件 + $this->parsePkWhere($data); + } + + if (empty($this->options['where']) && $this->model) { + $this->where($this->model->getWhere()); + } + + if (true !== $data && empty($this->options['where'])) { + // 如果条件为空 不进行删除操作 除非设置 1=1 + throw new Exception('delete without condition'); + } + + if (!empty($this->options['soft_delete'])) { + // 软删除 + list($field, $condition) = $this->options['soft_delete']; + if ($condition) { + unset($this->options['soft_delete']); + $this->options['data'] = [$field => $condition]; + + return $this->connection->update($this); + } + } + + $this->options['data'] = $data; + + return $this->connection->delete($this); + } + + /** + * 查找记录 + * @access public + * @param mixed $data 数据 + * @return Collection|array|static[] + * @throws Exception + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + public function select($data = null): Collection + { + if (!is_null($data)) { + // 主键条件分析 + $this->parsePkWhere($data); + } + + $resultSet = $this->connection->select($this); + + // 返回结果处理 + if (!empty($this->options['fail']) && count($resultSet) == 0) { + $this->throwNotFound(); + } + + // 数据列表读取后的处理 + if (!empty($this->model)) { + // 生成模型对象 + $resultSet = $this->resultSetToModelCollection($resultSet); + } else { + $this->resultSet($resultSet); + } + + return $resultSet; + } + + /** + * 查找单条记录 + * @access public + * @param mixed $data 查询数据 + * @return array|Model|null|static|mixed + * @throws Exception + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + public function find($data = null) + { + if (!is_null($data)) { + // AR模式分析主键条件 + $this->parsePkWhere($data); + } + + if (empty($this->options['where']) && empty($this->options['order'])) { + $result = []; + } else { + $result = $this->connection->find($this); + } + + // 数据处理 + if (empty($result)) { + return $this->resultToEmpty(); + } + + if (!empty($this->model)) { + // 返回模型对象 + $this->resultToModel($result); + } else { + $this->result($result); + } + + return $result; + } + + /** + * 分析表达式(可用于查询或者写入操作) + * @access public + * @return array + */ + public function parseOptions(): array + { + $options = $this->getOptions(); + + // 获取数据表 + if (empty($options['table'])) { + $options['table'] = $this->getTable(); + } + + if (!isset($options['where'])) { + $options['where'] = []; + } elseif (isset($options['view'])) { + // 视图查询条件处理 + $this->parseView($options); + } + + foreach (['data', 'order', 'join', 'union', 'filter', 'json', 'with_attr', 'with_relation_attr'] as $name) { + if (!isset($options[$name])) { + $options[$name] = []; + } + } + + if (!isset($options['strict'])) { + $options['strict'] = $this->connection->getConfig('fields_strict'); + } + + foreach (['master', 'lock', 'fetch_sql', 'array', 'distinct', 'procedure', 'with_cache'] as $name) { + if (!isset($options[$name])) { + $options[$name] = false; + } + } + + foreach (['group', 'having', 'limit', 'force', 'comment', 'partition', 'duplicate', 'extra'] as $name) { + if (!isset($options[$name])) { + $options[$name] = ''; + } + } + + if (isset($options['page'])) { + // 根据页数计算limit + [$page, $listRows] = $options['page']; + $page = $page > 0 ? $page : 1; + $listRows = $listRows ?: (is_numeric($options['limit']) ? $options['limit'] : 20); + $offset = $listRows * ($page - 1); + $options['limit'] = $offset . ',' . $listRows; + } + + $this->options = $options; + + return $options; + } + + /** + * 分析数据是否存在更新条件 + * @access public + * @param array $data 数据 + * @return bool + * @throws Exception + */ + public function parseUpdateData(&$data): bool + { + $pk = $this->getPk(); + $isUpdate = false; + // 如果存在主键数据 则自动作为更新条件 + if (is_string($pk) && isset($data[$pk])) { + $this->where($pk, '=', $data[$pk]); + $this->options['key'] = $data[$pk]; + unset($data[$pk]); + $isUpdate = true; + } elseif (is_array($pk)) { + foreach ($pk as $field) { + if (isset($data[$field])) { + $this->where($field, '=', $data[$field]); + $isUpdate = true; + } else { + // 如果缺少复合主键数据则不执行 + throw new Exception('miss complex primary data'); + } + unset($data[$field]); + } + } + + return $isUpdate; + } + + /** + * 把主键值转换为查询条件 支持复合主键 + * @access public + * @param array|string $data 主键数据 + * @return void + * @throws Exception + */ + public function parsePkWhere($data): void + { + $pk = $this->getPk(); + + if (is_string($pk)) { + // 获取数据表 + if (empty($this->options['table'])) { + $this->options['table'] = $this->getTable(); + } + + $table = is_array($this->options['table']) ? key($this->options['table']) : $this->options['table']; + + if (!empty($this->options['alias'][$table])) { + $alias = $this->options['alias'][$table]; + } + + $key = isset($alias) ? $alias . '.' . $pk : $pk; + // 根据主键查询 + if (is_array($data)) { + $this->where($key, 'in', $data); + } else { + $this->where($key, '=', $data); + $this->options['key'] = $data; + } + } + } + + /** + * 获取模型的更新条件 + * @access protected + * @param array $options 查询参数 + */ + protected function getModelUpdateCondition(array $options) + { + return $options['where']['AND'] ?? null; + } +} diff --git a/vendor/topthink/think-orm/src/db/Builder.php b/vendor/topthink/think-orm/src/db/Builder.php new file mode 100644 index 0000000..2bde77c --- /dev/null +++ b/vendor/topthink/think-orm/src/db/Builder.php @@ -0,0 +1,1369 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db; + +use Closure; +use PDO; +use think\db\exception\DbException as Exception; + +/** + * Db Builder + */ +abstract class Builder +{ + /** + * Connection对象 + * @var ConnectionInterface + */ + protected $connection; + + /** + * 查询表达式映射 + * @var array + */ + protected $exp = ['NOTLIKE' => 'NOT LIKE', 'NOTIN' => 'NOT IN', 'NOTBETWEEN' => 'NOT BETWEEN', 'NOTEXISTS' => 'NOT EXISTS', 'NOTNULL' => 'NOT NULL', 'NOTBETWEEN TIME' => 'NOT BETWEEN TIME']; + + /** + * 查询表达式解析 + * @var array + */ + protected $parser = [ + 'parseCompare' => ['=', '<>', '>', '>=', '<', '<='], + 'parseLike' => ['LIKE', 'NOT LIKE'], + 'parseBetween' => ['NOT BETWEEN', 'BETWEEN'], + 'parseIn' => ['NOT IN', 'IN'], + 'parseExp' => ['EXP'], + 'parseNull' => ['NOT NULL', 'NULL'], + 'parseBetweenTime' => ['BETWEEN TIME', 'NOT BETWEEN TIME'], + 'parseTime' => ['< TIME', '> TIME', '<= TIME', '>= TIME'], + 'parseExists' => ['NOT EXISTS', 'EXISTS'], + 'parseColumn' => ['COLUMN'], + ]; + + /** + * SELECT SQL表达式 + * @var string + */ + protected $selectSql = 'SELECT%DISTINCT%%EXTRA% %FIELD% FROM %TABLE%%FORCE%%JOIN%%WHERE%%GROUP%%HAVING%%UNION%%ORDER%%LIMIT% %LOCK%%COMMENT%'; + + /** + * INSERT SQL表达式 + * @var string + */ + protected $insertSql = '%INSERT%%EXTRA% INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%'; + + /** + * INSERT ALL SQL表达式 + * @var string + */ + protected $insertAllSql = '%INSERT%%EXTRA% INTO %TABLE% (%FIELD%) %DATA% %COMMENT%'; + + /** + * UPDATE SQL表达式 + * @var string + */ + protected $updateSql = 'UPDATE%EXTRA% %TABLE% SET %SET%%JOIN%%WHERE%%ORDER%%LIMIT% %LOCK%%COMMENT%'; + + /** + * DELETE SQL表达式 + * @var string + */ + protected $deleteSql = 'DELETE%EXTRA% FROM %TABLE%%USING%%JOIN%%WHERE%%ORDER%%LIMIT% %LOCK%%COMMENT%'; + + /** + * 架构函数 + * @access public + * @param ConnectionInterface $connection 数据库连接对象实例 + */ + public function __construct(ConnectionInterface $connection) + { + $this->connection = $connection; + } + + /** + * 获取当前的连接对象实例 + * @access public + * @return ConnectionInterface + */ + public function getConnection(): ConnectionInterface + { + return $this->connection; + } + + /** + * 注册查询表达式解析 + * @access public + * @param string $name 解析方法 + * @param array $parser 匹配表达式数据 + * @return $this + */ + public function bindParser(string $name, array $parser) + { + $this->parser[$name] = $parser; + return $this; + } + + /** + * 数据分析 + * @access protected + * @param Query $query 查询对象 + * @param array $data 数据 + * @param array $fields 字段信息 + * @param array $bind 参数绑定 + * @return array + */ + protected function parseData(Query $query, array $data = [], array $fields = [], array $bind = []): array + { + if (empty($data)) { + return []; + } + + $options = $query->getOptions(); + + // 获取绑定信息 + if (empty($bind)) { + $bind = $query->getFieldsBindType(); + } + + if (empty($fields)) { + if (empty($options['field']) || '*' == $options['field']) { + $fields = array_keys($bind); + } else { + $fields = $options['field']; + } + } + + $result = []; + + foreach ($data as $key => $val) { + $item = $this->parseKey($query, $key, true); + + if ($val instanceof Raw) { + $result[$item] = $this->parseRaw($query, $val); + continue; + } elseif (!is_scalar($val) && (in_array($key, (array) $query->getOptions('json')) || 'json' == $query->getFieldType($key))) { + $val = json_encode($val); + } + + if (false !== strpos($key, '->')) { + [$key, $name] = explode('->', $key, 2); + $item = $this->parseKey($query, $key); + + $result[$item . '->' . $name] = 'json_set(' . $item . ', \'$.' . $name . '\', ' . $this->parseDataBind($query, $key . '->' . $name, $val, $bind) . ')'; + } elseif (false === strpos($key, '.') && !in_array($key, $fields, true)) { + if ($options['strict']) { + throw new Exception('fields not exists:[' . $key . ']'); + } + } elseif (is_null($val)) { + $result[$item] = 'NULL'; + } elseif (is_array($val) && !empty($val) && is_string($val[0])) { + switch (strtoupper($val[0])) { + case 'INC': + $result[$item] = $item . ' + ' . floatval($val[1]); + break; + case 'DEC': + $result[$item] = $item . ' - ' . floatval($val[1]); + break; + } + } elseif (is_scalar($val)) { + // 过滤非标量数据 + if (!$query->isAutoBind() && PDO::PARAM_STR == $bind[$key]) { + $val = '\'' . $val . '\''; + } + + $result[$item] = !$query->isAutoBind() ? $val : $this->parseDataBind($query, $key, $val, $bind); + } + } + + return $result; + } + + /** + * 数据绑定处理 + * @access protected + * @param Query $query 查询对象 + * @param string $key 字段名 + * @param mixed $data 数据 + * @param array $bind 绑定数据 + * @return string + */ + protected function parseDataBind(Query $query, string $key, $data, array $bind = []): string + { + if ($data instanceof Raw) { + return $this->parseRaw($query, $data); + } + + $name = $query->bindValue($data, $bind[$key] ?? PDO::PARAM_STR); + + return ':' . $name; + } + + /** + * 字段名分析 + * @access public + * @param Query $query 查询对象 + * @param mixed $key 字段名 + * @param bool $strict 严格检测 + * @return string + */ + public function parseKey(Query $query, $key, bool $strict = false): string + { + return $key; + } + + /** + * 查询额外参数分析 + * @access protected + * @param Query $query 查询对象 + * @param string $extra 额外参数 + * @return string + */ + protected function parseExtra(Query $query, string $extra): string + { + return preg_match('/^[\w]+$/i', $extra) ? ' ' . strtoupper($extra) : ''; + } + + /** + * field分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $fields 字段名 + * @return string + */ + protected function parseField(Query $query, $fields): string + { + if (is_array($fields)) { + // 支持 'field1'=>'field2' 这样的字段别名定义 + $array = []; + + foreach ($fields as $key => $field) { + if ($field instanceof Raw) { + $array[] = $this->parseRaw($query, $field); + } elseif (!is_numeric($key)) { + $array[] = $this->parseKey($query, $key) . ' AS ' . $this->parseKey($query, $field, true); + } else { + $array[] = $this->parseKey($query, $field); + } + } + + $fieldsStr = implode(',', $array); + } else { + $fieldsStr = '*'; + } + + return $fieldsStr; + } + + /** + * table分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $tables 表名 + * @return string + */ + protected function parseTable(Query $query, $tables): string + { + $item = []; + $options = $query->getOptions(); + + foreach ((array) $tables as $key => $table) { + if ($table instanceof Raw) { + $item[] = $this->parseRaw($query, $table); + } elseif (!is_numeric($key)) { + $item[] = $this->parseKey($query, $key) . ' ' . $this->parseKey($query, $table); + } elseif (isset($options['alias'][$table])) { + $item[] = $this->parseKey($query, $table) . ' ' . $this->parseKey($query, $options['alias'][$table]); + } else { + $item[] = $this->parseKey($query, $table); + } + } + + return implode(',', $item); + } + + /** + * where分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $where 查询条件 + * @return string + */ + protected function parseWhere(Query $query, array $where): string + { + $options = $query->getOptions(); + $whereStr = $this->buildWhere($query, $where); + + if (!empty($options['soft_delete'])) { + // 附加软删除条件 + [$field, $condition] = $options['soft_delete']; + + $binds = $query->getFieldsBindType(); + $whereStr = $whereStr ? '( ' . $whereStr . ' ) AND ' : ''; + $whereStr = $whereStr . $this->parseWhereItem($query, $field, $condition, $binds); + } + + return empty($whereStr) ? '' : ' WHERE ' . $whereStr; + } + + /** + * 生成查询条件SQL + * @access public + * @param Query $query 查询对象 + * @param mixed $where 查询条件 + * @return string + */ + public function buildWhere(Query $query, array $where): string + { + if (empty($where)) { + $where = []; + } + + $whereStr = ''; + + $binds = $query->getFieldsBindType(); + + foreach ($where as $logic => $val) { + $str = $this->parseWhereLogic($query, $logic, $val, $binds); + + $whereStr .= empty($whereStr) ? substr(implode(' ', $str), strlen($logic) + 1) : implode(' ', $str); + } + + return $whereStr; + } + + /** + * 不同字段使用相同查询条件(AND) + * @access protected + * @param Query $query 查询对象 + * @param string $logic Logic + * @param array $val 查询条件 + * @param array $binds 参数绑定 + * @return array + */ + protected function parseWhereLogic(Query $query, string $logic, array $val, array $binds = []): array + { + $where = []; + foreach ($val as $value) { + if ($value instanceof Raw) { + $where[] = ' ' . $logic . ' ( ' . $this->parseRaw($query, $value) . ' )'; + continue; + } + + if (is_array($value)) { + if (key($value) !== 0) { + throw new Exception('where express error:' . var_export($value, true)); + } + $field = array_shift($value); + } elseif (true === $value) { + $where[] = ' ' . $logic . ' 1 '; + continue; + } elseif (!($value instanceof Closure)) { + throw new Exception('where express error:' . var_export($value, true)); + } + + if ($value instanceof Closure) { + // 使用闭包查询 + $whereClosureStr = $this->parseClosureWhere($query, $value, $logic); + if ($whereClosureStr) { + $where[] = $whereClosureStr; + } + } elseif (is_array($field)) { + $where[] = $this->parseMultiWhereField($query, $value, $field, $logic, $binds); + } elseif ($field instanceof Raw) { + $where[] = ' ' . $logic . ' ' . $this->parseWhereItem($query, $field, $value, $binds); + } elseif (strpos($field, '|')) { + $where[] = $this->parseFieldsOr($query, $value, $field, $logic, $binds); + } elseif (strpos($field, '&')) { + $where[] = $this->parseFieldsAnd($query, $value, $field, $logic, $binds); + } else { + // 对字段使用表达式查询 + $field = is_string($field) ? $field : ''; + $where[] = ' ' . $logic . ' ' . $this->parseWhereItem($query, $field, $value, $binds); + } + } + + return $where; + } + + /** + * 不同字段使用相同查询条件(AND) + * @access protected + * @param Query $query 查询对象 + * @param mixed $value 查询条件 + * @param string $field 查询字段 + * @param string $logic Logic + * @param array $binds 参数绑定 + * @return string + */ + protected function parseFieldsAnd(Query $query, $value, string $field, string $logic, array $binds): string + { + $item = []; + + foreach (explode('&', $field) as $k) { + $item[] = $this->parseWhereItem($query, $k, $value, $binds); + } + + return ' ' . $logic . ' ( ' . implode(' AND ', $item) . ' )'; + } + + /** + * 不同字段使用相同查询条件(OR) + * @access protected + * @param Query $query 查询对象 + * @param mixed $value 查询条件 + * @param string $field 查询字段 + * @param string $logic Logic + * @param array $binds 参数绑定 + * @return string + */ + protected function parseFieldsOr(Query $query, $value, string $field, string $logic, array $binds): string + { + $item = []; + + foreach (explode('|', $field) as $k) { + $item[] = $this->parseWhereItem($query, $k, $value, $binds); + } + + return ' ' . $logic . ' ( ' . implode(' OR ', $item) . ' )'; + } + + /** + * 闭包查询 + * @access protected + * @param Query $query 查询对象 + * @param Closure $value 查询条件 + * @param string $logic Logic + * @return string + */ + protected function parseClosureWhere(Query $query, Closure $value, string $logic): string + { + $newQuery = $query->newQuery(); + $value($newQuery); + $whereClosure = $this->buildWhere($newQuery, $newQuery->getOptions('where') ?: []); + + if (!empty($whereClosure)) { + $query->bind($newQuery->getBind(false)); + $where = ' ' . $logic . ' ( ' . $whereClosure . ' )'; + } + + return $where ?? ''; + } + + /** + * 复合条件查询 + * @access protected + * @param Query $query 查询对象 + * @param mixed $value 查询条件 + * @param mixed $field 查询字段 + * @param string $logic Logic + * @param array $binds 参数绑定 + * @return string + */ + protected function parseMultiWhereField(Query $query, $value, $field, string $logic, array $binds): string + { + array_unshift($value, $field); + + $where = []; + foreach ($value as $item) { + $where[] = $this->parseWhereItem($query, array_shift($item), $item, $binds); + } + + return ' ' . $logic . ' ( ' . implode(' AND ', $where) . ' )'; + } + + /** + * where子单元分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $field 查询字段 + * @param array $val 查询条件 + * @param array $binds 参数绑定 + * @return string + */ + protected function parseWhereItem(Query $query, $field, array $val, array $binds = []): string + { + // 字段分析 + $key = $field ? $this->parseKey($query, $field, true) : ''; + + [$exp, $value] = $val; + + // 检测操作符 + if (!is_string($exp)) { + throw new Exception('where express error:' . var_export($exp, true)); + } + + $exp = strtoupper($exp); + if (isset($this->exp[$exp])) { + $exp = $this->exp[$exp]; + } + + if (is_string($field) && 'LIKE' != $exp) { + $bindType = $binds[$field] ?? PDO::PARAM_STR; + } else { + $bindType = PDO::PARAM_STR; + } + + if ($value instanceof Raw) { + + } elseif (is_object($value) && method_exists($value, '__toString')) { + // 对象数据写入 + $value = $value->__toString(); + } + + if (is_scalar($value) && !in_array($exp, ['EXP', 'NOT NULL', 'NULL', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN']) && strpos($exp, 'TIME') === false) { + if (is_string($value) && 0 === strpos($value, ':') && $query->isBind(substr($value, 1))) { + } else { + $name = $query->bindValue($value, $bindType); + $value = ':' . $name; + } + } + + // 解析查询表达式 + foreach ($this->parser as $fun => $parse) { + if (in_array($exp, $parse)) { + return $this->$fun($query, $key, $exp, $value, $field, $bindType, $val[2] ?? 'AND'); + } + } + + throw new Exception('where express error:' . $exp); + } + + /** + * 模糊查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param array $value + * @param string $field + * @param integer $bindType + * @param string $logic + * @return string + */ + protected function parseLike(Query $query, string $key, string $exp, $value, $field, int $bindType, string $logic): string + { + // 模糊匹配 + if (is_array($value)) { + $array = []; + foreach ($value as $item) { + $name = $query->bindValue($item, PDO::PARAM_STR); + $array[] = $key . ' ' . $exp . ' :' . $name; + } + + $whereStr = '(' . implode(' ' . strtoupper($logic) . ' ', $array) . ')'; + } else { + $whereStr = $key . ' ' . $exp . ' ' . $value; + } + + return $whereStr; + } + + /** + * 表达式查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param array $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseExp(Query $query, string $key, string $exp, Raw $value, string $field, int $bindType): string + { + // 表达式查询 + return '( ' . $key . ' ' . $this->parseRaw($query, $value) . ' )'; + } + + /** + * 表达式查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param array $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseColumn(Query $query, string $key, $exp, array $value, string $field, int $bindType): string + { + // 字段比较查询 + [$op, $field] = $value; + + if (!in_array(trim($op), ['=', '<>', '>', '>=', '<', '<='])) { + throw new Exception('where express error:' . var_export($value, true)); + } + + return '( ' . $key . ' ' . $op . ' ' . $this->parseKey($query, $field, true) . ' )'; + } + + /** + * Null查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseNull(Query $query, string $key, string $exp, $value, $field, int $bindType): string + { + // NULL 查询 + return $key . ' IS ' . $exp; + } + + /** + * 范围查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseBetween(Query $query, string $key, string $exp, $value, $field, int $bindType): string + { + // BETWEEN 查询 + $data = is_array($value) ? $value : explode(',', $value); + + $min = $query->bindValue($data[0], $bindType); + $max = $query->bindValue($data[1], $bindType); + + return $key . ' ' . $exp . ' :' . $min . ' AND :' . $max . ' '; + } + + /** + * Exists查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseExists(Query $query, string $key, string $exp, $value, string $field, int $bindType): string + { + // EXISTS 查询 + if ($value instanceof Closure) { + $value = $this->parseClosure($query, $value, false); + } elseif ($value instanceof Raw) { + $value = $this->parseRaw($query, $value); + } else { + throw new Exception('where express error:' . $value); + } + + return $exp . ' ( ' . $value . ' )'; + } + + /** + * 时间比较查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseTime(Query $query, string $key, string $exp, $value, $field, int $bindType): string + { + return $key . ' ' . substr($exp, 0, 2) . ' ' . $this->parseDateTime($query, $value, $field, $bindType); + } + + /** + * 大小比较查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseCompare(Query $query, string $key, string $exp, $value, $field, int $bindType): string + { + if (is_array($value)) { + throw new Exception('where express error:' . $exp . var_export($value, true)); + } + + // 比较运算 + if ($value instanceof Closure) { + $value = $this->parseClosure($query, $value); + } elseif ($value instanceof Raw) { + $value = $this->parseRaw($query, $value); + } + + if ('=' == $exp && is_null($value)) { + return $key . ' IS NULL'; + } + + return $key . ' ' . $exp . ' ' . $value; + } + + /** + * 时间范围查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseBetweenTime(Query $query, string $key, string $exp, $value, $field, int $bindType): string + { + if (is_string($value)) { + $value = explode(',', $value); + } + + return $key . ' ' . substr($exp, 0, -4) + . $this->parseDateTime($query, $value[0], $field, $bindType) + . ' AND ' + . $this->parseDateTime($query, $value[1], $field, $bindType); + + } + + /** + * IN查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseIn(Query $query, string $key, string $exp, $value, $field, int $bindType): string + { + // IN 查询 + if ($value instanceof Closure) { + $value = $this->parseClosure($query, $value, false); + } elseif ($value instanceof Raw) { + $value = $this->parseRaw($query, $value); + } else { + $value = array_unique(is_array($value) ? $value : explode(',', (string) $value)); + if (count($value) === 0) { + return 'IN' == $exp ? '0 = 1' : '1 = 1'; + } + + if ($query->isAutoBind()) { + $array = []; + foreach ($value as $v) { + $name = $query->bindValue($v, $bindType); + $array[] = ':' . $name; + } + $value = implode(',', $array); + } elseif (PDO::PARAM_STR == $bindType) { + $value = '\'' . implode('\',\'', $value) . '\''; + } else { + $value = implode(',', $value); + } + + if (false === strpos($value, ',')) { + return $key . ('IN' == $exp ? ' = ' : ' <> ') . $value; + } + } + + return $key . ' ' . $exp . ' (' . $value . ')'; + } + + /** + * 闭包子查询 + * @access protected + * @param Query $query 查询对象 + * @param \Closure $call + * @param bool $show + * @return string + */ + protected function parseClosure(Query $query, Closure $call, bool $show = true): string + { + $newQuery = $query->newQuery()->removeOption(); + $call($newQuery); + + return $newQuery->buildSql($show); + } + + /** + * 日期时间条件解析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $value + * @param string $key + * @param integer $bindType + * @return string + */ + protected function parseDateTime(Query $query, $value, string $key, int $bindType): string + { + $options = $query->getOptions(); + + // 获取时间字段类型 + if (strpos($key, '.')) { + [$table, $key] = explode('.', $key); + + if (isset($options['alias']) && $pos = array_search($table, $options['alias'])) { + $table = $pos; + } + } else { + $table = $options['table']; + } + + $type = $query->getFieldType($key); + + if ($type) { + if (is_string($value)) { + $value = strtotime($value) ?: $value; + } + + if (is_int($value)) { + if (preg_match('/(datetime|timestamp)/is', $type)) { + // 日期及时间戳类型 + $value = date('Y-m-d H:i:s', $value); + } elseif (preg_match('/(date)/is', $type)) { + // 日期及时间戳类型 + $value = date('Y-m-d', $value); + } + } + } + + $name = $query->bindValue($value, $bindType); + + return ':' . $name; + } + + /** + * limit分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $limit + * @return string + */ + protected function parseLimit(Query $query, string $limit): string + { + return (!empty($limit) && false === strpos($limit, '(')) ? ' LIMIT ' . $limit . ' ' : ''; + } + + /** + * join分析 + * @access protected + * @param Query $query 查询对象 + * @param array $join + * @return string + */ + protected function parseJoin(Query $query, array $join): string + { + $joinStr = ''; + + foreach ($join as $item) { + [$table, $type, $on] = $item; + + if (strpos($on, '=')) { + [$val1, $val2] = explode('=', $on, 2); + + $condition = $this->parseKey($query, $val1) . '=' . $this->parseKey($query, $val2); + } else { + $condition = $on; + } + + $table = $this->parseTable($query, $table); + + $joinStr .= ' ' . $type . ' JOIN ' . $table . ' ON ' . $condition; + } + + return $joinStr; + } + + /** + * order分析 + * @access protected + * @param Query $query 查询对象 + * @param array $order + * @return string + */ + protected function parseOrder(Query $query, array $order): string + { + $array = []; + foreach ($order as $key => $val) { + if ($val instanceof Raw) { + $array[] = $this->parseRaw($query, $val); + } elseif (is_array($val) && preg_match('/^[\w\.]+$/', $key)) { + $array[] = $this->parseOrderField($query, $key, $val); + } elseif ('[rand]' == $val) { + $array[] = $this->parseRand($query); + } elseif (is_string($val)) { + if (is_numeric($key)) { + [$key, $sort] = explode(' ', strpos($val, ' ') ? $val : $val . ' '); + } else { + $sort = $val; + } + + if (preg_match('/^[\w\.]+$/', $key)) { + $sort = strtoupper($sort); + $sort = in_array($sort, ['ASC', 'DESC'], true) ? ' ' . $sort : ''; + $array[] = $this->parseKey($query, $key, true) . $sort; + } else { + throw new Exception('order express error:' . $key); + } + } + } + + return empty($array) ? '' : ' ORDER BY ' . implode(',', $array); + } + + /** + * 分析Raw对象 + * @access protected + * @param Query $query 查询对象 + * @param Raw $raw Raw对象 + * @return string + */ + protected function parseRaw(Query $query, Raw $raw): string + { + $sql = $raw->getValue(); + $bind = $raw->getBind(); + + if ($bind) { + $query->bindParams($sql, $bind); + } + + return $sql; + } + + /** + * 随机排序 + * @access protected + * @param Query $query 查询对象 + * @return string + */ + protected function parseRand(Query $query): string + { + return ''; + } + + /** + * orderField分析 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param array $val + * @return string + */ + protected function parseOrderField(Query $query, string $key, array $val): string + { + if (isset($val['sort'])) { + $sort = $val['sort']; + unset($val['sort']); + } else { + $sort = ''; + } + + $sort = strtoupper($sort); + $sort = in_array($sort, ['ASC', 'DESC'], true) ? ' ' . $sort : ''; + $bind = $query->getFieldsBindType(); + + foreach ($val as $k => $item) { + $val[$k] = $this->parseDataBind($query, $key, $item, $bind); + } + + return 'field(' . $this->parseKey($query, $key, true) . ',' . implode(',', $val) . ')' . $sort; + } + + /** + * group分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $group + * @return string + */ + protected function parseGroup(Query $query, $group): string + { + if (empty($group)) { + return ''; + } + + if (is_string($group)) { + $group = explode(',', $group); + } + + $val = []; + foreach ($group as $key) { + $val[] = $this->parseKey($query, $key); + } + + return ' GROUP BY ' . implode(',', $val); + } + + /** + * having分析 + * @access protected + * @param Query $query 查询对象 + * @param string $having + * @return string + */ + protected function parseHaving(Query $query, string $having): string + { + return !empty($having) ? ' HAVING ' . $having : ''; + } + + /** + * comment分析 + * @access protected + * @param Query $query 查询对象 + * @param string $comment + * @return string + */ + protected function parseComment(Query $query, string $comment): string + { + if (false !== strpos($comment, '*/')) { + $comment = strstr($comment, '*/', true); + } + + return !empty($comment) ? ' /* ' . $comment . ' */' : ''; + } + + /** + * distinct分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $distinct + * @return string + */ + protected function parseDistinct(Query $query, bool $distinct): string + { + return !empty($distinct) ? ' DISTINCT ' : ''; + } + + /** + * union分析 + * @access protected + * @param Query $query 查询对象 + * @param array $union + * @return string + */ + protected function parseUnion(Query $query, array $union): string + { + if (empty($union)) { + return ''; + } + + $type = $union['type']; + unset($union['type']); + + foreach ($union as $u) { + if ($u instanceof Closure) { + $sql[] = $type . ' ' . $this->parseClosure($query, $u); + } elseif (is_string($u)) { + $sql[] = $type . ' ( ' . $u . ' )'; + } + } + + return ' ' . implode(' ', $sql); + } + + /** + * index分析,可在操作链中指定需要强制使用的索引 + * @access protected + * @param Query $query 查询对象 + * @param mixed $index + * @return string + */ + protected function parseForce(Query $query, $index): string + { + if (empty($index)) { + return ''; + } + + if (is_array($index)) { + $index = join(',', $index); + } + + return sprintf(" FORCE INDEX ( %s ) ", $index); + } + + /** + * 设置锁机制 + * @access protected + * @param Query $query 查询对象 + * @param bool|string $lock + * @return string + */ + protected function parseLock(Query $query, $lock = false): string + { + if (is_bool($lock)) { + return $lock ? ' FOR UPDATE ' : ''; + } + + if (is_string($lock) && !empty($lock)) { + return ' ' . trim($lock) . ' '; + } else { + return ''; + } + } + + /** + * 生成查询SQL + * @access public + * @param Query $query 查询对象 + * @param bool $one 是否仅获取一个记录 + * @return string + */ + public function select(Query $query, bool $one = false): string + { + $options = $query->getOptions(); + + return str_replace( + ['%TABLE%', '%DISTINCT%', '%EXTRA%', '%FIELD%', '%JOIN%', '%WHERE%', '%GROUP%', '%HAVING%', '%ORDER%', '%LIMIT%', '%UNION%', '%LOCK%', '%COMMENT%', '%FORCE%'], + [ + $this->parseTable($query, $options['table']), + $this->parseDistinct($query, $options['distinct']), + $this->parseExtra($query, $options['extra']), + $this->parseField($query, $options['field'] ?? '*'), + $this->parseJoin($query, $options['join']), + $this->parseWhere($query, $options['where']), + $this->parseGroup($query, $options['group']), + $this->parseHaving($query, $options['having']), + $this->parseOrder($query, $options['order']), + $this->parseLimit($query, $one ? '1' : $options['limit']), + $this->parseUnion($query, $options['union']), + $this->parseLock($query, $options['lock']), + $this->parseComment($query, $options['comment']), + $this->parseForce($query, $options['force']), + ], + $this->selectSql + ); + } + + /** + * 生成Insert SQL + * @access public + * @param Query $query 查询对象 + * @return string + */ + public function insert(Query $query): string + { + $options = $query->getOptions(); + + // 分析并处理数据 + $data = $this->parseData($query, $options['data']); + if (empty($data)) { + return ''; + } + + $fields = array_keys($data); + $values = array_values($data); + + return str_replace( + ['%INSERT%', '%TABLE%', '%EXTRA%', '%FIELD%', '%DATA%', '%COMMENT%'], + [ + !empty($options['replace']) ? 'REPLACE' : 'INSERT', + $this->parseTable($query, $options['table']), + $this->parseExtra($query, $options['extra']), + implode(' , ', $fields), + implode(' , ', $values), + $this->parseComment($query, $options['comment']), + ], + $this->insertSql + ); + } + + /** + * 生成insertall SQL + * @access public + * @param Query $query 查询对象 + * @param array $dataSet 数据集 + * @return string + */ + public function insertAll(Query $query, array $dataSet): string + { + $options = $query->getOptions(); + + // 获取绑定信息 + $bind = $query->getFieldsBindType(); + + // 获取合法的字段 + if (empty($options['field']) || '*' == $options['field']) { + $allowFields = array_keys($bind); + } else { + $allowFields = $options['field']; + } + + $fields = []; + $values = []; + + foreach ($dataSet as $k => $data) { + $data = $this->parseData($query, $data, $allowFields, $bind); + + $values[] = 'SELECT ' . implode(',', array_values($data)); + + if (!isset($insertFields)) { + $insertFields = array_keys($data); + } + } + + foreach ($insertFields as $field) { + $fields[] = $this->parseKey($query, $field); + } + + return str_replace( + ['%INSERT%', '%TABLE%', '%EXTRA%', '%FIELD%', '%DATA%', '%COMMENT%'], + [ + !empty($options['replace']) ? 'REPLACE' : 'INSERT', + $this->parseTable($query, $options['table']), + $this->parseExtra($query, $options['extra']), + implode(' , ', $fields), + implode(' UNION ALL ', $values), + $this->parseComment($query, $options['comment']), + ], + $this->insertAllSql + ); + } + + /** + * 生成insertall SQL + * @access public + * @param Query $query 查询对象 + * @param array $keys 字段名 + * @param array $datas 数据 + * @return string + */ + public function insertAllByKeys(Query $query, array $keys, array $datas): string + { + $options = $query->getOptions(); + + // 获取绑定信息 + $bind = $query->getFieldsBindType(); + $fields = []; + $values = []; + + foreach ($keys as $field) { + $fields[] = $this->parseKey($query, $field); + } + + foreach ($datas as $data) { + foreach ($data as $key => &$val) { + if (!$query->isAutoBind()) { + $val = PDO::PARAM_STR == $bind[$keys[$key]] ? '\'' . $val . '\'' : $val; + } else { + $val = $this->parseDataBind($query, $keys[$key], $val, $bind); + } + } + + $values[] = 'SELECT ' . implode(',', $data); + } + + return str_replace( + ['%INSERT%', '%TABLE%', '%EXTRA%', '%FIELD%', '%DATA%', '%COMMENT%'], + [ + !empty($options['replace']) ? 'REPLACE' : 'INSERT', + $this->parseTable($query, $options['table']), + $this->parseExtra($query, $options['extra']), + implode(' , ', $fields), + implode(' UNION ALL ', $values), + $this->parseComment($query, $options['comment']), + ], + $this->insertAllSql + ); + } + + /** + * 生成slect insert SQL + * @access public + * @param Query $query 查询对象 + * @param array $fields 数据 + * @param string $table 数据表 + * @return string + */ + public function selectInsert(Query $query, array $fields, string $table): string + { + foreach ($fields as &$field) { + $field = $this->parseKey($query, $field, true); + } + + return 'INSERT INTO ' . $this->parseTable($query, $table) . ' (' . implode(',', $fields) . ') ' . $this->select($query); + } + + /** + * 生成update SQL + * @access public + * @param Query $query 查询对象 + * @return string + */ + public function update(Query $query): string + { + $options = $query->getOptions(); + + $data = $this->parseData($query, $options['data']); + + if (empty($data)) { + return ''; + } + + $set = []; + foreach ($data as $key => $val) { + $set[] = $key . ' = ' . $val; + } + + return str_replace( + ['%TABLE%', '%EXTRA%', '%SET%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'], + [ + $this->parseTable($query, $options['table']), + $this->parseExtra($query, $options['extra']), + implode(' , ', $set), + $this->parseJoin($query, $options['join']), + $this->parseWhere($query, $options['where']), + $this->parseOrder($query, $options['order']), + $this->parseLimit($query, $options['limit']), + $this->parseLock($query, $options['lock']), + $this->parseComment($query, $options['comment']), + ], + $this->updateSql + ); + } + + /** + * 生成delete SQL + * @access public + * @param Query $query 查询对象 + * @return string + */ + public function delete(Query $query): string + { + $options = $query->getOptions(); + + return str_replace( + ['%TABLE%', '%EXTRA%', '%USING%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'], + [ + $this->parseTable($query, $options['table']), + $this->parseExtra($query, $options['extra']), + !empty($options['using']) ? ' USING ' . $this->parseTable($query, $options['using']) . ' ' : '', + $this->parseJoin($query, $options['join']), + $this->parseWhere($query, $options['where']), + $this->parseOrder($query, $options['order']), + $this->parseLimit($query, $options['limit']), + $this->parseLock($query, $options['lock']), + $this->parseComment($query, $options['comment']), + ], + $this->deleteSql + ); + } +} diff --git a/vendor/topthink/think-orm/src/db/CacheItem.php b/vendor/topthink/think-orm/src/db/CacheItem.php new file mode 100644 index 0000000..839f384 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/CacheItem.php @@ -0,0 +1,209 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db; + +use DateInterval; +use DateTime; +use DateTimeInterface; +use think\db\exception\InvalidArgumentException; + +/** + * CacheItem实现类 + */ +class CacheItem +{ + /** + * 缓存Key + * @var string + */ + protected $key; + + /** + * 缓存内容 + * @var mixed + */ + protected $value; + + /** + * 过期时间 + * @var int|DateTimeInterface + */ + protected $expire; + + /** + * 缓存tag + * @var string + */ + protected $tag; + + /** + * 缓存是否命中 + * @var bool + */ + protected $isHit = false; + + public function __construct(string $key = null) + { + $this->key = $key; + } + + /** + * 为此缓存项设置「键」 + * @access public + * @param string $key + * @return $this + */ + public function setKey(string $key) + { + $this->key = $key; + return $this; + } + + /** + * 返回当前缓存项的「键」 + * @access public + * @return string + */ + public function getKey() + { + return $this->key; + } + + /** + * 返回当前缓存项的有效期 + * @access public + * @return DateTimeInterface|int|null + */ + public function getExpire() + { + if ($this->expire instanceof DateTimeInterface) { + return $this->expire; + } + + return $this->expire ? $this->expire - time() : null; + } + + /** + * 获取缓存Tag + * @access public + * @return string|array + */ + public function getTag() + { + return $this->tag; + } + + /** + * 凭借此缓存项的「键」从缓存系统里面取出缓存项 + * @access public + * @return mixed + */ + public function get() + { + return $this->value; + } + + /** + * 确认缓存项的检查是否命中 + * @access public + * @return bool + */ + public function isHit(): bool + { + return $this->isHit; + } + + /** + * 为此缓存项设置「值」 + * @access public + * @param mixed $value + * @return $this + */ + public function set($value) + { + $this->value = $value; + $this->isHit = true; + return $this; + } + + /** + * 为此缓存项设置所属标签 + * @access public + * @param string|array $tag + * @return $this + */ + public function tag($tag = null) + { + $this->tag = $tag; + return $this; + } + + /** + * 设置缓存项的有效期 + * @access public + * @param mixed $expire + * @return $this + */ + public function expire($expire) + { + if (is_null($expire)) { + $this->expire = null; + } elseif (is_numeric($expire) || $expire instanceof DateInterval) { + $this->expiresAfter($expire); + } elseif ($expire instanceof DateTimeInterface) { + $this->expire = $expire; + } else { + throw new InvalidArgumentException('not support datetime'); + } + + return $this; + } + + /** + * 设置缓存项的准确过期时间点 + * @access public + * @param DateTimeInterface $expiration + * @return $this + */ + public function expiresAt($expiration) + { + if ($expiration instanceof DateTimeInterface) { + $this->expire = $expiration; + } else { + throw new InvalidArgumentException('not support datetime'); + } + + return $this; + } + + /** + * 设置缓存项的过期时间 + * @access public + * @param int|DateInterval $timeInterval + * @return $this + * @throws InvalidArgumentException + */ + public function expiresAfter($timeInterval) + { + if ($timeInterval instanceof DateInterval) { + $this->expire = (int) DateTime::createFromFormat('U', (string) time())->add($timeInterval)->format('U'); + } elseif (is_numeric($timeInterval)) { + $this->expire = $timeInterval + time(); + } else { + throw new InvalidArgumentException('not support datetime'); + } + + return $this; + } + +} diff --git a/vendor/topthink/think-orm/src/db/Connection.php b/vendor/topthink/think-orm/src/db/Connection.php new file mode 100644 index 0000000..015cf44 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/Connection.php @@ -0,0 +1,347 @@ + +// +---------------------------------------------------------------------- +declare(strict_types=1); + +namespace think\db; + +use Psr\SimpleCache\CacheInterface; +use think\DbManager; + +/** + * 数据库连接基础类 + */ +abstract class Connection implements ConnectionInterface +{ + + /** + * 当前SQL指令 + * @var string + */ + protected $queryStr = ''; + + /** + * 返回或者影响记录数 + * @var int + */ + protected $numRows = 0; + + /** + * 事务指令数 + * @var int + */ + protected $transTimes = 0; + + /** + * 错误信息 + * @var string + */ + protected $error = ''; + + /** + * 数据库连接ID 支持多个连接 + * @var array + */ + protected $links = []; + + /** + * 当前连接ID + * @var object + */ + protected $linkID; + + /** + * 当前读连接ID + * @var object + */ + protected $linkRead; + + /** + * 当前写连接ID + * @var object + */ + protected $linkWrite; + + /** + * 数据表信息 + * @var array + */ + protected $info = []; + + /** + * 查询开始时间 + * @var float + */ + protected $queryStartTime; + + /** + * Builder对象 + * @var Builder + */ + protected $builder; + + /** + * Db对象 + * @var DbManager + */ + protected $db; + + /** + * 是否读取主库 + * @var bool + */ + protected $readMaster = false; + + /** + * 数据库连接参数配置 + * @var array + */ + protected $config = []; + + /** + * 缓存对象 + * @var CacheInterface + */ + protected $cache; + + /** + * 架构函数 读取数据库配置信息 + * @access public + * @param array $config 数据库配置数组 + */ + public function __construct(array $config = []) + { + if (!empty($config)) { + $this->config = array_merge($this->config, $config); + } + + // 创建Builder对象 + $class = $this->getBuilderClass(); + + $this->builder = new $class($this); + } + + /** + * 获取当前的builder实例对象 + * @access public + * @return Builder + */ + public function getBuilder() + { + return $this->builder; + } + + /** + * 创建查询对象 + */ + public function newQuery() + { + $class = $this->getQueryClass(); + + /** @var BaseQuery $query */ + $query = new $class($this); + + $timeRule = $this->db->getConfig('time_query_rule'); + if (!empty($timeRule)) { + $query->timeRule($timeRule); + } + + return $query; + } + + /** + * 指定表名开始查询 + * @param $table + * @return BaseQuery + */ + public function table($table) + { + return $this->newQuery()->table($table); + } + + /** + * 指定表名开始查询(不带前缀) + * @param $name + * @return BaseQuery + */ + public function name($name) + { + return $this->newQuery()->name($name); + } + + /** + * 设置当前的数据库Db对象 + * @access public + * @param DbManager $db + * @return void + */ + public function setDb(DbManager $db) + { + $this->db = $db; + } + + /** + * 设置当前的缓存对象 + * @access public + * @param CacheInterface $cache + * @return void + */ + public function setCache(CacheInterface $cache) + { + $this->cache = $cache; + } + + /** + * 获取当前的缓存对象 + * @access public + * @return CacheInterface|null + */ + public function getCache() + { + return $this->cache; + } + + /** + * 获取数据库的配置参数 + * @access public + * @param string $config 配置名称 + * @return mixed + */ + public function getConfig(string $config = '') + { + if ('' === $config) { + return $this->config; + } + + return $this->config[$config] ?? null; + } + + /** + * 数据库SQL监控 + * @access protected + * @param string $sql 执行的SQL语句 留空自动获取 + * @param bool $master 主从标记 + * @return void + */ + protected function trigger(string $sql = '', bool $master = false): void + { + $listen = $this->db->getListen(); + if (empty($listen)) { + $listen[] = function ($sql, $time, $master) { + if (0 === strpos($sql, 'CONNECT:')) { + $this->db->log($sql); + return; + } + + // 记录SQL + if (is_bool($master)) { + // 分布式记录当前操作的主从 + $master = $master ? 'master|' : 'slave|'; + } else { + $master = ''; + } + + $this->db->log($sql . ' [ ' . $master . 'RunTime:' . $time . 's ]'); + }; + } + + $runtime = number_format((microtime(true) - $this->queryStartTime), 6); + $sql = $sql ?: $this->getLastsql(); + + if (empty($this->config['deploy'])) { + $master = null; + } + + foreach ($listen as $callback) { + if (is_callable($callback)) { + $callback($sql, $runtime, $master); + } + } + } + + /** + * 缓存数据 + * @access protected + * @param CacheItem $cacheItem 缓存Item + */ + protected function cacheData(CacheItem $cacheItem) + { + if ($cacheItem->getTag() && method_exists($this->cache, 'tag')) { + $this->cache->tag($cacheItem->getTag())->set($cacheItem->getKey(), $cacheItem->get(), $cacheItem->getExpire()); + } else { + $this->cache->set($cacheItem->getKey(), $cacheItem->get(), $cacheItem->getExpire()); + } + } + + /** + * 分析缓存Key + * @access protected + * @param BaseQuery $query 查询对象 + * @param string $method 查询方法 + * @return string + */ + protected function getCacheKey(BaseQuery $query, string $method = ''): string + { + if (!empty($query->getOptions('key')) && empty($method)) { + $key = 'think_' . $this->getConfig('database') . '.' . $query->getTable() . '|' . $query->getOptions('key'); + } else { + $key = $query->getQueryGuid(); + } + + return $key; + } + + /** + * 分析缓存 + * @access protected + * @param BaseQuery $query 查询对象 + * @param array $cache 缓存信息 + * @param string $method 查询方法 + * @return CacheItem + */ + protected function parseCache(BaseQuery $query, array $cache, string $method = ''): CacheItem + { + [$key, $expire, $tag] = $cache; + + if ($key instanceof CacheItem) { + $cacheItem = $key; + } else { + if (true === $key) { + $key = $this->getCacheKey($query, $method); + } + + $cacheItem = new CacheItem($key); + $cacheItem->expire($expire); + $cacheItem->tag($tag); + } + + return $cacheItem; + } + + /** + * 获取返回或者影响的记录数 + * @access public + * @return integer + */ + public function getNumRows(): int + { + return $this->numRows; + } + + /** + * 析构方法 + * @access public + */ + public function __destruct() + { + // 关闭连接 + $this->close(); + } +} diff --git a/vendor/topthink/think-orm/src/db/ConnectionInterface.php b/vendor/topthink/think-orm/src/db/ConnectionInterface.php new file mode 100644 index 0000000..18fe131 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/ConnectionInterface.php @@ -0,0 +1,190 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db; + +use Psr\SimpleCache\CacheInterface; +use think\DbManager; + +/** + * Connection interface + */ +interface ConnectionInterface +{ + /** + * 获取当前连接器类对应的Query类 + * @access public + * @return string + */ + public function getQueryClass(): string; + + /** + * 指定表名开始查询 + * @param $table + * @return BaseQuery + */ + public function table($table); + + /** + * 指定表名开始查询(不带前缀) + * @param $name + * @return BaseQuery + */ + public function name($name); + + /** + * 连接数据库方法 + * @access public + * @param array $config 接参数 + * @param integer $linkNum 连接序号 + * @return mixed + */ + public function connect(array $config = [], $linkNum = 0); + + /** + * 设置当前的数据库Db对象 + * @access public + * @param DbManager $db + * @return void + */ + public function setDb(DbManager $db); + + /** + * 设置当前的缓存对象 + * @access public + * @param CacheInterface $cache + * @return void + */ + public function setCache(CacheInterface $cache); + + /** + * 获取数据库的配置参数 + * @access public + * @param string $config 配置名称 + * @return mixed + */ + public function getConfig(string $config = ''); + + /** + * 关闭数据库(或者重新连接) + * @access public + * @return $this + */ + public function close(); + + /** + * 查找单条记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return array + */ + public function find(BaseQuery $query): array; + + /** + * 查找记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return array + */ + public function select(BaseQuery $query): array; + + /** + * 插入记录 + * @access public + * @param BaseQuery $query 查询对象 + * @param boolean $getLastInsID 返回自增主键 + * @return mixed + */ + public function insert(BaseQuery $query, bool $getLastInsID = false); + + /** + * 批量插入记录 + * @access public + * @param BaseQuery $query 查询对象 + * @param mixed $dataSet 数据集 + * @return integer + */ + public function insertAll(BaseQuery $query, array $dataSet = []): int; + + /** + * 更新记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return integer + */ + public function update(BaseQuery $query): int; + + /** + * 删除记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return int + */ + public function delete(BaseQuery $query): int; + + /** + * 得到某个字段的值 + * @access public + * @param BaseQuery $query 查询对象 + * @param string $field 字段名 + * @param mixed $default 默认值 + * @return mixed + */ + public function value(BaseQuery $query, string $field, $default = null); + + /** + * 得到某个列的数组 + * @access public + * @param BaseQuery $query 查询对象 + * @param string|array $column 字段名 多个字段用逗号分隔 + * @param string $key 索引 + * @return array + */ + public function column(BaseQuery $query, $column, string $key = ''): array; + + /** + * 执行数据库事务 + * @access public + * @param callable $callback 数据操作方法回调 + * @return mixed + */ + public function transaction(callable $callback); + + /** + * 启动事务 + * @access public + * @return void + */ + public function startTrans(); + + /** + * 用于非自动提交状态下面的查询提交 + * @access public + * @return void + */ + public function commit(); + + /** + * 事务回滚 + * @access public + * @return void + */ + public function rollback(); + + /** + * 获取最近一次查询的sql语句 + * @access public + * @return string + */ + public function getLastSql(): string; + +} diff --git a/vendor/topthink/think-orm/src/db/Fetch.php b/vendor/topthink/think-orm/src/db/Fetch.php new file mode 100644 index 0000000..a997a85 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/Fetch.php @@ -0,0 +1,492 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db; + +use think\db\exception\DbException as Exception; +use think\helper\Str; + +/** + * SQL获取类 + */ +class Fetch +{ + /** + * 查询对象 + * @var Query + */ + protected $query; + + /** + * Connection对象 + * @var Connection + */ + protected $connection; + + /** + * Builder对象 + * @var Builder + */ + protected $builder; + + /** + * 创建一个查询SQL获取对象 + * + * @param Query $query 查询对象 + */ + public function __construct(Query $query) + { + $this->query = $query; + $this->connection = $query->getConnection(); + $this->builder = $this->connection->getBuilder(); + } + + /** + * 聚合查询 + * @access protected + * @param string $aggregate 聚合方法 + * @param string $field 字段名 + * @return string + */ + protected function aggregate(string $aggregate, string $field): string + { + $this->query->parseOptions(); + + $field = $aggregate . '(' . $this->builder->parseKey($this->query, $field) . ') AS think_' . strtolower($aggregate); + + return $this->value($field, 0, false); + } + + /** + * 得到某个字段的值 + * @access public + * @param string $field 字段名 + * @param mixed $default 默认值 + * @param bool $one + * @return string + */ + public function value(string $field, $default = null, bool $one = true): string + { + $options = $this->query->parseOptions(); + + if (isset($options['field'])) { + $this->query->removeOption('field'); + } + + $this->query->setOption('field', (array) $field); + + // 生成查询SQL + $sql = $this->builder->select($this->query, $one); + + if (isset($options['field'])) { + $this->query->setOption('field', $options['field']); + } else { + $this->query->removeOption('field'); + } + + return $this->fetch($sql); + } + + /** + * 得到某个列的数组 + * @access public + * @param string $field 字段名 多个字段用逗号分隔 + * @param string $key 索引 + * @return string + */ + public function column(string $field, string $key = ''): string + { + $options = $this->query->parseOptions(); + + if (isset($options['field'])) { + $this->query->removeOption('field'); + } + + if ($key && '*' != $field) { + $field = $key . ',' . $field; + } + + $field = array_map('trim', explode(',', $field)); + + $this->query->setOption('field', $field); + + // 生成查询SQL + $sql = $this->builder->select($this->query); + + if (isset($options['field'])) { + $this->query->setOption('field', $options['field']); + } else { + $this->query->removeOption('field'); + } + + return $this->fetch($sql); + } + + /** + * 插入记录 + * @access public + * @param array $data 数据 + * @return string + */ + public function insert(array $data = []): string + { + $options = $this->query->parseOptions(); + + if (!empty($data)) { + $this->query->setOption('data', $data); + } + + $sql = $this->builder->insert($this->query); + + return $this->fetch($sql); + } + + /** + * 插入记录并获取自增ID + * @access public + * @param array $data 数据 + * @return string + */ + public function insertGetId(array $data = []): string + { + return $this->insert($data); + } + + /** + * 保存数据 自动判断insert或者update + * @access public + * @param array $data 数据 + * @param bool $forceInsert 是否强制insert + * @return string + */ + public function save(array $data = [], bool $forceInsert = false): string + { + if ($forceInsert) { + return $this->insert($data); + } + + $data = array_merge($this->query->getOptions('data') ?: [], $data); + + $this->query->setOption('data', $data); + + if ($this->query->getOptions('where')) { + $isUpdate = true; + } else { + $isUpdate = $this->query->parseUpdateData($data); + } + + return $isUpdate ? $this->update() : $this->insert(); + } + + /** + * 批量插入记录 + * @access public + * @param array $dataSet 数据集 + * @param integer $limit 每次写入数据限制 + * @return string + */ + public function insertAll(array $dataSet = [], int $limit = null): string + { + $options = $this->query->parseOptions(); + + if (empty($dataSet)) { + $dataSet = $options['data']; + } + + if (empty($limit) && !empty($options['limit'])) { + $limit = $options['limit']; + } + + if ($limit) { + $array = array_chunk($dataSet, $limit, true); + $fetchSql = []; + foreach ($array as $item) { + $sql = $this->builder->insertAll($this->query, $item); + $bind = $this->query->getBind(); + + $fetchSql[] = $this->connection->getRealSql($sql, $bind); + } + + return implode(';', $fetchSql); + } + + $sql = $this->builder->insertAll($this->query, $dataSet); + + return $this->fetch($sql); + } + + /** + * 通过Select方式插入记录 + * @access public + * @param array $fields 要插入的数据表字段名 + * @param string $table 要插入的数据表名 + * @return string + */ + public function selectInsert(array $fields, string $table): string + { + $this->query->parseOptions(); + + $sql = $this->builder->selectInsert($this->query, $fields, $table); + + return $this->fetch($sql); + } + + /** + * 更新记录 + * @access public + * @param mixed $data 数据 + * @return string + */ + public function update(array $data = []): string + { + $options = $this->query->parseOptions(); + + $data = !empty($data) ? $data : $options['data']; + + $pk = $this->query->getPk(); + + if (empty($options['where'])) { + // 如果存在主键数据 则自动作为更新条件 + if (is_string($pk) && isset($data[$pk])) { + $this->query->where($pk, '=', $data[$pk]); + unset($data[$pk]); + } elseif (is_array($pk)) { + // 增加复合主键支持 + foreach ($pk as $field) { + if (isset($data[$field])) { + $this->query->where($field, '=', $data[$field]); + } else { + // 如果缺少复合主键数据则不执行 + throw new Exception('miss complex primary data'); + } + unset($data[$field]); + } + } + + if (empty($this->query->getOptions('where'))) { + // 如果没有任何更新条件则不执行 + throw new Exception('miss update condition'); + } + } + + // 更新数据 + $this->query->setOption('data', $data); + + // 生成UPDATE SQL语句 + $sql = $this->builder->update($this->query); + + return $this->fetch($sql); + } + + /** + * 删除记录 + * @access public + * @param mixed $data 表达式 true 表示强制删除 + * @return string + */ + public function delete($data = null): string + { + $options = $this->query->parseOptions(); + + if (!is_null($data) && true !== $data) { + // AR模式分析主键条件 + $this->query->parsePkWhere($data); + } + + if (!empty($options['soft_delete'])) { + // 软删除 + [$field, $condition] = $options['soft_delete']; + if ($condition) { + $this->query->setOption('soft_delete', null); + $this->query->setOption('data', [$field => $condition]); + // 生成删除SQL语句 + $sql = $this->builder->delete($this->query); + return $this->fetch($sql); + } + } + + // 生成删除SQL语句 + $sql = $this->builder->delete($this->query); + + return $this->fetch($sql); + } + + /** + * 查找记录 返回SQL + * @access public + * @param mixed $data + * @return string + */ + public function select($data = null): string + { + $this->query->parseOptions(); + + if (!is_null($data)) { + // 主键条件分析 + $this->query->parsePkWhere($data); + } + + // 生成查询SQL + $sql = $this->builder->select($this->query); + + return $this->fetch($sql); + } + + /** + * 查找单条记录 返回SQL语句 + * @access public + * @param mixed $data + * @return string + */ + public function find($data = null): string + { + $this->query->parseOptions(); + + if (!is_null($data)) { + // AR模式分析主键条件 + $this->query->parsePkWhere($data); + } + + // 生成查询SQL + $sql = $this->builder->select($this->query, true); + + // 获取实际执行的SQL语句 + return $this->fetch($sql); + } + + /** + * 查找多条记录 如果不存在则抛出异常 + * @access public + * @param mixed $data + * @return string + */ + public function selectOrFail($data = null): string + { + return $this->select($data); + } + + /** + * 查找单条记录 如果不存在则抛出异常 + * @access public + * @param mixed $data + * @return string + */ + public function findOrFail($data = null): string + { + return $this->find($data); + } + + /** + * 查找单条记录 不存在返回空数据(或者空模型) + * @access public + * @param mixed $data 数据 + * @return string + */ + public function findOrEmpty($data = null) + { + return $this->find($data); + } + + /** + * 获取实际的SQL语句 + * @access public + * @param string $sql + * @return string + */ + public function fetch(string $sql): string + { + $bind = $this->query->getBind(); + + return $this->connection->getRealSql($sql, $bind); + } + + /** + * COUNT查询 + * @access public + * @param string $field 字段名 + * @return string + */ + public function count(string $field = '*'): string + { + $options = $this->query->parseOptions(); + + if (!empty($options['group'])) { + // 支持GROUP + $subSql = $this->query->field('count(' . $field . ') AS think_count')->buildSql(); + $query = $this->query->newQuery()->table([$subSql => '_group_count_']); + + return $query->fetchsql()->aggregate('COUNT', '*'); + } else { + return $this->aggregate('COUNT', $field); + } + } + + /** + * SUM查询 + * @access public + * @param string $field 字段名 + * @return string + */ + public function sum(string $field): string + { + return $this->aggregate('SUM', $field); + } + + /** + * MIN查询 + * @access public + * @param string $field 字段名 + * @return string + */ + public function min(string $field): string + { + return $this->aggregate('MIN', $field); + } + + /** + * MAX查询 + * @access public + * @param string $field 字段名 + * @return string + */ + public function max(string $field): string + { + return $this->aggregate('MAX', $field); + } + + /** + * AVG查询 + * @access public + * @param string $field 字段名 + * @return string + */ + public function avg(string $field): string + { + return $this->aggregate('AVG', $field); + } + + public function __call($method, $args) + { + if (strtolower(substr($method, 0, 5)) == 'getby') { + // 根据某个字段获取记录 + $field = Str::snake(substr($method, 5)); + return $this->where($field, '=', $args[0])->find(); + } elseif (strtolower(substr($method, 0, 10)) == 'getfieldby') { + // 根据某个字段获取记录的某个值 + $name = Str::snake(substr($method, 10)); + return $this->where($name, '=', $args[0])->value($args[1]); + } + + $result = call_user_func_array([$this->query, $method], $args); + return $result === $this->query ? $this : $result; + } +} diff --git a/vendor/topthink/think-orm/src/db/Mongo.php b/vendor/topthink/think-orm/src/db/Mongo.php new file mode 100644 index 0000000..cf6e9c4 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/Mongo.php @@ -0,0 +1,708 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); +namespace think\db; + +use MongoDB\Driver\Command; +use MongoDB\Driver\Cursor; +use MongoDB\Driver\Exception\AuthenticationException; +use MongoDB\Driver\Exception\ConnectionException; +use MongoDB\Driver\Exception\InvalidArgumentException; +use MongoDB\Driver\Exception\RuntimeException; +use MongoDB\Driver\ReadPreference; +use MongoDB\Driver\WriteConcern; +use think\db\exception\DbException as Exception; +use think\Paginator; + +class Mongo extends BaseQuery +{ + /** + * 当前数据库连接对象 + * @var \think\db\connector\Mongo + */ + protected $connection; + + /** + * 执行指令 返回数据集 + * @access public + * @param Command $command 指令 + * @param string $dbName + * @param ReadPreference $readPreference readPreference + * @param string|array $typeMap 指定返回的typeMap + * @return mixed + * @throws AuthenticationException + * @throws InvalidArgumentException + * @throws ConnectionException + * @throws RuntimeException + */ + public function command(Command $command, string $dbName = '', ReadPreference $readPreference = null, $typeMap = null) + { + return $this->connection->command($command, $dbName, $readPreference, $typeMap); + } + + /** + * 执行command + * @access public + * @param string|array|object $command 指令 + * @param mixed $extra 额外参数 + * @param string $db 数据库名 + * @return array + */ + public function cmd($command, $extra = null, string $db = ''): array + { + $this->parseOptions(); + return $this->connection->cmd($this, $command, $extra, $db); + } + + /** + * 指定distinct查询 + * @access public + * @param string $field 字段名 + * @return array + */ + public function getDistinct(string $field) + { + $result = $this->cmd('distinct', $field); + return $result[0]['values']; + } + + /** + * 获取数据库的所有collection + * @access public + * @param string $db 数据库名称 留空为当前数据库 + * @throws Exception + */ + public function listCollections(string $db = '') + { + $cursor = $this->cmd('listCollections', null, $db); + $result = []; + foreach ($cursor as $collection) { + $result[] = $collection['name']; + } + + return $result; + } + + /** + * COUNT查询 + * @access public + * @param string $field 字段名 + * @return integer + */ + public function count(string $field = null): int + { + $result = $this->cmd('count'); + + return $result[0]['n']; + } + + /** + * 聚合查询 + * @access public + * @param string $aggregate 聚合指令 + * @param string $field 字段名 + * @param bool $force 强制转为数字类型 + * @return mixed + */ + public function aggregate(string $aggregate, $field, bool $force = false) + { + $result = $this->cmd('aggregate', [strtolower($aggregate), $field]); + $value = $result[0]['aggregate'] ?? 0; + + if ($force) { + $value += 0; + } + + return $value; + } + + /** + * 多聚合操作 + * + * @param array $aggregate 聚合指令, 可以聚合多个参数, 如 ['sum' => 'field1', 'avg' => 'field2'] + * @param array $groupBy 类似mysql里面的group字段, 可以传入多个字段, 如 ['field_a', 'field_b', 'field_c'] + * @return array 查询结果 + */ + public function multiAggregate(array $aggregate, array $groupBy): array + { + $result = $this->cmd('multiAggregate', [$aggregate, $groupBy]); + + foreach ($result as &$row) { + if (isset($row['_id']) && !empty($row['_id'])) { + foreach ($row['_id'] as $k => $v) { + $row[$k] = $v; + } + unset($row['_id']); + } + } + + return $result; + } + + /** + * 字段值增长 + * @access public + * @param string $field 字段名 + * @param float $step 增长值 + * @return $this + */ + public function inc(string $field, float $step = 1) + { + $this->options['data'][$field] = ['$inc', $step]; + + return $this; + } + + /** + * 字段值减少 + * @access public + * @param string $field 字段名 + * @param float $step 减少值 + * @return $this + */ + public function dec(string $field, float $step = 1) + { + return $this->inc($field, -1 * $step); + } + + /** + * 指定当前操作的Collection + * @access public + * @param string $table 表名 + * @return $this + */ + public function table($table) + { + $this->options['table'] = $table; + + return $this; + } + + /** + * table方法的别名 + * @access public + * @param string $collection + * @return $this + */ + public function collection(string $collection) + { + return $this->table($collection); + } + + /** + * 设置typeMap + * @access public + * @param string|array $typeMap + * @return $this + */ + public function typeMap($typeMap) + { + $this->options['typeMap'] = $typeMap; + return $this; + } + + /** + * awaitData + * @access public + * @param bool $awaitData + * @return $this + */ + public function awaitData(bool $awaitData) + { + $this->options['awaitData'] = $awaitData; + return $this; + } + + /** + * batchSize + * @access public + * @param integer $batchSize + * @return $this + */ + public function batchSize(int $batchSize) + { + $this->options['batchSize'] = $batchSize; + return $this; + } + + /** + * exhaust + * @access public + * @param bool $exhaust + * @return $this + */ + public function exhaust(bool $exhaust) + { + $this->options['exhaust'] = $exhaust; + return $this; + } + + /** + * 设置modifiers + * @access public + * @param array $modifiers + * @return $this + */ + public function modifiers(array $modifiers) + { + $this->options['modifiers'] = $modifiers; + return $this; + } + + /** + * 设置noCursorTimeout + * @access public + * @param bool $noCursorTimeout + * @return $this + */ + public function noCursorTimeout(bool $noCursorTimeout) + { + $this->options['noCursorTimeout'] = $noCursorTimeout; + return $this; + } + + /** + * 设置oplogReplay + * @access public + * @param bool $oplogReplay + * @return $this + */ + public function oplogReplay(bool $oplogReplay) + { + $this->options['oplogReplay'] = $oplogReplay; + return $this; + } + + /** + * 设置partial + * @access public + * @param bool $partial + * @return $this + */ + public function partial(bool $partial) + { + $this->options['partial'] = $partial; + return $this; + } + + /** + * maxTimeMS + * @access public + * @param string $maxTimeMS + * @return $this + */ + public function maxTimeMS(string $maxTimeMS) + { + $this->options['maxTimeMS'] = $maxTimeMS; + return $this; + } + + /** + * collation + * @access public + * @param array $collation + * @return $this + */ + public function collation(array $collation) + { + $this->options['collation'] = $collation; + return $this; + } + + /** + * 设置是否REPLACE + * @access public + * @param bool $replace 是否使用REPLACE写入数据 + * @return $this + */ + public function replace(bool $replace = true) + { + return $this; + } + + /** + * 设置返回字段 + * @access public + * @param mixed $field 字段信息 + * @return $this + */ + public function field($field) + { + if (empty($field) || '*' == $field) { + return $this; + } + + if (is_string($field)) { + $field = array_map('trim', explode(',', $field)); + } + + $projection = []; + foreach ($field as $key => $val) { + if (is_numeric($key)) { + $projection[$val] = 1; + } else { + $projection[$key] = $val; + } + } + + $this->options['projection'] = $projection; + + return $this; + } + + /** + * 指定要排除的查询字段 + * @access public + * @param array|string $field 要排除的字段 + * @return $this + */ + public function withoutField($field) + { + if (empty($field) || '*' == $field) { + return $this; + } + + if (is_string($field)) { + $field = array_map('trim', explode(',', $field)); + } + + $projection = []; + foreach ($field as $key => $val) { + if (is_numeric($key)) { + $projection[$val] = 0; + } else { + $projection[$key] = $val; + } + } + + $this->options['projection'] = $projection; + return $this; + } + + /** + * 设置skip + * @access public + * @param integer $skip + * @return $this + */ + public function skip(int $skip) + { + $this->options['skip'] = $skip; + return $this; + } + + /** + * 设置slaveOk + * @access public + * @param bool $slaveOk + * @return $this + */ + public function slaveOk(bool $slaveOk) + { + $this->options['slaveOk'] = $slaveOk; + return $this; + } + + /** + * 指定查询数量 + * @access public + * @param int $offset 起始位置 + * @param int $length 查询数量 + * @return $this + */ + public function limit(int $offset, int $length = null) + { + if (is_null($length)) { + $length = $offset; + $offset = 0; + } + + $this->options['skip'] = $offset; + $this->options['limit'] = $length; + + return $this; + } + + /** + * 设置sort + * @access public + * @param array|string $field + * @param string $order + * @return $this + */ + public function order($field, string $order = '') + { + if (is_array($field)) { + $this->options['sort'] = $field; + } else { + $this->options['sort'][$field] = 'asc' == strtolower($order) ? 1 : -1; + } + return $this; + } + + /** + * 设置tailable + * @access public + * @param bool $tailable + * @return $this + */ + public function tailable(bool $tailable) + { + $this->options['tailable'] = $tailable; + return $this; + } + + /** + * 设置writeConcern对象 + * @access public + * @param WriteConcern $writeConcern + * @return $this + */ + public function writeConcern(WriteConcern $writeConcern) + { + $this->options['writeConcern'] = $writeConcern; + return $this; + } + + /** + * 获取当前数据表的主键 + * @access public + * @return string|array + */ + public function getPk() + { + return $this->pk ?: $this->connection->getConfig('pk'); + } + + /** + * 执行查询但只返回Cursor对象 + * @access public + * @return Cursor + */ + public function getCursor(): Cursor + { + $this->parseOptions(); + + return $this->connection->getCursor($this); + } + + /** + * 获取当前的查询标识 + * @access public + * @param mixed $data 要序列化的数据 + * @return string + */ + public function getQueryGuid($data = null): string + { + return md5($this->getConfig('database') . serialize(var_export($data ?: $this->options, true))); + } + + /** + * 分页查询 + * @access public + * @param int|array $listRows 每页数量 数组表示配置参数 + * @param int|bool $simple 是否简洁模式或者总记录数 + * @return Paginator + * @throws Exception + */ + public function paginate($listRows = null, $simple = false): Paginator + { + if (is_int($simple)) { + $total = $simple; + $simple = false; + } + + $defaultConfig = [ + 'query' => [], //url额外参数 + 'fragment' => '', //url锚点 + 'var_page' => 'page', //分页变量 + 'list_rows' => 15, //每页数量 + ]; + + if (is_array($listRows)) { + $config = array_merge($defaultConfig, $listRows); + $listRows = intval($config['list_rows']); + } else { + $config = $defaultConfig; + $listRows = intval($listRows ?: $config['list_rows']); + } + + $page = isset($config['page']) ? (int) $config['page'] : Paginator::getCurrentPage($config['var_page']); + + $page = $page < 1 ? 1 : $page; + + $config['path'] = $config['path'] ?? Paginator::getCurrentPath(); + + if (!isset($total) && !$simple) { + $options = $this->getOptions(); + + unset($this->options['order'], $this->options['limit'], $this->options['page'], $this->options['field']); + + $total = $this->count(); + $results = $this->options($options)->page($page, $listRows)->select(); + } elseif ($simple) { + $results = $this->limit(($page - 1) * $listRows, $listRows + 1)->select(); + $total = null; + } else { + $results = $this->page($page, $listRows)->select(); + } + + $this->removeOption('limit'); + $this->removeOption('page'); + + return Paginator::make($results, $listRows, $page, $total, $simple, $config); + } + + /** + * 分批数据返回处理 + * @access public + * @param integer $count 每次处理的数据数量 + * @param callable $callback 处理回调方法 + * @param string|array $column 分批处理的字段名 + * @param string $order 字段排序 + * @return bool + * @throws Exception + */ + public function chunk(int $count, callable $callback, $column = null, string $order = 'asc'): bool + { + $options = $this->getOptions(); + $column = $column ?: $this->getPk(); + + if (isset($options['order'])) { + unset($options['order']); + } + + if (is_array($column)) { + $times = 1; + $query = $this->options($options)->page($times, $count); + } else { + $query = $this->options($options)->limit($count); + + if (strpos($column, '.')) { + [$alias, $key] = explode('.', $column); + } else { + $key = $column; + } + } + + $resultSet = $query->order($column, $order)->select(); + + while (count($resultSet) > 0) { + if (false === call_user_func($callback, $resultSet)) { + return false; + } + + if (isset($times)) { + $times++; + $query = $this->options($options)->page($times, $count); + } else { + $end = $resultSet->pop(); + $lastId = is_array($end) ? $end[$key] : $end->getData($key); + + $query = $this->options($options) + ->limit($count) + ->where($column, 'asc' == strtolower($order) ? '>' : '<', $lastId); + } + + $resultSet = $query->order($column, $order)->select(); + } + + return true; + } + + /** + * 分析表达式(可用于查询或者写入操作) + * @access public + * @return array + */ + public function parseOptions(): array + { + $options = $this->options; + + // 获取数据表 + if (empty($options['table'])) { + $options['table'] = $this->getTable(); + } + + foreach (['where', 'data', 'projection', 'filter', 'json', 'with_attr', 'with_relation_attr'] as $name) { + if (!isset($options[$name])) { + $options[$name] = []; + } + } + + $modifiers = empty($options['modifiers']) ? [] : $options['modifiers']; + if (isset($options['comment'])) { + $modifiers['$comment'] = $options['comment']; + } + + if (isset($options['maxTimeMS'])) { + $modifiers['$maxTimeMS'] = $options['maxTimeMS']; + } + + if (!empty($modifiers)) { + $options['modifiers'] = $modifiers; + } + + if (!isset($options['typeMap'])) { + $options['typeMap'] = $this->getConfig('type_map'); + } + + if (!isset($options['limit'])) { + $options['limit'] = 0; + } + + foreach (['master', 'fetch_sql', 'fetch_cursor'] as $name) { + if (!isset($options[$name])) { + $options[$name] = false; + } + } + + if (isset($options['page'])) { + // 根据页数计算limit + [$page, $listRows] = $options['page']; + + $page = $page > 0 ? $page : 1; + $listRows = $listRows > 0 ? $listRows : (is_numeric($options['limit']) ? $options['limit'] : 20); + $offset = $listRows * ($page - 1); + $options['skip'] = intval($offset); + $options['limit'] = intval($listRows); + } + + $this->options = $options; + + return $options; + } + + /** + * 获取字段类型信息 + * @access public + * @return array + */ + public function getFieldsType(): array + { + if (!empty($this->options['field_type'])) { + return $this->options['field_type']; + } + + return []; + } + + /** + * 获取字段类型信息 + * @access public + * @param string $field 字段名 + * @return string|null + */ + public function getFieldType(string $field) + { + $fieldType = $this->getFieldsType(); + + return $fieldType[$field] ?? null; + } +} diff --git a/vendor/topthink/think-orm/src/db/PDOConnection.php b/vendor/topthink/think-orm/src/db/PDOConnection.php new file mode 100644 index 0000000..8f89fe3 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/PDOConnection.php @@ -0,0 +1,1915 @@ + +// +---------------------------------------------------------------------- +declare(strict_types=1); + +namespace think\db; + +use Closure; +use PDO; +use PDOStatement; +use think\db\exception\BindParamException; +use think\db\exception\DbEventException; +use think\db\exception\DbException; +use think\db\exception\PDOException; +use think\Model; + +/** + * 数据库连接基础类 + * @property PDO[] $links + * @property PDO $linkID + * @property PDO $linkRead + * @property PDO $linkWrite + */ +abstract class PDOConnection extends Connection +{ + const PARAM_FLOAT = 21; + + /** + * 数据库连接参数配置 + * @var array + */ + protected $config = [ + // 数据库类型 + 'type' => '', + // 服务器地址 + 'hostname' => '', + // 数据库名 + 'database' => '', + // 用户名 + 'username' => '', + // 密码 + 'password' => '', + // 端口 + 'hostport' => '', + // 连接dsn + 'dsn' => '', + // 数据库连接参数 + 'params' => [], + // 数据库编码默认采用utf8 + 'charset' => 'utf8', + // 数据库表前缀 + 'prefix' => '', + // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器) + 'deploy' => 0, + // 数据库读写是否分离 主从式有效 + 'rw_separate' => false, + // 读写分离后 主服务器数量 + 'master_num' => 1, + // 指定从服务器序号 + 'slave_no' => '', + // 模型写入后自动读取主服务器 + 'read_master' => false, + // 是否严格检查字段是否存在 + 'fields_strict' => true, + // 开启字段缓存 + 'fields_cache' => false, + // 监听SQL + 'trigger_sql' => true, + // Builder类 + 'builder' => '', + // Query类 + 'query' => '', + // 是否需要断线重连 + 'break_reconnect' => false, + // 断线标识字符串 + 'break_match_str' => [], + // 自动参数绑定 + 'auto_param_bind' => true, + ]; + + /** + * PDO操作实例 + * @var PDOStatement + */ + protected $PDOStatement; + + /** + * 当前SQL指令 + * @var string + */ + protected $queryStr = ''; + + /** + * 事务指令数 + * @var int + */ + protected $transTimes = 0; + + /** + * 重连次数 + * @var int + */ + protected $reConnectTimes = 0; + + /** + * 查询结果类型 + * @var int + */ + protected $fetchType = PDO::FETCH_ASSOC; + + /** + * 字段属性大小写 + * @var int + */ + protected $attrCase = PDO::CASE_LOWER; + + /** + * 数据表信息 + * @var array + */ + protected $info = []; + + /** + * 查询开始时间 + * @var float + */ + protected $queryStartTime; + + /** + * PDO连接参数 + * @var array + */ + protected $params = [ + PDO::ATTR_CASE => PDO::CASE_NATURAL, + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL, + PDO::ATTR_STRINGIFY_FETCHES => false, + PDO::ATTR_EMULATE_PREPARES => false, + ]; + + /** + * 参数绑定类型映射 + * @var array + */ + protected $bindType = [ + 'string' => PDO::PARAM_STR, + 'str' => PDO::PARAM_STR, + 'integer' => PDO::PARAM_INT, + 'int' => PDO::PARAM_INT, + 'boolean' => PDO::PARAM_BOOL, + 'bool' => PDO::PARAM_BOOL, + 'float' => self::PARAM_FLOAT, + 'datetime' => PDO::PARAM_STR, + 'timestamp' => PDO::PARAM_STR, + ]; + + /** + * 服务器断线标识字符 + * @var array + */ + protected $breakMatchStr = [ + 'server has gone away', + 'no connection to the server', + 'Lost connection', + 'is dead or not enabled', + 'Error while sending', + 'decryption failed or bad record mac', + 'server closed the connection unexpectedly', + 'SSL connection has been closed unexpectedly', + 'Error writing data to the connection', + 'Resource deadlock avoided', + 'failed with errno', + 'child connection forced to terminate due to client_idle_limit', + 'query_wait_timeout', + 'reset by peer', + 'Physical connection is not usable', + 'TCP Provider: Error code 0x68', + 'ORA-03114', + 'Packets out of order. Expected', + 'Adaptive Server connection failed', + 'Communication link failure', + 'connection is no longer usable', + 'Login timeout expired', + 'SQLSTATE[HY000] [2002] Connection refused', + 'running with the --read-only option so it cannot execute this statement', + 'The connection is broken and recovery is not possible. The connection is marked by the client driver as unrecoverable. No attempt was made to restore the connection.', + 'SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo failed: Try again', + 'SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo failed: Name or service not known', + 'SQLSTATE[HY000]: General error: 7 SSL SYSCALL error: EOF detected', + 'SQLSTATE[HY000] [2002] Connection timed out', + 'SSL: Connection timed out', + 'SQLSTATE[HY000]: General error: 1105 The last transaction was aborted due to Seamless Scaling. Please retry.', + ]; + + /** + * 绑定参数 + * @var array + */ + protected $bind = []; + + /** + * 获取当前连接器类对应的Query类 + * @access public + * @return string + */ + public function getQueryClass(): string + { + return $this->getConfig('query') ?: Query::class; + } + + /** + * 获取当前连接器类对应的Builder类 + * @access public + * @return string + */ + public function getBuilderClass(): string + { + return $this->getConfig('builder') ?: '\\think\\db\\builder\\' . ucfirst($this->getConfig('type')); + } + + /** + * 解析pdo连接的dsn信息 + * @access protected + * @param array $config 连接信息 + * @return string + */ + abstract protected function parseDsn(array $config): string; + + /** + * 取得数据表的字段信息 + * @access public + * @param string $tableName 数据表名称 + * @return array + */ + abstract public function getFields(string $tableName): array; + + /** + * 取得数据库的表信息 + * @access public + * @param string $dbName 数据库名称 + * @return array + */ + abstract public function getTables(string $dbName = ''): array; + + /** + * 对返数据表字段信息进行大小写转换出来 + * @access public + * @param array $info 字段信息 + * @return array + */ + public function fieldCase(array $info): array + { + // 字段大小写转换 + switch ($this->attrCase) { + case PDO::CASE_LOWER: + $info = array_change_key_case($info); + break; + case PDO::CASE_UPPER: + $info = array_change_key_case($info, CASE_UPPER); + break; + case PDO::CASE_NATURAL: + default: + // 不做转换 + } + + return $info; + } + + /** + * 获取字段类型 + * @access protected + * @param string $type 字段类型 + * @return string + */ + protected function getFieldType(string $type): string + { + if (0 === stripos($type, 'set') || 0 === stripos($type, 'enum')) { + $result = 'string'; + } elseif (preg_match('/(double|float|decimal|real|numeric)/is', $type)) { + $result = 'float'; + } elseif (preg_match('/(int|serial|bit)/is', $type)) { + $result = 'int'; + } elseif (preg_match('/bool/is', $type)) { + $result = 'bool'; + } elseif (0 === stripos($type, 'timestamp')) { + $result = 'timestamp'; + } elseif (0 === stripos($type, 'datetime')) { + $result = 'datetime'; + } elseif (0 === stripos($type, 'date')) { + $result = 'date'; + } else { + $result = 'string'; + } + + return $result; + } + + /** + * 获取字段绑定类型 + * @access public + * @param string $type 字段类型 + * @return integer + */ + public function getFieldBindType(string $type): int + { + if (in_array($type, ['integer', 'string', 'float', 'boolean', 'bool', 'int', 'str'])) { + $bind = $this->bindType[$type]; + } elseif (0 === strpos($type, 'set') || 0 === strpos($type, 'enum')) { + $bind = PDO::PARAM_STR; + } elseif (preg_match('/(double|float|decimal|real|numeric)/is', $type)) { + $bind = self::PARAM_FLOAT; + } elseif (preg_match('/(int|serial|bit)/is', $type)) { + $bind = PDO::PARAM_INT; + } elseif (preg_match('/bool/is', $type)) { + $bind = PDO::PARAM_BOOL; + } else { + $bind = PDO::PARAM_STR; + } + + return $bind; + } + + /** + * 获取数据表信息缓存key + * @access protected + * @param string $schema 数据表名称 + * @return string + */ + protected function getSchemaCacheKey(string $schema): string + { + return $this->getConfig('hostname') . ':' . $this->getConfig('hostport') . '@' . $schema; + } + + /** + * @param string $tableName 数据表名称 + * @param bool $force 强制从数据库获取 + * @return array + */ + public function getSchemaInfo(string $tableName, $force = false) + { + if (!strpos($tableName, '.')) { + $schema = $this->getConfig('database') . '.' . $tableName; + } else { + $schema = $tableName; + } + + if (!isset($this->info[$schema]) || $force) { + // 读取字段缓存 + $cacheKey = $this->getSchemaCacheKey($schema); + if ($this->config['fields_cache'] && !empty($this->cache) && !$force) { + $info = $this->cache->get($cacheKey); + } + + if (empty($info)) { + $info = $this->getTableFieldsInfo($tableName); + if (!empty($this->cache) && ($this->config['fields_cache'] || $force)) { + $this->cache->set($cacheKey, $info); + } + } + + $pk = $info['_pk'] ?? null; + $autoinc = $info['_autoinc'] ?? null; + unset($info['_pk'], $info['_autoinc']); + + $bind = []; + foreach ($info as $name => $val) { + $bind[$name] = $this->getFieldBindType($val); + } + + $this->info[$schema] = [ + 'fields' => array_keys($info), + 'type' => $info, + 'bind' => $bind, + 'pk' => $pk, + 'autoinc' => $autoinc, + ]; + } + + return $this->info[$schema]; + } + + /** + * 获取数据表信息 + * @access public + * @param mixed $tableName 数据表名 留空自动获取 + * @param string $fetch 获取信息类型 包括 fields type bind pk + * @return mixed + */ + public function getTableInfo($tableName, string $fetch = '') + { + if (is_array($tableName)) { + $tableName = key($tableName) ?: current($tableName); + } + + if (strpos($tableName, ',') || strpos($tableName, ')')) { + // 多表不获取字段信息 + return []; + } + + [$tableName] = explode(' ', $tableName); + + $info = $this->getSchemaInfo($tableName); + + return $fetch ? $info[$fetch] : $info; + } + + /** + * 获取数据表的字段信息 + * @access public + * @param string $tableName 数据表名 + * @return array + */ + public function getTableFieldsInfo(string $tableName): array + { + $fields = $this->getFields($tableName); + $info = []; + + foreach ($fields as $key => $val) { + // 记录字段类型 + $info[$key] = $this->getFieldType($val['type']); + + if (!empty($val['primary'])) { + $pk[] = $key; + } + + if (!empty($val['autoinc'])) { + $autoinc = $key; + } + } + + if (isset($pk)) { + // 设置主键 + $pk = count($pk) > 1 ? $pk : $pk[0]; + $info['_pk'] = $pk; + } + + if (isset($autoinc)) { + $info['_autoinc'] = $autoinc; + } + + return $info; + } + + /** + * 获取数据表的主键 + * @access public + * @param mixed $tableName 数据表名 + * @return string|array + */ + public function getPk($tableName) + { + return $this->getTableInfo($tableName, 'pk'); + } + + /** + * 获取数据表的自增主键 + * @access public + * @param mixed $tableName 数据表名 + * @return string + */ + public function getAutoInc($tableName) + { + return $this->getTableInfo($tableName, 'autoinc'); + } + + /** + * 获取数据表字段信息 + * @access public + * @param mixed $tableName 数据表名 + * @return array + */ + public function getTableFields($tableName): array + { + return $this->getTableInfo($tableName, 'fields'); + } + + /** + * 获取数据表字段类型 + * @access public + * @param mixed $tableName 数据表名 + * @param string $field 字段名 + * @return array|string + */ + public function getFieldsType($tableName, string $field = null) + { + $result = $this->getTableInfo($tableName, 'type'); + + if ($field && isset($result[$field])) { + return $result[$field]; + } + + return $result; + } + + /** + * 获取数据表绑定信息 + * @access public + * @param mixed $tableName 数据表名 + * @return array + */ + public function getFieldsBind($tableName): array + { + return $this->getTableInfo($tableName, 'bind'); + } + + /** + * 连接数据库方法 + * @access public + * @param array $config 连接参数 + * @param integer $linkNum 连接序号 + * @param array|bool $autoConnection 是否自动连接主数据库(用于分布式) + * @return PDO + * @throws PDOException + */ + public function connect(array $config = [], $linkNum = 0, $autoConnection = false): PDO + { + if (isset($this->links[$linkNum])) { + return $this->links[$linkNum]; + } + + if (empty($config)) { + $config = $this->config; + } else { + $config = array_merge($this->config, $config); + } + + // 连接参数 + if (isset($config['params']) && is_array($config['params'])) { + $params = $config['params'] + $this->params; + } else { + $params = $this->params; + } + + // 记录当前字段属性大小写设置 + $this->attrCase = $params[PDO::ATTR_CASE]; + + if (!empty($config['break_match_str'])) { + $this->breakMatchStr = array_merge($this->breakMatchStr, (array) $config['break_match_str']); + } + + try { + if (empty($config['dsn'])) { + $config['dsn'] = $this->parseDsn($config); + } + + $startTime = microtime(true); + + $this->links[$linkNum] = $this->createPdo($config['dsn'], $config['username'], $config['password'], $params); + + // SQL监控 + if (!empty($config['trigger_sql'])) { + $this->trigger('CONNECT:[ UseTime:' . number_format(microtime(true) - $startTime, 6) . 's ] ' . $config['dsn']); + } + + return $this->links[$linkNum]; + } catch (\PDOException $e) { + if ($autoConnection) { + $this->db->log($e->getMessage(), 'error'); + return $this->connect($autoConnection, $linkNum); + } else { + throw $e; + } + } + } + + /** + * 视图查询 + * @access public + * @param array $args + * @return BaseQuery + */ + public function view(...$args) + { + return $this->newQuery()->view(...$args); + } + + /** + * 创建PDO实例 + * @param $dsn + * @param $username + * @param $password + * @param $params + * @return PDO + */ + protected function createPdo($dsn, $username, $password, $params) + { + return new PDO($dsn, $username, $password, $params); + } + + /** + * 释放查询结果 + * @access public + */ + public function free(): void + { + $this->PDOStatement = null; + } + + /** + * 获取PDO对象 + * @access public + * @return PDO|false + */ + public function getPdo() + { + if (!$this->linkID) { + return false; + } + + return $this->linkID; + } + + /** + * 执行查询 使用生成器返回数据 + * @access public + * @param BaseQuery $query 查询对象 + * @param string $sql sql指令 + * @param Model|null $model 模型对象实例 + * @param null $condition 查询条件 + * @return \Generator + * @throws DbException + */ + public function getCursor(BaseQuery $query, string $sql, $model = null, $condition = null) + { + $this->queryPDOStatement($query, $sql); + + // 返回结果集 + while ($result = $this->PDOStatement->fetch($this->fetchType)) { + if ($model) { + yield $model->newInstance($result, $condition); + } else { + yield $result; + } + } + } + + /** + * 执行查询 返回数据集 + * @access public + * @param string $sql sql指令 + * @param array $bind 参数绑定 + * @param bool $master 主库读取 + * @return array + * @throws DbException + */ + public function query(string $sql, array $bind = [], bool $master = false): array + { + return $this->pdoQuery($this->newQuery()->bind($bind), $sql, $master); + } + + /** + * 执行语句 + * @access public + * @param string $sql sql指令 + * @param array $bind 参数绑定 + * @return int + * @throws DbException + */ + public function execute(string $sql, array $bind = []): int + { + return $this->pdoExecute($this->newQuery()->bind($bind), $sql, true); + } + + /** + * 执行查询 返回数据集 + * @access protected + * @param BaseQuery $query 查询对象 + * @param mixed $sql sql指令 + * @param bool $master 主库读取 + * @return array + * @throws DbException + */ + protected function pdoQuery(BaseQuery $query, $sql, bool $master = null): array + { + // 分析查询表达式 + $query->parseOptions(); + $bind = $query->getBind(); + + if ($query->getOptions('cache')) { + // 检查查询缓存 + $cacheItem = $this->parseCache($query, $query->getOptions('cache')); + if (!$query->getOptions('force_cache')) { + $key = $cacheItem->getKey(); + + $data = $this->cache->get($key); + + if (null !== $data) { + return $data; + } + } + } + + if ($sql instanceof Closure) { + $sql = $sql($query); + $bind = $query->getBind(); + } + + if (!isset($master)) { + $master = $query->getOptions('master') ? true : false; + } + + $procedure = $query->getOptions('procedure') ? true : in_array(strtolower(substr(trim($sql), 0, 4)), ['call', 'exec']); + + $this->getPDOStatement($sql, $bind, $master, $procedure); + + $resultSet = $this->getResult($procedure); + $requireCache = $query->getOptions('cache_always') || !empty($resultSet); + + if (isset($cacheItem) && $requireCache) { + // 缓存数据集 + $cacheItem->set($resultSet); + $this->cacheData($cacheItem); + } + + return $resultSet; + } + + /** + * 执行查询但只返回PDOStatement对象 + * @access public + * @param BaseQuery $query 查询对象 + * @return \PDOStatement + * @throws DbException + */ + public function pdo(BaseQuery $query): PDOStatement + { + // 生成查询SQL + $sql = $this->builder->select($query); + + return $this->queryPDOStatement($query, $sql); + } + + /** + * 执行查询但只返回PDOStatement对象 + * @access public + * @param string $sql sql指令 + * @param array $bind 参数绑定 + * @param bool $master 是否在主服务器读操作 + * @param bool $procedure 是否为存储过程调用 + * @return PDOStatement + * @throws DbException + */ + public function getPDOStatement(string $sql, array $bind = [], bool $master = false, bool $procedure = false): PDOStatement + { + try { + $this->initConnect($this->readMaster ?: $master); + // 记录SQL语句 + $this->queryStr = $sql; + $this->bind = $bind; + + $this->db->updateQueryTimes(); + $this->queryStartTime = microtime(true); + + // 预处理 + $this->PDOStatement = $this->linkID->prepare($sql); + + // 参数绑定 + if ($procedure) { + $this->bindParam($bind); + } else { + $this->bindValue($bind); + } + + // 执行查询 + $this->PDOStatement->execute(); + + // SQL监控 + if (!empty($this->config['trigger_sql'])) { + $this->trigger('', $master); + } + + $this->reConnectTimes = 0; + + return $this->PDOStatement; + } catch (\Throwable | \Exception $e) { + if ($this->transTimes > 0) { + // 事务活动中时不应该进行重试,应直接中断执行,防止造成污染。 + if ($this->isBreak($e)) { + // 尝试对事务计数进行重置 + $this->transTimes = 0; + } + } else { + if ($this->reConnectTimes < 4 && $this->isBreak($e)) { + ++$this->reConnectTimes; + return $this->close()->getPDOStatement($sql, $bind, $master, $procedure); + } + } + + if ($e instanceof \PDOException) { + throw new PDOException($e, $this->config, $this->getLastsql()); + } else { + throw $e; + } + } + } + + /** + * 执行语句 + * @access protected + * @param BaseQuery $query 查询对象 + * @param string $sql sql指令 + * @param bool $origin 是否原生查询 + * @return int + * @throws DbException + */ + protected function pdoExecute(BaseQuery $query, string $sql, bool $origin = false): int + { + if ($origin) { + $query->parseOptions(); + } + + $this->queryPDOStatement($query->master(true), $sql); + + if (!$origin && !empty($this->config['deploy']) && !empty($this->config['read_master'])) { + $this->readMaster = true; + } + + $this->numRows = $this->PDOStatement->rowCount(); + + if ($query->getOptions('cache')) { + // 清理缓存数据 + $cacheItem = $this->parseCache($query, $query->getOptions('cache')); + $key = $cacheItem->getKey(); + $tag = $cacheItem->getTag(); + + if (isset($key) && $this->cache->has($key)) { + $this->cache->delete($key); + } elseif (!empty($tag) && method_exists($this->cache, 'tag')) { + $this->cache->tag($tag)->clear(); + } + } + + return $this->numRows; + } + + /** + * @param BaseQuery $query + * @param string $sql + * @return PDOStatement + * @throws DbException + */ + protected function queryPDOStatement(BaseQuery $query, string $sql): PDOStatement + { + $options = $query->getOptions(); + $bind = $query->getBind(); + $master = !empty($options['master']) ? true : false; + $procedure = !empty($options['procedure']) ? true : in_array(strtolower(substr(trim($sql), 0, 4)), ['call', 'exec']); + + return $this->getPDOStatement($sql, $bind, $master, $procedure); + } + + /** + * 查找单条记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return array + * @throws DbException + */ + public function find(BaseQuery $query): array + { + // 事件回调 + try { + $this->db->trigger('before_find', $query); + } catch (DbEventException $e) { + return []; + } + + // 执行查询 + $resultSet = $this->pdoQuery($query, function ($query) { + return $this->builder->select($query, true); + }); + + return $resultSet[0] ?? []; + } + + /** + * 使用游标查询记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return \Generator + */ + public function cursor(BaseQuery $query) + { + // 分析查询表达式 + $options = $query->parseOptions(); + + // 生成查询SQL + $sql = $this->builder->select($query); + + $condition = $options['where']['AND'] ?? null; + + // 执行查询操作 + return $this->getCursor($query, $sql, $query->getModel(), $condition); + } + + /** + * 查找记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return array + * @throws DbException + */ + public function select(BaseQuery $query): array + { + try { + $this->db->trigger('before_select', $query); + } catch (DbEventException $e) { + return []; + } + + // 执行查询操作 + return $this->pdoQuery($query, function ($query) { + return $this->builder->select($query); + }); + } + + /** + * 插入记录 + * @access public + * @param BaseQuery $query 查询对象 + * @param boolean $getLastInsID 返回自增主键 + * @return mixed + */ + public function insert(BaseQuery $query, bool $getLastInsID = false) + { + // 分析查询表达式 + $options = $query->parseOptions(); + + // 生成SQL语句 + $sql = $this->builder->insert($query); + + // 执行操作 + $result = '' == $sql ? 0 : $this->pdoExecute($query, $sql); + + if ($result) { + $sequence = $options['sequence'] ?? null; + $lastInsId = $this->getLastInsID($query, $sequence); + + $data = $options['data']; + + if ($lastInsId) { + $pk = $query->getAutoInc(); + if ($pk) { + $data[$pk] = $lastInsId; + } + } + + $query->setOption('data', $data); + + $this->db->trigger('after_insert', $query); + + if ($getLastInsID && $lastInsId) { + return $lastInsId; + } + } + + return $result; + } + + /** + * 批量插入记录 + * @access public + * @param BaseQuery $query 查询对象 + * @param mixed $dataSet 数据集 + * @param integer $limit 每次写入数据限制 + * @return integer + * @throws \Exception + * @throws \Throwable + */ + public function insertAll(BaseQuery $query, array $dataSet = [], int $limit = 0): int + { + $query->parseOptions(); + + if (!is_array(reset($dataSet))) { + return 0; + } + + if (0 === $limit && count($dataSet) >= 5000) { + $limit = 1000; + } + + if ($limit) { + // 分批写入 自动启动事务支持 + $this->startTrans(); + + try { + $array = array_chunk($dataSet, $limit, true); + $count = 0; + + foreach ($array as $item) { + $sql = $this->builder->insertAll($query, $item); + $count += $this->pdoExecute($query, $sql); + } + + // 提交事务 + $this->commit(); + } catch (\Exception | \Throwable $e) { + $this->rollback(); + throw $e; + } + + return $count; + } + + $sql = $this->builder->insertAll($query, $dataSet); + + return $this->pdoExecute($query, $sql); + } + + /** + * 批量插入记录 + * @access public + * @param BaseQuery $query 查询对象 + * @param array $keys 键值 + * @param array $values 数据 + * @param integer $limit 每次写入数据限制 + * @return integer + * @throws \Exception + * @throws \Throwable + */ + public function insertAllByKeys(BaseQuery $query, array $keys, array $values, int $limit = 0): int + { + $query->parseOptions(); + + if (0 === $limit && count($values) >= 5000) { + $limit = 1000; + } + + if ($limit) { + // 分批写入 自动启动事务支持 + $this->startTrans(); + + try { + $array = array_chunk($values, $limit, true); + $count = 0; + + foreach ($array as $item) { + $sql = $this->builder->insertAllByKeys($query, $keys, $item); + $count += $this->pdoExecute($query, $sql); + } + + // 提交事务 + $this->commit(); + } catch (\Exception | \Throwable $e) { + $this->rollback(); + throw $e; + } + + return $count; + } + + $sql = $this->builder->insertAllByKeys($query, $keys, $values); + + return $this->pdoExecute($query, $sql); + } + + /** + * 通过Select方式插入记录 + * @access public + * @param BaseQuery $query 查询对象 + * @param array $fields 要插入的数据表字段名 + * @param string $table 要插入的数据表名 + * @return integer + * @throws PDOException + */ + public function selectInsert(BaseQuery $query, array $fields, string $table): int + { + // 分析查询表达式 + $query->parseOptions(); + + $sql = $this->builder->selectInsert($query, $fields, $table); + + return $this->pdoExecute($query, $sql); + } + + /** + * 更新记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return integer + * @throws PDOException + */ + public function update(BaseQuery $query): int + { + $query->parseOptions(); + + // 生成UPDATE SQL语句 + $sql = $this->builder->update($query); + + // 执行操作 + $result = '' == $sql ? 0 : $this->pdoExecute($query, $sql); + + if ($result) { + $this->db->trigger('after_update', $query); + } + + return $result; + } + + /** + * 删除记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return int + * @throws PDOException + */ + public function delete(BaseQuery $query): int + { + // 分析查询表达式 + $query->parseOptions(); + + // 生成删除SQL语句 + $sql = $this->builder->delete($query); + + // 执行操作 + $result = $this->pdoExecute($query, $sql); + + if ($result) { + $this->db->trigger('after_delete', $query); + } + + return $result; + } + + /** + * 得到某个字段的值 + * @access public + * @param BaseQuery $query 查询对象 + * @param string $field 字段名 + * @param mixed $default 默认值 + * @param bool $one 返回一个值 + * @return mixed + */ + public function value(BaseQuery $query, string $field, $default = null, bool $one = true) + { + $options = $query->parseOptions(); + + if (isset($options['field'])) { + $query->removeOption('field'); + } + + if (isset($options['group'])) { + $query->group(''); + } + + $query->setOption('field', (array) $field); + + if (!empty($options['cache'])) { + $cacheItem = $this->parseCache($query, $options['cache'], 'value'); + + if (!$query->getOptions('force_cache')) { + $key = $cacheItem->getKey(); + + if ($this->cache->has($key)) { + return $this->cache->get($key); + } + } + } + + // 生成查询SQL + $sql = $this->builder->select($query, $one); + + if (isset($options['field'])) { + $query->setOption('field', $options['field']); + } else { + $query->removeOption('field'); + } + + if (isset($options['group'])) { + $query->setOption('group', $options['group']); + } + + // 执行查询操作 + $pdo = $this->getPDOStatement($sql, $query->getBind(), $options['master']); + + $result = $pdo->fetchColumn(); + + if (isset($cacheItem)) { + // 缓存数据 + $cacheItem->set($result); + $this->cacheData($cacheItem); + } + + return false !== $result ? $result : $default; + } + + /** + * 得到某个字段的值 + * @access public + * @param BaseQuery $query 查询对象 + * @param string $aggregate 聚合方法 + * @param mixed $field 字段名 + * @param bool $force 强制转为数字类型 + * @return mixed + */ + public function aggregate(BaseQuery $query, string $aggregate, $field, bool $force = false) + { + if (is_string($field) && 0 === stripos($field, 'DISTINCT ')) { + [$distinct, $field] = explode(' ', $field); + } + + $field = $aggregate . '(' . (!empty($distinct) ? 'DISTINCT ' : '') . $this->builder->parseKey($query, $field, true) . ') AS think_' . strtolower($aggregate); + + $result = $this->value($query, $field, 0); + + return $force ? (float) $result : $result; + } + + /** + * 得到某个列的数组 + * @access public + * @param BaseQuery $query 查询对象 + * @param string|array $column 字段名 多个字段用逗号分隔 + * @param string $key 索引 + * @return array + */ + public function column(BaseQuery $query, $column, string $key = ''): array + { + $options = $query->parseOptions(); + + if (isset($options['field'])) { + $query->removeOption('field'); + } + + if (empty($key) || trim($key) === '') { + $key = null; + } + + if (is_string($column)) { + $column = trim($column); + if ('*' !== $column) { + $column = array_map('trim', explode(',', $column)); + } + } elseif (is_array($column)) { + if (in_array('*', $column)) { + $column = '*'; + } + } else { + throw new DbException('not support type'); + } + + $field = $column; + if ('*' !== $column && $key && !in_array($key, $column)) { + $field[] = $key; + } + + $query->setOption('field', $field); + + if (!empty($options['cache'])) { + // 判断查询缓存 + $cacheItem = $this->parseCache($query, $options['cache'], 'column'); + if (!$query->getOptions('force_cache')) { + $name = $cacheItem->getKey(); + + if ($this->cache->has($name)) { + return $this->cache->get($name); + } + } + } + + // 生成查询SQL + $sql = $this->builder->select($query); + + if (isset($options['field'])) { + $query->setOption('field', $options['field']); + } else { + $query->removeOption('field'); + } + + // 执行查询操作 + $pdo = $this->getPDOStatement($sql, $query->getBind(), $options['master']); + $resultSet = $pdo->fetchAll(PDO::FETCH_ASSOC); + + if (is_string($key) && strpos($key, '.')) { + [$alias, $key] = explode('.', $key); + } + + if (empty($resultSet)) { + $result = []; + } elseif ('*' !== $column && count($column) === 1) { + $column = array_shift($column); + if (strpos($column, ' ')) { + $column = substr(strrchr(trim($column), ' '), 1); + } + + if (strpos($column, '.')) { + [$alias, $column] = explode('.', $column); + } + + if (strpos($column, '->')) { + $column = $this->builder->parseKey($query, $column); + } + + $result = array_column($resultSet, $column, $key); + } elseif ($key) { + $result = array_column($resultSet, null, $key); + } else { + $result = $resultSet; + } + + if (isset($cacheItem)) { + // 缓存数据 + $cacheItem->set($result); + $this->cacheData($cacheItem); + } + + return $result; + } + + /** + * 根据参数绑定组装最终的SQL语句 便于调试 + * @access public + * @param string $sql 带参数绑定的sql语句 + * @param array $bind 参数绑定列表 + * @return string + */ + public function getRealSql(string $sql, array $bind = []): string + { + foreach ($bind as $key => $val) { + $value = strval(is_array($val) ? $val[0] : $val); + $type = is_array($val) ? $val[1] : PDO::PARAM_STR; + + if (self::PARAM_FLOAT == $type || PDO::PARAM_STR == $type) { + $value = '\'' . addslashes($value) . '\''; + } elseif (PDO::PARAM_INT == $type && '' === $value) { + $value = '0'; + } + + // 判断占位符 + $sql = is_numeric($key) ? + substr_replace($sql, $value, strpos($sql, '?'), 1) : + substr_replace($sql, $value, strpos($sql, ':' . $key), strlen(':' . $key)); + } + + return rtrim($sql); + } + + /** + * 参数绑定 + * 支持 ['name'=>'value','id'=>123] 对应命名占位符 + * 或者 ['value',123] 对应问号占位符 + * @access public + * @param array $bind 要绑定的参数列表 + * @return void + * @throws BindParamException + */ + protected function bindValue(array $bind = []): void + { + foreach ($bind as $key => $val) { + // 占位符 + $param = is_numeric($key) ? $key + 1 : ':' . $key; + + if (is_array($val)) { + if (PDO::PARAM_INT == $val[1] && '' === $val[0]) { + $val[0] = 0; + } elseif (self::PARAM_FLOAT == $val[1]) { + $val[0] = is_string($val[0]) ? (float) $val[0] : $val[0]; + $val[1] = PDO::PARAM_STR; + } + + $result = $this->PDOStatement->bindValue($param, $val[0], $val[1]); + } else { + $result = $this->PDOStatement->bindValue($param, $val); + } + + if (!$result) { + throw new BindParamException( + "Error occurred when binding parameters '{$param}'", + $this->config, + $this->getLastsql(), + $bind + ); + } + } + } + + /** + * 存储过程的输入输出参数绑定 + * @access public + * @param array $bind 要绑定的参数列表 + * @return void + * @throws BindParamException + */ + protected function bindParam(array $bind): void + { + foreach ($bind as $key => $val) { + $param = is_numeric($key) ? $key + 1 : ':' . $key; + + if (is_array($val)) { + array_unshift($val, $param); + $result = call_user_func_array([$this->PDOStatement, 'bindParam'], $val); + } else { + $result = $this->PDOStatement->bindValue($param, $val); + } + + if (!$result) { + $param = array_shift($val); + + throw new BindParamException( + "Error occurred when binding parameters '{$param}'", + $this->config, + $this->getLastsql(), + $bind + ); + } + } + } + + /** + * 获得数据集数组 + * @access protected + * @param bool $procedure 是否存储过程 + * @return array + */ + protected function getResult(bool $procedure = false): array + { + if ($procedure) { + // 存储过程返回结果 + return $this->procedure(); + } + + $result = $this->PDOStatement->fetchAll($this->fetchType); + + $this->numRows = count($result); + + return $result; + } + + /** + * 获得存储过程数据集 + * @access protected + * @return array + */ + protected function procedure(): array + { + $item = []; + + do { + $result = $this->getResult(); + if (!empty($result)) { + $item[] = $result; + } + } while ($this->PDOStatement->nextRowset()); + + $this->numRows = count($item); + + return $item; + } + + /** + * 执行数据库事务 + * @access public + * @param callable $callback 数据操作方法回调 + * @return mixed + * @throws PDOException + * @throws \Exception + * @throws \Throwable + */ + public function transaction(callable $callback) + { + $this->startTrans(); + + try { + $result = null; + if (is_callable($callback)) { + $result = $callback($this); + } + + $this->commit(); + return $result; + } catch (\Exception | \Throwable $e) { + $this->rollback(); + throw $e; + } + } + + /** + * 启动事务 + * @access public + * @return void + * @throws \PDOException + * @throws \Exception + */ + public function startTrans(): void + { + try { + $this->initConnect(true); + + ++$this->transTimes; + + if (1 == $this->transTimes) { + $this->linkID->beginTransaction(); + } elseif ($this->transTimes > 1 && $this->supportSavepoint() && $this->linkID->inTransaction()) { + $this->linkID->exec( + $this->parseSavepoint('trans' . $this->transTimes) + ); + } + $this->reConnectTimes = 0; + } catch (\Throwable | \Exception $e) { + if (1 === $this->transTimes && $this->reConnectTimes < 4 && $this->isBreak($e)) { + --$this->transTimes; + ++$this->reConnectTimes; + $this->close()->startTrans(); + } else { + if ($this->isBreak($e)) { + // 尝试对事务计数进行重置 + $this->transTimes = 0; + } + throw $e; + } + } + } + + /** + * 用于非自动提交状态下面的查询提交 + * @access public + * @return void + * @throws \PDOException + */ + public function commit(): void + { + $this->initConnect(true); + + if (1 == $this->transTimes && $this->linkID->inTransaction()) { + $this->linkID->commit(); + } + + --$this->transTimes; + } + + /** + * 事务回滚 + * @access public + * @return void + * @throws \PDOException + */ + public function rollback(): void + { + $this->initConnect(true); + + if ($this->linkID->inTransaction()) { + if (1 == $this->transTimes) { + $this->linkID->rollBack(); + } elseif ($this->transTimes > 1 && $this->supportSavepoint()) { + $this->linkID->exec( + $this->parseSavepointRollBack('trans' . $this->transTimes) + ); + } + } + + $this->transTimes = max(0, $this->transTimes - 1); + } + + /** + * 是否支持事务嵌套 + * @return bool + */ + protected function supportSavepoint(): bool + { + return false; + } + + /** + * 生成定义保存点的SQL + * @access protected + * @param string $name 标识 + * @return string + */ + protected function parseSavepoint(string $name): string + { + return 'SAVEPOINT ' . $name; + } + + /** + * 生成回滚到保存点的SQL + * @access protected + * @param string $name 标识 + * @return string + */ + protected function parseSavepointRollBack(string $name): string + { + return 'ROLLBACK TO SAVEPOINT ' . $name; + } + + /** + * 批处理执行SQL语句 + * 批处理的指令都认为是execute操作 + * @access public + * @param BaseQuery $query 查询对象 + * @param array $sqlArray SQL批处理指令 + * @return bool + */ + public function batchQuery(BaseQuery $query, array $sqlArray = []): bool + { + // 自动启动事务支持 + $this->startTrans(); + + try { + foreach ($sqlArray as $sql) { + $this->pdoExecute($query, $sql); + } + // 提交事务 + $this->commit(); + } catch (\Exception $e) { + $this->rollback(); + throw $e; + } + + return true; + } + + /** + * 关闭数据库(或者重新连接) + * @access public + * @return $this + */ + public function close() + { + $this->linkID = null; + $this->linkWrite = null; + $this->linkRead = null; + $this->links = []; + $this->transTimes = 0; + + $this->free(); + + return $this; + } + + /** + * 是否断线 + * @access protected + * @param \PDOException|\Exception $e 异常对象 + * @return bool + */ + protected function isBreak($e): bool + { + if (!$this->config['break_reconnect']) { + return false; + } + + $error = $e->getMessage(); + + foreach ($this->breakMatchStr as $msg) { + if (false !== stripos($error, $msg)) { + return true; + } + } + + return false; + } + + /** + * 获取最近一次查询的sql语句 + * @access public + * @return string + */ + public function getLastSql(): string + { + return $this->getRealSql($this->queryStr, $this->bind); + } + + /** + * 获取最近插入的ID + * @access public + * @param BaseQuery $query 查询对象 + * @param string $sequence 自增序列名 + * @return mixed + */ + public function getLastInsID(BaseQuery $query, string $sequence = null) + { + try { + $insertId = $this->linkID->lastInsertId($sequence); + } catch (\Exception $e) { + $insertId = ''; + } + + return $this->autoInsIDType($query, $insertId); + } + + /** + * 获取最近插入的ID + * @access public + * @param BaseQuery $query 查询对象 + * @param string $insertId 自增ID + * @return mixed + */ + protected function autoInsIDType(BaseQuery $query, string $insertId) + { + $pk = $query->getAutoInc(); + + if ($pk) { + $type = $this->getFieldsBind($query->getTable())[$pk]; + + if (PDO::PARAM_INT == $type) { + $insertId = (int) $insertId; + } elseif (self::PARAM_FLOAT == $type) { + $insertId = (float) $insertId; + } + } + + return $insertId; + } + + /** + * 获取最近的错误信息 + * @access public + * @return string + */ + public function getError(): string + { + if ($this->PDOStatement) { + $error = $this->PDOStatement->errorInfo(); + $error = $error[1] . ':' . $error[2]; + } else { + $error = ''; + } + + if ('' != $this->queryStr) { + $error .= "\n [ SQL语句 ] : " . $this->getLastsql(); + } + + return $error; + } + + /** + * 初始化数据库连接 + * @access protected + * @param boolean $master 是否主服务器 + * @return void + */ + protected function initConnect(bool $master = true): void + { + if (!empty($this->config['deploy'])) { + // 采用分布式数据库 + if ($master || $this->transTimes) { + if (!$this->linkWrite) { + $this->linkWrite = $this->multiConnect(true); + } + + $this->linkID = $this->linkWrite; + } else { + if (!$this->linkRead) { + $this->linkRead = $this->multiConnect(false); + } + + $this->linkID = $this->linkRead; + } + } elseif (!$this->linkID) { + // 默认单数据库 + $this->linkID = $this->connect(); + } + } + + /** + * 连接分布式服务器 + * @access protected + * @param boolean $master 主服务器 + * @return PDO + */ + protected function multiConnect(bool $master = false): PDO + { + $config = []; + + // 分布式数据库配置解析 + foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn', 'charset'] as $name) { + $config[$name] = is_string($this->config[$name]) ? explode(',', $this->config[$name]) : $this->config[$name]; + } + + // 主服务器序号 + $m = floor(mt_rand(0, $this->config['master_num'] - 1)); + + if ($this->config['rw_separate']) { + // 主从式采用读写分离 + if ($master) // 主服务器写入 + { + $r = $m; + } elseif (is_numeric($this->config['slave_no'])) { + // 指定服务器读 + $r = $this->config['slave_no']; + } else { + // 读操作连接从服务器 每次随机连接的数据库 + $r = floor(mt_rand($this->config['master_num'], count($config['hostname']) - 1)); + } + } else { + // 读写操作不区分服务器 每次随机连接的数据库 + $r = floor(mt_rand(0, count($config['hostname']) - 1)); + } + $dbMaster = false; + + if ($m != $r) { + $dbMaster = []; + foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn', 'charset'] as $name) { + $dbMaster[$name] = $config[$name][$m] ?? $config[$name][0]; + } + } + + $dbConfig = []; + + foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn', 'charset'] as $name) { + $dbConfig[$name] = $config[$name][$r] ?? $config[$name][0]; + } + + return $this->connect($dbConfig, $r, $r == $m ? false : $dbMaster); + } + + /** + * 获取数据库的唯一标识 + * @access public + * @param string $suffix 标识后缀 + * @return string + */ + public function getUniqueXid(string $suffix = ''): string + { + return $this->config['hostname'] . '_' . $this->config['database'] . $suffix; + } + + /** + * 执行数据库Xa事务 + * @access public + * @param callable $callback 数据操作方法回调 + * @param array $dbs 多个查询对象或者连接对象 + * @return mixed + * @throws PDOException + * @throws \Exception + * @throws \Throwable + */ + public function transactionXa(callable $callback, array $dbs = []) + { + $xid = uniqid('xa'); + + if (empty($dbs)) { + $dbs[] = $this; + } + + foreach ($dbs as $key => $db) { + if ($db instanceof BaseQuery) { + $db = $db->getConnection(); + + $dbs[$key] = $db; + } + + $db->startTransXa($db->getUniqueXid('_' . $xid) ); + } + + try { + $result = null; + if (is_callable($callback)) { + $result = $callback($this); + } + + foreach ($dbs as $db) { + $db->prepareXa($db->getUniqueXid('_' . $xid)); + } + + foreach ($dbs as $db) { + $db->commitXa($db->getUniqueXid('_' . $xid) ); + } + + return $result; + } catch (\Exception | \Throwable $e) { + foreach ($dbs as $db) { + $db->rollbackXa($db->getUniqueXid('_' . $xid) ); + } + throw $e; + } + } + + /** + * 启动XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function startTransXa(string $xid): void + { + } + + /** + * 预编译XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function prepareXa(string $xid): void + { + } + + /** + * 提交XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function commitXa(string $xid): void + { + } + + /** + * 回滚XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function rollbackXa(string $xid): void + { + } +} diff --git a/vendor/topthink/think-orm/src/db/Query.php b/vendor/topthink/think-orm/src/db/Query.php new file mode 100644 index 0000000..35e66bc --- /dev/null +++ b/vendor/topthink/think-orm/src/db/Query.php @@ -0,0 +1,450 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db; + +use PDOStatement; + +/** + * PDO数据查询类 + */ +class Query extends BaseQuery +{ + use concern\JoinAndViewQuery; + use concern\ParamsBind; + use concern\TableFieldInfo; + + /** + * 表达式方式指定Field排序 + * @access public + * @param string $field 排序字段 + * @param array $bind 参数绑定 + * @return $this + */ + public function orderRaw(string $field, array $bind = []) + { + $this->options['order'][] = new Raw($field, $bind); + + return $this; + } + + /** + * 表达式方式指定查询字段 + * @access public + * @param string $field 字段名 + * @return $this + */ + public function fieldRaw(string $field) + { + $this->options['field'][] = new Raw($field); + + return $this; + } + + /** + * 指定Field排序 orderField('id',[1,2,3],'desc') + * @access public + * @param string $field 排序字段 + * @param array $values 排序值 + * @param string $order 排序 desc/asc + * @return $this + */ + public function orderField(string $field, array $values, string $order = '') + { + if (!empty($values)) { + $values['sort'] = $order; + + $this->options['order'][$field] = $values; + } + + return $this; + } + + /** + * 随机排序 + * @access public + * @return $this + */ + public function orderRand() + { + $this->options['order'][] = '[rand]'; + return $this; + } + + /** + * 使用表达式设置数据 + * @access public + * @param string $field 字段名 + * @param string $value 字段值 + * @return $this + */ + public function exp(string $field, string $value) + { + $this->options['data'][$field] = new Raw($value); + return $this; + } + + /** + * 表达式方式指定当前操作的数据表 + * @access public + * @param mixed $table 表名 + * @return $this + */ + public function tableRaw(string $table) + { + $this->options['table'] = new Raw($table); + + return $this; + } + + /** + * 获取执行的SQL语句而不进行实际的查询 + * @access public + * @param bool $fetch 是否返回sql + * @return $this|Fetch + */ + public function fetchSql(bool $fetch = true) + { + $this->options['fetch_sql'] = $fetch; + + if ($fetch) { + return new Fetch($this); + } + + return $this; + } + + /** + * 批处理执行SQL语句 + * 批处理的指令都认为是execute操作 + * @access public + * @param array $sql SQL批处理指令 + * @return bool + */ + public function batchQuery(array $sql = []): bool + { + return $this->connection->batchQuery($this, $sql); + } + + /** + * USING支持 用于多表删除 + * @access public + * @param mixed $using USING + * @return $this + */ + public function using($using) + { + $this->options['using'] = $using; + return $this; + } + + /** + * 存储过程调用 + * @access public + * @param bool $procedure 是否为存储过程查询 + * @return $this + */ + public function procedure(bool $procedure = true) + { + $this->options['procedure'] = $procedure; + return $this; + } + + /** + * 指定group查询 + * @access public + * @param string|array $group GROUP + * @return $this + */ + public function group($group) + { + $this->options['group'] = $group; + return $this; + } + + /** + * 指定having查询 + * @access public + * @param string $having having + * @return $this + */ + public function having(string $having) + { + $this->options['having'] = $having; + return $this; + } + + /** + * 指定distinct查询 + * @access public + * @param bool $distinct 是否唯一 + * @return $this + */ + public function distinct(bool $distinct = true) + { + $this->options['distinct'] = $distinct; + return $this; + } + + /** + * 指定强制索引 + * @access public + * @param string $force 索引名称 + * @return $this + */ + public function force(string $force) + { + $this->options['force'] = $force; + return $this; + } + + /** + * 查询注释 + * @access public + * @param string $comment 注释 + * @return $this + */ + public function comment(string $comment) + { + $this->options['comment'] = $comment; + return $this; + } + + /** + * 设置是否REPLACE + * @access public + * @param bool $replace 是否使用REPLACE写入数据 + * @return $this + */ + public function replace(bool $replace = true) + { + $this->options['replace'] = $replace; + return $this; + } + + /** + * 设置当前查询所在的分区 + * @access public + * @param string|array $partition 分区名称 + * @return $this + */ + public function partition($partition) + { + $this->options['partition'] = $partition; + return $this; + } + + /** + * 设置DUPLICATE + * @access public + * @param array|string|Raw $duplicate DUPLICATE信息 + * @return $this + */ + public function duplicate($duplicate) + { + $this->options['duplicate'] = $duplicate; + return $this; + } + + /** + * 设置查询的额外参数 + * @access public + * @param string $extra 额外信息 + * @return $this + */ + public function extra(string $extra) + { + $this->options['extra'] = $extra; + return $this; + } + + /** + * 创建子查询SQL + * @access public + * @param bool $sub 是否添加括号 + * @return string + * @throws Exception + */ + public function buildSql(bool $sub = true): string + { + return $sub ? '( ' . $this->fetchSql()->select() . ' )' : $this->fetchSql()->select(); + } + + /** + * 获取当前数据表的主键 + * @access public + * @return string|array + */ + public function getPk() + { + if (empty($this->pk)) { + $this->pk = $this->connection->getPk($this->getTable()); + } + + return $this->pk; + } + + /** + * 指定数据表自增主键 + * @access public + * @param string $autoinc 自增键 + * @return $this + */ + public function autoinc(string $autoinc) + { + $this->autoinc = $autoinc; + return $this; + } + + /** + * 获取当前数据表的自增主键 + * @access public + * @return string|null + */ + public function getAutoInc() + { + $tableName = $this->getTable(); + + if (empty($this->autoinc) && $tableName) { + $this->autoinc = $this->connection->getAutoInc($tableName); + } + + return $this->autoinc; + } + + /** + * 字段值增长 + * @access public + * @param string $field 字段名 + * @param float $step 增长值 + * @return $this + */ + public function inc(string $field, float $step = 1) + { + $this->options['data'][$field] = ['INC', $step]; + + return $this; + } + + /** + * 字段值减少 + * @access public + * @param string $field 字段名 + * @param float $step 增长值 + * @return $this + */ + public function dec(string $field, float $step = 1) + { + $this->options['data'][$field] = ['DEC', $step]; + return $this; + } + + /** + * 获取当前的查询标识 + * @access public + * @param mixed $data 要序列化的数据 + * @return string + */ + public function getQueryGuid($data = null): string + { + return md5($this->getConfig('database') . serialize(var_export($data ?: $this->options, true)) . serialize($this->getBind(false))); + } + + /** + * 执行查询但只返回PDOStatement对象 + * @access public + * @return PDOStatement + */ + public function getPdo(): PDOStatement + { + return $this->connection->pdo($this); + } + + /** + * 使用游标查找记录 + * @access public + * @param mixed $data 数据 + * @return \Generator + */ + public function cursor($data = null) + { + if (!is_null($data)) { + // 主键条件分析 + $this->parsePkWhere($data); + } + + $this->options['data'] = $data; + + $connection = clone $this->connection; + + return $connection->cursor($this); + } + + /** + * 分批数据返回处理 + * @access public + * @param integer $count 每次处理的数据数量 + * @param callable $callback 处理回调方法 + * @param string|array $column 分批处理的字段名 + * @param string $order 字段排序 + * @return bool + * @throws Exception + */ + public function chunk(int $count, callable $callback, $column = null, string $order = 'asc'): bool + { + $options = $this->getOptions(); + $column = $column ?: $this->getPk(); + + if (isset($options['order'])) { + unset($options['order']); + } + + $bind = $this->bind; + + if (is_array($column)) { + $times = 1; + $query = $this->options($options)->page($times, $count); + } else { + $query = $this->options($options)->limit($count); + + if (strpos($column, '.')) { + [$alias, $key] = explode('.', $column); + } else { + $key = $column; + } + } + + $resultSet = $query->order($column, $order)->select(); + + while (count($resultSet) > 0) { + if (false === call_user_func($callback, $resultSet)) { + return false; + } + + if (isset($times)) { + $times++; + $query = $this->options($options)->page($times, $count); + } else { + $end = $resultSet->pop(); + $lastId = is_array($end) ? $end[$key] : $end->getData($key); + + $query = $this->options($options) + ->limit($count) + ->where($column, 'asc' == strtolower($order) ? '>' : '<', $lastId); + } + + $resultSet = $query->bind($bind)->order($column, $order)->select(); + } + + return true; + } +} diff --git a/vendor/topthink/think-orm/src/db/Raw.php b/vendor/topthink/think-orm/src/db/Raw.php new file mode 100644 index 0000000..b956ff6 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/Raw.php @@ -0,0 +1,67 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db; + +/** + * SQL Raw + */ +class Raw +{ + /** + * 查询表达式 + * + * @var string + */ + protected $value; + + /** + * 参数绑定 + * + * @var array + */ + protected $bind = []; + + /** + * 创建一个查询表达式 + * + * @param string $value + * @param array $bind + * @return void + */ + public function __construct(string $value, array $bind = []) + { + $this->value = $value; + $this->bind = $bind; + } + + /** + * 获取表达式 + * + * @return string + */ + public function getValue(): string + { + return $this->value; + } + + /** + * 获取参数绑定 + * + * @return string + */ + public function getBind(): array + { + return $this->bind; + } + +} diff --git a/vendor/topthink/think-orm/src/db/Where.php b/vendor/topthink/think-orm/src/db/Where.php new file mode 100644 index 0000000..0880460 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/Where.php @@ -0,0 +1,182 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db; + +use ArrayAccess; + +/** + * 数组查询对象 + */ +class Where implements ArrayAccess +{ + /** + * 查询表达式 + * @var array + */ + protected $where = []; + + /** + * 是否需要把查询条件两边增加括号 + * @var bool + */ + protected $enclose = false; + + /** + * 创建一个查询表达式 + * + * @param array $where 查询条件数组 + * @param bool $enclose 是否增加括号 + */ + public function __construct(array $where = [], bool $enclose = false) + { + $this->where = $where; + $this->enclose = $enclose; + } + + /** + * 设置是否添加括号 + * @access public + * @param bool $enclose + * @return $this + */ + public function enclose(bool $enclose = true) + { + $this->enclose = $enclose; + return $this; + } + + /** + * 解析为Query对象可识别的查询条件数组 + * @access public + * @return array + */ + public function parse(): array + { + $where = []; + + foreach ($this->where as $key => $val) { + if ($val instanceof Raw) { + $where[] = [$key, 'exp', $val]; + } elseif (is_null($val)) { + $where[] = [$key, 'NULL', '']; + } elseif (is_array($val)) { + $where[] = $this->parseItem($key, $val); + } else { + $where[] = [$key, '=', $val]; + } + } + + return $this->enclose ? [$where] : $where; + } + + /** + * 分析查询表达式 + * @access protected + * @param string $field 查询字段 + * @param array $where 查询条件 + * @return array + */ + protected function parseItem(string $field, array $where = []): array + { + $op = $where[0]; + $condition = $where[1] ?? null; + + if (is_array($op)) { + // 同一字段多条件查询 + array_unshift($where, $field); + } elseif (is_null($condition)) { + if (is_string($op) && in_array(strtoupper($op), ['NULL', 'NOTNULL', 'NOT NULL'], true)) { + // null查询 + $where = [$field, $op, '']; + } elseif (is_null($op) || '=' == $op) { + $where = [$field, 'NULL', '']; + } elseif ('<>' == $op) { + $where = [$field, 'NOTNULL', '']; + } else { + // 字段相等查询 + $where = [$field, '=', $op]; + } + } else { + $where = [$field, $op, $condition]; + } + + return $where; + } + + /** + * 修改器 设置数据对象的值 + * @access public + * @param string $name 名称 + * @param mixed $value 值 + * @return void + */ + public function __set($name, $value) + { + $this->where[$name] = $value; + } + + /** + * 获取器 获取数据对象的值 + * @access public + * @param string $name 名称 + * @return mixed + */ + public function __get($name) + { + return $this->where[$name] ?? null; + } + + /** + * 检测数据对象的值 + * @access public + * @param string $name 名称 + * @return bool + */ + public function __isset($name) + { + return isset($this->where[$name]); + } + + /** + * 销毁数据对象的值 + * @access public + * @param string $name 名称 + * @return void + */ + public function __unset($name) + { + unset($this->where[$name]); + } + + // ArrayAccess + public function offsetSet($name, $value) + { + $this->__set($name, $value); + } + + public function offsetExists($name) + { + return $this->__isset($name); + } + + public function offsetUnset($name) + { + $this->__unset($name); + } + + public function offsetGet($name) + { + return $this->__get($name); + } + +} diff --git a/vendor/topthink/think-orm/src/db/builder/Mongo.php b/vendor/topthink/think-orm/src/db/builder/Mongo.php new file mode 100644 index 0000000..85ee551 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/builder/Mongo.php @@ -0,0 +1,673 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); +namespace think\db\builder; + +use MongoDB\BSON\Javascript; +use MongoDB\BSON\ObjectID; +use MongoDB\BSON\Regex; +use MongoDB\Driver\BulkWrite; +use MongoDB\Driver\Command; +use MongoDB\Driver\Exception\InvalidArgumentException; +use MongoDB\Driver\Query as MongoQuery; +use think\db\connector\Mongo as Connection; +use think\db\exception\DbException as Exception; +use think\db\Mongo as Query; + +class Mongo +{ + // connection对象实例 + protected $connection; + // 最后插入ID + protected $insertId = []; + // 查询表达式 + protected $exp = ['<>' => 'ne', '=' => 'eq', '>' => 'gt', '>=' => 'gte', '<' => 'lt', '<=' => 'lte', 'in' => 'in', 'not in' => 'nin', 'nin' => 'nin', 'mod' => 'mod', 'exists' => 'exists', 'null' => 'null', 'notnull' => 'not null', 'not null' => 'not null', 'regex' => 'regex', 'type' => 'type', 'all' => 'all', '> time' => '> time', '< time' => '< time', 'between' => 'between', 'not between' => 'not between', 'between time' => 'between time', 'not between time' => 'not between time', 'notbetween time' => 'not between time', 'like' => 'like', 'near' => 'near', 'size' => 'size']; + + /** + * 架构函数 + * @access public + * @param Connection $connection 数据库连接对象实例 + */ + public function __construct(Connection $connection) + { + $this->connection = $connection; + } + + /** + * 获取当前的连接对象实例 + * @access public + * @return Connection + */ + public function getConnection(): Connection + { + return $this->connection; + } + + /** + * key分析 + * @access protected + * @param string $key + * @return string + */ + protected function parseKey(Query $query, string $key): string + { + if (0 === strpos($key, '__TABLE__.')) { + [$collection, $key] = explode('.', $key, 2); + } + + if ('id' == $key && $this->connection->getConfig('pk_convert_id')) { + $key = '_id'; + } + + return trim($key); + } + + /** + * value分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $value + * @param string $field + * @return string + */ + protected function parseValue(Query $query, $value, $field = '') + { + if ('_id' == $field && 'ObjectID' == $this->connection->getConfig('pk_type') && is_string($value)) { + try { + return new ObjectID($value); + } catch (InvalidArgumentException $e) { + return new ObjectID(); + } + } + + return $value; + } + + /** + * insert数据分析 + * @access protected + * @param Query $query 查询对象 + * @param array $data 数据 + * @return array + */ + protected function parseData(Query $query, array $data): array + { + if (empty($data)) { + return []; + } + + $result = []; + + foreach ($data as $key => $val) { + $item = $this->parseKey($query, $key); + + if (is_object($val)) { + $result[$item] = $val; + } elseif (isset($val[0]) && 'exp' == $val[0]) { + $result[$item] = $val[1]; + } else { + $result[$item] = $this->parseValue($query, $val, $key); + } + } + + return $result; + } + + /** + * Set数据分析 + * @access protected + * @param Query $query 查询对象 + * @param array $data 数据 + * @return array + */ + protected function parseSet(Query $query, array $data): array + { + if (empty($data)) { + return []; + } + + $result = []; + + foreach ($data as $key => $val) { + $item = $this->parseKey($query, $key); + + if (is_array($val) && isset($val[0]) && is_string($val[0]) && 0 === strpos($val[0], '$')) { + $result[$val[0]][$item] = $this->parseValue($query, $val[1], $key); + } else { + $result['$set'][$item] = $this->parseValue($query, $val, $key); + } + } + + return $result; + } + + /** + * 生成查询过滤条件 + * @access public + * @param Query $query 查询对象 + * @param mixed $where + * @return array + */ + public function parseWhere(Query $query, array $where): array + { + if (empty($where)) { + $where = []; + } + + $filter = []; + foreach ($where as $logic => $val) { + $logic = '$' . strtolower($logic); + foreach ($val as $field => $value) { + if (is_array($value)) { + if (key($value) !== 0) { + throw new Exception('where express error:' . var_export($value, true)); + } + $field = array_shift($value); + } elseif (!($value instanceof \Closure)) { + throw new Exception('where express error:' . var_export($value, true)); + } + + if ($value instanceof \Closure) { + // 使用闭包查询 + $query = new Query($this->connection); + call_user_func_array($value, [ & $query]); + $filter[$logic][] = $this->parseWhere($query, $query->getOptions('where')); + } else { + if (strpos($field, '|')) { + // 不同字段使用相同查询条件(OR) + $array = explode('|', $field); + foreach ($array as $k) { + $filter['$or'][] = $this->parseWhereItem($query, $k, $value); + } + } elseif (strpos($field, '&')) { + // 不同字段使用相同查询条件(AND) + $array = explode('&', $field); + foreach ($array as $k) { + $filter['$and'][] = $this->parseWhereItem($query, $k, $value); + } + } else { + // 对字段使用表达式查询 + $field = is_string($field) ? $field : ''; + $filter[$logic][] = $this->parseWhereItem($query, $field, $value); + } + } + } + } + + $options = $query->getOptions(); + if (!empty($options['soft_delete'])) { + // 附加软删除条件 + [$field, $condition] = $options['soft_delete']; + $filter['$and'][] = $this->parseWhereItem($query, $field, $condition); + } + + return $filter; + } + + // where子单元分析 + protected function parseWhereItem(Query $query, $field, $val): array + { + $key = $field ? $this->parseKey($query, $field) : ''; + // 查询规则和条件 + if (!is_array($val)) { + $val = ['=', $val]; + } + [$exp, $value] = $val; + + // 对一个字段使用多个查询条件 + if (is_array($exp)) { + $data = []; + foreach ($val as $value) { + $exp = $value[0]; + $value = $value[1]; + if (!in_array($exp, $this->exp)) { + $exp = strtolower($exp); + if (isset($this->exp[$exp])) { + $exp = $this->exp[$exp]; + } + } + $k = '$' . $exp; + $data[$k] = $value; + } + $result[$key] = $data; + return $result; + } elseif (!in_array($exp, $this->exp)) { + $exp = strtolower($exp); + if (isset($this->exp[$exp])) { + $exp = $this->exp[$exp]; + } else { + throw new Exception('where express error:' . $exp); + } + } + + $result = []; + if ('=' == $exp) { + // 普通查询 + $result[$key] = $this->parseValue($query, $value, $key); + } elseif (in_array($exp, ['neq', 'ne', 'gt', 'egt', 'gte', 'lt', 'lte', 'elt', 'mod'])) { + // 比较运算 + $k = '$' . $exp; + $result[$key] = [$k => $this->parseValue($query, $value, $key)]; + } elseif ('null' == $exp) { + // NULL 查询 + $result[$key] = null; + } elseif ('not null' == $exp) { + $result[$key] = ['$ne' => null]; + } elseif ('all' == $exp) { + // 满足所有指定条件 + $result[$key] = ['$all', $this->parseValue($query, $value, $key)]; + } elseif ('between' == $exp) { + // 区间查询 + $value = is_array($value) ? $value : explode(',', $value); + $result[$key] = ['$gte' => $this->parseValue($query, $value[0], $key), '$lte' => $this->parseValue($query, $value[1], $key)]; + } elseif ('not between' == $exp) { + // 范围查询 + $value = is_array($value) ? $value : explode(',', $value); + $result[$key] = ['$lt' => $this->parseValue($query, $value[0], $key), '$gt' => $this->parseValue($query, $value[1], $key)]; + } elseif ('exists' == $exp) { + // 字段是否存在 + $result[$key] = ['$exists' => (bool) $value]; + } elseif ('type' == $exp) { + // 类型查询 + $result[$key] = ['$type' => intval($value)]; + } elseif ('exp' == $exp) { + // 表达式查询 + $result['$where'] = $value instanceof Javascript ? $value : new Javascript($value); + } elseif ('like' == $exp) { + // 模糊查询 采用正则方式 + $result[$key] = $value instanceof Regex ? $value : new Regex($value, 'i'); + } elseif (in_array($exp, ['nin', 'in'])) { + // IN 查询 + $value = is_array($value) ? $value : explode(',', $value); + foreach ($value as $k => $val) { + $value[$k] = $this->parseValue($query, $val, $key); + } + $result[$key] = ['$' . $exp => $value]; + } elseif ('regex' == $exp) { + $result[$key] = $value instanceof Regex ? $value : new Regex($value, 'i'); + } elseif ('< time' == $exp) { + $result[$key] = ['$lt' => $this->parseDateTime($query, $value, $field)]; + } elseif ('> time' == $exp) { + $result[$key] = ['$gt' => $this->parseDateTime($query, $value, $field)]; + } elseif ('between time' == $exp) { + // 区间查询 + $value = is_array($value) ? $value : explode(',', $value); + $result[$key] = ['$gte' => $this->parseDateTime($query, $value[0], $field), '$lte' => $this->parseDateTime($query, $value[1], $field)]; + } elseif ('not between time' == $exp) { + // 范围查询 + $value = is_array($value) ? $value : explode(',', $value); + $result[$key] = ['$lt' => $this->parseDateTime($query, $value[0], $field), '$gt' => $this->parseDateTime($query, $value[1], $field)]; + } elseif ('near' == $exp) { + // 经纬度查询 + $result[$key] = ['$near' => $this->parseValue($query, $value, $key)]; + } elseif ('size' == $exp) { + // 元素长度查询 + $result[$key] = ['$size' => intval($value)]; + } else { + // 普通查询 + $result[$key] = $this->parseValue($query, $value, $key); + } + + return $result; + } + + /** + * 日期时间条件解析 + * @access protected + * @param Query $query 查询对象 + * @param string $value + * @param string $key + * @return string + */ + protected function parseDateTime(Query $query, $value, $key) + { + // 获取时间字段类型 + $type = $query->getFieldType($key); + + if ($type) { + if (is_string($value)) { + $value = strtotime($value) ?: $value; + } + + if (is_int($value)) { + if (preg_match('/(datetime|timestamp)/is', $type)) { + // 日期及时间戳类型 + $value = date('Y-m-d H:i:s', $value); + } elseif (preg_match('/(date)/is', $type)) { + // 日期及时间戳类型 + $value = date('Y-m-d', $value); + } + } + } + + return $value; + } + + /** + * 获取最后写入的ID 如果是insertAll方法的话 返回所有写入的ID + * @access public + * @return mixed + */ + public function getLastInsID() + { + return $this->insertId; + } + + /** + * 生成insert BulkWrite对象 + * @access public + * @param Query $query 查询对象 + * @return BulkWrite + */ + public function insert(Query $query): BulkWrite + { + // 分析并处理数据 + $options = $query->getOptions(); + + $data = $this->parseData($query, $options['data']); + + $bulk = new BulkWrite; + + if ($insertId = $bulk->insert($data)) { + $this->insertId = $insertId; + } + + $this->log('insert', $data, $options); + + return $bulk; + } + + /** + * 生成insertall BulkWrite对象 + * @access public + * @param Query $query 查询对象 + * @param array $dataSet 数据集 + * @return BulkWrite + */ + public function insertAll(Query $query, array $dataSet): BulkWrite + { + $bulk = new BulkWrite; + $options = $query->getOptions(); + + $this->insertId = []; + foreach ($dataSet as $data) { + // 分析并处理数据 + $data = $this->parseData($query, $data); + if ($insertId = $bulk->insert($data)) { + $this->insertId[] = $insertId; + } + } + + $this->log('insert', $dataSet, $options); + + return $bulk; + } + + /** + * 生成update BulkWrite对象 + * @access public + * @param Query $query 查询对象 + * @return BulkWrite + */ + public function update(Query $query): BulkWrite + { + $options = $query->getOptions(); + + $data = $this->parseSet($query, $options['data']); + $where = $this->parseWhere($query, $options['where']); + + if (1 == $options['limit']) { + $updateOptions = ['multi' => false]; + } else { + $updateOptions = ['multi' => true]; + } + + $bulk = new BulkWrite; + + $bulk->update($where, $data, $updateOptions); + + $this->log('update', $data, $where); + + return $bulk; + } + + /** + * 生成delete BulkWrite对象 + * @access public + * @param Query $query 查询对象 + * @return BulkWrite + */ + public function delete(Query $query): BulkWrite + { + $options = $query->getOptions(); + $where = $this->parseWhere($query, $options['where']); + + $bulk = new BulkWrite; + + if (1 == $options['limit']) { + $deleteOptions = ['limit' => 1]; + } else { + $deleteOptions = ['limit' => 0]; + } + + $bulk->delete($where, $deleteOptions); + + $this->log('remove', $where, $deleteOptions); + + return $bulk; + } + + /** + * 生成Mongo查询对象 + * @access public + * @param Query $query 查询对象 + * @param bool $one 是否仅获取一个记录 + * @return MongoQuery + */ + public function select(Query $query, bool $one = false): MongoQuery + { + $options = $query->getOptions(); + + $where = $this->parseWhere($query, $options['where']); + + if ($one) { + $options['limit'] = 1; + } + + $query = new MongoQuery($where, $options); + + $this->log('find', $where, $options); + + return $query; + } + + /** + * 生成Count命令 + * @access public + * @param Query $query 查询对象 + * @return Command + */ + public function count(Query $query): Command + { + $options = $query->getOptions(); + + $cmd['count'] = $options['table']; + $cmd['query'] = (object) $this->parseWhere($query, $options['where']); + + foreach (['hint', 'limit', 'maxTimeMS', 'skip'] as $option) { + if (isset($options[$option])) { + $cmd[$option] = $options[$option]; + } + } + + $command = new Command($cmd); + $this->log('cmd', 'count', $cmd); + + return $command; + } + + /** + * 聚合查询命令 + * @access public + * @param Query $query 查询对象 + * @param array $extra 指令和字段 + * @return Command + */ + public function aggregate(Query $query, array $extra): Command + { + $options = $query->getOptions(); + [$fun, $field] = $extra; + + if ('id' == $field && $this->connection->getConfig('pk_convert_id')) { + $field = '_id'; + } + + $group = isset($options['group']) ? '$' . $options['group'] : null; + + $pipeline = [ + ['$match' => (object) $this->parseWhere($query, $options['where'])], + ['$group' => ['_id' => $group, 'aggregate' => ['$' . $fun => '$' . $field]]], + ]; + + $cmd = [ + 'aggregate' => $options['table'], + 'allowDiskUse' => true, + 'pipeline' => $pipeline, + 'cursor' => new \stdClass, + ]; + + foreach (['explain', 'collation', 'bypassDocumentValidation', 'readConcern'] as $option) { + if (isset($options[$option])) { + $cmd[$option] = $options[$option]; + } + } + + $command = new Command($cmd); + + $this->log('aggregate', $cmd); + + return $command; + } + + /** + * 多聚合查询命令, 可以对多个字段进行 group by 操作 + * + * @param Query $query 查询对象 + * @param array $extra 指令和字段 + * @return Command + */ + public function multiAggregate(Query $query, $extra): Command + { + $options = $query->getOptions(); + + [$aggregate, $groupBy] = $extra; + + $groups = ['_id' => []]; + + foreach ($groupBy as $field) { + $groups['_id'][$field] = '$' . $field; + } + + foreach ($aggregate as $fun => $field) { + $groups[$field . '_' . $fun] = ['$' . $fun => '$' . $field]; + } + + $pipeline = [ + ['$match' => (object) $this->parseWhere($query, $options['where'])], + ['$group' => $groups], + ]; + + $cmd = [ + 'aggregate' => $options['table'], + 'allowDiskUse' => true, + 'pipeline' => $pipeline, + 'cursor' => new \stdClass, + ]; + + foreach (['explain', 'collation', 'bypassDocumentValidation', 'readConcern'] as $option) { + if (isset($options[$option])) { + $cmd[$option] = $options[$option]; + } + } + + $command = new Command($cmd); + $this->log('group', $cmd); + + return $command; + } + + /** + * 生成distinct命令 + * @access public + * @param Query $query 查询对象 + * @param string $field 字段名 + * @return Command + */ + public function distinct(Query $query, $field): Command + { + $options = $query->getOptions(); + + $cmd = [ + 'distinct' => $options['table'], + 'key' => $field, + ]; + + if (!empty($options['where'])) { + $cmd['query'] = (object) $this->parseWhere($query, $options['where']); + } + + if (isset($options['maxTimeMS'])) { + $cmd['maxTimeMS'] = $options['maxTimeMS']; + } + + $command = new Command($cmd); + + $this->log('cmd', 'distinct', $cmd); + + return $command; + } + + /** + * 查询所有的collection + * @access public + * @return Command + */ + public function listcollections(): Command + { + $cmd = ['listCollections' => 1]; + $command = new Command($cmd); + + $this->log('cmd', 'listCollections', $cmd); + + return $command; + } + + /** + * 查询数据表的状态信息 + * @access public + * @param Query $query 查询对象 + * @return Command + */ + public function collStats(Query $query): Command + { + $options = $query->getOptions(); + + $cmd = ['collStats' => $options['table']]; + $command = new Command($cmd); + + $this->log('cmd', 'collStats', $cmd); + + return $command; + } + + protected function log($type, $data, $options = []) + { + $this->connection->mongoLog($type, $data, $options); + } +} diff --git a/vendor/topthink/think-orm/src/db/builder/Mysql.php b/vendor/topthink/think-orm/src/db/builder/Mysql.php new file mode 100644 index 0000000..7dda33a --- /dev/null +++ b/vendor/topthink/think-orm/src/db/builder/Mysql.php @@ -0,0 +1,480 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\builder; + +use PDO; +use think\db\Builder; +use think\db\exception\DbException as Exception; +use think\db\Query; +use think\db\Raw; + +/** + * mysql数据库驱动 + */ +class Mysql extends Builder +{ + /** + * 查询表达式解析 + * @var array + */ + protected $parser = [ + 'parseCompare' => ['=', '<>', '>', '>=', '<', '<='], + 'parseLike' => ['LIKE', 'NOT LIKE'], + 'parseBetween' => ['NOT BETWEEN', 'BETWEEN'], + 'parseIn' => ['NOT IN', 'IN'], + 'parseExp' => ['EXP'], + 'parseRegexp' => ['REGEXP', 'NOT REGEXP'], + 'parseNull' => ['NOT NULL', 'NULL'], + 'parseBetweenTime' => ['BETWEEN TIME', 'NOT BETWEEN TIME'], + 'parseTime' => ['< TIME', '> TIME', '<= TIME', '>= TIME'], + 'parseExists' => ['NOT EXISTS', 'EXISTS'], + 'parseColumn' => ['COLUMN'], + 'parseFindInSet' => ['FIND IN SET'], + ]; + + /** + * SELECT SQL表达式 + * @var string + */ + protected $selectSql = 'SELECT%DISTINCT%%EXTRA% %FIELD% FROM %TABLE%%PARTITION%%FORCE%%JOIN%%WHERE%%GROUP%%HAVING%%UNION%%ORDER%%LIMIT% %LOCK%%COMMENT%'; + + /** + * INSERT SQL表达式 + * @var string + */ + protected $insertSql = '%INSERT%%EXTRA% INTO %TABLE%%PARTITION% SET %SET% %DUPLICATE%%COMMENT%'; + + /** + * INSERT ALL SQL表达式 + * @var string + */ + protected $insertAllSql = '%INSERT%%EXTRA% INTO %TABLE%%PARTITION% (%FIELD%) VALUES %DATA% %DUPLICATE%%COMMENT%'; + + /** + * UPDATE SQL表达式 + * @var string + */ + protected $updateSql = 'UPDATE%EXTRA% %TABLE%%PARTITION% %JOIN% SET %SET% %WHERE% %ORDER%%LIMIT% %LOCK%%COMMENT%'; + + /** + * DELETE SQL表达式 + * @var string + */ + protected $deleteSql = 'DELETE%EXTRA% FROM %TABLE%%PARTITION%%USING%%JOIN%%WHERE%%ORDER%%LIMIT% %LOCK%%COMMENT%'; + + /** + * 生成查询SQL + * @access public + * @param Query $query 查询对象 + * @param bool $one 是否仅获取一个记录 + * @return string + */ + public function select(Query $query, bool $one = false): string + { + $options = $query->getOptions(); + + return str_replace( + ['%TABLE%', '%PARTITION%', '%DISTINCT%', '%EXTRA%', '%FIELD%', '%JOIN%', '%WHERE%', '%GROUP%', '%HAVING%', '%ORDER%', '%LIMIT%', '%UNION%', '%LOCK%', '%COMMENT%', '%FORCE%'], + [ + $this->parseTable($query, $options['table']), + $this->parsePartition($query, $options['partition']), + $this->parseDistinct($query, $options['distinct']), + $this->parseExtra($query, $options['extra']), + $this->parseField($query, $options['field'] ?? '*'), + $this->parseJoin($query, $options['join']), + $this->parseWhere($query, $options['where']), + $this->parseGroup($query, $options['group']), + $this->parseHaving($query, $options['having']), + $this->parseOrder($query, $options['order']), + $this->parseLimit($query, $one ? '1' : $options['limit']), + $this->parseUnion($query, $options['union']), + $this->parseLock($query, $options['lock']), + $this->parseComment($query, $options['comment']), + $this->parseForce($query, $options['force']), + ], + $this->selectSql + ); + } + + /** + * 生成Insert SQL + * @access public + * @param Query $query 查询对象 + * @return string + */ + public function insert(Query $query): string + { + $options = $query->getOptions(); + + // 分析并处理数据 + $data = $this->parseData($query, $options['data']); + if (empty($data)) { + return ''; + } + + $set = []; + foreach ($data as $key => $val) { + $set[] = $key . ' = ' . $val; + } + + return str_replace( + ['%INSERT%', '%EXTRA%', '%TABLE%', '%PARTITION%', '%SET%', '%DUPLICATE%', '%COMMENT%'], + [ + !empty($options['replace']) ? 'REPLACE' : 'INSERT', + $this->parseExtra($query, $options['extra']), + $this->parseTable($query, $options['table']), + $this->parsePartition($query, $options['partition']), + implode(' , ', $set), + $this->parseDuplicate($query, $options['duplicate']), + $this->parseComment($query, $options['comment']), + ], + $this->insertSql + ); + } + + /** + * 生成insertall SQL + * @access public + * @param Query $query 查询对象 + * @param array $dataSet 数据集 + * @return string + */ + public function insertAll(Query $query, array $dataSet): string + { + $options = $query->getOptions(); + + // 获取绑定信息 + $bind = $query->getFieldsBindType(); + + // 获取合法的字段 + if (empty($options['field']) || '*' == $options['field']) { + $allowFields = array_keys($bind); + } else { + $allowFields = $options['field']; + } + + $fields = []; + $values = []; + + foreach ($dataSet as $data) { + $data = $this->parseData($query, $data, $allowFields, $bind); + + $values[] = '( ' . implode(',', array_values($data)) . ' )'; + + if (!isset($insertFields)) { + $insertFields = array_keys($data); + } + } + + foreach ($insertFields as $field) { + $fields[] = $this->parseKey($query, $field); + } + + return str_replace( + ['%INSERT%', '%EXTRA%', '%TABLE%', '%PARTITION%', '%FIELD%', '%DATA%', '%DUPLICATE%', '%COMMENT%'], + [ + !empty($options['replace']) ? 'REPLACE' : 'INSERT', + $this->parseExtra($query, $options['extra']), + $this->parseTable($query, $options['table']), + $this->parsePartition($query, $options['partition']), + implode(' , ', $fields), + implode(' , ', $values), + $this->parseDuplicate($query, $options['duplicate']), + $this->parseComment($query, $options['comment']), + ], + $this->insertAllSql + ); + } + + /** + * 生成insertall SQL + * @access public + * @param Query $query 查询对象 + * @param array $keys 键值 + * @param array $values 数据 + * @return string + */ + public function insertAllByKeys(Query $query, array $keys, array $datas): string + { + $options = $query->getOptions(); + + // 获取绑定信息 + $bind = $query->getFieldsBindType(); + $fields = []; + $values = []; + + foreach ($keys as $field) { + $fields[] = $this->parseKey($query, $field); + } + + foreach ($datas as $data) { + foreach ($data as $key => &$val) { + if (!$query->isAutoBind()) { + $val = PDO::PARAM_STR == $bind[$keys[$key]] ? '\'' . $val . '\'' : $val; + } else { + $val = $this->parseDataBind($query, $keys[$key], $val, $bind); + } + } + $values[] = '( ' . implode(',', $data) . ' )'; + } + + return str_replace( + ['%INSERT%', '%EXTRA%', '%TABLE%', '%PARTITION%', '%FIELD%', '%DATA%', '%DUPLICATE%', '%COMMENT%'], + [ + !empty($options['replace']) ? 'REPLACE' : 'INSERT', + $this->parseExtra($query, $options['extra']), + $this->parseTable($query, $options['table']), + $this->parsePartition($query, $options['partition']), + implode(' , ', $fields), + implode(' , ', $values), + $this->parseDuplicate($query, $options['duplicate']), + $this->parseComment($query, $options['comment']), + ], + $this->insertAllSql + ); + } + + /** + * 生成update SQL + * @access public + * @param Query $query 查询对象 + * @return string + */ + public function update(Query $query): string + { + $options = $query->getOptions(); + + $data = $this->parseData($query, $options['data']); + + if (empty($data)) { + return ''; + } + + $set = []; + foreach ($data as $key => $val) { + $set[] = (strpos($key, '->') ? strstr($key, '->', true) : $key) . ' = ' . $val; + } + + return str_replace( + ['%TABLE%', '%PARTITION%', '%EXTRA%', '%SET%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'], + [ + $this->parseTable($query, $options['table']), + $this->parsePartition($query, $options['partition']), + $this->parseExtra($query, $options['extra']), + implode(' , ', $set), + $this->parseJoin($query, $options['join']), + $this->parseWhere($query, $options['where']), + $this->parseOrder($query, $options['order']), + $this->parseLimit($query, $options['limit']), + $this->parseLock($query, $options['lock']), + $this->parseComment($query, $options['comment']), + ], + $this->updateSql + ); + } + + /** + * 生成delete SQL + * @access public + * @param Query $query 查询对象 + * @return string + */ + public function delete(Query $query): string + { + $options = $query->getOptions(); + + return str_replace( + ['%TABLE%', '%PARTITION%', '%EXTRA%', '%USING%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'], + [ + $this->parseTable($query, $options['table']), + $this->parsePartition($query, $options['partition']), + $this->parseExtra($query, $options['extra']), + !empty($options['using']) ? ' USING ' . $this->parseTable($query, $options['using']) . ' ' : '', + $this->parseJoin($query, $options['join']), + $this->parseWhere($query, $options['where']), + $this->parseOrder($query, $options['order']), + $this->parseLimit($query, $options['limit']), + $this->parseLock($query, $options['lock']), + $this->parseComment($query, $options['comment']), + ], + $this->deleteSql + ); + } + + /** + * 正则查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @return string + */ + protected function parseRegexp(Query $query, string $key, string $exp, $value, string $field): string + { + if ($value instanceof Raw) { + $value = $this->parseRaw($query, $value); + } + + return $key . ' ' . $exp . ' ' . $value; + } + + /** + * FIND_IN_SET 查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @return string + */ + protected function parseFindInSet(Query $query, string $key, string $exp, $value, string $field): string + { + if ($value instanceof Raw) { + $value = $this->parseRaw($query, $value); + } + + return 'FIND_IN_SET(' . $value . ', ' . $key . ')'; + } + + /** + * 字段和表名处理 + * @access public + * @param Query $query 查询对象 + * @param mixed $key 字段名 + * @param bool $strict 严格检测 + * @return string + */ + public function parseKey(Query $query, $key, bool $strict = false): string + { + if (is_int($key)) { + return (string) $key; + } elseif ($key instanceof Raw) { + return $this->parseRaw($query, $key); + } + + $key = trim($key); + + if (strpos($key, '->>') && false === strpos($key, '(')) { + // JSON字段支持 + [$field, $name] = explode('->>', $key, 2); + + return $this->parseKey($query, $field, true) . '->>\'$' . (strpos($name, '[') === 0 ? '' : '.') . str_replace('->>', '.', $name) . '\''; + } elseif (strpos($key, '->') && false === strpos($key, '(')) { + // JSON字段支持 + [$field, $name] = explode('->', $key, 2); + return 'json_extract(' . $this->parseKey($query, $field, true) . ', \'$' . (strpos($name, '[') === 0 ? '' : '.') . str_replace('->', '.', $name) . '\')'; + } elseif (strpos($key, '.') && !preg_match('/[,\'\"\(\)`\s]/', $key)) { + [$table, $key] = explode('.', $key, 2); + + $alias = $query->getOptions('alias'); + + if ('__TABLE__' == $table) { + $table = $query->getOptions('table'); + $table = is_array($table) ? array_shift($table) : $table; + } + + if (isset($alias[$table])) { + $table = $alias[$table]; + } + } + + if ($strict && !preg_match('/^[\w\.\*]+$/', $key)) { + throw new Exception('not support data:' . $key); + } + + if ('*' != $key && !preg_match('/[,\'\"\*\(\)`.\s]/', $key)) { + $key = '`' . $key . '`'; + } + + if (isset($table)) { + if (strpos($table, '.')) { + $table = str_replace('.', '`.`', $table); + } + + $key = '`' . $table . '`.' . $key; + } + + return $key; + } + + /** + * 随机排序 + * @access protected + * @param Query $query 查询对象 + * @return string + */ + protected function parseRand(Query $query): string + { + return 'rand()'; + } + + /** + * Partition 分析 + * @access protected + * @param Query $query 查询对象 + * @param string|array $partition 分区 + * @return string + */ + protected function parsePartition(Query $query, $partition): string + { + if ('' == $partition) { + return ''; + } + + if (is_string($partition)) { + $partition = explode(',', $partition); + } + + return ' PARTITION (' . implode(' , ', $partition) . ') '; + } + + /** + * ON DUPLICATE KEY UPDATE 分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $duplicate + * @return string + */ + protected function parseDuplicate(Query $query, $duplicate): string + { + if ('' == $duplicate) { + return ''; + } + + if ($duplicate instanceof Raw) { + return ' ON DUPLICATE KEY UPDATE ' . $this->parseRaw($query, $duplicate) . ' '; + } + + if (is_string($duplicate)) { + $duplicate = explode(',', $duplicate); + } + + $updates = []; + foreach ($duplicate as $key => $val) { + if (is_numeric($key)) { + $val = $this->parseKey($query, $val); + $updates[] = $val . ' = VALUES(' . $val . ')'; + } elseif ($val instanceof Raw) { + $updates[] = $this->parseKey($query, $key) . " = " . $this->parseRaw($query, $val); + } else { + $name = $query->bindValue($val, $query->getConnection()->getFieldBindType($key)); + $updates[] = $this->parseKey($query, $key) . " = :" . $name; + } + } + + return ' ON DUPLICATE KEY UPDATE ' . implode(' , ', $updates) . ' '; + } +} diff --git a/vendor/topthink/think-orm/src/db/builder/Oracle.php b/vendor/topthink/think-orm/src/db/builder/Oracle.php new file mode 100644 index 0000000..38feb5d --- /dev/null +++ b/vendor/topthink/think-orm/src/db/builder/Oracle.php @@ -0,0 +1,129 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\builder; + +use think\db\Builder; +use think\db\Query; +use think\db\exception\DbException as Exception; +use think\db\Raw; + +/** + * Oracle数据库驱动 + */ +class Oracle extends Builder +{ + protected $selectSql = 'SELECT * FROM (SELECT thinkphp.*, rownum AS numrow FROM (SELECT %DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%%ORDER%) thinkphp ) %LIMIT%%COMMENT%'; + + /** + * limit分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $limit + * @return string + */ + protected function parseLimit(Query $query, string $limit): string + { + $limitStr = ''; + + if (!empty($limit)) { + $limit = explode(',', $limit); + + if (count($limit) > 1) { + $limitStr = "(numrow>" . $limit[0] . ") AND (numrow<=" . ($limit[0] + $limit[1]) . ")"; + } else { + $limitStr = "(numrow>0 AND numrow<=" . $limit[0] . ")"; + } + + } + + return $limitStr ? ' WHERE ' . $limitStr : ''; + } + + /** + * 设置锁机制 + * @access protected + * @param Query $query 查询对象 + * @param bool|false $lock + * @return string + */ + protected function parseLock(Query $query, $lock = false): string + { + if (!$lock) { + return ''; + } + + return ' FOR UPDATE NOWAIT '; + } + + /** + * 字段和表名处理 + * @access public + * @param Query $query 查询对象 + * @param string $key + * @param bool $strict + * @return string + * @throws Exception + */ + public function parseKey(Query $query, $key, bool $strict = false): string + { + if (is_int($key)) { + return (string) $key; + } elseif ($key instanceof Raw) { + return $this->parseRaw($query, $key); + } + + $key = trim($key); + + if (strpos($key, '->') && false === strpos($key, '(')) { + // JSON字段支持 + [$field, $name] = explode($key, '->'); + $key = $field . '."' . $name . '"'; + } elseif (strpos($key, '.') && !preg_match('/[,\'\"\(\)\[\s]/', $key)) { + [$table, $key] = explode('.', $key, 2); + + $alias = $query->getOptions('alias'); + + if ('__TABLE__' == $table) { + $table = $query->getOptions('table'); + $table = is_array($table) ? array_shift($table) : $table; + } + + if (isset($alias[$table])) { + $table = $alias[$table]; + } + } + + if ($strict && !preg_match('/^[\w\.\*]+$/', $key)) { + throw new Exception('not support data:' . $key); + } + + if ('*' != $key && !preg_match('/[,\'\"\*\(\)\[.\s]/', $key)) { + $key = '"' . $key . '"'; + } + + if (isset($table)) { + $key = '"' . $table . '".' . $key; + } + + return $key; + } + + /** + * 随机排序 + * @access protected + * @param Query $query 查询对象 + * @return string + */ + protected function parseRand(Query $query): string + { + return 'DBMS_RANDOM.value'; + } +} diff --git a/vendor/topthink/think-orm/src/db/builder/Pgsql.php b/vendor/topthink/think-orm/src/db/builder/Pgsql.php new file mode 100644 index 0000000..4eace0a --- /dev/null +++ b/vendor/topthink/think-orm/src/db/builder/Pgsql.php @@ -0,0 +1,118 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\builder; + +use think\db\Builder; +use think\db\Query; +use think\db\Raw; + +/** + * Pgsql数据库驱动 + */ +class Pgsql extends Builder +{ + /** + * INSERT SQL表达式 + * @var string + */ + protected $insertSql = 'INSERT INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%'; + + /** + * INSERT ALL SQL表达式 + * @var string + */ + protected $insertAllSql = 'INSERT INTO %TABLE% (%FIELD%) %DATA% %COMMENT%'; + + /** + * limit分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $limit + * @return string + */ + public function parseLimit(Query $query, string $limit): string + { + $limitStr = ''; + + if (!empty($limit)) { + $limit = explode(',', $limit); + if (count($limit) > 1) { + $limitStr .= ' LIMIT ' . $limit[1] . ' OFFSET ' . $limit[0] . ' '; + } else { + $limitStr .= ' LIMIT ' . $limit[0] . ' '; + } + } + + return $limitStr; + } + + /** + * 字段和表名处理 + * @access public + * @param Query $query 查询对象 + * @param mixed $key 字段名 + * @param bool $strict 严格检测 + * @return string + */ + public function parseKey(Query $query, $key, bool $strict = false): string + { + if (is_int($key)) { + return (string) $key; + } elseif ($key instanceof Raw) { + return $this->parseRaw($query, $key); + } + + $key = trim($key); + + if (strpos($key, '->') && false === strpos($key, '(')) { + // JSON字段支持 + [$field, $name] = explode('->', $key); + $key = '"' . $field . '"' . '->>\'' . $name . '\''; + } elseif (strpos($key, '.')) { + [$table, $key] = explode('.', $key, 2); + + $alias = $query->getOptions('alias'); + + if ('__TABLE__' == $table) { + $table = $query->getOptions('table'); + $table = is_array($table) ? array_shift($table) : $table; + } + + if (isset($alias[$table])) { + $table = $alias[$table]; + } + + if ('*' != $key && !preg_match('/[,\"\*\(\).\s]/', $key)) { + $key = '"' . $key . '"'; + } + } + + if (isset($table)) { + $key = $table . '.' . $key; + } + + return $key; + } + + /** + * 随机排序 + * @access protected + * @param Query $query 查询对象 + * @return string + */ + protected function parseRand(Query $query): string + { + return 'RANDOM()'; + } + +} diff --git a/vendor/topthink/think-orm/src/db/builder/Sqlite.php b/vendor/topthink/think-orm/src/db/builder/Sqlite.php new file mode 100644 index 0000000..ff17c5d --- /dev/null +++ b/vendor/topthink/think-orm/src/db/builder/Sqlite.php @@ -0,0 +1,113 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\builder; + +use think\db\Builder; +use think\db\Query; +use think\db\Raw; + +/** + * Sqlite数据库驱动 + */ +class Sqlite extends Builder +{ + /** + * limit + * @access public + * @param Query $query 查询对象 + * @param mixed $limit + * @return string + */ + public function parseLimit(Query $query, string $limit): string + { + $limitStr = ''; + + if (!empty($limit)) { + $limit = explode(',', $limit); + if (count($limit) > 1) { + $limitStr .= ' LIMIT ' . $limit[1] . ' OFFSET ' . $limit[0] . ' '; + } else { + $limitStr .= ' LIMIT ' . $limit[0] . ' '; + } + } + + return $limitStr; + } + + /** + * 随机排序 + * @access protected + * @param Query $query 查询对象 + * @return string + */ + protected function parseRand(Query $query): string + { + return 'RANDOM()'; + } + + /** + * 字段和表名处理 + * @access public + * @param Query $query 查询对象 + * @param mixed $key 字段名 + * @param bool $strict 严格检测 + * @return string + */ + public function parseKey(Query $query, $key, bool $strict = false): string + { + if (is_int($key)) { + return (string) $key; + } elseif ($key instanceof Raw) { + return $this->parseRaw($query, $key); + } + + $key = trim($key); + + if (strpos($key, '.') && !preg_match('/[,\'\"\(\)`\s]/', $key)) { + [$table, $key] = explode('.', $key, 2); + + $alias = $query->getOptions('alias'); + + if ('__TABLE__' == $table) { + $table = $query->getOptions('table'); + $table = is_array($table) ? array_shift($table) : $table; + } + + if (isset($alias[$table])) { + $table = $alias[$table]; + } + } + + if ('*' != $key && !preg_match('/[,\'\"\*\(\)`.\s]/', $key)) { + $key = '`' . $key . '`'; + } + + if (isset($table)) { + $key = '`' . $table . '`.' . $key; + } + + return $key; + } + + /** + * 设置锁机制 + * @access protected + * @param Query $query 查询对象 + * @param bool|string $lock + * @return string + */ + protected function parseLock(Query $query, $lock = false): string + { + return ''; + } +} diff --git a/vendor/topthink/think-orm/src/db/builder/Sqlsrv.php b/vendor/topthink/think-orm/src/db/builder/Sqlsrv.php new file mode 100644 index 0000000..779b5e3 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/builder/Sqlsrv.php @@ -0,0 +1,184 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\builder; + +use think\db\Builder; +use think\db\exception\DbException as Exception; +use think\db\Query; +use think\db\Raw; + +/** + * Sqlsrv数据库驱动 + */ +class Sqlsrv extends Builder +{ + /** + * SELECT SQL表达式 + * @var string + */ + protected $selectSql = 'SELECT T1.* FROM (SELECT thinkphp.*, ROW_NUMBER() OVER (%ORDER%) AS ROW_NUMBER FROM (SELECT %DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%) AS thinkphp) AS T1 %LIMIT%%COMMENT%'; + /** + * SELECT INSERT SQL表达式 + * @var string + */ + protected $selectInsertSql = 'SELECT %DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%'; + + /** + * UPDATE SQL表达式 + * @var string + */ + protected $updateSql = 'UPDATE %TABLE% SET %SET% FROM %TABLE% %JOIN% %WHERE% %LIMIT% %LOCK%%COMMENT%'; + + /** + * DELETE SQL表达式 + * @var string + */ + protected $deleteSql = 'DELETE FROM %TABLE% %USING% FROM %TABLE% %JOIN% %WHERE% %LIMIT% %LOCK%%COMMENT%'; + + /** + * INSERT SQL表达式 + * @var string + */ + protected $insertSql = 'INSERT INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%'; + + /** + * INSERT ALL SQL表达式 + * @var string + */ + protected $insertAllSql = 'INSERT INTO %TABLE% (%FIELD%) %DATA% %COMMENT%'; + + /** + * order分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $order + * @return string + */ + protected function parseOrder(Query $query, array $order): string + { + if (empty($order)) { + return ' ORDER BY rand()'; + } + + $array = []; + + foreach ($order as $key => $val) { + if ($val instanceof Raw) { + $array[] = $this->parseRaw($query, $val); + } elseif ('[rand]' == $val) { + $array[] = $this->parseRand($query); + } else { + if (is_numeric($key)) { + [$key, $sort] = explode(' ', strpos($val, ' ') ? $val : $val . ' '); + } else { + $sort = $val; + } + + $sort = in_array(strtolower($sort), ['asc', 'desc'], true) ? ' ' . $sort : ''; + $array[] = $this->parseKey($query, $key, true) . $sort; + } + } + + return ' ORDER BY ' . implode(',', $array); + } + + /** + * 随机排序 + * @access protected + * @param Query $query 查询对象 + * @return string + */ + protected function parseRand(Query $query): string + { + return 'rand()'; + } + + /** + * 字段和表名处理 + * @access public + * @param Query $query 查询对象 + * @param mixed $key 字段名 + * @param bool $strict 严格检测 + * @return string + */ + public function parseKey(Query $query, $key, bool $strict = false): string + { + if (is_int($key)) { + return (string) $key; + } elseif ($key instanceof Raw) { + return $this->parseRaw($query, $key); + } + + $key = trim($key); + + if (strpos($key, '.') && !preg_match('/[,\'\"\(\)\[\s]/', $key)) { + [$table, $key] = explode('.', $key, 2); + + $alias = $query->getOptions('alias'); + + if ('__TABLE__' == $table) { + $table = $query->getOptions('table'); + $table = is_array($table) ? array_shift($table) : $table; + } + + if (isset($alias[$table])) { + $table = $alias[$table]; + } + } + + if ($strict && !preg_match('/^[\w\.\*]+$/', $key)) { + throw new Exception('not support data:' . $key); + } + + if ('*' != $key && !preg_match('/[,\'\"\*\(\)\[.\s]/', $key)) { + $key = '[' . $key . ']'; + } + + if (isset($table)) { + $key = '[' . $table . '].' . $key; + } + + return $key; + } + + /** + * limit + * @access protected + * @param Query $query 查询对象 + * @param mixed $limit + * @return string + */ + protected function parseLimit(Query $query, string $limit): string + { + if (empty($limit)) { + return ''; + } + + $limit = explode(',', $limit); + + if (count($limit) > 1) { + $limitStr = '(T1.ROW_NUMBER BETWEEN ' . $limit[0] . ' + 1 AND ' . $limit[0] . ' + ' . $limit[1] . ')'; + } else { + $limitStr = '(T1.ROW_NUMBER BETWEEN 1 AND ' . $limit[0] . ")"; + } + + return 'WHERE ' . $limitStr; + } + + public function selectInsert(Query $query, array $fields, string $table): string + { + $this->selectSql = $this->selectInsertSql; + + return parent::selectInsert($query, $fields, $table); + } + +} diff --git a/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php b/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php new file mode 100644 index 0000000..d137d05 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php @@ -0,0 +1,122 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\concern; + +use think\db\exception\DbException; +use think\db\Raw; + +/** + * 聚合查询 + */ +trait AggregateQuery +{ + /** + * 聚合查询 + * @access protected + * @param string $aggregate 聚合方法 + * @param string|Raw $field 字段名 + * @param bool $force 强制转为数字类型 + * @return mixed + */ + protected function aggregate(string $aggregate, $field, bool $force = false) + { + return $this->connection->aggregate($this, $aggregate, $field, $force); + } + + /** + * COUNT查询 + * @access public + * @param string|Raw $field 字段名 + * @return int + */ + public function count(string $field = '*'): int + { + if (!empty($this->options['group'])) { + // 支持GROUP + + if (!preg_match('/^[\w\.\*]+$/', $field)) { + throw new DbException('not support data:' . $field); + } + + $options = $this->getOptions(); + if (isset($options['cache'])) { + $cache = $options['cache']; + unset($options['cache']); + } + + $subSql = $this->options($options) + ->field('count(' . $field . ') AS think_count') + ->bind($this->bind) + ->buildSql(); + + $query = $this->newQuery(); + if (isset($cache)) { + $query->setOption('cache', $cache); + } + $query->table([$subSql => '_group_count_']); + + $count = $query->aggregate('COUNT', '*'); + } else { + $count = $this->aggregate('COUNT', $field); + } + + return (int) $count; + } + + /** + * SUM查询 + * @access public + * @param string|Raw $field 字段名 + * @return float + */ + public function sum($field): float + { + return $this->aggregate('SUM', $field, true); + } + + /** + * MIN查询 + * @access public + * @param string|Raw $field 字段名 + * @param bool $force 强制转为数字类型 + * @return mixed + */ + public function min($field, bool $force = true) + { + return $this->aggregate('MIN', $field, $force); + } + + /** + * MAX查询 + * @access public + * @param string|Raw $field 字段名 + * @param bool $force 强制转为数字类型 + * @return mixed + */ + public function max($field, bool $force = true) + { + return $this->aggregate('MAX', $field, $force); + } + + /** + * AVG查询 + * @access public + * @param string|Raw $field 字段名 + * @return float + */ + public function avg($field): float + { + return $this->aggregate('AVG', $field, true); + } + +} diff --git a/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php b/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php new file mode 100644 index 0000000..2828949 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php @@ -0,0 +1,228 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\concern; + +use think\db\Raw; + +/** + * JOIN和VIEW查询 + */ +trait JoinAndViewQuery +{ + + /** + * 查询SQL组装 join + * @access public + * @param mixed $join 关联的表名 + * @param mixed $condition 条件 + * @param string $type JOIN类型 + * @param array $bind 参数绑定 + * @return $this + */ + public function join($join, string $condition = null, string $type = 'INNER', array $bind = []) + { + $table = $this->getJoinTable($join); + + if (!empty($bind) && $condition) { + $this->bindParams($condition, $bind); + } + + $this->options['join'][] = [$table, strtoupper($type), $condition]; + + return $this; + } + + /** + * LEFT JOIN + * @access public + * @param mixed $join 关联的表名 + * @param mixed $condition 条件 + * @param array $bind 参数绑定 + * @return $this + */ + public function leftJoin($join, string $condition = null, array $bind = []) + { + return $this->join($join, $condition, 'LEFT', $bind); + } + + /** + * RIGHT JOIN + * @access public + * @param mixed $join 关联的表名 + * @param mixed $condition 条件 + * @param array $bind 参数绑定 + * @return $this + */ + public function rightJoin($join, string $condition = null, array $bind = []) + { + return $this->join($join, $condition, 'RIGHT', $bind); + } + + /** + * FULL JOIN + * @access public + * @param mixed $join 关联的表名 + * @param mixed $condition 条件 + * @param array $bind 参数绑定 + * @return $this + */ + public function fullJoin($join, string $condition = null, array $bind = []) + { + return $this->join($join, $condition, 'FULL'); + } + + /** + * 获取Join表名及别名 支持 + * ['prefix_table或者子查询'=>'alias'] 'table alias' + * @access protected + * @param array|string|Raw $join JION表名 + * @param string $alias 别名 + * @return string|array + */ + protected function getJoinTable($join, &$alias = null) + { + if (is_array($join)) { + $table = $join; + $alias = array_shift($join); + return $table; + } elseif ($join instanceof Raw) { + return $join; + } + + $join = trim($join); + + if (false !== strpos($join, '(')) { + // 使用子查询 + $table = $join; + } else { + // 使用别名 + if (strpos($join, ' ')) { + // 使用别名 + [$table, $alias] = explode(' ', $join); + } else { + $table = $join; + if (false === strpos($join, '.')) { + $alias = $join; + } + } + + if ($this->prefix && false === strpos($table, '.') && 0 !== strpos($table, $this->prefix)) { + $table = $this->getTable($table); + } + } + + if (!empty($alias) && $table != $alias) { + $table = [$table => $alias]; + } + + return $table; + } + + /** + * 指定JOIN查询字段 + * @access public + * @param string|array $join 数据表 + * @param string|array $field 查询字段 + * @param string $on JOIN条件 + * @param string $type JOIN类型 + * @param array $bind 参数绑定 + * @return $this + */ + public function view($join, $field = true, $on = null, string $type = 'INNER', array $bind = []) + { + $this->options['view'] = true; + + $fields = []; + $table = $this->getJoinTable($join, $alias); + + if (true === $field) { + $fields = $alias . '.*'; + } else { + if (is_string($field)) { + $field = explode(',', $field); + } + + foreach ($field as $key => $val) { + if (is_numeric($key)) { + $fields[] = $alias . '.' . $val; + + $this->options['map'][$val] = $alias . '.' . $val; + } else { + if (preg_match('/[,=\.\'\"\(\s]/', $key)) { + $name = $key; + } else { + $name = $alias . '.' . $key; + } + + $fields[] = $name . ' AS ' . $val; + + $this->options['map'][$val] = $name; + } + } + } + + $this->field($fields); + + if ($on) { + $this->join($table, $on, $type, $bind); + } else { + $this->table($table); + } + + return $this; + } + + /** + * 视图查询处理 + * @access protected + * @param array $options 查询参数 + * @return void + */ + protected function parseView(array &$options): void + { + foreach (['AND', 'OR'] as $logic) { + if (isset($options['where'][$logic])) { + foreach ($options['where'][$logic] as $key => $val) { + if (array_key_exists($key, $options['map'])) { + array_shift($val); + array_unshift($val, $options['map'][$key]); + $options['where'][$logic][$options['map'][$key]] = $val; + unset($options['where'][$logic][$key]); + } + } + } + } + + if (isset($options['order'])) { + // 视图查询排序处理 + foreach ($options['order'] as $key => $val) { + if (is_numeric($key) && is_string($val)) { + if (strpos($val, ' ')) { + [$field, $sort] = explode(' ', $val); + if (array_key_exists($field, $options['map'])) { + $options['order'][$options['map'][$field]] = $sort; + unset($options['order'][$key]); + } + } elseif (array_key_exists($val, $options['map'])) { + $options['order'][$options['map'][$val]] = 'asc'; + unset($options['order'][$key]); + } + } elseif (array_key_exists($key, $options['map'])) { + $options['order'][$options['map'][$key]] = $val; + unset($options['order'][$key]); + } + } + } + } + +} diff --git a/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php b/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php new file mode 100644 index 0000000..cabd65b --- /dev/null +++ b/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php @@ -0,0 +1,562 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\concern; + +use Closure; +use think\helper\Str; +use think\Model; +use think\model\Collection as ModelCollection; + +/** + * 模型及关联查询 + */ +trait ModelRelationQuery +{ + + /** + * 当前模型对象 + * @var Model + */ + protected $model; + + /** + * 指定模型 + * @access public + * @param Model $model 模型对象实例 + * @return $this + */ + public function model(Model $model) + { + $this->model = $model; + return $this; + } + + /** + * 获取当前的模型对象 + * @access public + * @return Model|null + */ + public function getModel() + { + return $this->model; + } + + /** + * 设置需要隐藏的输出属性 + * @access public + * @param array $hidden 属性列表 + * @return $this + */ + public function hidden(array $hidden = []) + { + $this->options['hidden'] = $hidden; + + return $this; + } + + /** + * 设置需要输出的属性 + * @access public + * @param array $visible + * @return $this + */ + public function visible(array $visible = []) + { + $this->options['visible'] = $visible; + + return $this; + } + + /** + * 设置需要附加的输出属性 + * @access public + * @param array $append 属性列表 + * @return $this + */ + public function append(array $append = []) + { + $this->options['append'] = $append; + + return $this; + } + + /** + * 添加查询范围 + * @access public + * @param array|string|Closure $scope 查询范围定义 + * @param array $args 参数 + * @return $this + */ + public function scope($scope, ...$args) + { + // 查询范围的第一个参数始终是当前查询对象 + array_unshift($args, $this); + + if ($scope instanceof Closure) { + call_user_func_array($scope, $args); + return $this; + } + + if (is_string($scope)) { + $scope = explode(',', $scope); + } + + if ($this->model) { + // 检查模型类的查询范围方法 + foreach ($scope as $name) { + $method = 'scope' . trim($name); + + if (method_exists($this->model, $method)) { + call_user_func_array([$this->model, $method], $args); + } + } + } + + return $this; + } + + /** + * 设置关联查询 + * @access public + * @param array $relation 关联名称 + * @return $this + */ + public function relation(array $relation) + { + if (empty($this->model) || empty($relation)) { + return $this; + } + + $this->options['relation'] = $relation; + return $this; + } + + /** + * 使用搜索器条件搜索字段 + * @access public + * @param string|array $fields 搜索字段 + * @param mixed $data 搜索数据 + * @param string $prefix 字段前缀标识 + * @return $this + */ + public function withSearch($fields, $data = [], string $prefix = '') + { + if (is_string($fields)) { + $fields = explode(',', $fields); + } + + $likeFields = $this->getConfig('match_like_fields') ?: []; + + foreach ($fields as $key => $field) { + if ($field instanceof Closure) { + $field($this, $data[$key] ?? null, $data, $prefix); + } elseif ($this->model) { + // 检测搜索器 + $fieldName = is_numeric($key) ? $field : $key; + $method = 'search' . Str::studly($fieldName) . 'Attr'; + + if (method_exists($this->model, $method)) { + $this->model->$method($this, $data[$field] ?? null, $data, $prefix); + } elseif (isset($data[$field])) { + $this->where($fieldName, in_array($fieldName, $likeFields) ? 'like' : '=', in_array($fieldName, $likeFields) ? '%' . $data[$field] . '%' : $data[$field]); + } + } + } + + return $this; + } + + /** + * 设置数据字段获取器 + * @access public + * @param string|array $name 字段名 + * @param callable $callback 闭包获取器 + * @return $this + */ + public function withAttr($name, callable $callback = null) + { + if (is_array($name)) { + foreach ($name as $key => $val) { + $this->withAttr($key, $val); + } + return $this; + } + + $this->options['with_attr'][$name] = $callback; + + if (strpos($name, '.')) { + [$relation, $field] = explode('.', $name); + + if (!empty($this->options['json']) && in_array($relation, $this->options['json'])) { + + } else { + $this->options['with_relation_attr'][$relation][$field] = $callback; + unset($this->options['with_attr'][$name]); + } + } + + return $this; + } + + /** + * 关联预载入 In方式 + * @access public + * @param array|string $with 关联方法名称 + * @return $this + */ + public function with($with) + { + if (empty($this->model) || empty($with)) { + return $this; + } + + $this->options['with'] = (array) $with; + return $this; + } + + /** + * 关联预载入 JOIN方式 + * @access protected + * @param array|string $with 关联方法名 + * @param string $joinType JOIN方式 + * @return $this + */ + public function withJoin($with, string $joinType = '') + { + if (empty($this->model) || empty($with)) { + return $this; + } + + $with = (array) $with; + $first = true; + + foreach ($with as $key => $relation) { + $closure = null; + $field = true; + + if ($relation instanceof Closure) { + // 支持闭包查询过滤关联条件 + $closure = $relation; + $relation = $key; + } elseif (is_array($relation)) { + $field = $relation; + $relation = $key; + } elseif (is_string($relation) && strpos($relation, '.')) { + $relation = strstr($relation, '.', true); + } + + $result = $this->model->eagerly($this, $relation, $field, $joinType, $closure, $first); + + if (!$result) { + unset($with[$key]); + } else { + $first = false; + } + } + + $this->via(); + $this->options['with_join'] = $with; + + return $this; + } + + /** + * 关联统计 + * @access protected + * @param array|string $relations 关联方法名 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param bool $subQuery 是否使用子查询 + * @return $this + */ + protected function withAggregate($relations, string $aggregate = 'count', $field = '*', bool $subQuery = true) + { + if (empty($this->model)) { + return $this; + } + + if (!$subQuery) { + $this->options['with_aggregate'][] = [(array) $relations, $aggregate, $field]; + return $this; + } + + if (!isset($this->options['field'])) { + $this->field('*'); + } + + $this->model->relationCount($this, (array) $relations, $aggregate, $field, true); + return $this; + } + + /** + * 关联缓存 + * @access public + * @param string|array|bool $relation 关联方法名 + * @param mixed $key 缓存key + * @param integer|\DateTime $expire 缓存有效期 + * @param string $tag 缓存标签 + * @return $this + */ + public function withCache($relation = true, $key = true, $expire = null, string $tag = null) + { + if (empty($this->model)) { + return $this; + } + + if (false === $relation || false === $key || !$this->getConnection()->getCache()) { + return $this; + } + + if ($key instanceof \DateTimeInterface || $key instanceof \DateInterval || (is_int($key) && is_null($expire))) { + $expire = $key; + $key = true; + } + + if (true === $relation || is_numeric($relation)) { + $this->options['with_cache'] = $relation; + return $this; + } + + $relations = (array) $relation; + foreach ($relations as $name => $relation) { + if (!is_numeric($name)) { + $this->options['with_cache'][$name] = is_array($relation) ? $relation : [$key, $relation, $tag]; + } else { + $this->options['with_cache'][$relation] = [$key, $expire, $tag]; + } + } + + return $this; + } + + /** + * 关联统计 + * @access public + * @param string|array $relation 关联方法名 + * @param bool $subQuery 是否使用子查询 + * @return $this + */ + public function withCount($relation, bool $subQuery = true) + { + return $this->withAggregate($relation, 'count', '*', $subQuery); + } + + /** + * 关联统计Sum + * @access public + * @param string|array $relation 关联方法名 + * @param string $field 字段 + * @param bool $subQuery 是否使用子查询 + * @return $this + */ + public function withSum($relation, string $field, bool $subQuery = true) + { + return $this->withAggregate($relation, 'sum', $field, $subQuery); + } + + /** + * 关联统计Max + * @access public + * @param string|array $relation 关联方法名 + * @param string $field 字段 + * @param bool $subQuery 是否使用子查询 + * @return $this + */ + public function withMax($relation, string $field, bool $subQuery = true) + { + return $this->withAggregate($relation, 'max', $field, $subQuery); + } + + /** + * 关联统计Min + * @access public + * @param string|array $relation 关联方法名 + * @param string $field 字段 + * @param bool $subQuery 是否使用子查询 + * @return $this + */ + public function withMin($relation, string $field, bool $subQuery = true) + { + return $this->withAggregate($relation, 'min', $field, $subQuery); + } + + /** + * 关联统计Avg + * @access public + * @param string|array $relation 关联方法名 + * @param string $field 字段 + * @param bool $subQuery 是否使用子查询 + * @return $this + */ + public function withAvg($relation, string $field, bool $subQuery = true) + { + return $this->withAggregate($relation, 'avg', $field, $subQuery); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $relation 关联方法名 + * @param mixed $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return $this + */ + public function has(string $relation, string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '') + { + return $this->model->has($relation, $operator, $count, $id, $joinType, $this); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $relation 关联方法名 + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @param string $joinType JOIN类型 + * @return $this + */ + public function hasWhere(string $relation, $where = [], string $fields = '*', string $joinType = '') + { + return $this->model->hasWhere($relation, $where, $fields, $joinType, $this); + } + + /** + * JSON字段数据转换 + * @access protected + * @param array $result 查询数据 + * @return void + */ + protected function jsonModelResult(array &$result): void + { + $withAttr = $this->options['with_attr']; + foreach ($this->options['json'] as $name) { + if (!isset($result[$name])) { + continue; + } + + $jsonData = json_decode($result[$name], true); + + if (isset($withAttr[$name])) { + foreach ($withAttr[$name] as $key => $closure) { + $jsonData[$key] = $closure($jsonData[$key] ?? null, $jsonData); + } + } + + $result[$name] = !$this->options['json_assoc'] ? (object) $jsonData : $jsonData; + } + } + + /** + * 查询数据转换为模型数据集对象 + * @access protected + * @param array $resultSet 数据集 + * @return ModelCollection + */ + protected function resultSetToModelCollection(array $resultSet): ModelCollection + { + if (empty($resultSet)) { + return $this->model->toCollection(); + } + + $this->options['is_resultSet'] = true; + + foreach ($resultSet as $key => &$result) { + // 数据转换为模型对象 + $this->resultToModel($result); + } + + foreach (['with', 'with_join'] as $with) { + // 关联预载入 + if (!empty($this->options[$with])) { + $result->eagerlyResultSet( + $resultSet, + $this->options[$with], + $this->options['with_relation_attr'], + 'with_join' == $with ? true : false, + $this->options['with_cache'] ?? false + ); + } + } + + // 模型数据集转换 + return $this->model->toCollection($resultSet); + } + + /** + * 查询数据转换为模型对象 + * @access protected + * @param array $result 查询数据 + * @return void + */ + protected function resultToModel(array &$result): void + { + // JSON数据处理 + if (!empty($this->options['json'])) { + $this->jsonModelResult($result); + } + + $result = $this->model->newInstance( + $result, + !empty($this->options['is_resultSet']) ? null : $this->getModelUpdateCondition($this->options), + $this->options + ); + + // 模型数据处理 + foreach ($this->options['filter'] as $filter) { + call_user_func_array($filter, [$result, $this->options]); + } + + // 关联查询 + if (!empty($this->options['relation'])) { + $result->relationQuery($this->options['relation'], $this->options['with_relation_attr']); + } + + // 关联预载入查询 + if (empty($this->options['is_resultSet'])) { + foreach (['with', 'with_join'] as $with) { + if (!empty($this->options[$with])) { + $result->eagerlyResult( + $this->options[$with], + $this->options['with_relation_attr'], + 'with_join' == $with ? true : false, + $this->options['with_cache'] ?? false + ); + } + } + } + + // 关联统计查询 + if (!empty($this->options['with_aggregate'])) { + foreach ($this->options['with_aggregate'] as $val) { + $result->relationCount($this, $val[0], $val[1], $val[2], false); + } + } + + // 动态获取器 + if (!empty($this->options['with_attr'])) { + $result->withAttr($this->options['with_attr']); + } + + foreach (['hidden', 'visible', 'append'] as $name) { + if (!empty($this->options[$name])) { + $result->$name($this->options[$name]); + } + } + + // 刷新原始数据 + $result->refreshOrigin(); + } + +} diff --git a/vendor/topthink/think-orm/src/db/concern/ParamsBind.php b/vendor/topthink/think-orm/src/db/concern/ParamsBind.php new file mode 100644 index 0000000..88293fa --- /dev/null +++ b/vendor/topthink/think-orm/src/db/concern/ParamsBind.php @@ -0,0 +1,133 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\concern; + +use PDO; + +/** + * 参数绑定支持 + */ +trait ParamsBind +{ + /** + * 当前参数绑定 + * @var array + */ + protected $bind = []; + + /** + * 批量参数绑定 + * @access public + * @param array $value 绑定变量值 + * @return $this + */ + public function bind(array $value) + { + $this->bind = array_merge($this->bind, $value); + return $this; + } + + /** + * 单个参数绑定 + * @access public + * @param mixed $value 绑定变量值 + * @param integer $type 绑定类型 + * @param string $name 绑定标识 + * @return string + */ + public function bindValue($value, int $type = null, string $name = null) + { + $name = $name ?: 'ThinkBind_' . (count($this->bind) + 1) . '_' . mt_rand() . '_'; + + $this->bind[$name] = [$value, $type ?: PDO::PARAM_STR]; + return $name; + } + + /** + * 检测参数是否已经绑定 + * @access public + * @param string $key 参数名 + * @return bool + */ + public function isBind($key) + { + return isset($this->bind[$key]); + } + + /** + * 设置自动参数绑定 + * @access public + * @param bool $bind 是否自动参数绑定 + * @return $this + */ + public function autoBind(bool $bind) + { + $this->options['auto_bind'] = $bind; + return $this; + } + + /** + * 检测是否开启自动参数绑定 + * @access public + * @return bool + */ + public function isAutoBind(): bool + { + $autoBind = $this->getConfig('auto_param_bind'); + if (null !== $this->getOptions('auto_bind')) { + $autoBind = $this->getOptions('auto_bind'); + } + + return (bool) $autoBind; + } + + /** + * 参数绑定 + * @access public + * @param string $sql 绑定的sql表达式 + * @param array $bind 参数绑定 + * @return void + */ + public function bindParams(string &$sql, array $bind = []): void + { + foreach ($bind as $key => $value) { + if (is_array($value)) { + $name = $this->bindValue($value[0], $value[1], $value[2] ?? null); + } else { + $name = $this->bindValue($value); + } + + if (is_numeric($key)) { + $sql = substr_replace($sql, ':' . $name, strpos($sql, '?'), 1); + } else { + $sql = str_replace(':' . $key, ':' . $name, $sql); + } + } + } + + /** + * 获取绑定的参数 并清空 + * @access public + * @param bool $clear 是否清空绑定数据 + * @return array + */ + public function getBind(bool $clear = true): array + { + $bind = $this->bind; + if ($clear) { + $this->bind = []; + } + + return $bind; + } +} diff --git a/vendor/topthink/think-orm/src/db/concern/ResultOperation.php b/vendor/topthink/think-orm/src/db/concern/ResultOperation.php new file mode 100644 index 0000000..ea26916 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/concern/ResultOperation.php @@ -0,0 +1,227 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\concern; + +use Closure; +use think\Collection; +use think\db\exception\DataNotFoundException; +use think\db\exception\DbException; +use think\db\exception\ModelNotFoundException; +use think\db\Query; +use think\helper\Str; +use think\Model; + +/** + * 查询数据处理 + */ +trait ResultOperation +{ + /** + * 设置数据处理(支持模型) + * @access public + * @param callable $filter 数据处理Callable + * @param string $index 索引(唯一) + * @return $this + */ + public function filter(callable $filter, string $index = null) + { + if ($index) { + $this->options['filter'][$index] = $filter; + } else { + $this->options['filter'][] = $filter; + } + return $this; + } + + /** + * 是否允许返回空数据(或空模型) + * @access public + * @param bool $allowEmpty 是否允许为空 + * @return $this + */ + public function allowEmpty(bool $allowEmpty = true) + { + $this->options['allow_empty'] = $allowEmpty; + return $this; + } + + /** + * 设置查询数据不存在是否抛出异常 + * @access public + * @param bool $fail 数据不存在是否抛出异常 + * @return $this + */ + public function failException(bool $fail = true) + { + $this->options['fail'] = $fail; + return $this; + } + + /** + * 处理数据 + * @access protected + * @param array $result 查询数据 + * @return void + */ + protected function result(array &$result): void + { + // JSON数据处理 + if (!empty($this->options['json'])) { + $this->jsonResult($result); + } + + // 查询数据处理 + foreach ($this->options['filter'] as $filter) { + $result = call_user_func_array($filter, [$result, $this->options]); + } + + // 获取器 + if (!empty($this->options['with_attr'])) { + $this->getResultAttr($result, $this->options['with_attr']); + } + } + + /** + * 处理数据集 + * @access public + * @param array $resultSet 数据集 + * @param bool $toCollection 是否转为对象 + * @return void + */ + protected function resultSet(array &$resultSet, bool $toCollection = true): void + { + foreach ($resultSet as &$result) { + $this->result($result); + } + + // 返回Collection对象 + if ($toCollection) { + $resultSet = new Collection($resultSet); + } + } + + /** + * 使用获取器处理数据 + * @access protected + * @param array $result 查询数据 + * @param array $withAttr 字段获取器 + * @return void + */ + protected function getResultAttr(array &$result, array $withAttr = []): void + { + foreach ($withAttr as $name => $closure) { + $name = Str::snake($name); + + if (strpos($name, '.')) { + // 支持JSON字段 获取器定义 + [$key, $field] = explode('.', $name); + + if (isset($result[$key])) { + $result[$key][$field] = $closure($result[$key][$field] ?? null, $result[$key]); + } + } else { + $result[$name] = $closure($result[$name] ?? null, $result); + } + } + } + + /** + * 处理空数据 + * @access protected + * @return array|Model|null|static + * @throws DbException + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + protected function resultToEmpty() + { + if (!empty($this->options['fail'])) { + $this->throwNotFound(); + } elseif (!empty($this->options['allow_empty'])) { + return !empty($this->model) ? $this->model->newInstance() : []; + } + } + + /** + * 查找单条记录 不存在返回空数据(或者空模型) + * @access public + * @param mixed $data 数据 + * @return array|Model|static|mixed + */ + public function findOrEmpty($data = null) + { + return $this->allowEmpty(true)->find($data); + } + + /** + * JSON字段数据转换 + * @access protected + * @param array $result 查询数据 + * @return void + */ + protected function jsonResult(array &$result): void + { + foreach ($this->options['json'] as $name) { + if (!isset($result[$name])) { + continue; + } + + $result[$name] = json_decode($result[$name], true); + } + } + + /** + * 查询失败 抛出异常 + * @access protected + * @return void + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + protected function throwNotFound(): void + { + if (!empty($this->model)) { + $class = get_class($this->model); + throw new ModelNotFoundException('model data Not Found:' . $class, $class, $this->options); + } + + $table = $this->getTable(); + throw new DataNotFoundException('table data not Found:' . $table, $table, $this->options); + } + + /** + * 查找多条记录 如果不存在则抛出异常 + * @access public + * @param array|string|Query|Closure $data 数据 + * @return array|Collection|static[] + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + public function selectOrFail($data = null) + { + return $this->failException(true)->select($data); + } + + /** + * 查找单条记录 如果不存在则抛出异常 + * @access public + * @param array|string|Query|Closure $data 数据 + * @return array|Model|static|mixed + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + public function findOrFail($data = null) + { + return $this->failException(true)->find($data); + } + +} diff --git a/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php b/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php new file mode 100644 index 0000000..9070bef --- /dev/null +++ b/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php @@ -0,0 +1,99 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\concern; + +/** + * 数据字段信息 + */ +trait TableFieldInfo +{ + + /** + * 获取数据表字段信息 + * @access public + * @param string $tableName 数据表名 + * @return array + */ + public function getTableFields($tableName = ''): array + { + if ('' == $tableName) { + $tableName = $this->getTable(); + } + + return $this->connection->getTableFields($tableName); + } + + /** + * 获取详细字段类型信息 + * @access public + * @param string $tableName 数据表名称 + * @return array + */ + public function getFields(string $tableName = ''): array + { + return $this->connection->getFields($tableName ?: $this->getTable()); + } + + /** + * 获取字段类型信息 + * @access public + * @return array + */ + public function getFieldsType(): array + { + if (!empty($this->options['field_type'])) { + return $this->options['field_type']; + } + + return $this->connection->getFieldsType($this->getTable()); + } + + /** + * 获取字段类型信息 + * @access public + * @param string $field 字段名 + * @return string|null + */ + public function getFieldType(string $field) + { + $fieldType = $this->getFieldsType(); + + return $fieldType[$field] ?? null; + } + + /** + * 获取字段类型信息 + * @access public + * @return array + */ + public function getFieldsBindType(): array + { + $fieldType = $this->getFieldsType(); + + return array_map([$this->connection, 'getFieldBindType'], $fieldType); + } + + /** + * 获取字段类型信息 + * @access public + * @param string $field 字段名 + * @return int + */ + public function getFieldBindType(string $field): int + { + $fieldType = $this->getFieldType($field); + + return $this->connection->getFieldBindType($fieldType ?: ''); + } + +} diff --git a/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php b/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php new file mode 100644 index 0000000..69b7eae --- /dev/null +++ b/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php @@ -0,0 +1,214 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\concern; + +/** + * 时间查询支持 + */ +trait TimeFieldQuery +{ + /** + * 日期查询表达式 + * @var array + */ + protected $timeRule = [ + 'today' => ['today', 'tomorrow -1second'], + 'yesterday' => ['yesterday', 'today -1second'], + 'week' => ['this week 00:00:00', 'next week 00:00:00 -1second'], + 'last week' => ['last week 00:00:00', 'this week 00:00:00 -1second'], + 'month' => ['first Day of this month 00:00:00', 'first Day of next month 00:00:00 -1second'], + 'last month' => ['first Day of last month 00:00:00', 'first Day of this month 00:00:00 -1second'], + 'year' => ['this year 1/1', 'next year 1/1 -1second'], + 'last year' => ['last year 1/1', 'this year 1/1 -1second'], + ]; + + /** + * 添加日期或者时间查询规则 + * @access public + * @param array $rule 时间表达式 + * @return $this + */ + public function timeRule(array $rule) + { + $this->timeRule = array_merge($this->timeRule, $rule); + return $this; + } + + /** + * 查询日期或者时间 + * @access public + * @param string $field 日期字段名 + * @param string $op 比较运算符或者表达式 + * @param string|array $range 比较范围 + * @param string $logic AND OR + * @return $this + */ + public function whereTime(string $field, string $op, $range = null, string $logic = 'AND') + { + if (is_null($range)) { + if (isset($this->timeRule[$op])) { + $range = $this->timeRule[$op]; + } else { + $range = $op; + } + $op = is_array($range) ? 'between' : '>='; + } + + return $this->parseWhereExp($logic, $field, strtolower($op) . ' time', $range, [], true); + } + + /** + * 查询某个时间间隔数据 + * @access public + * @param string $field 日期字段名 + * @param string $start 开始时间 + * @param string $interval 时间间隔单位 day/month/year/week/hour/minute/second + * @param int $step 间隔 + * @param string $logic AND OR + * @return $this + */ + public function whereTimeInterval(string $field, string $start, string $interval = 'day', int $step = 1, string $logic = 'AND') + { + $startTime = strtotime($start); + $endTime = strtotime(($step > 0 ? '+' : '-') . abs($step) . ' ' . $interval . (abs($step) > 1 ? 's' : ''), $startTime); + + return $this->whereTime($field, 'between', $step > 0 ? [$startTime, $endTime - 1] : [$endTime, $startTime - 1], $logic); + } + + /** + * 查询月数据 whereMonth('time_field', '2018-1') + * @access public + * @param string $field 日期字段名 + * @param string $month 月份信息 + * @param int $step 间隔 + * @param string $logic AND OR + * @return $this + */ + public function whereMonth(string $field, string $month = 'this month', int $step = 1, string $logic = 'AND') + { + if (in_array($month, ['this month', 'last month'])) { + $month = date('Y-m', strtotime($month)); + } + + return $this->whereTimeInterval($field, $month, 'month', $step, $logic); + } + + /** + * 查询周数据 whereWeek('time_field', '2018-1-1') 从2018-1-1开始的一周数据 + * @access public + * @param string $field 日期字段名 + * @param string $week 周信息 + * @param int $step 间隔 + * @param string $logic AND OR + * @return $this + */ + public function whereWeek(string $field, string $week = 'this week', int $step = 1, string $logic = 'AND') + { + if (in_array($week, ['this week', 'last week'])) { + $week = date('Y-m-d', strtotime($week)); + } + + return $this->whereTimeInterval($field, $week, 'week', $step, $logic); + } + + /** + * 查询年数据 whereYear('time_field', '2018') + * @access public + * @param string $field 日期字段名 + * @param string $year 年份信息 + * @param int $step 间隔 + * @param string $logic AND OR + * @return $this + */ + public function whereYear(string $field, string $year = 'this year', int $step = 1, string $logic = 'AND') + { + if (in_array($year, ['this year', 'last year'])) { + $year = date('Y', strtotime($year)); + } + + return $this->whereTimeInterval($field, $year . '-1-1', 'year', $step, $logic); + } + + /** + * 查询日数据 whereDay('time_field', '2018-1-1') + * @access public + * @param string $field 日期字段名 + * @param string $day 日期信息 + * @param int $step 间隔 + * @param string $logic AND OR + * @return $this + */ + public function whereDay(string $field, string $day = 'today', int $step = 1, string $logic = 'AND') + { + if (in_array($day, ['today', 'yesterday'])) { + $day = date('Y-m-d', strtotime($day)); + } + + return $this->whereTimeInterval($field, $day, 'day', $step, $logic); + } + + /** + * 查询日期或者时间范围 whereBetweenTime('time_field', '2018-1-1','2018-1-15') + * @access public + * @param string $field 日期字段名 + * @param string|int $startTime 开始时间 + * @param string|int $endTime 结束时间 + * @param string $logic AND OR + * @return $this + */ + public function whereBetweenTime(string $field, $startTime, $endTime, string $logic = 'AND') + { + return $this->whereTime($field, 'between', [$startTime, $endTime], $logic); + } + + /** + * 查询日期或者时间范围 whereNotBetweenTime('time_field', '2018-1-1','2018-1-15') + * @access public + * @param string $field 日期字段名 + * @param string|int $startTime 开始时间 + * @param string|int $endTime 结束时间 + * @return $this + */ + public function whereNotBetweenTime(string $field, $startTime, $endTime) + { + return $this->whereTime($field, '<', $startTime) + ->whereTime($field, '>', $endTime, 'OR'); + } + + /** + * 查询当前时间在两个时间字段范围 whereBetweenTimeField('start_time', 'end_time') + * @access public + * @param string $startField 开始时间字段 + * @param string $endField 结束时间字段 + * @return $this + */ + public function whereBetweenTimeField(string $startField, string $endField) + { + return $this->whereTime($startField, '<=', time()) + ->whereTime($endField, '>=', time()); + } + + /** + * 查询当前时间不在两个时间字段范围 whereNotBetweenTimeField('start_time', 'end_time') + * @access public + * @param string $startField 开始时间字段 + * @param string $endField 结束时间字段 + * @return $this + */ + public function whereNotBetweenTimeField(string $startField, string $endField) + { + return $this->whereTime($startField, '>', time()) + ->whereTime($endField, '<', time(), 'OR'); + } + +} diff --git a/vendor/topthink/think-orm/src/db/concern/Transaction.php b/vendor/topthink/think-orm/src/db/concern/Transaction.php new file mode 100644 index 0000000..b586132 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/concern/Transaction.php @@ -0,0 +1,122 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\concern; + +/** + * 事务支持 + */ +trait Transaction +{ + + /** + * 执行数据库Xa事务 + * @access public + * @param callable $callback 数据操作方法回调 + * @param array $dbs 多个查询对象或者连接对象 + * @return mixed + * @throws PDOException + * @throws \Exception + * @throws \Throwable + */ + public function transactionXa(callable $callback, array $dbs = []) + { + return $this->connection->transactionXa($callback, $dbs); + } + + /** + * 执行数据库事务 + * @access public + * @param callable $callback 数据操作方法回调 + * @return mixed + */ + public function transaction(callable $callback) + { + return $this->connection->transaction($callback); + } + + /** + * 启动事务 + * @access public + * @return void + */ + public function startTrans(): void + { + $this->connection->startTrans(); + } + + /** + * 用于非自动提交状态下面的查询提交 + * @access public + * @return void + * @throws PDOException + */ + public function commit(): void + { + $this->connection->commit(); + } + + /** + * 事务回滚 + * @access public + * @return void + * @throws PDOException + */ + public function rollback(): void + { + $this->connection->rollback(); + } + + /** + * 启动XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function startTransXa(string $xid): void + { + $this->connection->startTransXa($xid); + } + + /** + * 预编译XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function prepareXa(string $xid): void + { + $this->connection->prepareXa($xid); + } + + /** + * 提交XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function commitXa(string $xid): void + { + $this->connection->commitXa($xid); + } + + /** + * 回滚XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function rollbackXa(string $xid): void + { + $this->connection->rollbackXa($xid); + } +} diff --git a/vendor/topthink/think-orm/src/db/concern/WhereQuery.php b/vendor/topthink/think-orm/src/db/concern/WhereQuery.php new file mode 100644 index 0000000..f1b49db --- /dev/null +++ b/vendor/topthink/think-orm/src/db/concern/WhereQuery.php @@ -0,0 +1,529 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\concern; + +use Closure; +use think\db\BaseQuery; +use think\db\Raw; + +trait WhereQuery +{ + /** + * 指定AND查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $op 查询表达式 + * @param mixed $condition 查询条件 + * @return $this + */ + public function where($field, $op = null, $condition = null) + { + if ($field instanceof $this) { + $this->parseQueryWhere($field); + return $this; + } elseif (true === $field || 1 === $field) { + $this->options['where']['AND'][] = true; + return $this; + } + + $param = func_get_args(); + array_shift($param); + return $this->parseWhereExp('AND', $field, $op, $condition, $param); + } + + /** + * 解析Query对象查询条件 + * @access public + * @param BaseQuery $query 查询对象 + * @return void + */ + protected function parseQueryWhere(BaseQuery $query): void + { + $this->options['where'] = $query->getOptions('where') ?? []; + + if ($query->getOptions('via')) { + $via = $query->getOptions('via'); + foreach ($this->options['where'] as $logic => &$where) { + foreach ($where as $key => &$val) { + if (is_array($val) && !strpos($val[0], '.')) { + $val[0] = $via . '.' . $val[0]; + } + } + } + } + + $this->bind($query->getBind(false)); + } + + /** + * 指定OR查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $op 查询表达式 + * @param mixed $condition 查询条件 + * @return $this + */ + public function whereOr($field, $op = null, $condition = null) + { + $param = func_get_args(); + array_shift($param); + return $this->parseWhereExp('OR', $field, $op, $condition, $param); + } + + /** + * 指定XOR查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $op 查询表达式 + * @param mixed $condition 查询条件 + * @return $this + */ + public function whereXor($field, $op = null, $condition = null) + { + $param = func_get_args(); + array_shift($param); + return $this->parseWhereExp('XOR', $field, $op, $condition, $param); + } + + /** + * 指定Null查询条件 + * @access public + * @param mixed $field 查询字段 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNull(string $field, string $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'NULL', null, [], true); + } + + /** + * 指定NotNull查询条件 + * @access public + * @param mixed $field 查询字段 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotNull(string $field, string $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'NOTNULL', null, [], true); + } + + /** + * 指定Exists查询条件 + * @access public + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereExists($condition, string $logic = 'AND') + { + if (is_string($condition)) { + $condition = new Raw($condition); + } + + $this->options['where'][strtoupper($logic)][] = ['', 'EXISTS', $condition]; + return $this; + } + + /** + * 指定NotExists查询条件 + * @access public + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotExists($condition, string $logic = 'AND') + { + if (is_string($condition)) { + $condition = new Raw($condition); + } + + $this->options['where'][strtoupper($logic)][] = ['', 'NOT EXISTS', $condition]; + return $this; + } + + /** + * 指定In查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereIn(string $field, $condition, string $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'IN', $condition, [], true); + } + + /** + * 指定NotIn查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotIn(string $field, $condition, string $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'NOT IN', $condition, [], true); + } + + /** + * 指定Like查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereLike(string $field, $condition, string $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'LIKE', $condition, [], true); + } + + /** + * 指定NotLike查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotLike(string $field, $condition, string $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'NOT LIKE', $condition, [], true); + } + + /** + * 指定Between查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereBetween(string $field, $condition, string $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'BETWEEN', $condition, [], true); + } + + /** + * 指定NotBetween查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotBetween(string $field, $condition, string $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'NOT BETWEEN', $condition, [], true); + } + + /** + * 指定FIND_IN_SET查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereFindInSet(string $field, $condition, string $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'FIND IN SET', $condition, [], true); + } + + /** + * 比较两个字段 + * @access public + * @param string $field1 查询字段 + * @param string $operator 比较操作符 + * @param string $field2 比较字段 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereColumn(string $field1, string $operator, string $field2 = null, string $logic = 'AND') + { + if (is_null($field2)) { + $field2 = $operator; + $operator = '='; + } + + return $this->parseWhereExp($logic, $field1, 'COLUMN', [$operator, $field2], [], true); + } + + /** + * 设置软删除字段及条件 + * @access public + * @param string $field 查询字段 + * @param mixed $condition 查询条件 + * @return $this + */ + public function useSoftDelete(string $field, $condition = null) + { + if ($field) { + $this->options['soft_delete'] = [$field, $condition]; + } + + return $this; + } + + /** + * 指定Exp查询条件 + * @access public + * @param mixed $field 查询字段 + * @param string $where 查询条件 + * @param array $bind 参数绑定 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereExp(string $field, string $where, array $bind = [], string $logic = 'AND') + { + $this->options['where'][$logic][] = [$field, 'EXP', new Raw($where, $bind)]; + + return $this; + } + + /** + * 指定字段Raw查询 + * @access public + * @param string $field 查询字段表达式 + * @param mixed $op 查询表达式 + * @param string $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereFieldRaw(string $field, $op, $condition = null, string $logic = 'AND') + { + if (is_null($condition)) { + $condition = $op; + $op = '='; + } + + $this->options['where'][$logic][] = [new Raw($field), $op, $condition]; + return $this; + } + + /** + * 指定表达式查询条件 + * @access public + * @param string $where 查询条件 + * @param array $bind 参数绑定 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereRaw(string $where, array $bind = [], string $logic = 'AND') + { + $this->options['where'][$logic][] = new Raw($where, $bind); + + return $this; + } + + /** + * 指定表达式查询条件 OR + * @access public + * @param string $where 查询条件 + * @param array $bind 参数绑定 + * @return $this + */ + public function whereOrRaw(string $where, array $bind = []) + { + return $this->whereRaw($where, $bind, 'OR'); + } + + /** + * 分析查询表达式 + * @access protected + * @param string $logic 查询逻辑 and or xor + * @param mixed $field 查询字段 + * @param mixed $op 查询表达式 + * @param mixed $condition 查询条件 + * @param array $param 查询参数 + * @param bool $strict 严格模式 + * @return $this + */ + protected function parseWhereExp(string $logic, $field, $op, $condition, array $param = [], bool $strict = false) + { + $logic = strtoupper($logic); + + if (is_string($field) && !empty($this->options['via']) && false === strpos($field, '.')) { + $field = $this->options['via'] . '.' . $field; + } + + if ($strict) { + // 使用严格模式查询 + if ('=' == $op) { + $where = $this->whereEq($field, $condition); + } else { + $where = [$field, $op, $condition, $logic]; + } + } elseif (is_array($field)) { + // 解析数组批量查询 + return $this->parseArrayWhereItems($field, $logic); + } elseif ($field instanceof Closure) { + $where = $field; + } elseif (is_string($field)) { + if ($condition instanceof Raw) { + + } elseif (preg_match('/[,=\<\'\"\(\s]/', $field)) { + return $this->whereRaw($field, is_array($op) ? $op : [], $logic); + } elseif (is_string($op) && strtolower($op) == 'exp' && !is_null($condition)) { + $bind = isset($param[2]) && is_array($param[2]) ? $param[2] : []; + return $this->whereExp($field, $condition, $bind, $logic); + } + + $where = $this->parseWhereItem($logic, $field, $op, $condition, $param); + } + + if (!empty($where)) { + $this->options['where'][$logic][] = $where; + } + + return $this; + } + + /** + * 分析查询表达式 + * @access protected + * @param string $logic 查询逻辑 and or xor + * @param mixed $field 查询字段 + * @param mixed $op 查询表达式 + * @param mixed $condition 查询条件 + * @param array $param 查询参数 + * @return array + */ + protected function parseWhereItem(string $logic, $field, $op, $condition, array $param = []): array + { + if (is_array($op)) { + // 同一字段多条件查询 + array_unshift($param, $field); + $where = $param; + } elseif ($field && is_null($condition)) { + if (is_string($op) && in_array(strtoupper($op), ['NULL', 'NOTNULL', 'NOT NULL'], true)) { + // null查询 + $where = [$field, $op, '']; + } elseif ('=' === $op || is_null($op)) { + $where = [$field, 'NULL', '']; + } elseif ('<>' === $op) { + $where = [$field, 'NOTNULL', '']; + } else { + // 字段相等查询 + $where = $this->whereEq($field, $op); + } + } elseif (is_string($op) && in_array(strtoupper($op), ['EXISTS', 'NOT EXISTS', 'NOTEXISTS'], true)) { + $where = [$field, $op, is_string($condition) ? new Raw($condition) : $condition]; + } else { + $where = $field ? [$field, $op, $condition, $param[2] ?? null] : []; + } + + return $where; + } + + /** + * 相等查询的主键处理 + * @access protected + * @param string $field 字段名 + * @param mixed $value 字段值 + * @return array + */ + protected function whereEq(string $field, $value): array + { + if ($this->getPk() == $field) { + $this->options['key'] = $value; + } + + return [$field, '=', $value]; + } + + /** + * 数组批量查询 + * @access protected + * @param array $field 批量查询 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + protected function parseArrayWhereItems(array $field, string $logic) + { + $where = []; + foreach ($field as $key => $val) { + if (is_int($key)) { + $where[] = $val; + } elseif ($val instanceof Raw) { + $where[] = [$key, 'exp', $val]; + } else { + $where[] = is_null($val) ? [$key, 'NULL', ''] : [$key, is_array($val) ? 'IN' : '=', $val]; + } + } + + if (!empty($where)) { + $this->options['where'][$logic] = isset($this->options['where'][$logic]) ? + array_merge($this->options['where'][$logic], $where) : $where; + } + + return $this; + } + + /** + * 去除某个查询条件 + * @access public + * @param string $field 查询字段 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function removeWhereField(string $field, string $logic = 'AND') + { + $logic = strtoupper($logic); + + if (isset($this->options['where'][$logic])) { + foreach ($this->options['where'][$logic] as $key => $val) { + if (is_array($val) && $val[0] == $field) { + unset($this->options['where'][$logic][$key]); + } + } + } + + return $this; + } + + /** + * 条件查询 + * @access public + * @param mixed $condition 满足条件(支持闭包) + * @param Closure|array $query 满足条件后执行的查询表达式(闭包或数组) + * @param Closure|array $otherwise 不满足条件后执行 + * @return $this + */ + public function when($condition, $query, $otherwise = null) + { + if ($condition instanceof Closure) { + $condition = $condition($this); + } + + if ($condition) { + if ($query instanceof Closure) { + $query($this, $condition); + } elseif (is_array($query)) { + $this->where($query); + } + } elseif ($otherwise) { + if ($otherwise instanceof Closure) { + $otherwise($this, $condition); + } elseif (is_array($otherwise)) { + $this->where($otherwise); + } + } + + return $this; + } +} diff --git a/vendor/topthink/think-orm/src/db/connector/Mongo.php b/vendor/topthink/think-orm/src/db/connector/Mongo.php new file mode 100644 index 0000000..418dc50 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/connector/Mongo.php @@ -0,0 +1,1175 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\connector; + +use Closure; +use MongoDB\BSON\ObjectID; +use MongoDB\Driver\BulkWrite; +use MongoDB\Driver\Command; +use MongoDB\Driver\Cursor; +use MongoDB\Driver\Exception\AuthenticationException; +use MongoDB\Driver\Exception\BulkWriteException; +use MongoDB\Driver\Exception\ConnectionException; +use MongoDB\Driver\Exception\InvalidArgumentException; +use MongoDB\Driver\Exception\RuntimeException; +use MongoDB\Driver\Manager; +use MongoDB\Driver\Query as MongoQuery; +use MongoDB\Driver\ReadPreference; +use MongoDB\Driver\WriteConcern; +use think\db\BaseQuery; +use think\db\builder\Mongo as Builder; +use think\db\Connection; +use think\db\exception\DbEventException; +use think\db\exception\DbException as Exception; +use think\db\Mongo as Query; + +/** + * Mongo数据库驱动 + * @property Manager[] $links + * @property Manager $linkRead + * @property Manager $linkWrite + */ +class Mongo extends Connection +{ + + // 查询数据类型 + protected $dbName = ''; + protected $typeMap = 'array'; + protected $mongo; // MongoDb Object + protected $cursor; // MongoCursor Object + protected $session_uuid; // sessions会话列表当前会话数组key 随机生成 + protected $sessions = []; // 会话列表 + + /** @var Builder */ + protected $builder; + + // 数据库连接参数配置 + protected $config = [ + // 数据库类型 + 'type' => '', + // 服务器地址 + 'hostname' => '', + // 数据库名 + 'database' => '', + // 是否是复制集 + 'is_replica_set' => false, + // 用户名 + 'username' => '', + // 密码 + 'password' => '', + // 端口 + 'hostport' => '', + // 连接dsn + 'dsn' => '', + // 数据库连接参数 + 'params' => [], + // 数据库编码默认采用utf8 + 'charset' => 'utf8', + // 主键名 + 'pk' => '_id', + // 主键类型 + 'pk_type' => 'ObjectID', + // 数据库表前缀 + 'prefix' => '', + // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器) + 'deploy' => 0, + // 数据库读写是否分离 主从式有效 + 'rw_separate' => false, + // 读写分离后 主服务器数量 + 'master_num' => 1, + // 指定从服务器序号 + 'slave_no' => '', + // 是否严格检查字段是否存在 + 'fields_strict' => true, + // 开启字段缓存 + 'fields_cache' => false, + // 监听SQL + 'trigger_sql' => true, + // 自动写入时间戳字段 + 'auto_timestamp' => false, + // 时间字段取出后的默认时间格式 + 'datetime_format' => 'Y-m-d H:i:s', + // 是否_id转换为id + 'pk_convert_id' => false, + // typeMap + 'type_map' => ['root' => 'array', 'document' => 'array'], + ]; + + /** + * 获取当前连接器类对应的Query类 + * @access public + * @return string + */ + public function getQueryClass(): string + { + return Query::class; + } + + /** + * 获取当前的builder实例对象 + * @access public + * @return Builder + */ + public function getBuilder() + { + return $this->builder; + } + + /** + * 获取当前连接器类对应的Builder类 + * @access public + * @return string + */ + public function getBuilderClass(): string + { + return Builder::class; + } + + /** + * 连接数据库方法 + * @access public + * @param array $config 连接参数 + * @param integer $linkNum 连接序号 + * @return Manager + * @throws InvalidArgumentException + * @throws RuntimeException + */ + public function connect(array $config = [], $linkNum = 0) + { + if (!isset($this->links[$linkNum])) { + if (empty($config)) { + $config = $this->config; + } else { + $config = array_merge($this->config, $config); + } + + $this->dbName = $config['database']; + $this->typeMap = $config['type_map']; + + if ($config['pk_convert_id'] && '_id' == $config['pk']) { + $this->config['pk'] = 'id'; + } + + if (empty($config['dsn'])) { + $config['dsn'] = 'mongodb://' . ($config['username'] ? "{$config['username']}" : '') . ($config['password'] ? ":{$config['password']}@" : '') . $config['hostname'] . ($config['hostport'] ? ":{$config['hostport']}" : ''); + } + + $startTime = microtime(true); + + $this->links[$linkNum] = new Manager($config['dsn'], $config['params']); + + if (!empty($config['trigger_sql'])) { + // 记录数据库连接信息 + $this->trigger('CONNECT:[ UseTime:' . number_format(microtime(true) - $startTime, 6) . 's ] ' . $config['dsn']); + } + + } + + return $this->links[$linkNum]; + } + + /** + * 获取Mongo Manager对象 + * @access public + * @return Manager|null + */ + public function getMongo() + { + return $this->mongo ?: null; + } + + /** + * 设置/获取当前操作的database + * @access public + * @param string $db db + * @return string + */ + public function db(string $db = null) + { + if (is_null($db)) { + return $this->dbName; + } else { + $this->dbName = $db; + } + } + + /** + * 执行查询但只返回Cursor对象 + * @access public + * @param Query $query 查询对象 + * @return Cursor + */ + public function cursor($query) + { + // 分析查询表达式 + $options = $query->parseOptions(); + + // 生成MongoQuery对象 + $mongoQuery = $this->builder->select($query); + + $master = $query->getOptions('master') ? true : false; + + // 执行查询操作 + return $this->getCursor($query, $mongoQuery, $master); + } + + /** + * 执行查询并返回Cursor对象 + * @access public + * @param BaseQuery $query 查询对象 + * @param MongoQuery|Closure $mongoQuery Mongo查询对象 + * @param bool $master 是否主库操作 + * @return Cursor + * @throws AuthenticationException + * @throws InvalidArgumentException + * @throws ConnectionException + * @throws RuntimeException + */ + public function getCursor(BaseQuery $query, $mongoQuery, bool $master = false): Cursor + { + $this->initConnect($master); + $this->db->updateQueryTimes(); + + $options = $query->getOptions(); + $namespace = $options['table']; + + if (false === strpos($namespace, '.')) { + $namespace = $this->dbName . '.' . $namespace; + } + + if (!empty($this->queryStr)) { + // 记录执行指令 + $this->queryStr = 'db' . strstr($namespace, '.') . '.' . $this->queryStr; + } + + if ($mongoQuery instanceof Closure) { + $mongoQuery = $mongoQuery($query); + } + + $readPreference = $options['readPreference'] ?? null; + $this->queryStartTime = microtime(true); + + if ($session = $this->getSession()) { + $this->cursor = $this->mongo->executeQuery($namespace, $query, [ + 'readPreference' => is_null($readPreference) ? new ReadPreference(ReadPreference::RP_PRIMARY) : $readPreference, + 'session' => $session, + ]); + } else { + $this->cursor = $this->mongo->executeQuery($namespace, $mongoQuery, $readPreference); + } + + // SQL监控 + if (!empty($this->config['trigger_sql'])) { + $this->trigger('', $master); + } + + return $this->cursor; + } + + /** + * 执行查询 返回数据集 + * @access public + * @param MongoQuery $query 查询对象 + * @return mixed + * @throws AuthenticationException + * @throws InvalidArgumentException + * @throws ConnectionException + * @throws RuntimeException + */ + public function query(MongoQuery $query) + { + return $this->mongoQuery($this->newQuery(), $query); + } + + /** + * 执行语句 + * @access public + * @param BulkWrite $bulk + * @return int + * @throws AuthenticationException + * @throws InvalidArgumentException + * @throws ConnectionException + * @throws RuntimeException + * @throws BulkWriteException + */ + public function execute(BulkWrite $bulk) + { + return $this->mongoExecute($this->newQuery(), $bulk); + } + + /** + * 执行查询 + * @access protected + * @param BaseQuery $query 查询对象 + * @param MongoQuery|Closure $mongoQuery Mongo查询对象 + * @return array + * @throws AuthenticationException + * @throws InvalidArgumentException + * @throws ConnectionException + * @throws RuntimeException + */ + protected function mongoQuery(BaseQuery $query, $mongoQuery): array + { + $options = $query->parseOptions(); + + if ($query->getOptions('cache')) { + // 检查查询缓存 + $cacheItem = $this->parseCache($query, $query->getOptions('cache')); + $key = $cacheItem->getKey(); + + if ($this->cache->has($key)) { + return $this->cache->get($key); + } + } + + if ($mongoQuery instanceof Closure) { + $mongoQuery = $mongoQuery($query); + } + + $master = $query->getOptions('master') ? true : false; + $this->getCursor($query, $mongoQuery, $master); + + $resultSet = $this->getResult($options['typeMap']); + + if (isset($cacheItem) && $resultSet) { + // 缓存数据集 + $cacheItem->set($resultSet); + $this->cacheData($cacheItem); + } + + return $resultSet; + } + + /** + * 执行写操作 + * @access protected + * @param BaseQuery $query + * @param BulkWrite $bulk + * + * @return WriteResult + * @throws AuthenticationException + * @throws InvalidArgumentException + * @throws ConnectionException + * @throws RuntimeException + * @throws BulkWriteException + */ + protected function mongoExecute(BaseQuery $query, BulkWrite $bulk) + { + $this->initConnect(true); + $this->db->updateQueryTimes(); + + $options = $query->getOptions(); + + $namespace = $options['table']; + if (false === strpos($namespace, '.')) { + $namespace = $this->dbName . '.' . $namespace; + } + + if (!empty($this->queryStr)) { + // 记录执行指令 + $this->queryStr = 'db' . strstr($namespace, '.') . '.' . $this->queryStr; + } + + $writeConcern = $options['writeConcern'] ?? null; + $this->queryStartTime = microtime(true); + + if ($session = $this->getSession()) { + $writeResult = $this->mongo->executeBulkWrite($namespace, $bulk, [ + 'session' => $session, + 'writeConcern' => is_null($writeConcern) ? new WriteConcern(1) : $writeConcern, + ]); + } else { + $writeResult = $this->mongo->executeBulkWrite($namespace, $bulk, $writeConcern); + } + + // SQL监控 + if (!empty($this->config['trigger_sql'])) { + $this->trigger(); + } + + $this->numRows = $writeResult->getMatchedCount(); + + if ($query->getOptions('cache')) { + // 清理缓存数据 + $cacheItem = $this->parseCache($query, $query->getOptions('cache')); + $key = $cacheItem->getKey(); + $tag = $cacheItem->getTag(); + + if (isset($key) && $this->cache->has($key)) { + $this->cache->delete($key); + } elseif (!empty($tag) && method_exists($this->cache, 'tag')) { + $this->cache->tag($tag)->clear(); + } + } + + return $writeResult; + } + + /** + * 执行指令 + * @access public + * @param Command $command 指令 + * @param string $dbName 当前数据库名 + * @param ReadPreference $readPreference readPreference + * @param string|array $typeMap 指定返回的typeMap + * @param bool $master 是否主库操作 + * @return array + * @throws AuthenticationException + * @throws InvalidArgumentException + * @throws ConnectionException + * @throws RuntimeException + */ + public function command(Command $command, string $dbName = '', ReadPreference $readPreference = null, $typeMap = null, bool $master = false): array + { + $this->initConnect($master); + $this->db->updateQueryTimes(); + + $this->queryStartTime = microtime(true); + + $dbName = $dbName ?: $this->dbName; + + if (!empty($this->queryStr)) { + $this->queryStr = 'db.' . $this->queryStr; + } + + if ($session = $this->getSession()) { + $this->cursor = $this->mongo->executeCommand($dbName, $command, [ + 'readPreference' => is_null($readPreference) ? new ReadPreference(ReadPreference::RP_PRIMARY) : $readPreference, + 'session' => $session, + ]); + } else { + $this->cursor = $this->mongo->executeCommand($dbName, $command, $readPreference); + } + + // SQL监控 + if (!empty($this->config['trigger_sql'])) { + $this->trigger('', $master); + } + + return $this->getResult($typeMap); + } + + /** + * 获得数据集 + * @access protected + * @param string|array $typeMap 指定返回的typeMap + * @return mixed + */ + protected function getResult($typeMap = null): array + { + // 设置结果数据类型 + if (is_null($typeMap)) { + $typeMap = $this->typeMap; + } + + $typeMap = is_string($typeMap) ? ['root' => $typeMap] : $typeMap; + + $this->cursor->setTypeMap($typeMap); + + // 获取数据集 + $result = $this->cursor->toArray(); + + if ($this->getConfig('pk_convert_id')) { + // 转换ObjectID 字段 + foreach ($result as &$data) { + $this->convertObjectID($data); + } + } + + $this->numRows = count($result); + + return $result; + } + + /** + * ObjectID处理 + * @access protected + * @param array $data 数据 + * @return void + */ + protected function convertObjectID(array &$data): void + { + if (isset($data['_id']) && is_object($data['_id'])) { + $data['id'] = $data['_id']->__toString(); + unset($data['_id']); + } + } + + /** + * 数据库日志记录(仅供参考) + * @access public + * @param string $type 类型 + * @param mixed $data 数据 + * @param array $options 参数 + * @return void + */ + public function mongoLog(string $type, $data, array $options = []) + { + if (!$this->config['trigger_sql']) { + return; + } + + if (is_array($data)) { + array_walk_recursive($data, function (&$value) { + if ($value instanceof ObjectID) { + $value = $value->__toString(); + } + }); + } + + switch (strtolower($type)) { + case 'aggregate': + $this->queryStr = 'runCommand(' . ($data ? json_encode($data) : '') . ');'; + break; + case 'find': + $this->queryStr = $type . '(' . ($data ? json_encode($data) : '') . ')'; + + if (isset($options['sort'])) { + $this->queryStr .= '.sort(' . json_encode($options['sort']) . ')'; + } + + if (isset($options['skip'])) { + $this->queryStr .= '.skip(' . $options['skip'] . ')'; + } + + if (isset($options['limit'])) { + $this->queryStr .= '.limit(' . $options['limit'] . ')'; + } + + $this->queryStr .= ';'; + break; + case 'insert': + case 'remove': + $this->queryStr = $type . '(' . ($data ? json_encode($data) : '') . ');'; + break; + case 'update': + $this->queryStr = $type . '(' . json_encode($options) . ',' . json_encode($data) . ');'; + break; + case 'cmd': + $this->queryStr = $data . '(' . json_encode($options) . ');'; + break; + } + + $this->options = $options; + } + + /** + * 获取最近执行的指令 + * @access public + * @return string + */ + public function getLastSql(): string + { + return $this->queryStr; + } + + /** + * 关闭数据库 + * @access public + */ + public function close() + { + $this->mongo = null; + $this->cursor = null; + $this->linkRead = null; + $this->linkWrite = null; + $this->links = []; + } + + /** + * 初始化数据库连接 + * @access protected + * @param boolean $master 是否主服务器 + * @return void + */ + protected function initConnect(bool $master = true): void + { + if (!empty($this->config['deploy'])) { + // 采用分布式数据库 + if ($master) { + if (!$this->linkWrite) { + $this->linkWrite = $this->multiConnect(true); + } + + $this->mongo = $this->linkWrite; + } else { + if (!$this->linkRead) { + $this->linkRead = $this->multiConnect(false); + } + + $this->mongo = $this->linkRead; + } + } elseif (!$this->mongo) { + // 默认单数据库 + $this->mongo = $this->connect(); + } + } + + /** + * 连接分布式服务器 + * @access protected + * @param boolean $master 主服务器 + * @return Manager + */ + protected function multiConnect(bool $master = false): Manager + { + $config = []; + // 分布式数据库配置解析 + foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn'] as $name) { + $config[$name] = is_string($this->config[$name]) ? explode(',', $this->config[$name]) : $this->config[$name]; + } + + // 主服务器序号 + $m = floor(mt_rand(0, $this->config['master_num'] - 1)); + + if ($this->config['rw_separate']) { + // 主从式采用读写分离 + if ($master) // 主服务器写入 + { + if ($this->config['is_replica_set']) { + return $this->replicaSetConnect(); + } else { + $r = $m; + } + } elseif (is_numeric($this->config['slave_no'])) { + // 指定服务器读 + $r = $this->config['slave_no']; + } else { + // 读操作连接从服务器 每次随机连接的数据库 + $r = floor(mt_rand($this->config['master_num'], count($config['hostname']) - 1)); + } + } else { + // 读写操作不区分服务器 每次随机连接的数据库 + $r = floor(mt_rand(0, count($config['hostname']) - 1)); + } + + $dbConfig = []; + + foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn'] as $name) { + $dbConfig[$name] = $config[$name][$r] ?? $config[$name][0]; + } + + return $this->connect($dbConfig, $r); + } + + /** + * 创建基于复制集的连接 + * @return Manager + */ + public function replicaSetConnect(): Manager + { + $this->dbName = $this->config['database']; + $this->typeMap = $this->config['type_map']; + + $startTime = microtime(true); + + $this->config['params']['replicaSet'] = $this->config['database']; + + $manager = new Manager($this->buildUrl(), $this->config['params']); + + // 记录数据库连接信息 + if (!empty($config['trigger_sql'])) { + $this->trigger('CONNECT:ReplicaSet[ UseTime:' . number_format(microtime(true) - $startTime, 6) . 's ] ' . $this->config['dsn']); + } + + return $manager; + } + + /** + * 根据配置信息 生成适用于连接复制集的 URL + * @return string + */ + private function buildUrl(): string + { + $url = 'mongodb://' . ($this->config['username'] ? "{$this->config['username']}" : '') . ($this->config['password'] ? ":{$this->config['password']}@" : ''); + + $hostList = is_string($this->config['hostname']) ? explode(',', $this->config['hostname']) : $this->config['hostname']; + $portList = is_string($this->config['hostport']) ? explode(',', $this->config['hostport']) : $this->config['hostport']; + + for ($i = 0; $i < count($hostList); $i++) { + $url = $url . $hostList[$i] . ':' . $portList[0] . ','; + } + + return rtrim($url, ",") . '/'; + } + + /** + * 插入记录 + * @access public + * @param BaseQuery $query 查询对象 + * @param boolean $getLastInsID 返回自增主键 + * @return mixed + * @throws AuthenticationException + * @throws InvalidArgumentException + * @throws ConnectionException + * @throws RuntimeException + * @throws BulkWriteException + */ + public function insert(BaseQuery $query, bool $getLastInsID = false) + { + // 分析查询表达式 + $options = $query->parseOptions(); + + if (empty($options['data'])) { + throw new Exception('miss data to insert'); + } + + // 生成bulk对象 + $bulk = $this->builder->insert($query); + + $writeResult = $this->mongoExecute($query, $bulk); + $result = $writeResult->getInsertedCount(); + + if ($result) { + $data = $options['data']; + $lastInsId = $this->getLastInsID($query); + + if ($lastInsId) { + $pk = $query->getPk(); + $data[$pk] = $lastInsId; + } + + $query->setOption('data', $data); + + $this->db->trigger('after_insert', $query); + + if ($getLastInsID) { + return $lastInsId; + } + } + + return $result; + } + + /** + * 获取最近插入的ID + * @access public + * @param BaseQuery $query 查询对象 + * @return mixed + */ + public function getLastInsID(BaseQuery $query) + { + $id = $this->builder->getLastInsID(); + + if (is_array($id)) { + array_walk($id, function (&$item, $key) { + if ($item instanceof ObjectID) { + $item = $item->__toString(); + } + }); + } elseif ($id instanceof ObjectID) { + $id = $id->__toString(); + } + + return $id; + } + + /** + * 批量插入记录 + * @access public + * @param BaseQuery $query 查询对象 + * @param array $dataSet 数据集 + * @return integer + * @throws AuthenticationException + * @throws InvalidArgumentException + * @throws ConnectionException + * @throws RuntimeException + * @throws BulkWriteException + */ + public function insertAll(BaseQuery $query, array $dataSet = []): int + { + // 分析查询表达式 + $query->parseOptions(); + + if (!is_array(reset($dataSet))) { + return 0; + } + + // 生成bulkWrite对象 + $bulk = $this->builder->insertAll($query, $dataSet); + + $writeResult = $this->mongoExecute($query, $bulk); + + return $writeResult->getInsertedCount(); + } + + /** + * 更新记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return int + * @throws Exception + * @throws AuthenticationException + * @throws InvalidArgumentException + * @throws ConnectionException + * @throws RuntimeException + * @throws BulkWriteException + */ + public function update(BaseQuery $query): int + { + $query->parseOptions(); + + // 生成bulkWrite对象 + $bulk = $this->builder->update($query); + + $writeResult = $this->mongoExecute($query, $bulk); + + $result = $writeResult->getModifiedCount(); + + if ($result) { + $this->db->trigger('after_update', $query); + } + + return $result; + } + + /** + * 删除记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return int + * @throws Exception + * @throws AuthenticationException + * @throws InvalidArgumentException + * @throws ConnectionException + * @throws RuntimeException + * @throws BulkWriteException + */ + public function delete(BaseQuery $query): int + { + // 分析查询表达式 + $query->parseOptions(); + + // 生成bulkWrite对象 + $bulk = $this->builder->delete($query); + + // 执行操作 + $writeResult = $this->mongoExecute($query, $bulk); + + $result = $writeResult->getDeletedCount(); + + if ($result) { + $this->db->trigger('after_delete', $query); + } + + return $result; + } + + /** + * 查找记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return array + * @throws ModelNotFoundException + * @throws DataNotFoundException + * @throws AuthenticationException + * @throws InvalidArgumentException + * @throws ConnectionException + * @throws RuntimeException + */ + public function select(BaseQuery $query): array + { + try { + $this->db->trigger('before_select', $query); + } catch (DbEventException $e) { + return []; + } + + return $this->mongoQuery($query, function ($query) { + return $this->builder->select($query); + }); + } + + /** + * 查找单条记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return array + * @throws ModelNotFoundException + * @throws DataNotFoundException + * @throws AuthenticationException + * @throws InvalidArgumentException + * @throws ConnectionException + * @throws RuntimeException + */ + public function find(BaseQuery $query): array + { + // 事件回调 + try { + $this->db->trigger('before_find', $query); + } catch (DbEventException $e) { + return []; + } + + // 执行查询 + $resultSet = $this->mongoQuery($query, function ($query) { + return $this->builder->select($query, true); + }); + + return $resultSet[0] ?? []; + } + + /** + * 得到某个字段的值 + * @access public + * @param string $field 字段名 + * @param mixed $default 默认值 + * @return mixed + */ + public function value(BaseQuery $query, string $field, $default = null) + { + $options = $query->parseOptions(); + + if (isset($options['projection'])) { + $query->removeOption('projection'); + } + + $query->setOption('projection', (array) $field); + + if (!empty($options['cache'])) { + $cacheItem = $this->parseCache($query, $options['cache']); + $key = $cacheItem->getKey(); + + if ($this->cache->has($key)) { + return $this->cache->get($key); + } + } + + $mongoQuery = $this->builder->select($query, true); + + if (isset($options['projection'])) { + $query->setOption('projection', $options['projection']); + } else { + $query->removeOption('projection'); + } + + // 执行查询操作 + $resultSet = $this->mongoQuery($query, $mongoQuery); + + if (!empty($resultSet)) { + $data = array_shift($resultSet); + $result = $data[$field]; + } else { + $result = false; + } + + if (isset($cacheItem) && false !== $result) { + // 缓存数据 + $cacheItem->set($result); + $this->cacheData($cacheItem); + } + + return false !== $result ? $result : $default; + } + + /** + * 得到某个列的数组 + * @access public + * @param BaseQuery $query + * @param string|array $field 字段名 多个字段用逗号分隔 + * @param string $key 索引 + * @return array + */ + public function column(BaseQuery $query, $field, string $key = ''): array + { + $options = $query->parseOptions(); + + if (isset($options['projection'])) { + $query->removeOption('projection'); + } + + if (is_array($field)) { + $field = implode(',', $field); + } + if ($key && '*' != $field) { + $projection = $key . ',' . $field; + } else { + $projection = $field; + } + + $query->field($projection); + + if (!empty($options['cache'])) { + // 判断查询缓存 + $cacheItem = $this->parseCache($query, $options['cache']); + $key = $cacheItem->getKey(); + + if ($this->cache->has($key)) { + return $this->cache->get($key); + } + } + + $mongoQuery = $this->builder->select($query); + + if (isset($options['projection'])) { + $query->setOption('projection', $options['projection']); + } else { + $query->removeOption('projection'); + } + + // 执行查询操作 + $resultSet = $this->mongoQuery($query, $mongoQuery); + + if (('*' == $field || strpos($field, ',')) && $key) { + $result = array_column($resultSet, null, $key); + } elseif (!empty($resultSet)) { + $result = array_column($resultSet, $field, $key); + } else { + $result = []; + } + + if (isset($cacheItem)) { + // 缓存数据 + $cacheItem->set($result); + $this->cacheData($cacheItem); + } + + return $result; + } + + /** + * 执行command + * @access public + * @param BaseQuery $query 查询对象 + * @param string|array|object $command 指令 + * @param mixed $extra 额外参数 + * @param string $db 数据库名 + * @return array + */ + public function cmd(BaseQuery $query, $command, $extra = null, string $db = ''): array + { + if (is_array($command) || is_object($command)) { + + $this->mongoLog('cmd', 'cmd', $command); + + // 直接创建Command对象 + $command = new Command($command); + } else { + // 调用Builder封装的Command对象 + $command = $this->builder->$command($query, $extra); + } + + return $this->command($command, $db); + } + + /** + * 获取数据库字段 + * @access public + * @param mixed $tableName 数据表名 + * @return array + */ + public function getTableFields($tableName): array + { + return []; + } + + /** + * 执行数据库事务 + * @access public + * @param callable $callback 数据操作方法回调 + * @return mixed + * @throws PDOException + * @throws \Exception + * @throws \Throwable + */ + public function transaction(callable $callback) + { + $this->startTrans(); + try { + $result = null; + if (is_callable($callback)) { + $result = call_user_func_array($callback, [$this]); + } + $this->commit(); + return $result; + } catch (\Exception $e) { + $this->rollback(); + throw $e; + } catch (\Throwable $e) { + $this->rollback(); + throw $e; + } + } + + /** + * 启动事务 + * @access public + * @return void + * @throws \PDOException + * @throws \Exception + */ + public function startTrans() + { + $this->initConnect(true); + $this->session_uuid = uniqid(); + $this->sessions[$this->session_uuid] = $this->getMongo()->startSession(); + + $this->sessions[$this->session_uuid]->startTransaction([]); + } + + /** + * 用于非自动提交状态下面的查询提交 + * @access public + * @return void + * @throws PDOException + */ + public function commit() + { + if ($session = $this->getSession()) { + $session->commitTransaction(); + $this->setLastSession(); + } + } + + /** + * 事务回滚 + * @access public + * @return void + * @throws PDOException + */ + public function rollback() + { + if ($session = $this->getSession()) { + $session->abortTransaction(); + $this->setLastSession(); + } + } + + /** + * 结束当前会话,设置上一个会话为当前会话 + * @author klinson + */ + protected function setLastSession() + { + if ($session = $this->getSession()) { + $session->endSession(); + unset($this->sessions[$this->session_uuid]); + if (empty($this->sessions)) { + $this->session_uuid = null; + } else { + end($this->sessions); + $this->session_uuid = key($this->sessions); + } + } + } + + /** + * 获取当前会话 + * @return \MongoDB\Driver\Session|null + * @author klinson + */ + public function getSession() + { + return ($this->session_uuid && isset($this->sessions[$this->session_uuid])) + ? $this->sessions[$this->session_uuid] + : null; + } +} diff --git a/vendor/topthink/think-orm/src/db/connector/Mysql.php b/vendor/topthink/think-orm/src/db/connector/Mysql.php new file mode 100644 index 0000000..fd9e63a --- /dev/null +++ b/vendor/topthink/think-orm/src/db/connector/Mysql.php @@ -0,0 +1,162 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\connector; + +use PDO; +use think\db\PDOConnection; + +/** + * mysql数据库驱动 + */ +class Mysql extends PDOConnection +{ + + /** + * 解析pdo连接的dsn信息 + * @access protected + * @param array $config 连接信息 + * @return string + */ + protected function parseDsn(array $config): string + { + if (!empty($config['socket'])) { + $dsn = 'mysql:unix_socket=' . $config['socket']; + } elseif (!empty($config['hostport'])) { + $dsn = 'mysql:host=' . $config['hostname'] . ';port=' . $config['hostport']; + } else { + $dsn = 'mysql:host=' . $config['hostname']; + } + $dsn .= ';dbname=' . $config['database']; + + if (!empty($config['charset'])) { + $dsn .= ';charset=' . $config['charset']; + } + + return $dsn; + } + + /** + * 取得数据表的字段信息 + * @access public + * @param string $tableName + * @return array + */ + public function getFields(string $tableName): array + { + [$tableName] = explode(' ', $tableName); + + if (false === strpos($tableName, '`')) { + if (strpos($tableName, '.')) { + $tableName = str_replace('.', '`.`', $tableName); + } + $tableName = '`' . $tableName . '`'; + } + + $sql = 'SHOW FULL COLUMNS FROM ' . $tableName; + $pdo = $this->getPDOStatement($sql); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + + if (!empty($result)) { + foreach ($result as $key => $val) { + $val = array_change_key_case($val); + + $info[$val['field']] = [ + 'name' => $val['field'], + 'type' => $val['type'], + 'notnull' => 'NO' == $val['null'], + 'default' => $val['default'], + 'primary' => strtolower($val['key']) == 'pri', + 'autoinc' => strtolower($val['extra']) == 'auto_increment', + 'comment' => $val['comment'], + ]; + } + } + + return $this->fieldCase($info); + } + + /** + * 取得数据库的表信息 + * @access public + * @param string $dbName + * @return array + */ + public function getTables(string $dbName = ''): array + { + $sql = !empty($dbName) ? 'SHOW TABLES FROM ' . $dbName : 'SHOW TABLES '; + $pdo = $this->getPDOStatement($sql); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + + foreach ($result as $key => $val) { + $info[$key] = current($val); + } + + return $info; + } + + protected function supportSavepoint(): bool + { + return true; + } + + /** + * 启动XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function startTransXa(string $xid): void + { + $this->initConnect(true); + $this->linkID->exec("XA START '$xid'"); + } + + /** + * 预编译XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function prepareXa(string $xid): void + { + $this->initConnect(true); + $this->linkID->exec("XA END '$xid'"); + $this->linkID->exec("XA PREPARE '$xid'"); + } + + /** + * 提交XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function commitXa(string $xid): void + { + $this->initConnect(true); + $this->linkID->exec("XA COMMIT '$xid'"); + } + + /** + * 回滚XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function rollbackXa(string $xid): void + { + $this->initConnect(true); + $this->linkID->exec("XA ROLLBACK '$xid'"); + } +} diff --git a/vendor/topthink/think-orm/src/db/connector/Oracle.php b/vendor/topthink/think-orm/src/db/connector/Oracle.php new file mode 100644 index 0000000..c8e957a --- /dev/null +++ b/vendor/topthink/think-orm/src/db/connector/Oracle.php @@ -0,0 +1,119 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\connector; + +use PDO; +use think\db\BaseQuery; +use think\db\PDOConnection; + +/** + * Oracle数据库驱动 + */ +class Oracle extends PDOConnection +{ + /** + * 解析pdo连接的dsn信息 + * @access protected + * @param array $config 连接信息 + * @return string + */ + protected function parseDsn(array $config): string + { + $dsn = 'oci:dbname='; + + if (!empty($config['hostname'])) { + // Oracle Instant Client + $dsn .= '//' . $config['hostname'] . ($config['hostport'] ? ':' . $config['hostport'] : '') . '/'; + } + + $dsn .= $config['database']; + + if (!empty($config['charset'])) { + $dsn .= ';charset=' . $config['charset']; + } + + return $dsn; + } + + /** + * 取得数据表的字段信息 + * @access public + * @param string $tableName + * @return array + */ + public function getFields(string $tableName): array + { + [$tableName] = explode(' ', $tableName); + $sql = "select a.column_name,data_type,DECODE (nullable, 'Y', 0, 1) notnull,data_default, DECODE (A .column_name,b.column_name,1,0) pk from all_tab_columns a,(select column_name from all_constraints c, all_cons_columns col where c.constraint_name = col.constraint_name and c.constraint_type = 'P' and c.table_name = '" . $tableName . "' ) b where table_name = '" . $tableName . "' and a.column_name = b.column_name (+)"; + + $pdo = $this->getPDOStatement($sql); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + + if ($result) { + foreach ($result as $key => $val) { + $val = array_change_key_case($val); + + $info[$val['column_name']] = [ + 'name' => $val['column_name'], + 'type' => $val['data_type'], + 'notnull' => $val['notnull'], + 'default' => $val['data_default'], + 'primary' => $val['pk'], + 'autoinc' => $val['pk'], + ]; + } + } + + return $this->fieldCase($info); + } + + /** + * 取得数据库的表信息(暂时实现取得用户表信息) + * @access public + * @param string $dbName + * @return array + */ + public function getTables(string $dbName = ''): array + { + $sql = 'select table_name from all_tables'; + $pdo = $this->getPDOStatement($sql); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + + foreach ($result as $key => $val) { + $info[$key] = current($val); + } + + return $info; + } + + /** + * 获取最近插入的ID + * @access public + * @param BaseQuery $query 查询对象 + * @param string|null $sequence 自增序列名 + * @return mixed + */ + public function getLastInsID(BaseQuery $query, string $sequence = null) + { + if(!is_null($sequence)) { + $pdo = $this->linkID->query("select {$sequence}.currval as id from dual"); + $result = $pdo->fetchColumn(); + } + + return $result ?? null; + } + + protected function supportSavepoint(): bool + { + return true; + } +} diff --git a/vendor/topthink/think-orm/src/db/connector/Pgsql.php b/vendor/topthink/think-orm/src/db/connector/Pgsql.php new file mode 100644 index 0000000..fec8f84 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/connector/Pgsql.php @@ -0,0 +1,108 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\connector; + +use PDO; +use think\db\PDOConnection; + +/** + * Pgsql数据库驱动 + */ +class Pgsql extends PDOConnection +{ + + /** + * 默认PDO连接参数 + * @var array + */ + protected $params = [ + PDO::ATTR_CASE => PDO::CASE_NATURAL, + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL, + PDO::ATTR_STRINGIFY_FETCHES => false, + ]; + + /** + * 解析pdo连接的dsn信息 + * @access protected + * @param array $config 连接信息 + * @return string + */ + protected function parseDsn(array $config): string + { + $dsn = 'pgsql:dbname=' . $config['database'] . ';host=' . $config['hostname']; + + if (!empty($config['hostport'])) { + $dsn .= ';port=' . $config['hostport']; + } + + return $dsn; + } + + /** + * 取得数据表的字段信息 + * @access public + * @param string $tableName + * @return array + */ + public function getFields(string $tableName): array + { + [$tableName] = explode(' ', $tableName); + $sql = 'select fields_name as "field",fields_type as "type",fields_not_null as "null",fields_key_name as "key",fields_default as "default",fields_default as "extra" from table_msg(\'' . $tableName . '\');'; + + $pdo = $this->getPDOStatement($sql); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + + if (!empty($result)) { + foreach ($result as $key => $val) { + $val = array_change_key_case($val); + + $info[$val['field']] = [ + 'name' => $val['field'], + 'type' => $val['type'], + 'notnull' => (bool) ('' !== $val['null']), + 'default' => $val['default'], + 'primary' => !empty($val['key']), + 'autoinc' => (0 === strpos($val['extra'], 'nextval(')), + ]; + } + } + + return $this->fieldCase($info); + } + + /** + * 取得数据库的表信息 + * @access public + * @param string $dbName + * @return array + */ + public function getTables(string $dbName = ''): array + { + $sql = "select tablename as Tables_in_test from pg_tables where schemaname ='public'"; + $pdo = $this->getPDOStatement($sql); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + + foreach ($result as $key => $val) { + $info[$key] = current($val); + } + + return $info; + } + + protected function supportSavepoint(): bool + { + return true; + } +} diff --git a/vendor/topthink/think-orm/src/db/connector/Sqlite.php b/vendor/topthink/think-orm/src/db/connector/Sqlite.php new file mode 100644 index 0000000..3e42a90 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/connector/Sqlite.php @@ -0,0 +1,96 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\connector; + +use PDO; +use think\db\PDOConnection; + +/** + * Sqlite数据库驱动 + */ +class Sqlite extends PDOConnection +{ + + /** + * 解析pdo连接的dsn信息 + * @access protected + * @param array $config 连接信息 + * @return string + */ + protected function parseDsn(array $config): string + { + $dsn = 'sqlite:' . $config['database']; + + return $dsn; + } + + /** + * 取得数据表的字段信息 + * @access public + * @param string $tableName + * @return array + */ + public function getFields(string $tableName): array + { + [$tableName] = explode(' ', $tableName); + $sql = 'PRAGMA table_info( \'' . $tableName . '\' )'; + + $pdo = $this->getPDOStatement($sql); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + + if (!empty($result)) { + foreach ($result as $key => $val) { + $val = array_change_key_case($val); + + $info[$val['name']] = [ + 'name' => $val['name'], + 'type' => $val['type'], + 'notnull' => 1 === $val['notnull'], + 'default' => $val['dflt_value'], + 'primary' => '1' == $val['pk'], + 'autoinc' => '1' == $val['pk'], + ]; + } + } + + return $this->fieldCase($info); + } + + /** + * 取得数据库的表信息 + * @access public + * @param string $dbName + * @return array + */ + public function getTables(string $dbName = ''): array + { + $sql = "SELECT name FROM sqlite_master WHERE type='table' " + . "UNION ALL SELECT name FROM sqlite_temp_master " + . "WHERE type='table' ORDER BY name"; + + $pdo = $this->getPDOStatement($sql); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + + foreach ($result as $key => $val) { + $info[$key] = current($val); + } + + return $info; + } + + protected function supportSavepoint(): bool + { + return true; + } +} diff --git a/vendor/topthink/think-orm/src/db/connector/Sqlsrv.php b/vendor/topthink/think-orm/src/db/connector/Sqlsrv.php new file mode 100644 index 0000000..835605d --- /dev/null +++ b/vendor/topthink/think-orm/src/db/connector/Sqlsrv.php @@ -0,0 +1,126 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\connector; + +use PDO; +use think\db\PDOConnection; + +/** + * Sqlsrv数据库驱动 + */ +class Sqlsrv extends PDOConnection +{ + /** + * 默认PDO连接参数 + * @var array + */ + protected $params = [ + PDO::ATTR_CASE => PDO::CASE_NATURAL, + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL, + PDO::ATTR_STRINGIFY_FETCHES => false, + ]; + + /** + * 解析pdo连接的dsn信息 + * @access protected + * @param array $config 连接信息 + * @return string + */ + protected function parseDsn(array $config): string + { + $dsn = 'sqlsrv:Database=' . $config['database'] . ';Server=' . $config['hostname']; + + if (!empty($config['hostport'])) { + $dsn .= ',' . $config['hostport']; + } + + if (!empty($config['trust_server_certificate'])) { + $dsn .= ';TrustServerCertificate=' . $config['trust_server_certificate']; + } + + return $dsn; + } + + /** + * 取得数据表的字段信息 + * @access public + * @param string $tableName + * @return array + */ + public function getFields(string $tableName): array + { + [$tableName] = explode(' ', $tableName); + strpos($tableName, '.') && $tableName = substr($tableName, strpos($tableName, '.') + 1); + $sql = "SELECT column_name, data_type, column_default, is_nullable + FROM information_schema.tables AS t + JOIN information_schema.columns AS c + ON t.table_catalog = c.table_catalog + AND t.table_schema = c.table_schema + AND t.table_name = c.table_name + WHERE t.table_name = '$tableName'"; + + $pdo = $this->getPDOStatement($sql); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + + if (!empty($result)) { + foreach ($result as $key => $val) { + $val = array_change_key_case($val); + + $info[$val['column_name']] = [ + 'name' => $val['column_name'], + 'type' => $val['data_type'], + 'notnull' => (bool) ('' === $val['is_nullable']), // not null is empty, null is yes + 'default' => $val['column_default'], + 'primary' => false, + 'autoinc' => false, + ]; + } + } + + $sql = "SELECT column_name FROM information_schema.key_column_usage WHERE table_name='$tableName'"; + $pdo = $this->linkID->query($sql); + $result = $pdo->fetch(PDO::FETCH_ASSOC); + + if ($result) { + $info[$result['column_name']]['primary'] = true; + } + + return $this->fieldCase($info); + } + + /** + * 取得数据表的字段信息 + * @access public + * @param string $dbName + * @return array + */ + public function getTables(string $dbName = ''): array + { + $sql = "SELECT TABLE_NAME + FROM INFORMATION_SCHEMA.TABLES + WHERE TABLE_TYPE = 'BASE TABLE' + "; + + $pdo = $this->getPDOStatement($sql); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + + foreach ($result as $key => $val) { + $info[$key] = current($val); + } + + return $info; + } + +} diff --git a/vendor/topthink/think-orm/src/db/connector/pgsql.sql b/vendor/topthink/think-orm/src/db/connector/pgsql.sql new file mode 100644 index 0000000..e1a09a3 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/connector/pgsql.sql @@ -0,0 +1,117 @@ +CREATE OR REPLACE FUNCTION pgsql_type(a_type varchar) RETURNS varchar AS +$BODY$ +DECLARE + v_type varchar; +BEGIN + IF a_type='int8' THEN + v_type:='bigint'; + ELSIF a_type='int4' THEN + v_type:='integer'; + ELSIF a_type='int2' THEN + v_type:='smallint'; + ELSIF a_type='bpchar' THEN + v_type:='char'; + ELSE + v_type:=a_type; + END IF; + RETURN v_type; +END; +$BODY$ +LANGUAGE PLPGSQL; + +CREATE TYPE "public"."tablestruct" AS ( + "fields_key_name" varchar(100), + "fields_name" VARCHAR(200), + "fields_type" VARCHAR(20), + "fields_length" BIGINT, + "fields_not_null" VARCHAR(10), + "fields_default" VARCHAR(500), + "fields_comment" VARCHAR(1000) +); + +CREATE OR REPLACE FUNCTION "public"."table_msg" (a_schema_name varchar, a_table_name varchar) RETURNS SETOF "public"."tablestruct" AS +$body$ +DECLARE + v_ret tablestruct; + v_oid oid; + v_sql varchar; + v_rec RECORD; + v_key varchar; +BEGIN + SELECT + pg_class.oid INTO v_oid + FROM + pg_class + INNER JOIN pg_namespace ON (pg_class.relnamespace = pg_namespace.oid AND lower(pg_namespace.nspname) = a_schema_name) + WHERE + pg_class.relname=a_table_name; + IF NOT FOUND THEN + RETURN; + END IF; + + v_sql=' + SELECT + pg_attribute.attname AS fields_name, + pg_attribute.attnum AS fields_index, + pgsql_type(pg_type.typname::varchar) AS fields_type, + pg_attribute.atttypmod-4 as fields_length, + CASE WHEN pg_attribute.attnotnull THEN ''not null'' + ELSE '''' + END AS fields_not_null, + pg_attrdef.adsrc AS fields_default, + pg_description.description AS fields_comment + FROM + pg_attribute + INNER JOIN pg_class ON pg_attribute.attrelid = pg_class.oid + INNER JOIN pg_type ON pg_attribute.atttypid = pg_type.oid + LEFT OUTER JOIN pg_attrdef ON pg_attrdef.adrelid = pg_class.oid AND pg_attrdef.adnum = pg_attribute.attnum + LEFT OUTER JOIN pg_description ON pg_description.objoid = pg_class.oid AND pg_description.objsubid = pg_attribute.attnum + WHERE + pg_attribute.attnum > 0 + AND attisdropped <> ''t'' + AND pg_class.oid = ' || v_oid || ' + ORDER BY pg_attribute.attnum' ; + + FOR v_rec IN EXECUTE v_sql LOOP + v_ret.fields_name=v_rec.fields_name; + v_ret.fields_type=v_rec.fields_type; + IF v_rec.fields_length > 0 THEN + v_ret.fields_length:=v_rec.fields_length; + ELSE + v_ret.fields_length:=NULL; + END IF; + v_ret.fields_not_null=v_rec.fields_not_null; + v_ret.fields_default=v_rec.fields_default; + v_ret.fields_comment=v_rec.fields_comment; + SELECT constraint_name INTO v_key FROM information_schema.key_column_usage WHERE table_schema=a_schema_name AND table_name=a_table_name AND column_name=v_rec.fields_name; + IF FOUND THEN + v_ret.fields_key_name=v_key; + ELSE + v_ret.fields_key_name=''; + END IF; + RETURN NEXT v_ret; + END LOOP; + RETURN ; +END; +$body$ +LANGUAGE 'plpgsql' VOLATILE CALLED ON NULL INPUT SECURITY INVOKER; + +COMMENT ON FUNCTION "public"."table_msg"(a_schema_name varchar, a_table_name varchar) +IS '获得表信息'; + +---重载一个函数 +CREATE OR REPLACE FUNCTION "public"."table_msg" (a_table_name varchar) RETURNS SETOF "public"."tablestruct" AS +$body$ +DECLARE + v_ret tablestruct; +BEGIN + FOR v_ret IN SELECT * FROM table_msg('public',a_table_name) LOOP + RETURN NEXT v_ret; + END LOOP; + RETURN; +END; +$body$ +LANGUAGE 'plpgsql' VOLATILE CALLED ON NULL INPUT SECURITY INVOKER; + +COMMENT ON FUNCTION "public"."table_msg"(a_table_name varchar) +IS '获得表信息'; \ No newline at end of file diff --git a/vendor/topthink/think-orm/src/db/exception/BindParamException.php b/vendor/topthink/think-orm/src/db/exception/BindParamException.php new file mode 100644 index 0000000..08bb388 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/exception/BindParamException.php @@ -0,0 +1,35 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\exception; + +/** + * PDO参数绑定异常 + */ +class BindParamException extends DbException +{ + + /** + * BindParamException constructor. + * @access public + * @param string $message + * @param array $config + * @param string $sql + * @param array $bind + * @param int $code + */ + public function __construct(string $message, array $config, string $sql, array $bind, int $code = 10502) + { + $this->setData('Bind Param', $bind); + parent::__construct($message, $config, $sql, $code); + } +} diff --git a/vendor/topthink/think-orm/src/db/exception/DataNotFoundException.php b/vendor/topthink/think-orm/src/db/exception/DataNotFoundException.php new file mode 100644 index 0000000..d10dd43 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/exception/DataNotFoundException.php @@ -0,0 +1,43 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\exception; + +class DataNotFoundException extends DbException +{ + protected $table; + + /** + * DbException constructor. + * @access public + * @param string $message + * @param string $table + * @param array $config + */ + public function __construct(string $message, string $table = '', array $config = []) + { + $this->message = $message; + $this->table = $table; + + $this->setData('Database Config', $config); + } + + /** + * 获取数据表名 + * @access public + * @return string + */ + public function getTable() + { + return $this->table; + } +} diff --git a/vendor/topthink/think-orm/src/db/exception/DbEventException.php b/vendor/topthink/think-orm/src/db/exception/DbEventException.php new file mode 100644 index 0000000..394a1e8 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/exception/DbEventException.php @@ -0,0 +1,19 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\exception; + +/** + * Db事件异常 + */ +class DbEventException extends DbException +{ +} diff --git a/vendor/topthink/think-orm/src/db/exception/DbException.php b/vendor/topthink/think-orm/src/db/exception/DbException.php new file mode 100644 index 0000000..f68b21c --- /dev/null +++ b/vendor/topthink/think-orm/src/db/exception/DbException.php @@ -0,0 +1,44 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\exception; + +use think\Exception; + +/** + * Database相关异常处理类 + */ +class DbException extends Exception +{ + /** + * DbException constructor. + * @access public + * @param string $message + * @param array $config + * @param string $sql + * @param int $code + */ + public function __construct(string $message, array $config = [], string $sql = '', int $code = 10500) + { + $this->message = $message; + $this->code = $code; + + $this->setData('Database Status', [ + 'Error Code' => $code, + 'Error Message' => $message, + 'Error SQL' => $sql, + ]); + + unset($config['username'], $config['password']); + $this->setData('Database Config', $config); + } +} diff --git a/vendor/topthink/think-orm/src/db/exception/InvalidArgumentException.php b/vendor/topthink/think-orm/src/db/exception/InvalidArgumentException.php new file mode 100644 index 0000000..047e45e --- /dev/null +++ b/vendor/topthink/think-orm/src/db/exception/InvalidArgumentException.php @@ -0,0 +1,21 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); +namespace think\db\exception; + +use Psr\SimpleCache\InvalidArgumentException as SimpleCacheInvalidArgumentInterface; + +/** + * 非法数据异常 + */ +class InvalidArgumentException extends \InvalidArgumentException implements SimpleCacheInvalidArgumentInterface +{ +} diff --git a/vendor/topthink/think-orm/src/db/exception/ModelEventException.php b/vendor/topthink/think-orm/src/db/exception/ModelEventException.php new file mode 100644 index 0000000..767bc1a --- /dev/null +++ b/vendor/topthink/think-orm/src/db/exception/ModelEventException.php @@ -0,0 +1,19 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\exception; + +/** + * 模型事件异常 + */ +class ModelEventException extends DbException +{ +} diff --git a/vendor/topthink/think-orm/src/db/exception/ModelNotFoundException.php b/vendor/topthink/think-orm/src/db/exception/ModelNotFoundException.php new file mode 100644 index 0000000..84a1525 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/exception/ModelNotFoundException.php @@ -0,0 +1,44 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\exception; + +class ModelNotFoundException extends DbException +{ + protected $model; + + /** + * 构造方法 + * @access public + * @param string $message + * @param string $model + * @param array $config + */ + public function __construct(string $message, string $model = '', array $config = []) + { + $this->message = $message; + $this->model = $model; + + $this->setData('Database Config', $config); + } + + /** + * 获取模型类名 + * @access public + * @return string + */ + public function getModel() + { + return $this->model; + } + +} diff --git a/vendor/topthink/think-orm/src/db/exception/PDOException.php b/vendor/topthink/think-orm/src/db/exception/PDOException.php new file mode 100644 index 0000000..efe78b9 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/exception/PDOException.php @@ -0,0 +1,44 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\exception; + +/** + * PDO异常处理类 + * 重新封装了系统的\PDOException类 + */ +class PDOException extends DbException +{ + /** + * PDOException constructor. + * @access public + * @param \PDOException $exception + * @param array $config + * @param string $sql + * @param int $code + */ + public function __construct(\PDOException $exception, array $config = [], string $sql = '', int $code = 10501) + { + $error = $exception->errorInfo; + $message = $exception->getMessage(); + + if (!empty($error)) { + $this->setData('PDO Error Info', [ + 'SQLSTATE' => $error[0], + 'Driver Error Code' => isset($error[1]) ? $error[1] : 0, + 'Driver Error Message' => isset($error[2]) ? $error[2] : '', + ]); + } + + parent::__construct($message, $config, $sql, $code); + } +} diff --git a/vendor/topthink/think-orm/src/facade/Db.php b/vendor/topthink/think-orm/src/facade/Db.php new file mode 100644 index 0000000..b0296c6 --- /dev/null +++ b/vendor/topthink/think-orm/src/facade/Db.php @@ -0,0 +1,31 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\DbManager + * @mixin \think\DbManager + */ +class Db extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'think\DbManager'; + } +} diff --git a/vendor/topthink/think-orm/src/model/Collection.php b/vendor/topthink/think-orm/src/model/Collection.php new file mode 100644 index 0000000..079e856 --- /dev/null +++ b/vendor/topthink/think-orm/src/model/Collection.php @@ -0,0 +1,273 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\model; + +use think\Collection as BaseCollection; +use think\Model; +use think\Paginator; + +/** + * 模型数据集类 + * + * @template TKey of array-key + * @template TModel of \think\Model + * + * @extends BaseCollection + */ +class Collection extends BaseCollection +{ + /** + * 延迟预载入关联查询 + * @access public + * @param array|string $relation 关联 + * @param mixed $cache 关联缓存 + * @return $this + */ + public function load($relation, $cache = false) + { + if (!$this->isEmpty()) { + $item = current($this->items); + $item->eagerlyResultSet($this->items, (array) $relation, [], false, $cache); + } + + return $this; + } + + /** + * 删除数据集的数据 + * @access public + * @return bool + */ + public function delete(): bool + { + $this->each(function (Model $model) { + $model->delete(); + }); + + return true; + } + + /** + * 更新数据 + * @access public + * @param array $data 数据数组 + * @param array $allowField 允许字段 + * @return bool + */ + public function update(array $data, array $allowField = []): bool + { + $this->each(function (Model $model) use ($data, $allowField) { + if (!empty($allowField)) { + $model->allowField($allowField); + } + + $model->save($data); + }); + + return true; + } + + /** + * 设置需要隐藏的输出属性 + * @access public + * @param array $hidden 属性列表 + * @param bool $merge 是否合并 + * @return $this + */ + public function hidden(array $hidden, bool $merge = false) + { + $this->each(function (Model $model) use ($hidden, $merge) { + $model->hidden($hidden, $merge); + }); + + return $this; + } + + /** + * 设置需要输出的属性 + * @access public + * @param array $visible + * @param bool $merge 是否合并 + * @return $this + */ + public function visible(array $visible, bool $merge = false) + { + $this->each(function (Model $model) use ($visible, $merge) { + $model->visible($visible, $merge); + }); + + return $this; + } + + /** + * 设置需要追加的输出属性 + * @access public + * @param array $append 属性列表 + * @param bool $merge 是否合并 + * @return $this + */ + public function append(array $append, bool $merge = false) + { + $this->each(function (Model $model) use ($append, $merge) { + $model->append($append, $merge); + }); + + return $this; + } + + /** + * 设置模型输出场景 + * @access public + * @param string $scene 场景名称 + * @return $this + */ + public function scene(string $scene) + { + $this->each(function (Model $model) use ($scene) { + $model->scene($scene); + }); + + return $this; + } + + /** + * 设置父模型 + * @access public + * @param Model $parent 父模型 + * @return $this + */ + public function setParent(Model $parent) + { + $this->each(function (Model $model) use ($parent) { + $model->setParent($parent); + }); + + return $this; + } + + /** + * 设置数据字段获取器 + * @access public + * @param string|array $name 字段名 + * @param callable $callback 闭包获取器 + * @return $this + */ + public function withAttr($name, $callback = null) + { + $this->each(function (Model $model) use ($name, $callback) { + $model->withAttr($name, $callback); + }); + + return $this; + } + + /** + * 绑定(一对一)关联属性到当前模型 + * @access protected + * @param string $relation 关联名称 + * @param array $attrs 绑定属性 + * @return $this + * @throws Exception + */ + public function bindAttr(string $relation, array $attrs = []) + { + $this->each(function (Model $model) use ($relation, $attrs) { + $model->bindAttr($relation, $attrs); + }); + + return $this; + } + + /** + * 按指定键整理数据 + * + * @access public + * @param mixed $items 数据 + * @param string|null $indexKey 键名 + * @return array + */ + public function dictionary($items = null, string &$indexKey = null) + { + if ($items instanceof self || $items instanceof Paginator) { + $items = $items->all(); + } + + $items = is_null($items) ? $this->items : $items; + + if ($items && empty($indexKey)) { + $indexKey = $items[0]->getPk(); + } + + if (isset($indexKey) && is_string($indexKey)) { + return array_column($items, null, $indexKey); + } + + return $items; + } + + /** + * 比较数据集,返回差集 + * + * @access public + * @param mixed $items 数据 + * @param string|null $indexKey 指定比较的键名 + * @return static + */ + public function diff($items, string $indexKey = null) + { + if ($this->isEmpty()) { + return new static($items); + } + + $diff = []; + $dictionary = $this->dictionary($items, $indexKey); + + if (is_string($indexKey)) { + foreach ($this->items as $item) { + if (!isset($dictionary[$item[$indexKey]])) { + $diff[] = $item; + } + } + } + + return new static($diff); + } + + /** + * 比较数据集,返回交集 + * + * @access public + * @param mixed $items 数据 + * @param string|null $indexKey 指定比较的键名 + * @return static + */ + public function intersect($items, string $indexKey = null) + { + if ($this->isEmpty()) { + return new static([]); + } + + $intersect = []; + $dictionary = $this->dictionary($items, $indexKey); + + if (is_string($indexKey)) { + foreach ($this->items as $item) { + if (isset($dictionary[$item[$indexKey]])) { + $intersect[] = $item; + } + } + } + + return new static($intersect); + } +} diff --git a/vendor/topthink/think-orm/src/model/Pivot.php b/vendor/topthink/think-orm/src/model/Pivot.php new file mode 100644 index 0000000..ac16d9e --- /dev/null +++ b/vendor/topthink/think-orm/src/model/Pivot.php @@ -0,0 +1,70 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\model; + +use think\Model; + +/** + * 多对多中间表模型类 + */ +class Pivot extends Model +{ + + /** + * 父模型 + * @var Model + */ + public $parent; + + /** + * 是否时间自动写入 + * @var bool + */ + protected $autoWriteTimestamp = false; + + /** + * 架构函数 + * @access public + * @param array $data 数据 + * @param Model|null $parent 上级模型 + * @param string $table 中间数据表名 + */ + public function __construct(array $data = [], Model $parent = null, string $table = '') + { + $this->parent = $parent; + + if (is_null($this->name)) { + $this->name = $table; + } + + parent::__construct($data); + } + + /** + * 创建新的模型实例 + * @access public + * @param array $data 数据 + * @param mixed $where 更新条件 + * @param array $options 参数 + * @return Model + */ + public function newInstance(array $data = [], $where = null, array $options = []): Model + { + $model = parent::newInstance($data, $where, $options); + + $model->parent = $this->parent; + $model->name = $this->name; + + return $model; + } +} diff --git a/vendor/topthink/think-orm/src/model/Relation.php b/vendor/topthink/think-orm/src/model/Relation.php new file mode 100644 index 0000000..7bb6d9f --- /dev/null +++ b/vendor/topthink/think-orm/src/model/Relation.php @@ -0,0 +1,313 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\model; + +use Closure; +use ReflectionFunction; +use think\db\BaseQuery as Query; +use think\db\exception\DbException as Exception; +use think\Model; + +/** + * 模型关联基础类 + * @package think\model + * @mixin Query + */ +abstract class Relation +{ + /** + * 父模型对象 + * @var Model + */ + protected $parent; + + /** + * 当前关联的模型类名 + * @var string + */ + protected $model; + + /** + * 关联模型查询对象 + * @var Query + */ + protected $query; + + /** + * 关联表外键 + * @var string + */ + protected $foreignKey; + + /** + * 关联表主键 + * @var string + */ + protected $localKey; + + /** + * 是否执行关联基础查询 + * @var bool + */ + protected $baseQuery; + + /** + * 是否为自关联 + * @var bool + */ + protected $selfRelation = false; + + /** + * 关联数据字段限制 + * @var array + */ + protected $withField; + + /** + * 排除关联数据字段 + * @var array + */ + protected $withoutField; + + /** + * 默认数据 + * @var mixed + */ + protected $default; + + /** + * 获取关联的所属模型 + * @access public + * @return Model + */ + public function getParent(): Model + { + return $this->parent; + } + + /** + * 获取当前的关联模型类的Query实例 + * @access public + * @return Query + */ + public function getQuery() + { + return $this->query; + } + + /** + * 获取关联表外键 + * @access public + * @return string + */ + public function getForeignKey() + { + return $this->foreignKey; + } + + /** + * 获取关联表主键 + * @access public + * @return string + */ + public function getLocalKey() + { + return $this->localKey; + } + + /** + * 获取当前的关联模型类的实例 + * @access public + * @return Model + */ + public function getModel(): Model + { + return $this->query->getModel(); + } + + /** + * 当前关联是否为自关联 + * @access public + * @return bool + */ + public function isSelfRelation(): bool + { + return $this->selfRelation; + } + + /** + * 封装关联数据集 + * @access public + * @param array $resultSet 数据集 + * @param Model $parent 父模型 + * @return mixed + */ + protected function resultSetBuild(array $resultSet, Model $parent = null) + { + return (new $this->model)->toCollection($resultSet)->setParent($parent); + } + + protected function getQueryFields(string $model) + { + $fields = $this->query->getOptions('field'); + return $this->getRelationQueryFields($fields, $model); + } + + protected function getRelationQueryFields($fields, string $model) + { + if (empty($fields) || '*' == $fields) { + return $model . '.*'; + } + + if (is_string($fields)) { + $fields = explode(',', $fields); + } + + foreach ($fields as &$field) { + if (false === strpos($field, '.')) { + $field = $model . '.' . $field; + } + } + + return $fields; + } + + protected function getQueryWhere(array &$where, string $relation): void + { + foreach ($where as $key => &$val) { + if (is_string($key)) { + $where[] = [false === strpos($key, '.') ? $relation . '.' . $key : $key, '=', $val]; + unset($where[$key]); + } elseif (isset($val[0]) && false === strpos($val[0], '.')) { + $val[0] = $relation . '.' . $val[0]; + } + } + } + + /** + * 限制关联数据的字段 + * @access public + * @param array|string $field 关联字段限制 + * @return $this + */ + public function withField($field) + { + if (is_string($field)) { + $field = array_map('trim', explode(',', $field)); + } + + $this->withField = $field; + return $this; + } + + /** + * 排除关联数据的字段 + * @access public + * @param array|string $field 关联字段限制 + * @return $this + */ + public function withoutField($field) + { + if (is_string($field)) { + $field = array_map('trim', explode(',', $field)); + } + + $this->withoutField = $field; + return $this; + } + + /** + * 限制关联数据的数量 + * @access public + * @param int $limit 关联数量限制 + * @return $this + */ + public function withLimit(int $limit) + { + $this->query->limit($limit); + return $this; + } + + /** + * 设置关联数据不存在的时候默认值 + * @access public + * @param mixed $data 默认值 + * @return $this + */ + public function withDefault($data = null) + { + $this->default = $data; + return $this; + } + + /** + * 获取关联数据默认值 + * @access protected + * @return mixed + */ + protected function getDefaultModel() + { + if (is_array($this->default)) { + $model = (new $this->model)->data($this->default); + } elseif ($this->default instanceof Closure) { + $closure = $this->default; + $model = new $this->model; + $closure($model); + } else { + $model = $this->default; + } + + return $model; + } + + /** + * 判断闭包的参数类型 + * @access protected + * @return mixed + */ + protected function getClosureType(Closure $closure, $query = null) + { + $reflect = new ReflectionFunction($closure); + $params = $reflect->getParameters(); + + if (!empty($params)) { + $type = $params[0]->getType(); + $query = $query ?: $this->query; + return is_null($type) || Relation::class == $type->getName() ? $this : $query; + } + + return $this; + } + + /** + * 执行基础查询(仅执行一次) + * @access protected + * @return void + */ + protected function baseQuery(): void + {} + + public function __call($method, $args) + { + if ($this->query) { + // 执行基础查询 + $this->baseQuery(); + + $result = call_user_func_array([$this->query, $method], $args); + + return $result === $this->query ? $this : $result; + } + + throw new Exception('method not exists:' . __CLASS__ . '->' . $method); + } +} diff --git a/vendor/topthink/think-orm/src/model/concern/Attribute.php b/vendor/topthink/think-orm/src/model/concern/Attribute.php new file mode 100644 index 0000000..4359a4c --- /dev/null +++ b/vendor/topthink/think-orm/src/model/concern/Attribute.php @@ -0,0 +1,678 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\model\concern; + +use InvalidArgumentException; +use think\db\Raw; +use think\helper\Str; +use think\model\Relation; + +/** + * 模型数据处理 + */ +trait Attribute +{ + /** + * 数据表主键 复合主键使用数组定义 + * @var string|array + */ + protected $pk = 'id'; + + /** + * 数据表字段信息 留空则自动获取 + * @var array + */ + protected $schema = []; + + /** + * 当前允许写入的字段 + * @var array + */ + protected $field = []; + + /** + * 字段自动类型转换 + * @var array + */ + protected $type = []; + + /** + * 数据表废弃字段 + * @var array + */ + protected $disuse = []; + + /** + * 数据表只读字段 + * @var array + */ + protected $readonly = []; + + /** + * 当前模型数据 + * @var array + */ + private $data = []; + + /** + * 原始数据 + * @var array + */ + private $origin = []; + + /** + * JSON数据表字段 + * @var array + */ + protected $json = []; + + /** + * JSON数据表字段类型 + * @var array + */ + protected $jsonType = []; + + /** + * JSON数据取出是否需要转换为数组 + * @var bool + */ + protected $jsonAssoc = false; + + /** + * 是否严格字段大小写 + * @var bool + */ + protected $strict = true; + + /** + * 获取器数据 + * @var array + */ + private $get = []; + + /** + * 动态获取器 + * @var array + */ + private $withAttr = []; + + /** + * 获取模型对象的主键 + * @access public + * @return string|array + */ + public function getPk() + { + return $this->pk; + } + + /** + * 判断一个字段名是否为主键字段 + * @access public + * @param string $key 名称 + * @return bool + */ + protected function isPk(string $key): bool + { + $pk = $this->getPk(); + + if (is_string($pk) && $pk == $key) { + return true; + } elseif (is_array($pk) && in_array($key, $pk)) { + return true; + } + + return false; + } + + /** + * 获取模型对象的主键值 + * @access public + * @return mixed + */ + public function getKey() + { + $pk = $this->getPk(); + + if (is_string($pk) && array_key_exists($pk, $this->data)) { + return $this->data[$pk]; + } + + return; + } + + /** + * 设置允许写入的字段 + * @access public + * @param array $field 允许写入的字段 + * @return $this + */ + public function allowField(array $field) + { + $this->field = $field; + + return $this; + } + + /** + * 设置只读字段 + * @access public + * @param array $field 只读字段 + * @return $this + */ + public function readOnly(array $field) + { + $this->readonly = $field; + + return $this; + } + + /** + * 获取实际的字段名 + * @access protected + * @param string $name 字段名 + * @return string + */ + protected function getRealFieldName(string $name): string + { + if ($this->convertNameToCamel || !$this->strict) { + return Str::snake($name); + } + + return $name; + } + + /** + * 设置数据对象值 + * @access public + * @param array $data 数据 + * @param bool $set 是否调用修改器 + * @param array $allow 允许的字段名 + * @return $this + */ + public function data(array $data, bool $set = false, array $allow = []) + { + // 清空数据 + $this->data = []; + + // 废弃字段 + foreach ($this->disuse as $key) { + if (array_key_exists($key, $data)) { + unset($data[$key]); + } + } + + if (!empty($allow)) { + $result = []; + foreach ($allow as $name) { + if (isset($data[$name])) { + $result[$name] = $data[$name]; + } + } + $data = $result; + } + + if ($set) { + // 数据对象赋值 + $this->setAttrs($data); + } else { + $this->data = $data; + } + + return $this; + } + + /** + * 批量追加数据对象值 + * @access public + * @param array $data 数据 + * @param bool $set 是否需要进行数据处理 + * @return $this + */ + public function appendData(array $data, bool $set = false) + { + if ($set) { + $this->setAttrs($data); + } else { + $this->data = array_merge($this->data, $data); + } + + return $this; + } + + /** + * 刷新对象原始数据(为当前数据) + * @access public + * @return $this + */ + public function refreshOrigin() + { + $this->origin = $this->data; + return $this; + } + + /** + * 获取对象原始数据 如果不存在指定字段返回null + * @access public + * @param string $name 字段名 留空获取全部 + * @return mixed + */ + public function getOrigin(string $name = null) + { + if (is_null($name)) { + return $this->origin; + } + + $fieldName = $this->getRealFieldName($name); + + return array_key_exists($fieldName, $this->origin) ? $this->origin[$fieldName] : null; + } + + /** + * 获取当前对象数据 如果不存在指定字段返回false + * @access public + * @param string $name 字段名 留空获取全部 + * @return mixed + * @throws InvalidArgumentException + */ + public function getData(string $name = null) + { + if (is_null($name)) { + return $this->data; + } + + $fieldName = $this->getRealFieldName($name); + + if (array_key_exists($fieldName, $this->data)) { + return $this->data[$fieldName]; + } elseif (array_key_exists($fieldName, $this->relation)) { + return $this->relation[$fieldName]; + } + + throw new InvalidArgumentException('property not exists:' . static::class . '->' . $name); + } + + /** + * 获取变化的数据 并排除只读数据 + * @access public + * @return array + */ + public function getChangedData(): array + { + $data = $this->force ? $this->data : array_udiff_assoc($this->data, $this->origin, function ($a, $b) { + if ((empty($a) || empty($b)) && $a !== $b) { + return 1; + } + + return is_object($a) || $a != $b ? 1 : 0; + }); + + // 只读字段不允许更新 + foreach ($this->readonly as $key => $field) { + if (array_key_exists($field, $data)) { + unset($data[$field]); + } + } + + return $data; + } + + /** + * 直接设置数据对象值 + * @access public + * @param string $name 属性名 + * @param mixed $value 值 + * @return void + */ + public function set(string $name, $value): void + { + $name = $this->getRealFieldName($name); + + $this->data[$name] = $value; + unset($this->get[$name]); + } + + /** + * 通过修改器 批量设置数据对象值 + * @access public + * @param array $data 数据 + * @return void + */ + public function setAttrs(array $data): void + { + // 进行数据处理 + foreach ($data as $key => $value) { + $this->setAttr($key, $value, $data); + } + } + + /** + * 通过修改器 设置数据对象值 + * @access public + * @param string $name 属性名 + * @param mixed $value 属性值 + * @param array $data 数据 + * @return void + */ + public function setAttr(string $name, $value, array $data = []): void + { + $name = $this->getRealFieldName($name); + + // 检测修改器 + $method = 'set' . Str::studly($name) . 'Attr'; + + if (method_exists($this, $method)) { + $array = $this->data; + + $value = $this->$method($value, array_merge($this->data, $data)); + + if (is_null($value) && $array !== $this->data) { + return; + } + } elseif (isset($this->type[$name])) { + // 类型转换 + $value = $this->writeTransform($value, $this->type[$name]); + } elseif ($this->isRelationAttr($name)) { + $this->relation[$name] = $value; + } elseif ((array_key_exists($name, $this->origin) || empty($this->origin)) && is_object($value) && method_exists($value, '__toString')) { + // 对象类型 + $value = $value->__toString(); + } + + // 设置数据对象属性 + $this->data[$name] = $value; + unset($this->get[$name]); + } + + /** + * 数据写入 类型转换 + * @access protected + * @param mixed $value 值 + * @param string|array $type 要转换的类型 + * @return mixed + */ + protected function writeTransform($value, $type) + { + if (is_null($value)) { + return; + } + + if ($value instanceof Raw) { + return $value; + } + + if (is_array($type)) { + [$type, $param] = $type; + } elseif (strpos($type, ':')) { + [$type, $param] = explode(':', $type, 2); + } + + switch ($type) { + case 'integer': + $value = (int) $value; + break; + case 'float': + if (empty($param)) { + $value = (float) $value; + } else { + $value = (float) number_format($value, (int) $param, '.', ''); + } + break; + case 'boolean': + $value = (bool) $value; + break; + case 'timestamp': + if (!is_numeric($value)) { + $value = strtotime($value); + } + break; + case 'datetime': + $value = is_numeric($value) ? $value : strtotime($value); + $value = $this->formatDateTime('Y-m-d H:i:s.u', $value, true); + break; + case 'object': + if (is_object($value)) { + $value = json_encode($value, JSON_FORCE_OBJECT); + } + break; + case 'array': + $value = (array) $value; + // no break + case 'json': + $option = !empty($param) ? (int) $param : JSON_UNESCAPED_UNICODE; + $value = json_encode($value, $option); + break; + case 'serialize': + $value = serialize($value); + break; + default: + if (is_object($value) && false !== strpos($type, '\\') && method_exists($value, '__toString')) { + // 对象类型 + $value = $value->__toString(); + } + } + + return $value; + } + + /** + * 获取器 获取数据对象的值 + * @access public + * @param string $name 名称 + * @return mixed + * @throws InvalidArgumentException + */ + public function getAttr(string $name) + { + try { + $relation = false; + $value = $this->getData($name); + } catch (InvalidArgumentException $e) { + $relation = $this->isRelationAttr($name); + $value = null; + } + + return $this->getValue($name, $value, $relation); + } + + /** + * 获取经过获取器处理后的数据对象的值 + * @access protected + * @param string $name 字段名称 + * @param mixed $value 字段值 + * @param bool|string $relation 是否为关联属性或者关联名 + * @return mixed + * @throws InvalidArgumentException + */ + protected function getValue(string $name, $value, $relation = false) + { + // 检测属性获取器 + $fieldName = $this->getRealFieldName($name); + + if (array_key_exists($fieldName, $this->get)) { + return $this->get[$fieldName]; + } + + $method = 'get' . Str::studly($name) . 'Attr'; + if (isset($this->withAttr[$fieldName])) { + if ($relation) { + $value = $this->getRelationValue($relation); + } + + if (in_array($fieldName, $this->json) && is_array($this->withAttr[$fieldName])) { + $value = $this->getJsonValue($fieldName, $value); + } else { + $closure = $this->withAttr[$fieldName]; + if ($closure instanceof \Closure) { + $value = $closure($value, $this->data); + } + } + } elseif (method_exists($this, $method)) { + if ($relation) { + $value = $this->getRelationValue($relation); + } + + $value = $this->$method($value, $this->data); + } elseif (isset($this->type[$fieldName])) { + // 类型转换 + $value = $this->readTransform($value, $this->type[$fieldName]); + } elseif ($this->autoWriteTimestamp && in_array($fieldName, [$this->createTime, $this->updateTime])) { + $value = $this->getTimestampValue($value); + } elseif ($relation) { + $value = $this->getRelationValue($relation); + // 保存关联对象值 + $this->relation[$name] = $value; + } + + $this->get[$fieldName] = $value; + + return $value; + } + + /** + * 获取JSON字段属性值 + * @access protected + * @param string $name 属性名 + * @param mixed $value JSON数据 + * @return mixed + */ + protected function getJsonValue($name, $value) + { + if (is_null($value)) { + return $value; + } + + foreach ($this->withAttr[$name] as $key => $closure) { + if ($this->jsonAssoc) { + $value[$key] = $closure($value[$key], $value); + } else { + $value->$key = $closure($value->$key, $value); + } + } + + return $value; + } + + /** + * 获取关联属性值 + * @access protected + * @param string $relation 关联名 + * @return mixed + */ + protected function getRelationValue(string $relation) + { + $modelRelation = $this->$relation(); + + return $modelRelation instanceof Relation ? $this->getRelationData($modelRelation) : null; + } + + /** + * 数据读取 类型转换 + * @access protected + * @param mixed $value 值 + * @param string|array $type 要转换的类型 + * @return mixed + */ + protected function readTransform($value, $type) + { + if (is_null($value)) { + return; + } + + if (is_array($type)) { + [$type, $param] = $type; + } elseif (strpos($type, ':')) { + [$type, $param] = explode(':', $type, 2); + } + + switch ($type) { + case 'integer': + $value = (int) $value; + break; + case 'float': + if (empty($param)) { + $value = (float) $value; + } else { + $value = (float) number_format($value, (int) $param, '.', ''); + } + break; + case 'boolean': + $value = (bool) $value; + break; + case 'timestamp': + if (!is_null($value)) { + $format = !empty($param) ? $param : $this->dateFormat; + $value = $this->formatDateTime($format, $value, true); + } + break; + case 'datetime': + if (!is_null($value)) { + $format = !empty($param) ? $param : $this->dateFormat; + $value = $this->formatDateTime($format, $value); + } + break; + case 'json': + $value = json_decode($value, true); + break; + case 'array': + $value = empty($value) ? [] : json_decode($value, true); + break; + case 'object': + $value = empty($value) ? new \stdClass() : json_decode($value); + break; + case 'serialize': + try { + $value = unserialize($value); + } catch (\Exception $e) { + $value = null; + } + break; + default: + if (false !== strpos($type, '\\')) { + // 对象类型 + $value = new $type($value); + } + } + + return $value; + } + + /** + * 设置数据字段获取器 + * @access public + * @param string|array $name 字段名 + * @param callable $callback 闭包获取器 + * @return $this + */ + public function withAttr($name, callable $callback = null) + { + if (is_array($name)) { + foreach ($name as $key => $val) { + $this->withAttr($key, $val); + } + } else { + $name = $this->getRealFieldName($name); + + if (strpos($name, '.')) { + [$name, $key] = explode('.', $name); + + $this->withAttr[$name][$key] = $callback; + } else { + $this->withAttr[$name] = $callback; + } + } + + return $this; + } + +} diff --git a/vendor/topthink/think-orm/src/model/concern/Conversion.php b/vendor/topthink/think-orm/src/model/concern/Conversion.php new file mode 100644 index 0000000..136a90f --- /dev/null +++ b/vendor/topthink/think-orm/src/model/concern/Conversion.php @@ -0,0 +1,383 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\model\concern; + +use think\Collection; +use think\db\exception\DbException as Exception; +use think\helper\Str; +use think\Model; +use think\model\Collection as ModelCollection; +use think\model\relation\OneToOne; + +/** + * 模型数据转换处理 + */ +trait Conversion +{ + /** + * 数据输出显示的属性 + * @var array + */ + protected $visible = []; + + /** + * 数据输出隐藏的属性 + * @var array + */ + protected $hidden = []; + + /** + * 数据输出需要追加的属性 + * @var array + */ + protected $append = []; + + /** + * 场景 + * @var array + */ + protected $scene = []; + + /** + * 数据输出字段映射 + * @var array + */ + protected $mapping = []; + + /** + * 数据集对象名 + * @var string + */ + protected $resultSetType; + + /** + * 数据命名是否自动转为驼峰 + * @var bool + */ + protected $convertNameToCamel; + + /** + * 转换数据为驼峰命名(用于输出) + * @access public + * @param bool $toCamel 是否自动驼峰命名 + * @return $this + */ + public function convertNameToCamel(bool $toCamel = true) + { + $this->convertNameToCamel = $toCamel; + return $this; + } + + /** + * 设置需要附加的输出属性 + * @access public + * @param array $append 属性列表 + * @param bool $merge 是否合并 + * @return $this + */ + public function append(array $append = [], bool $merge = false) + { + if ($merge) { + $this->append = array_merge($this->append, $append); + } else { + $this->append = $append; + } + + return $this; + } + + /** + * 设置输出层场景 + * @access public + * @param string $scene 场景名称 + * @return $this + */ + public function scene(string $scene) + { + if (isset($this->scene[$scene])) { + $data = $this->scene[$scene]; + foreach (['append', 'hidden', 'visible'] as $name) { + if (isset($data[$name])) { + $this->$name($data[$name]); + } + } + } + + return $this; + } + + /** + * 设置附加关联对象的属性 + * @access public + * @param string $attr 关联属性 + * @param string|array $append 追加属性名 + * @return $this + * @throws Exception + */ + public function appendRelationAttr(string $attr, array $append) + { + $relation = Str::camel($attr); + + if (isset($this->relation[$relation])) { + $model = $this->relation[$relation]; + } else { + $model = $this->getRelationData($this->$relation()); + } + + if ($model instanceof Model) { + foreach ($append as $key => $attr) { + $key = is_numeric($key) ? $attr : $key; + if (isset($this->data[$key])) { + throw new Exception('bind attr has exists:' . $key); + } + + $this->data[$key] = $model->$attr; + } + } + + return $this; + } + + /** + * 设置需要隐藏的输出属性 + * @access public + * @param array $hidden 属性列表 + * @param bool $merge 是否合并 + * @return $this + */ + public function hidden(array $hidden = [], bool $merge = false) + { + if ($merge) { + $this->hidden = array_merge($this->hidden, $hidden); + } else { + $this->hidden = $hidden; + } + + return $this; + } + + /** + * 设置需要输出的属性 + * @access public + * @param array $visible + * @param bool $merge 是否合并 + * @return $this + */ + public function visible(array $visible = [], bool $merge = false) + { + if ($merge) { + $this->visible = array_merge($this->visible, $visible); + } else { + $this->visible = $visible; + } + + return $this; + } + + /** + * 设置属性的映射输出 + * @access public + * @param array $map + * @return $this + */ + public function mapping(array $map) + { + $this->mapping = $map; + + return $this; + } + + /** + * 转换当前模型对象为数组 + * @access public + * @return array + */ + public function toArray(): array + { + $item = $visible = $hidden = []; + $hasVisible = false; + + foreach ($this->visible as $key => $val) { + if (is_string($val)) { + if (strpos($val, '.')) { + [$relation, $name] = explode('.', $val); + $visible[$relation][] = $name; + } else { + $visible[$val] = true; + $hasVisible = true; + } + } + } + + foreach ($this->hidden as $key => $val) { + if (is_string($val)) { + if (strpos($val, '.')) { + [$relation, $name] = explode('.', $val); + $hidden[$relation][] = $name; + } else { + $hidden[$val] = true; + } + } + } + + // 追加属性(必须定义获取器) + foreach ($this->append as $key => $name) { + $this->appendAttrToArray($item, $key, $name, $visible, $hidden); + } + + // 合并关联数据 + $data = array_merge($this->data, $this->relation); + + foreach ($data as $key => $val) { + if ($val instanceof Model || $val instanceof ModelCollection) { + // 关联模型对象 + if (isset($visible[$key]) && is_array($visible[$key])) { + $val->visible($visible[$key]); + } elseif (isset($hidden[$key]) && is_array($hidden[$key])) { + $val->hidden($hidden[$key], true); + } + // 关联模型对象 + if (!isset($hidden[$key]) || true !== $hidden[$key]) { + $item[$key] = $val->toArray(); + } + } elseif (isset($visible[$key])) { + $item[$key] = $this->getAttr($key); + } elseif (!isset($hidden[$key]) && !$hasVisible) { + $item[$key] = $this->getAttr($key); + } + + if (isset($this->mapping[$key])) { + // 检查字段映射 + $mapName = $this->mapping[$key]; + $item[$mapName] = $item[$key]; + unset($item[$key]); + } + } + + if ($this->convertNameToCamel) { + foreach ($item as $key => $val) { + $name = Str::camel($key); + if ($name !== $key) { + $item[$name] = $val; + unset($item[$key]); + } + } + } + + return $item; + } + + protected function appendAttrToArray(array &$item, $key, $name, array $visible, array $hidden) + { + if (is_array($name)) { + // 批量追加关联对象属性 + $relation = $this->getRelationWith($key, $hidden, $visible); + $item[$key] = $relation ? $relation->append($name)->toArray() : []; + } elseif (strpos($name, '.')) { + // 追加单个关联对象属性 + [$key, $attr] = explode('.', $name); + $relation = $this->getRelationWith($key, $hidden, $visible); + $item[$key] = $relation ? $relation->append([$attr])->toArray() : []; + } else { + $value = $this->getAttr($name); + $item[$name] = $value; + + $this->getBindAttrValue($name, $value, $item); + } + } + + protected function getRelationWith(string $key, array $hidden, array $visible) + { + $relation = $this->getRelation($key, true); + if ($relation) { + if (isset($visible[$key])) { + $relation->visible($visible[$key]); + } elseif (isset($hidden[$key])) { + $relation->hidden($hidden[$key]); + } + } + return $relation; + } + + protected function getBindAttrValue(string $name, $value, array &$item = []) + { + $relation = $this->isRelationAttr($name); + if (!$relation) { + return false; + } + + $modelRelation = $this->$relation(); + + if ($modelRelation instanceof OneToOne) { + $bindAttr = $modelRelation->getBindAttr(); + + if (!empty($bindAttr)) { + unset($item[$name]); + } + + foreach ($bindAttr as $key => $attr) { + $key = is_numeric($key) ? $attr : $key; + + if (isset($item[$key])) { + throw new Exception('bind attr has exists:' . $key); + } + + $item[$key] = $value ? $value->getAttr($attr) : null; + } + } + } + + /** + * 转换当前模型对象为JSON字符串 + * @access public + * @param integer $options json参数 + * @return string + */ + public function toJson(int $options = JSON_UNESCAPED_UNICODE): string + { + return json_encode($this->toArray(), $options); + } + + public function __toString() + { + return $this->toJson(); + } + + // JsonSerializable + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return $this->toArray(); + } + + /** + * 转换数据集为数据集对象 + * @access public + * @param array|Collection $collection 数据集 + * @param string $resultSetType 数据集类 + * @return Collection + */ + public function toCollection(iterable $collection = [], string $resultSetType = null): Collection + { + $resultSetType = $resultSetType ?: $this->resultSetType; + + if ($resultSetType && false !== strpos($resultSetType, '\\')) { + $collection = new $resultSetType($collection); + } else { + $collection = new ModelCollection($collection); + } + + return $collection; + } + +} diff --git a/vendor/topthink/think-orm/src/model/concern/ModelEvent.php b/vendor/topthink/think-orm/src/model/concern/ModelEvent.php new file mode 100644 index 0000000..d2388ab --- /dev/null +++ b/vendor/topthink/think-orm/src/model/concern/ModelEvent.php @@ -0,0 +1,88 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\model\concern; + +use think\db\exception\ModelEventException; +use think\helper\Str; + +/** + * 模型事件处理 + */ +trait ModelEvent +{ + + /** + * Event对象 + * @var object + */ + protected static $event; + + /** + * 是否需要事件响应 + * @var bool + */ + protected $withEvent = true; + + /** + * 设置Event对象 + * @access public + * @param object $event Event对象 + * @return void + */ + public static function setEvent($event) + { + self::$event = $event; + } + + /** + * 当前操作的事件响应 + * @access protected + * @param bool $event 是否需要事件响应 + * @return $this + */ + public function withEvent(bool $event) + { + $this->withEvent = $event; + return $this; + } + + /** + * 触发事件 + * @access protected + * @param string $event 事件名 + * @return bool + */ + protected function trigger(string $event): bool + { + if (!$this->withEvent) { + return true; + } + + $call = 'on' . Str::studly($event); + + try { + if (method_exists(static::class, $call)) { + $result = call_user_func([static::class, $call], $this); + } elseif (is_object(self::$event) && method_exists(self::$event, 'trigger')) { + $result = self::$event->trigger('model.' . static::class . '.' . $event, $this); + $result = empty($result) ? true : end($result); + } else { + $result = true; + } + + return false === $result ? false : true; + } catch (ModelEventException $e) { + return false; + } + } +} diff --git a/vendor/topthink/think-orm/src/model/concern/OptimLock.php b/vendor/topthink/think-orm/src/model/concern/OptimLock.php new file mode 100644 index 0000000..5e61318 --- /dev/null +++ b/vendor/topthink/think-orm/src/model/concern/OptimLock.php @@ -0,0 +1,85 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\model\concern; + +use think\db\exception\DbException as Exception; + +/** + * 乐观锁 + */ +trait OptimLock +{ + protected function getOptimLockField() + { + return property_exists($this, 'optimLock') && isset($this->optimLock) ? $this->optimLock : 'lock_version'; + } + + /** + * 数据检查 + * @access protected + * @return void + */ + protected function checkData(): void + { + $this->isExists() ? $this->updateLockVersion() : $this->recordLockVersion(); + } + + /** + * 记录乐观锁 + * @access protected + * @return void + */ + protected function recordLockVersion(): void + { + $optimLock = $this->getOptimLockField(); + + if ($optimLock) { + $this->set($optimLock, 0); + } + } + + /** + * 更新乐观锁 + * @access protected + * @return void + */ + protected function updateLockVersion(): void + { + $optimLock = $this->getOptimLockField(); + + if ($optimLock && $lockVer = $this->getOrigin($optimLock)) { + // 更新乐观锁 + $this->set($optimLock, $lockVer + 1); + } + } + + public function getWhere() + { + $where = parent::getWhere(); + $optimLock = $this->getOptimLockField(); + + if ($optimLock && $lockVer = $this->getOrigin($optimLock)) { + $where[] = [$optimLock, '=', $lockVer]; + } + + return $where; + } + + protected function checkResult($result): void + { + if (!$result) { + throw new Exception('record has update'); + } + } + +} diff --git a/vendor/topthink/think-orm/src/model/concern/RelationShip.php b/vendor/topthink/think-orm/src/model/concern/RelationShip.php new file mode 100644 index 0000000..37b8af8 --- /dev/null +++ b/vendor/topthink/think-orm/src/model/concern/RelationShip.php @@ -0,0 +1,843 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\model\concern; + +use Closure; +use think\Collection; +use think\db\BaseQuery as Query; +use think\db\exception\DbException as Exception; +use think\helper\Str; +use think\Model; +use think\model\Relation; +use think\model\relation\BelongsTo; +use think\model\relation\BelongsToMany; +use think\model\relation\HasMany; +use think\model\relation\HasManyThrough; +use think\model\relation\HasOne; +use think\model\relation\HasOneThrough; +use think\model\relation\MorphMany; +use think\model\relation\MorphOne; +use think\model\relation\MorphTo; +use think\model\relation\MorphToMany; +use think\model\relation\OneToOne; + +/** + * 模型关联处理 + */ +trait RelationShip +{ + /** + * 父关联模型对象 + * @var object + */ + private $parent; + + /** + * 模型关联数据 + * @var array + */ + private $relation = []; + + /** + * 关联写入定义信息 + * @var array + */ + private $together = []; + + /** + * 关联自动写入信息 + * @var array + */ + protected $relationWrite = []; + + /** + * 设置父关联对象 + * @access public + * @param Model $model 模型对象 + * @return $this + */ + public function setParent(Model $model) + { + $this->parent = $model; + + return $this; + } + + /** + * 获取父关联对象 + * @access public + * @return Model + */ + public function getParent(): Model + { + return $this->parent; + } + + /** + * 获取当前模型的关联模型数据 + * @access public + * @param string $name 关联方法名 + * @param bool $auto 不存在是否自动获取 + * @return mixed + */ + public function getRelation(string $name = null, bool $auto = false) + { + if (is_null($name)) { + return $this->relation; + } + + if (array_key_exists($name, $this->relation)) { + return $this->relation[$name]; + } elseif ($auto) { + $relation = Str::camel($name); + return $this->getRelationValue($relation); + } + } + + /** + * 设置关联数据对象值 + * @access public + * @param string $name 属性名 + * @param mixed $value 属性值 + * @param array $data 数据 + * @return $this + */ + public function setRelation(string $name, $value, array $data = []) + { + // 检测修改器 + $method = 'set' . Str::studly($name) . 'Attr'; + + if (method_exists($this, $method)) { + $value = $this->$method($value, array_merge($this->data, $data)); + } + + $this->relation[$this->getRealFieldName($name)] = $value; + + return $this; + } + + /** + * 查询当前模型的关联数据 + * @access public + * @param array $relations 关联名 + * @param array $withRelationAttr 关联获取器 + * @return void + */ + public function relationQuery(array $relations, array $withRelationAttr = []): void + { + foreach ($relations as $key => $relation) { + $subRelation = []; + $closure = null; + + if ($relation instanceof Closure) { + // 支持闭包查询过滤关联条件 + $closure = $relation; + $relation = $key; + } + + if (is_array($relation)) { + $subRelation = $relation; + $relation = $key; + } elseif (strpos($relation, '.')) { + [$relation, $subRelation] = explode('.', $relation, 2); + } + + $method = Str::camel($relation); + $relationName = Str::snake($relation); + + $relationResult = $this->$method(); + + if (isset($withRelationAttr[$relationName])) { + $relationResult->withAttr($withRelationAttr[$relationName]); + } + + $this->relation[$relation] = $relationResult->getRelation((array) $subRelation, $closure); + } + } + + /** + * 关联数据写入 + * @access public + * @param array $relation 关联 + * @return $this + */ + public function together(array $relation) + { + $this->together = $relation; + + $this->checkAutoRelationWrite(); + + return $this; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $relation 关联方法名 + * @param mixed $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public static function has(string $relation, string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '', Query $query = null): Query + { + return (new static()) + ->$relation() + ->has($operator, $count, $id, $joinType, $query); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $relation 关联方法名 + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public static function hasWhere(string $relation, $where = [], string $fields = '*', string $joinType = '', Query $query = null): Query + { + return (new static()) + ->$relation() + ->hasWhere($where, $fields, $joinType, $query); + } + + /** + * 预载入关联查询 JOIN方式 + * @access public + * @param Query $query Query对象 + * @param string $relation 关联方法名 + * @param mixed $field 字段 + * @param string $joinType JOIN类型 + * @param Closure $closure 闭包 + * @param bool $first + * @return bool + */ + public function eagerly(Query $query, string $relation, $field, string $joinType = '', Closure $closure = null, bool $first = false): bool + { + $relation = Str::camel($relation); + $class = $this->$relation(); + + if ($class instanceof OneToOne) { + $class->eagerly($query, $relation, $field, $joinType, $closure, $first); + return true; + } else { + return false; + } + } + + /** + * 预载入关联查询 返回数据集 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 关联名 + * @param array $withRelationAttr 关联获取器 + * @param bool $join 是否为JOIN方式 + * @param mixed $cache 关联缓存 + * @return void + */ + public function eagerlyResultSet(array &$resultSet, array $relations, array $withRelationAttr = [], bool $join = false, $cache = false): void + { + foreach ($relations as $key => $relation) { + $subRelation = []; + $closure = null; + + if ($relation instanceof Closure) { + $closure = $relation; + $relation = $key; + } + + if (is_array($relation)) { + $subRelation = $relation; + $relation = $key; + } elseif (strpos($relation, '.')) { + [$relation, $subRelation] = explode('.', $relation, 2); + + $subRelation = [$subRelation]; + } + + $relationName = $relation; + $relation = Str::camel($relation); + + $relationResult = $this->$relation(); + + if (isset($withRelationAttr[$relationName])) { + $relationResult->withAttr($withRelationAttr[$relationName]); + } + + if (is_scalar($cache)) { + $relationCache = [$cache]; + } else { + $relationCache = $cache[$relationName] ?? $cache; + } + + $relationResult->eagerlyResultSet($resultSet, $relationName, $subRelation, $closure, $relationCache, $join); + } + } + + /** + * 预载入关联查询 返回模型对象 + * @access public + * @param array $relations 关联 + * @param array $withRelationAttr 关联获取器 + * @param bool $join 是否为JOIN方式 + * @param mixed $cache 关联缓存 + * @return void + */ + public function eagerlyResult(array $relations, array $withRelationAttr = [], bool $join = false, $cache = false): void + { + foreach ($relations as $key => $relation) { + $subRelation = []; + $closure = null; + + if ($relation instanceof Closure) { + $closure = $relation; + $relation = $key; + } + + if (is_array($relation)) { + $subRelation = $relation; + $relation = $key; + } elseif (strpos($relation, '.')) { + [$relation, $subRelation] = explode('.', $relation, 2); + + $subRelation = [$subRelation]; + } + + $relationName = $relation; + $relation = Str::camel($relation); + + $relationResult = $this->$relation(); + + if (isset($withRelationAttr[$relationName])) { + $relationResult->withAttr($withRelationAttr[$relationName]); + } + + if (is_scalar($cache)) { + $relationCache = [$cache]; + } else { + $relationCache = $cache[$relationName] ?? []; + } + + $relationResult->eagerlyResult($this, $relationName, $subRelation, $closure, $relationCache, $join); + } + } + + /** + * 绑定(一对一)关联属性到当前模型 + * @access protected + * @param string $relation 关联名称 + * @param array $attrs 绑定属性 + * @return $this + * @throws Exception + */ + public function bindAttr(string $relation, array $attrs = []) + { + $relation = $this->getRelation($relation, true); + + foreach ($attrs as $key => $attr) { + $key = is_numeric($key) ? $attr : $key; + $value = $this->getOrigin($key); + + if (!is_null($value)) { + throw new Exception('bind attr has exists:' . $key); + } + + $this->set($key, $relation ? $relation->$attr : null); + } + + return $this; + } + + /** + * 关联统计 + * @access public + * @param Query $query 查询对象 + * @param array $relations 关联名 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param bool $useSubQuery 子查询 + * @return void + */ + public function relationCount(Query $query, array $relations, string $aggregate = 'sum', string $field = '*', bool $useSubQuery = true): void + { + foreach ($relations as $key => $relation) { + $closure = $name = null; + + if ($relation instanceof Closure) { + $closure = $relation; + $relation = $key; + } elseif (is_string($key)) { + $name = $relation; + $relation = $key; + } + + $relation = Str::camel($relation); + + if ($useSubQuery) { + $count = $this->$relation()->getRelationCountQuery($closure, $aggregate, $field, $name); + } else { + $count = $this->$relation()->relationCount($this, $closure, $aggregate, $field, $name); + } + + if (empty($name)) { + $name = Str::snake($relation) . '_' . $aggregate; + } + + if ($useSubQuery) { + $query->field(['(' . $count . ')' => $name]); + } else { + $this->setAttr($name, $count); + } + } + } + + /** + * HAS ONE 关联定义 + * @access public + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 当前主键 + * @return HasOne + */ + public function hasOne(string $model, string $foreignKey = '', string $localKey = ''): HasOne + { + // 记录当前关联信息 + $model = $this->parseModel($model); + $localKey = $localKey ?: $this->getPk(); + $foreignKey = $foreignKey ?: $this->getForeignKey($this->name); + + return new HasOne($this, $model, $foreignKey, $localKey); + } + + /** + * BELONGS TO 关联定义 + * @access public + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 关联主键 + * @return BelongsTo + */ + public function belongsTo(string $model, string $foreignKey = '', string $localKey = ''): BelongsTo + { + // 记录当前关联信息 + $model = $this->parseModel($model); + $foreignKey = $foreignKey ?: $this->getForeignKey((new $model)->getName()); + $localKey = $localKey ?: (new $model)->getPk(); + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); + $relation = Str::snake($trace[1]['function']); + + return new BelongsTo($this, $model, $foreignKey, $localKey, $relation); + } + + /** + * HAS MANY 关联定义 + * @access public + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 当前主键 + * @return HasMany + */ + public function hasMany(string $model, string $foreignKey = '', string $localKey = ''): HasMany + { + // 记录当前关联信息 + $model = $this->parseModel($model); + $localKey = $localKey ?: $this->getPk(); + $foreignKey = $foreignKey ?: $this->getForeignKey($this->name); + + return new HasMany($this, $model, $foreignKey, $localKey); + } + + /** + * HAS MANY 远程关联定义 + * @access public + * @param string $model 模型名 + * @param string $through 中间模型名 + * @param string $foreignKey 关联外键 + * @param string $throughKey 关联外键 + * @param string $localKey 当前主键 + * @param string $throughPk 中间表主键 + * @return HasManyThrough + */ + public function hasManyThrough(string $model, string $through, string $foreignKey = '', string $throughKey = '', string $localKey = '', string $throughPk = ''): HasManyThrough + { + // 记录当前关联信息 + $model = $this->parseModel($model); + $through = $this->parseModel($through); + $localKey = $localKey ?: $this->getPk(); + $foreignKey = $foreignKey ?: $this->getForeignKey($this->name); + $throughKey = $throughKey ?: $this->getForeignKey((new $through)->getName()); + $throughPk = $throughPk ?: (new $through)->getPk(); + + return new HasManyThrough($this, $model, $through, $foreignKey, $throughKey, $localKey, $throughPk); + } + + /** + * HAS ONE 远程关联定义 + * @access public + * @param string $model 模型名 + * @param string $through 中间模型名 + * @param string $foreignKey 关联外键 + * @param string $throughKey 关联外键 + * @param string $localKey 当前主键 + * @param string $throughPk 中间表主键 + * @return HasOneThrough + */ + public function hasOneThrough(string $model, string $through, string $foreignKey = '', string $throughKey = '', string $localKey = '', string $throughPk = ''): HasOneThrough + { + // 记录当前关联信息 + $model = $this->parseModel($model); + $through = $this->parseModel($through); + $localKey = $localKey ?: $this->getPk(); + $foreignKey = $foreignKey ?: $this->getForeignKey($this->name); + $throughKey = $throughKey ?: $this->getForeignKey((new $through)->getName()); + $throughPk = $throughPk ?: (new $through)->getPk(); + + return new HasOneThrough($this, $model, $through, $foreignKey, $throughKey, $localKey, $throughPk); + } + + /** + * BELONGS TO MANY 关联定义 + * @access public + * @param string $model 模型名 + * @param string $middle 中间表/模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 当前模型关联键 + * @return BelongsToMany + */ + public function belongsToMany(string $model, string $middle = '', string $foreignKey = '', string $localKey = ''): BelongsToMany + { + // 记录当前关联信息 + $model = $this->parseModel($model); + $name = Str::snake(class_basename($model)); + $middle = $middle ?: Str::snake($this->name) . '_' . $name; + $foreignKey = $foreignKey ?: $name . '_id'; + $localKey = $localKey ?: $this->getForeignKey($this->name); + + return new BelongsToMany($this, $model, $middle, $foreignKey, $localKey); + } + + /** + * MORPH One 关联定义 + * @access public + * @param string $model 模型名 + * @param string|array $morph 多态字段信息 + * @param string $type 多态类型 + * @return MorphOne + */ + public function morphOne(string $model, $morph = null, string $type = ''): MorphOne + { + // 记录当前关联信息 + $model = $this->parseModel($model); + + if (is_null($morph)) { + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); + $morph = Str::snake($trace[1]['function']); + } + + if (is_array($morph)) { + [$morphType, $foreignKey] = $morph; + } else { + $morphType = $morph . '_type'; + $foreignKey = $morph . '_id'; + } + + $type = $type ?: get_class($this); + + return new MorphOne($this, $model, $foreignKey, $morphType, $type); + } + + /** + * MORPH MANY 关联定义 + * @access public + * @param string $model 模型名 + * @param string|array $morph 多态字段信息 + * @param string $type 多态类型 + * @return MorphMany + */ + public function morphMany(string $model, $morph = null, string $type = ''): MorphMany + { + // 记录当前关联信息 + $model = $this->parseModel($model); + + if (is_null($morph)) { + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); + $morph = Str::snake($trace[1]['function']); + } + + $type = $type ?: get_class($this); + + if (is_array($morph)) { + [$morphType, $foreignKey] = $morph; + } else { + $morphType = $morph . '_type'; + $foreignKey = $morph . '_id'; + } + + return new MorphMany($this, $model, $foreignKey, $morphType, $type); + } + + /** + * MORPH TO 关联定义 + * @access public + * @param string|array $morph 多态字段信息 + * @param array $alias 多态别名定义 + * @return MorphTo + */ + public function morphTo($morph = null, array $alias = []): MorphTo + { + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); + $relation = Str::snake($trace[1]['function']); + + if (is_null($morph)) { + $morph = $relation; + } + + // 记录当前关联信息 + if (is_array($morph)) { + [$morphType, $foreignKey] = $morph; + } else { + $morphType = $morph . '_type'; + $foreignKey = $morph . '_id'; + } + + return new MorphTo($this, $morphType, $foreignKey, $alias, $relation); + } + + /** + * MORPH TO MANY关联定义 + * @access public + * @param string $model 模型名 + * @param string $middle 中间表名/模型名 + * @param string|array $morph 多态字段信息 + * @param string $localKey 当前模型关联键 + * @return MorphToMany + */ + public function morphToMany(string $model, string $middle, $morph = null, string $localKey = null): MorphToMany + { + if (is_null($morph)) { + $morph = $middle; + } + + // 记录当前关联信息 + if (is_array($morph)) { + [$morphType, $morphKey] = $morph; + } else { + $morphType = $morph . '_type'; + $morphKey = $morph . '_id'; + } + + $model = $this->parseModel($model); + $name = Str::snake(class_basename($model)); + $localKey = $localKey ?: $this->getForeignKey($name); + + return new MorphToMany($this, $model, $middle, $morphType, $morphKey, $localKey); + } + + /** + * MORPH BY MANY关联定义 + * @access public + * @param string $model 模型名 + * @param string $middle 中间表名/模型名 + * @param string|array $morph 多态字段信息 + * @param string $foreignKey 关联外键 + * @return MorphToMany + */ + public function morphByMany(string $model, string $middle, $morph = null, string $foreignKey = null): MorphToMany + { + if (is_null($morph)) { + $morph = $middle; + } + + // 记录当前关联信息 + if (is_array($morph)) { + [$morphType, $morphKey] = $morph; + } else { + $morphType = $morph . '_type'; + $morphKey = $morph . '_id'; + } + + $model = $this->parseModel($model); + $foreignKey = $foreignKey ?: $this->getForeignKey($this->name); + + return new MorphToMany($this, $model, $middle, $morphType, $morphKey, $foreignKey, true); + } + + /** + * 解析模型的完整命名空间 + * @access protected + * @param string $model 模型名(或者完整类名) + * @return string + */ + protected function parseModel(string $model): string + { + if (false === strpos($model, '\\')) { + $path = explode('\\', static::class); + array_pop($path); + array_push($path, Str::studly($model)); + $model = implode('\\', $path); + } + + return $model; + } + + /** + * 获取模型的默认外键名 + * @access protected + * @param string $name 模型名 + * @return string + */ + protected function getForeignKey(string $name): string + { + if (strpos($name, '\\')) { + $name = class_basename($name); + } + + return Str::snake($name) . '_id'; + } + + /** + * 检查属性是否为关联属性 如果是则返回关联方法名 + * @access protected + * @param string $attr 关联属性名 + * @return string|false + */ + protected function isRelationAttr(string $attr) + { + $relation = Str::camel($attr); + + if ((method_exists($this, $relation) && !method_exists('think\Model', $relation)) || isset(static::$macro[static::class][$relation])) { + return $relation; + } + + return false; + } + + /** + * 智能获取关联模型数据 + * @access protected + * @param Relation $modelRelation 模型关联对象 + * @return mixed + */ + protected function getRelationData(Relation $modelRelation) + { + if ($this->parent && !$modelRelation->isSelfRelation() + && get_class($this->parent) == get_class($modelRelation->getModel()) + && $modelRelation instanceof OneToOne + ) { + return $this->parent; + } + + // 获取关联数据 + return $modelRelation->getRelation(); + } + + /** + * 关联数据自动写入检查 + * @access protected + * @return void + */ + protected function checkAutoRelationWrite(): void + { + foreach ($this->together as $key => $name) { + if (is_array($name)) { + if (key($name) === 0) { + $this->relationWrite[$key] = []; + // 绑定关联属性 + foreach ($name as $val) { + if (isset($this->data[$val])) { + $this->relationWrite[$key][$val] = $this->data[$val]; + } + } + } else { + // 直接传入关联数据 + $this->relationWrite[$key] = $name; + } + } elseif (isset($this->relation[$name])) { + $this->relationWrite[$name] = $this->relation[$name]; + } elseif (isset($this->data[$name])) { + $this->relationWrite[$name] = $this->data[$name]; + unset($this->data[$name]); + } + } + } + + /** + * 自动关联数据更新(针对一对一关联) + * @access protected + * @return void + */ + protected function autoRelationUpdate(): void + { + foreach ($this->relationWrite as $name => $val) { + if ($val instanceof Model) { + $val->exists(true)->save(); + } else { + $model = $this->getRelation($name, true); + + if ($model instanceof Model) { + $model->exists(true)->save($val); + } + } + } + } + + /** + * 自动关联数据写入(针对一对一关联) + * @access protected + * @return void + */ + protected function autoRelationInsert(): void + { + foreach ($this->relationWrite as $name => $val) { + $method = Str::camel($name); + $this->$method()->save($val); + } + } + + /** + * 自动关联数据删除(支持一对一及一对多关联) + * @access protected + * @param bool $force 强制删除 + * @return void + */ + protected function autoRelationDelete($force = false): void + { + foreach ($this->relationWrite as $key => $name) { + $name = is_numeric($key) ? $name : $key; + $result = $this->getRelation($name, true); + + if ($result instanceof Model) { + $result->force($force)->delete(); + } elseif ($result instanceof Collection) { + foreach ($result as $model) { + $model->force($force)->delete(); + } + } + } + } + + /** + * 移除当前模型的关联属性 + * @access public + * @return $this + */ + public function removeRelation() + { + $this->relation = []; + return $this; + } +} diff --git a/vendor/topthink/think-orm/src/model/concern/SoftDelete.php b/vendor/topthink/think-orm/src/model/concern/SoftDelete.php new file mode 100644 index 0000000..b7b1092 --- /dev/null +++ b/vendor/topthink/think-orm/src/model/concern/SoftDelete.php @@ -0,0 +1,235 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\model\concern; + +use think\db\BaseQuery as Query; +use think\Model; + +/** + * 数据软删除 + * @mixin Model + * @method $this withTrashed() + * @method $this onlyTrashed() + */ +trait SoftDelete +{ + + public function db($scope = []): Query + { + $query = parent::db($scope); + $this->withNoTrashed($query); + return $query; + } + + /** + * 判断当前实例是否被软删除 + * @access public + * @return bool + */ + public function trashed(): bool + { + $field = $this->getDeleteTimeField(); + + if ($field && !empty($this->getOrigin($field))) { + return true; + } + + return false; + } + + public function scopeWithTrashed(Query $query) + { + $query->removeOption('soft_delete'); + } + + public function scopeOnlyTrashed(Query $query) + { + $field = $this->getDeleteTimeField(true); + + if ($field) { + $query->useSoftDelete($field, $this->getWithTrashedExp()); + } + } + + /** + * 获取软删除数据的查询条件 + * @access protected + * @return array + */ + protected function getWithTrashedExp(): array + { + return is_null($this->defaultSoftDelete) ? ['notnull', ''] : ['<>', $this->defaultSoftDelete]; + } + + /** + * 删除当前的记录 + * @access public + * @return bool + */ + public function delete(): bool + { + if (!$this->isExists() || $this->isEmpty() || false === $this->trigger('BeforeDelete')) { + return false; + } + + $name = $this->getDeleteTimeField(); + $force = $this->isForce(); + + if ($name && !$force) { + // 软删除 + $this->set($name, $this->autoWriteTimestamp()); + + $this->exists()->withEvent(false)->save(); + + $this->withEvent(true); + } else { + // 读取更新条件 + $where = $this->getWhere(); + + // 删除当前模型数据 + $this->db() + ->where($where) + ->removeOption('soft_delete') + ->delete(); + + $this->lazySave(false); + } + + // 关联删除 + if (!empty($this->relationWrite)) { + $this->autoRelationDelete($force); + } + + $this->trigger('AfterDelete'); + + $this->exists(false); + + return true; + } + + /** + * 删除记录 + * @access public + * @param mixed $data 主键列表 支持闭包查询条件 + * @param bool $force 是否强制删除 + * @return bool + */ + public static function destroy($data, bool $force = false): bool + { + // 传入空值(包括空字符串和空数组)的时候不会做任何的数据删除操作,但传入0则是有效的 + if (empty($data) && 0 !== $data) { + return false; + } + $model = (new static()); + + $query = $model->db(false); + + // 仅当强制删除时包含软删除数据 + if ($force) { + $query->removeOption('soft_delete'); + } + + if (is_array($data) && key($data) !== 0) { + $query->where($data); + $data = null; + } elseif ($data instanceof \Closure) { + call_user_func_array($data, [&$query]); + $data = null; + } elseif (is_null($data)) { + return false; + } + + $resultSet = $query->select($data); + + foreach ($resultSet as $result) { + /** @var Model $result */ + $result->force($force)->delete(); + } + + return true; + } + + /** + * 恢复被软删除的记录 + * @access public + * @param array $where 更新条件 + * @return bool + */ + public function restore($where = []): bool + { + $name = $this->getDeleteTimeField(); + + if (!$name || false === $this->trigger('BeforeRestore')) { + return false; + } + + if (empty($where)) { + $pk = $this->getPk(); + if (is_string($pk)) { + $where[] = [$pk, '=', $this->getData($pk)]; + } + } + + // 恢复删除 + $this->db(false) + ->where($where) + ->useSoftDelete($name, $this->getWithTrashedExp()) + ->update([$name => $this->defaultSoftDelete]); + + $this->trigger('AfterRestore'); + + return true; + } + + /** + * 获取软删除字段 + * @access protected + * @param bool $read 是否查询操作 写操作的时候会自动去掉表别名 + * @return string|false + */ + protected function getDeleteTimeField(bool $read = false) + { + $field = property_exists($this, 'deleteTime') && isset($this->deleteTime) ? $this->deleteTime : 'delete_time'; + + if (false === $field) { + return false; + } + + if (false === strpos($field, '.')) { + $field = '__TABLE__.' . $field; + } + + if (!$read && strpos($field, '.')) { + $array = explode('.', $field); + $field = array_pop($array); + } + + return $field; + } + + /** + * 查询的时候默认排除软删除数据 + * @access protected + * @param Query $query + * @return void + */ + protected function withNoTrashed(Query $query): void + { + $field = $this->getDeleteTimeField(true); + + if ($field) { + $condition = is_null($this->defaultSoftDelete) ? ['null', ''] : ['=', $this->defaultSoftDelete]; + $query->useSoftDelete($field, $condition); + } + } +} diff --git a/vendor/topthink/think-orm/src/model/concern/TimeStamp.php b/vendor/topthink/think-orm/src/model/concern/TimeStamp.php new file mode 100644 index 0000000..9440c3e --- /dev/null +++ b/vendor/topthink/think-orm/src/model/concern/TimeStamp.php @@ -0,0 +1,223 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\model\concern; + +use DateTime; + +/** + * 自动时间戳 + */ +trait TimeStamp +{ + /** + * 是否需要自动写入时间戳 如果设置为字符串 则表示时间字段的类型 + * @var bool|string + */ + protected $autoWriteTimestamp; + + /** + * 创建时间字段 false表示关闭 + * @var false|string + */ + protected $createTime = 'create_time'; + + /** + * 更新时间字段 false表示关闭 + * @var false|string + */ + protected $updateTime = 'update_time'; + + /** + * 时间字段显示格式 + * @var string + */ + protected $dateFormat; + + /** + * 是否需要自动写入时间字段 + * @access public + * @param bool|string $auto + * @return $this + */ + public function isAutoWriteTimestamp($auto) + { + $this->autoWriteTimestamp = $this->checkTimeFieldType($auto); + + return $this; + } + + /** + * 检测时间字段的实际类型 + * @access public + * @param bool|string $type + * @return mixed + */ + protected function checkTimeFieldType($type) + { + if (true === $type) { + if (isset($this->type[$this->createTime])) { + $type = $this->type[$this->createTime]; + } elseif (isset($this->schema[$this->createTime]) && in_array($this->schema[$this->createTime], ['datetime', 'date', 'timestamp', 'int'])) { + $type = $this->schema[$this->createTime]; + } else { + $type = $this->getFieldType($this->createTime); + } + } + + return $type; + } + + /** + * 设置时间字段名称 + * @access public + * @param string $createTime + * @param string $updateTime + * @return $this + */ + public function setTimeField(string $createTime, string $updateTime) + { + $this->createTime = $createTime; + $this->updateTime = $updateTime; + + return $this; + } + + /** + * 获取自动写入时间字段 + * @access public + * @return bool|string + */ + public function getAutoWriteTimestamp() + { + return $this->autoWriteTimestamp; + } + + /** + * 设置时间字段格式化 + * @access public + * @param string|false $format + * @return $this + */ + public function setDateFormat($format) + { + $this->dateFormat = $format; + + return $this; + } + + /** + * 获取自动写入时间字段 + * @access public + * @return string|false + */ + public function getDateFormat() + { + return $this->dateFormat; + } + + /** + * 自动写入时间戳 + * @access protected + * @return mixed + */ + protected function autoWriteTimestamp() + { + // 检测时间字段类型 + $type = $this->checkTimeFieldType($this->autoWriteTimestamp); + + return is_string($type) ? $this->getTimeTypeValue($type) : time(); + } + + /** + * 获取指定类型的时间字段值 + * @access protected + * @param string $type 时间字段类型 + * @return mixed + */ + protected function getTimeTypeValue(string $type) + { + $value = time(); + + switch ($type) { + case 'datetime': + case 'date': + case 'timestamp': + $value = $this->formatDateTime('Y-m-d H:i:s.u'); + break; + default: + if (false !== strpos($type, '\\')) { + // 对象数据写入 + $obj = new $type(); + if (method_exists($obj, '__toString')) { + // 对象数据写入 + $value = $obj->__toString(); + } + } + } + + return $value; + } + + /** + * 时间日期字段格式化处理 + * @access protected + * @param mixed $format 日期格式 + * @param mixed $time 时间日期表达式 + * @param bool $timestamp 时间表达式是否为时间戳 + * @return mixed + */ + protected function formatDateTime($format, $time = 'now', bool $timestamp = false) + { + if (empty($time)) { + return $time; + } + + if (false === $format) { + return $time; + } elseif (false !== strpos($format, '\\')) { + return new $format($time); + } + + if ($time instanceof DateTime) { + $dateTime = $time; + } elseif ($timestamp) { + $dateTime = new DateTime(); + $dateTime->setTimestamp((int) $time); + } else { + $dateTime = new DateTime($time); + } + + return $dateTime->format($format); + } + + /** + * 获取时间字段值 + * @access protected + * @param mixed $value + * @return mixed + */ + protected function getTimestampValue($value) + { + $type = $this->checkTimeFieldType($this->autoWriteTimestamp); + + if (is_string($type) && in_array(strtolower($type), [ + 'datetime', 'date', 'timestamp', + ])) { + $value = $this->formatDateTime($this->dateFormat, $value); + } else { + $value = $this->formatDateTime($this->dateFormat, $value, true); + } + + return $value; + } +} diff --git a/vendor/topthink/think-orm/src/model/concern/Virtual.php b/vendor/topthink/think-orm/src/model/concern/Virtual.php new file mode 100644 index 0000000..66cdfb7 --- /dev/null +++ b/vendor/topthink/think-orm/src/model/concern/Virtual.php @@ -0,0 +1,90 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\model\concern; + +use think\db\BaseQuery as Query; +use think\db\exception\DbException as Exception; + +/** + * 虚拟模型 + */ +trait Virtual +{ + /** + * 获取当前模型的数据库查询对象 + * @access public + * @param array $scope 设置不使用的全局查询范围 + * @return Query + */ + public function db($scope = []): Query + { + throw new Exception('virtual model not support db query'); + } + + /** + * 获取字段类型信息 + * @access public + * @param string $field 字段名 + * @return string|null + */ + public function getFieldType(string $field) + {} + + /** + * 保存当前数据对象 + * @access public + * @param array $data 数据 + * @param string $sequence 自增序列名 + * @return bool + */ + public function save(array $data = [], string $sequence = null): bool + { + // 数据对象赋值 + $this->setAttrs($data); + + if ($this->isEmpty() || false === $this->trigger('BeforeWrite')) { + return false; + } + + // 写入回调 + $this->trigger('AfterWrite'); + + $this->exists(true); + + return true; + } + + /** + * 删除当前的记录 + * @access public + * @return bool + */ + public function delete(): bool + { + if (!$this->isExists() || $this->isEmpty() || false === $this->trigger('BeforeDelete')) { + return false; + } + + // 关联删除 + if (!empty($this->relationWrite)) { + $this->autoRelationDelete(); + } + + $this->trigger('AfterDelete'); + + $this->exists(false); + + return true; + } + +} diff --git a/vendor/topthink/think-orm/src/model/relation/BelongsTo.php b/vendor/topthink/think-orm/src/model/relation/BelongsTo.php new file mode 100644 index 0000000..dfac254 --- /dev/null +++ b/vendor/topthink/think-orm/src/model/relation/BelongsTo.php @@ -0,0 +1,334 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\model\relation; + +use Closure; +use think\db\BaseQuery as Query; +use think\Model; + +/** + * BelongsTo关联类 + */ +class BelongsTo extends OneToOne +{ + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 关联主键 + * @param string $relation 关联名 + */ + public function __construct(Model $parent, string $model, string $foreignKey, string $localKey, string $relation = null) + { + $this->parent = $parent; + $this->model = $model; + $this->foreignKey = $foreignKey; + $this->localKey = $localKey; + $this->query = (new $model)->db(); + $this->relation = $relation; + + if (get_class($parent) == $model) { + $this->selfRelation = true; + } + } + + /** + * 延迟获取关联数据 + * @access public + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包查询条件 + * @return Model + */ + public function getRelation(array $subRelation = [], Closure $closure = null) + { + if ($closure) { + $closure($this->getClosureType($closure)); + } + + $foreignKey = $this->foreignKey; + + $relationModel = $this->query + ->removeWhereField($this->localKey) + ->where($this->localKey, $this->parent->$foreignKey) + ->relation($subRelation) + ->find(); + + if ($relationModel) { + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($this->parent, $relationModel); + } + + $relationModel->setParent(clone $this->parent); + } else { + $relationModel = $this->getDefaultModel(); + } + + return $relationModel; + } + + /** + * 创建关联统计子查询 + * @access public + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 聚合字段别名 + * @return string + */ + public function getRelationCountQuery(Closure $closure = null, string $aggregate = 'count', string $field = '*', &$name = ''): string + { + if ($closure) { + $closure($this->getClosureType($closure), $name); + } + + return $this->query + ->whereExp($this->localKey, '=' . $this->parent->getTable() . '.' . $this->foreignKey) + ->fetchSql() + ->$aggregate($field); + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return integer + */ + public function relationCount(Model $result, Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null) + { + $foreignKey = $this->foreignKey; + + if (!isset($result->$foreignKey)) { + return 0; + } + + if ($closure) { + $closure($this->getClosureType($closure), $name); + } + + return $this->query + ->where($this->localKey, '=', $result->$foreignKey) + ->$aggregate($field); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '', Query $query = null): Query + { + $table = $this->query->getTable(); + $model = class_basename($this->parent); + $relation = class_basename($this->model); + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + $softDelete = $this->query->getOptions('soft_delete'); + $query = $query ?: $this->parent->db()->alias($model); + + return $query->whereExists(function ($query) use ($table, $model, $relation, $localKey, $foreignKey, $softDelete) { + $query->table([$table => $relation]) + ->field($relation . '.' . $localKey) + ->whereExp($model . '.' . $foreignKey, '=' . $relation . '.' . $localKey) + ->when($softDelete, function ($query) use ($softDelete, $relation) { + $query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null); + }); + }); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public function hasWhere($where = [], $fields = null, string $joinType = '', Query $query = null): Query + { + $table = $this->query->getTable(); + $model = class_basename($this->parent); + $relation = class_basename($this->model); + + if (is_array($where)) { + $this->getQueryWhere($where, $relation); + } elseif ($where instanceof Query) { + $where->via($relation); + } elseif ($where instanceof Closure) { + $where($this->query->via($relation)); + $where = $this->query; + } + + $fields = $this->getRelationQueryFields($fields, $model); + $softDelete = $this->query->getOptions('soft_delete'); + $query = $query ?: $this->parent->db(); + + return $query->alias($model) + ->field($fields) + ->join([$table => $relation], $model . '.' . $this->foreignKey . '=' . $relation . '.' . $this->localKey, $joinType ?: $this->joinType) + ->when($softDelete, function ($query) use ($softDelete, $relation) { + $query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null); + }) + ->where($where); + } + + /** + * 预载入关联查询(数据集) + * @access protected + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + protected function eagerlySet(array &$resultSet, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + $range = []; + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (isset($result->$foreignKey)) { + $range[] = $result->$foreignKey; + } + } + + if (!empty($range)) { + $this->query->removeWhereField($localKey); + + $data = $this->eagerlyWhere([ + [$localKey, 'in', $range], + ], $localKey, $subRelation, $closure, $cache); + + // 关联数据封装 + foreach ($resultSet as $result) { + // 关联模型 + if (!isset($data[$result->$foreignKey])) { + $relationModel = $this->getDefaultModel(); + } else { + $relationModel = $data[$result->$foreignKey]; + $relationModel->setParent(clone $result); + $relationModel->exists(true); + } + + // 设置关联属性 + $result->setRelation($relation, $relationModel); + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($result, $relationModel); + $result->hidden([$relation], true); + } + } + } + } + + /** + * 预载入关联查询(数据) + * @access protected + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + protected function eagerlyOne(Model $result, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + $this->query->removeWhereField($localKey); + + $data = $this->eagerlyWhere([ + [$localKey, '=', $result->$foreignKey], + ], $localKey, $subRelation, $closure, $cache); + + // 关联模型 + if (!isset($data[$result->$foreignKey])) { + $relationModel = $this->getDefaultModel(); + } else { + $relationModel = $data[$result->$foreignKey]; + $relationModel->setParent(clone $result); + $relationModel->exists(true); + } + + // 设置关联属性 + $result->setRelation($relation, $relationModel); + + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($result, $relationModel); + $result->hidden([$relation], true); + } + } + + /** + * 添加关联数据 + * @access public + * @param Model $model关联模型对象 + * @return Model + */ + public function associate(Model $model): Model + { + $this->parent->setAttr($this->foreignKey, $model->getKey()); + $this->parent->save(); + + return $this->parent->setRelation($this->relation, $model); + } + + /** + * 注销关联数据 + * @access public + * @return Model + */ + public function dissociate(): Model + { + $foreignKey = $this->foreignKey; + + $this->parent->setAttr($foreignKey, null); + $this->parent->save(); + + return $this->parent->setRelation($this->relation, null); + } + + /** + * 执行基础查询(仅执行一次) + * @access protected + * @return void + */ + protected function baseQuery(): void + { + if (empty($this->baseQuery)) { + if (isset($this->parent->{$this->foreignKey})) { + // 关联查询带入关联条件 + $this->query->where($this->localKey, '=', $this->parent->{$this->foreignKey}); + } + + $this->baseQuery = true; + } + } +} diff --git a/vendor/topthink/think-orm/src/model/relation/BelongsToMany.php b/vendor/topthink/think-orm/src/model/relation/BelongsToMany.php new file mode 100644 index 0000000..8c74f34 --- /dev/null +++ b/vendor/topthink/think-orm/src/model/relation/BelongsToMany.php @@ -0,0 +1,633 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use Closure; +use think\Collection; +use think\db\BaseQuery as Query; +use think\db\exception\DbException as Exception; +use think\db\Raw; +use think\Model; +use think\model\Pivot; +use think\model\Relation; + +/** + * 多对多关联类 + */ +class BelongsToMany extends Relation +{ + /** + * 中间表表名 + * @var string + */ + protected $middle; + + /** + * 中间表模型名称 + * @var string + */ + protected $pivotName; + + /** + * 中间表模型对象 + * @var Pivot + */ + protected $pivot; + + /** + * 中间表数据名称 + * @var string + */ + protected $pivotDataName = 'pivot'; + + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $middle 中间表/模型名 + * @param string $foreignKey 关联模型外键 + * @param string $localKey 当前模型关联键 + */ + public function __construct(Model $parent, string $model, string $middle, string $foreignKey, string $localKey) + { + $this->parent = $parent; + $this->model = $model; + $this->foreignKey = $foreignKey; + $this->localKey = $localKey; + + if (false !== strpos($middle, '\\')) { + $this->pivotName = $middle; + $this->middle = class_basename($middle); + } else { + $this->middle = $middle; + } + + $this->query = (new $model)->db(); + $this->pivot = $this->newPivot(); + } + + /** + * 设置中间表模型 + * @access public + * @param $pivot + * @return $this + */ + public function pivot(string $pivot) + { + $this->pivotName = $pivot; + return $this; + } + + /** + * 设置中间表数据名称 + * @access public + * @param string $name + * @return $this + */ + public function name(string $name) + { + $this->pivotDataName = $name; + return $this; + } + + /** + * 实例化中间表模型 + * @access public + * @param $data + * @return Pivot + * @throws Exception + */ + protected function newPivot(array $data = []): Pivot + { + $class = $this->pivotName ?: Pivot::class; + $pivot = new $class($data, $this->parent, $this->middle); + + if ($pivot instanceof Pivot) { + return $pivot; + } else { + throw new Exception('pivot model must extends: \think\model\Pivot'); + } + } + + /** + * 延迟获取关联数据 + * @access public + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包查询条件 + * @return Collection + */ + public function getRelation(array $subRelation = [], Closure $closure = null): Collection + { + if ($closure) { + $closure($this->getClosureType($closure)); + } + + return $this->relation($subRelation) + ->select() + ->setParent(clone $this->parent); + } + + /** + * 组装Pivot模型 + * @access public + * @param Model $result 模型对象 + * @return array + */ + protected function matchPivot(Model $result): array + { + $pivot = []; + foreach ($result->getData() as $key => $val) { + if (strpos($key, '__')) { + [$name, $attr] = explode('__', $key, 2); + + if ('pivot' == $name) { + $pivot[$attr] = $val; + unset($result->$key); + } + } + } + + $pivotData = $this->pivot->newInstance($pivot, [ + [$this->localKey, '=', $this->parent->getKey(), null], + [$this->foreignKey, '=', $result->getKey(), null], + ]); + + $result->setRelation($this->pivotDataName, $pivotData); + return $pivot; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Model + */ + public function has(string $operator = '>=', $count = 1, $id = '*', string $joinType = 'INNER', Query $query = null) + { + return $this->parent; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + * @throws Exception + */ + public function hasWhere($where = [], $fields = null, string $joinType = '', Query $query = null) + { + throw new Exception('relation not support: hasWhere'); + } + + /** + * 设置中间表的查询条件 + * @access public + * @param string $field + * @param string $op + * @param mixed $condition + * @return $this + */ + public function wherePivot($field, $op = null, $condition = null) + { + $this->query->where('pivot.' . $field, $op, $condition); + return $this; + } + + /** + * 预载入关联查询(数据集) + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation, Closure $closure = null, array $cache = []): void + { + $localKey = $this->localKey; + $pk = $resultSet[0]->getPk(); + $range = []; + + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (isset($result->$pk)) { + $range[] = $result->$pk; + } + } + + if (!empty($range)) { + // 查询关联数据 + $data = $this->eagerlyManyToMany([ + ['pivot.' . $localKey, 'in', $range], + ], $subRelation, $closure, $cache); + + // 关联数据封装 + foreach ($resultSet as $result) { + if (!isset($data[$result->$pk])) { + $data[$result->$pk] = []; + } + + $result->setRelation($relation, $this->resultSetBuild($data[$result->$pk], clone $this->parent)); + } + } + } + + /** + * 预载入关联查询(单个数据) + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResult(Model $result, string $relation, array $subRelation, Closure $closure = null, array $cache = []): void + { + $pk = $result->getPk(); + + if (isset($result->$pk)) { + $pk = $result->$pk; + // 查询管理数据 + $data = $this->eagerlyManyToMany([ + ['pivot.' . $this->localKey, '=', $pk], + ], $subRelation, $closure, $cache); + + // 关联数据封装 + if (!isset($data[$pk])) { + $data[$pk] = []; + } + + $result->setRelation($relation, $this->resultSetBuild($data[$pk], clone $this->parent)); + } + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return integer + */ + public function relationCount(Model $result, Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null): float + { + $pk = $result->getPk(); + + if (!isset($result->$pk)) { + return 0; + } + + $pk = $result->$pk; + + if ($closure) { + $closure($this->getClosureType($closure), $name); + } + + return $this->belongsToManyQuery($this->foreignKey, $this->localKey, [ + ['pivot.' . $this->localKey, '=', $pk], + ])->$aggregate($field); + } + + /** + * 获取关联统计子查询 + * @access public + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return string + */ + public function getRelationCountQuery(Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null): string + { + if ($closure) { + $closure($this->getClosureType($closure), $name); + } + + return $this->belongsToManyQuery($this->foreignKey, $this->localKey, [ + [ + 'pivot.' . $this->localKey, 'exp', new Raw('=' . $this->parent->db(false)->getTable() . '.' . $this->parent->getPk()), + ], + ])->fetchSql()->$aggregate($field); + } + + /** + * 多对多 关联模型预查询 + * @access protected + * @param array $where 关联预查询条件 + * @param array $subRelation 子关联 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return array + */ + protected function eagerlyManyToMany(array $where, array $subRelation = [], Closure $closure = null, array $cache = []): array + { + if ($closure) { + $closure($this->getClosureType($closure)); + } + + $withLimit = $this->query->getOptions('limit'); + if ($withLimit) { + $this->query->removeOption('limit'); + } + + // 预载入关联查询 支持嵌套预载入 + $list = $this->belongsToManyQuery($this->foreignKey, $this->localKey, $where) + ->with($subRelation) + ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null) + ->select(); + + // 组装模型数据 + $data = []; + foreach ($list as $set) { + $pivot = $this->matchPivot($set); + $key = $pivot[$this->localKey]; + + if ($withLimit && isset($data[$key]) && count($data[$key]) >= $withLimit) { + continue; + } + + $data[$key][] = $set; + } + + return $data; + } + + /** + * BELONGS TO MANY 关联查询 + * @access protected + * @param string $foreignKey 关联模型关联键 + * @param string $localKey 当前模型关联键 + * @param array $condition 关联查询条件 + * @return Query + */ + protected function belongsToManyQuery(string $foreignKey, string $localKey, array $condition = []): Query + { + // 关联查询封装 + if (empty($this->baseQuery)) { + $tableName = $this->query->getTable(); + $table = $this->pivot->db()->getTable(); + + if ($this->withoutField) { + $this->query->withoutField($this->withoutField); + } + + $fields = $this->getQueryFields($tableName); + + $this->query + ->field($fields) + ->tableField(true, $table, 'pivot', 'pivot__') + ->join([$table => 'pivot'], 'pivot.' . $foreignKey . '=' . $tableName . '.' . $this->query->getPk()) + ->where($condition); + + } + + return $this->query; + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @param array $pivot 中间表额外数据 + * @return array|Pivot + */ + public function save($data, array $pivot = []) + { + // 保存关联表/中间表数据 + return $this->attach($data, $pivot); + } + + /** + * 批量保存当前关联数据对象 + * @access public + * @param iterable $dataSet 数据集 + * @param array $pivot 中间表额外数据 + * @param bool $samePivot 额外数据是否相同 + * @return array|false + */ + public function saveAll(iterable $dataSet, array $pivot = [], bool $samePivot = false) + { + $result = []; + + foreach ($dataSet as $key => $data) { + if (!$samePivot) { + $pivotData = $pivot[$key] ?? []; + } else { + $pivotData = $pivot; + } + + $result[] = $this->attach($data, $pivotData); + } + + return empty($result) ? false : $result; + } + + /** + * 附加关联的一个中间表数据 + * @access public + * @param mixed $data 数据 可以使用数组、关联模型对象 或者 关联对象的主键 + * @param array $pivot 中间表额外数据 + * @return array|Pivot + * @throws Exception + */ + public function attach($data, array $pivot = []) + { + if (is_array($data)) { + if (key($data) === 0) { + $id = $data; + } else { + // 保存关联表数据 + $model = new $this->model; + $id = $model->insertGetId($data); + } + } elseif (is_numeric($data) || is_string($data)) { + // 根据关联表主键直接写入中间表 + $id = $data; + } elseif ($data instanceof Model) { + // 根据关联表主键直接写入中间表 + $id = $data->getKey(); + } + + if (!empty($id)) { + // 保存中间表数据 + $pivot[$this->localKey] = $this->parent->getKey(); + + $ids = (array) $id; + foreach ($ids as $id) { + $pivot[$this->foreignKey] = $id; + $this->pivot->replace() + ->exists(false) + ->data([]) + ->save($pivot); + $result[] = $this->newPivot($pivot); + } + + if (count($result) == 1) { + // 返回中间表模型对象 + $result = $result[0]; + } + + return $result; + } else { + throw new Exception('miss relation data'); + } + } + + /** + * 判断是否存在关联数据 + * @access public + * @param mixed $data 数据 可以使用关联模型对象 或者 关联对象的主键 + * @return Pivot|false + */ + public function attached($data) + { + if ($data instanceof Model) { + $id = $data->getKey(); + } else { + $id = $data; + } + + $pivot = $this->pivot + ->where($this->localKey, $this->parent->getKey()) + ->where($this->foreignKey, $id) + ->find(); + + return $pivot ?: false; + } + + /** + * 解除关联的一个中间表数据 + * @access public + * @param integer|array $data 数据 可以使用关联对象的主键 + * @param bool $relationDel 是否同时删除关联表数据 + * @return integer + */ + public function detach($data = null, bool $relationDel = false): int + { + if (is_array($data)) { + $id = $data; + } elseif (is_numeric($data) || is_string($data)) { + // 根据关联表主键直接写入中间表 + $id = $data; + } elseif ($data instanceof Model) { + // 根据关联表主键直接写入中间表 + $id = $data->getKey(); + } + + // 删除中间表数据 + $pivot = []; + $pivot[] = [$this->localKey, '=', $this->parent->getKey()]; + + if (isset($id)) { + $pivot[] = [$this->foreignKey, is_array($id) ? 'in' : '=', $id]; + } + + $result = $this->pivot->where($pivot)->delete(); + + // 删除关联表数据 + if (isset($id) && $relationDel) { + $model = $this->model; + $model::destroy($id); + } + + return $result; + } + + /** + * 数据同步 + * @access public + * @param array $ids + * @param bool $detaching + * @return array + */ + public function sync(array $ids, bool $detaching = true): array + { + $changes = [ + 'attached' => [], + 'detached' => [], + 'updated' => [], + ]; + + $current = $this->pivot + ->where($this->localKey, $this->parent->getKey()) + ->column($this->foreignKey); + + $records = []; + + foreach ($ids as $key => $value) { + if (!is_array($value)) { + $records[$value] = []; + } else { + $records[$key] = $value; + } + } + + $detach = array_diff($current, array_keys($records)); + + if ($detaching && count($detach) > 0) { + $this->detach($detach); + $changes['detached'] = $detach; + } + + foreach ($records as $id => $attributes) { + if (!in_array($id, $current)) { + $this->attach($id, $attributes); + $changes['attached'][] = $id; + } elseif (count($attributes) > 0 && $this->attach($id, $attributes)) { + $changes['updated'][] = $id; + } + } + + return $changes; + } + + /** + * 执行基础查询(仅执行一次) + * @access protected + * @return void + */ + protected function baseQuery(): void + { + if (empty($this->baseQuery)) { + $foreignKey = $this->foreignKey; + $localKey = $this->localKey; + + $this->query->filter(function ($result, $options) { + $this->matchPivot($result); + }); + + // 关联查询 + if (null === $this->parent->getKey()) { + $condition = ['pivot.' . $localKey, 'exp', new Raw('=' . $this->parent->getTable() . '.' . $this->parent->getPk())]; + } else { + $condition = ['pivot.' . $localKey, '=', $this->parent->getKey()]; + } + + $this->belongsToManyQuery($foreignKey, $localKey, [$condition]); + + $this->baseQuery = true; + } + } + +} diff --git a/vendor/topthink/think-orm/src/model/relation/HasMany.php b/vendor/topthink/think-orm/src/model/relation/HasMany.php new file mode 100644 index 0000000..4624955 --- /dev/null +++ b/vendor/topthink/think-orm/src/model/relation/HasMany.php @@ -0,0 +1,376 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\model\relation; + +use Closure; +use think\Collection; +use think\db\BaseQuery as Query; +use think\Model; +use think\model\Relation; + +/** + * 一对多关联类 + */ +class HasMany extends Relation +{ + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 当前模型主键 + */ + public function __construct(Model $parent, string $model, string $foreignKey, string $localKey) + { + $this->parent = $parent; + $this->model = $model; + $this->foreignKey = $foreignKey; + $this->localKey = $localKey; + $this->query = (new $model)->db(); + + if (get_class($parent) == $model) { + $this->selfRelation = true; + } + } + + /** + * 延迟获取关联数据 + * @access public + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包查询条件 + * @return Collection + */ + public function getRelation(array $subRelation = [], Closure $closure = null): Collection + { + if ($closure) { + $closure($this->getClosureType($closure)); + } + + return $this->query + ->where($this->foreignKey, $this->parent->{$this->localKey}) + ->relation($subRelation) + ->select() + ->setParent(clone $this->parent); + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation, Closure $closure = null, array $cache = []): void + { + $localKey = $this->localKey; + $range = []; + + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (isset($result->$localKey)) { + $range[] = $result->$localKey; + } + } + + if (!empty($range)) { + $data = $this->eagerlyOneToMany([ + [$this->foreignKey, 'in', $range], + ], $subRelation, $closure, $cache); + + // 关联数据封装 + foreach ($resultSet as $result) { + $pk = $result->$localKey; + if (!isset($data[$pk])) { + $data[$pk] = []; + } + + $result->setRelation($relation, $this->resultSetBuild($data[$pk], clone $this->parent)); + } + } + } + + /** + * 预载入关联查询 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResult(Model $result, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void + { + $localKey = $this->localKey; + + if (isset($result->$localKey)) { + $pk = $result->$localKey; + $data = $this->eagerlyOneToMany([ + [$this->foreignKey, '=', $pk], + ], $subRelation, $closure, $cache); + + // 关联数据封装 + if (!isset($data[$pk])) { + $data[$pk] = []; + } + + $result->setRelation($relation, $this->resultSetBuild($data[$pk], clone $this->parent)); + } + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return integer + */ + public function relationCount(Model $result, Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null) + { + $localKey = $this->localKey; + + if (!isset($result->$localKey)) { + return 0; + } + + if ($closure) { + $closure($this->getClosureType($closure), $name); + } + + return $this->query + ->where($this->foreignKey, '=', $result->$localKey) + ->$aggregate($field); + } + + /** + * 创建关联统计子查询 + * @access public + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return string + */ + public function getRelationCountQuery(Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null): string + { + if ($closure) { + $closure($this->getClosureType($closure), $name); + } + + return $this->query->alias($aggregate . '_table') + ->whereExp($aggregate . '_table.' . $this->foreignKey, '=' . $this->parent->getTable() . '.' . $this->localKey) + ->fetchSql() + ->$aggregate($field); + } + + /** + * 一对多 关联模型预查询 + * @access public + * @param array $where 关联预查询条件 + * @param array $subRelation 子关联 + * @param Closure $closure + * @param array $cache 关联缓存 + * @return array + */ + protected function eagerlyOneToMany(array $where, array $subRelation = [], Closure $closure = null, array $cache = []): array + { + $foreignKey = $this->foreignKey; + + $this->query->removeWhereField($this->foreignKey); + + // 预载入关联查询 支持嵌套预载入 + if ($closure) { + $this->baseQuery = true; + $closure($this->getClosureType($closure)); + } + + if ($this->withoutField) { + $this->query->withoutField($this->withoutField); + } + + $withLimit = $this->query->getOptions('limit'); + if ($withLimit) { + $this->query->removeOption('limit'); + } + + $list = $this->query + ->where($where) + ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null) + ->with($subRelation) + ->select(); + + // 组装模型数据 + $data = []; + + foreach ($list as $set) { + $key = $set->$foreignKey; + + if ($withLimit && isset($data[$key]) && count($data[$key]) >= $withLimit) { + continue; + } + + $data[$key][] = $set; + } + + return $data; + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 + * @param boolean $replace 是否自动识别更新和写入 + * @return Model|false + */ + public function save($data, bool $replace = true) + { + if ($data instanceof Model) { + $data = $data->getData(); + } + + $model = $this->make(); + + return $model->replace($replace)->save($data) ? $model : false; + } + + /** + * 创建关联对象实例 + * @param array|Model $data + * @return Model + */ + public function make($data = []): Model + { + if ($data instanceof Model) { + $data = $data->getData(); + } + + // 保存关联表数据 + $data[$this->foreignKey] = $this->parent->{$this->localKey}; + + return new $this->model($data); + } + + /** + * 批量保存当前关联数据对象 + * @access public + * @param iterable $dataSet 数据集 + * @param boolean $replace 是否自动识别更新和写入 + * @return array|false + */ + public function saveAll(iterable $dataSet, bool $replace = true) + { + $result = []; + + foreach ($dataSet as $key => $data) { + $result[] = $this->save($data, $replace); + } + + return empty($result) ? false : $result; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = 'INNER', Query $query = null): Query + { + $table = $this->query->getTable(); + + $model = class_basename($this->parent); + $relation = class_basename($this->model); + + if ('*' != $id) { + $id = $relation . '.' . (new $this->model)->getPk(); + } + + $softDelete = $this->query->getOptions('soft_delete'); + $query = $query ?: $this->parent->db()->alias($model); + + return $query->field($model . '.*') + ->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey, $joinType) + ->when($softDelete, function ($query) use ($softDelete, $relation) { + $query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null); + }) + ->group($relation . '.' . $this->foreignKey) + ->having('count(' . $id . ')' . $operator . $count); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public function hasWhere($where = [], $fields = null, string $joinType = '', Query $query = null): Query + { + $table = $this->query->getTable(); + $model = class_basename($this->parent); + $relation = class_basename($this->model); + + if (is_array($where)) { + $this->getQueryWhere($where, $relation); + } elseif ($where instanceof Query) { + $where->via($relation); + } elseif ($where instanceof Closure) { + $where($this->query->via($relation)); + $where = $this->query; + } + + $fields = $this->getRelationQueryFields($fields, $model); + $softDelete = $this->query->getOptions('soft_delete'); + $query = $query ?: $this->parent->db(); + + return $query->alias($model) + ->group($model . '.' . $this->localKey) + ->field($fields) + ->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey, $joinType) + ->when($softDelete, function ($query) use ($softDelete, $relation) { + $query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null); + }) + ->where($where); + } + + /** + * 执行基础查询(仅执行一次) + * @access protected + * @return void + */ + protected function baseQuery(): void + { + if (empty($this->baseQuery)) { + if (isset($this->parent->{$this->localKey})) { + // 关联查询带入关联条件 + $this->query->where($this->foreignKey, '=', $this->parent->{$this->localKey}); + } + + $this->baseQuery = true; + } + } + +} diff --git a/vendor/topthink/think-orm/src/model/relation/HasManyThrough.php b/vendor/topthink/think-orm/src/model/relation/HasManyThrough.php new file mode 100644 index 0000000..4f14399 --- /dev/null +++ b/vendor/topthink/think-orm/src/model/relation/HasManyThrough.php @@ -0,0 +1,395 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use Closure; +use think\Collection; +use think\db\BaseQuery as Query; +use think\helper\Str; +use think\Model; +use think\model\Relation; + +/** + * 远程一对多关联类 + */ +class HasManyThrough extends Relation +{ + /** + * 中间关联表外键 + * @var string + */ + protected $throughKey; + + /** + * 中间主键 + * @var string + */ + protected $throughPk; + + /** + * 中间表查询对象 + * @var Query + */ + protected $through; + + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 关联模型名 + * @param string $through 中间模型名 + * @param string $foreignKey 关联外键 + * @param string $throughKey 中间关联外键 + * @param string $localKey 当前模型主键 + * @param string $throughPk 中间模型主键 + */ + public function __construct(Model $parent, string $model, string $through, string $foreignKey, string $throughKey, string $localKey, string $throughPk) + { + $this->parent = $parent; + $this->model = $model; + $this->through = (new $through)->db(); + $this->foreignKey = $foreignKey; + $this->throughKey = $throughKey; + $this->localKey = $localKey; + $this->throughPk = $throughPk; + $this->query = (new $model)->db(); + } + + /** + * 延迟获取关联数据 + * @access public + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包查询条件 + * @return Collection + */ + public function getRelation(array $subRelation = [], Closure $closure = null) + { + if ($closure) { + $closure($this->getClosureType($closure)); + } + + $this->baseQuery(); + + return $this->query->relation($subRelation) + ->select() + ->setParent(clone $this->parent); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '', Query $query = null): Query + { + $model = Str::snake(class_basename($this->parent)); + $throughTable = $this->through->getTable(); + $pk = $this->throughPk; + $throughKey = $this->throughKey; + $relation = new $this->model; + $relationTable = $relation->getTable(); + $softDelete = $this->query->getOptions('soft_delete'); + + if ('*' != $id) { + $id = $relationTable . '.' . $relation->getPk(); + } + $query = $query ?: $this->parent->db()->alias($model); + + return $query->field($model . '.*') + ->join($throughTable, $throughTable . '.' . $this->foreignKey . '=' . $model . '.' . $this->localKey) + ->join($relationTable, $relationTable . '.' . $throughKey . '=' . $throughTable . '.' . $this->throughPk) + ->when($softDelete, function ($query) use ($softDelete, $relationTable) { + $query->where($relationTable . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null); + }) + ->group($relationTable . '.' . $this->throughKey) + ->having('count(' . $id . ')' . $operator . $count); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public function hasWhere($where = [], $fields = null, $joinType = '', Query $query = null): Query + { + $model = Str::snake(class_basename($this->parent)); + $throughTable = $this->through->getTable(); + $pk = $this->throughPk; + $throughKey = $this->throughKey; + $modelTable = (new $this->model)->getTable(); + + if (is_array($where)) { + $this->getQueryWhere($where, $modelTable); + } elseif ($where instanceof Query) { + $where->via($modelTable); + } elseif ($where instanceof Closure) { + $where($this->query->via($modelTable)); + $where = $this->query; + } + + $fields = $this->getRelationQueryFields($fields, $model); + $softDelete = $this->query->getOptions('soft_delete'); + $query = $query ?: $this->parent->db(); + + return $query->alias($model) + ->join($throughTable, $throughTable . '.' . $this->foreignKey . '=' . $model . '.' . $this->localKey) + ->join($modelTable, $modelTable . '.' . $throughKey . '=' . $throughTable . '.' . $this->throughPk, $joinType) + ->when($softDelete, function ($query) use ($softDelete, $modelTable) { + $query->where($modelTable . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null); + }) + ->group($modelTable . '.' . $this->throughKey) + ->where($where) + ->field($fields); + } + + /** + * 预载入关联查询(数据集) + * @access protected + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + $range = []; + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (isset($result->$localKey)) { + $range[] = $result->$localKey; + } + } + + if (!empty($range)) { + $this->query->removeWhereField($foreignKey); + + $data = $this->eagerlyWhere([ + [$this->foreignKey, 'in', $range], + ], $foreignKey, $subRelation, $closure, $cache); + + // 关联数据封装 + foreach ($resultSet as $result) { + $pk = $result->$localKey; + if (!isset($data[$pk])) { + $data[$pk] = []; + } + + // 设置关联属性 + $result->setRelation($relation, $this->resultSetBuild($data[$pk], clone $this->parent)); + } + } + } + + /** + * 预载入关联查询(数据) + * @access protected + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResult(Model $result, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + $pk = $result->$localKey; + + $this->query->removeWhereField($foreignKey); + + $data = $this->eagerlyWhere([ + [$foreignKey, '=', $pk], + ], $foreignKey, $subRelation, $closure, $cache); + + // 关联数据封装 + if (!isset($data[$pk])) { + $data[$pk] = []; + } + + $result->setRelation($relation, $this->resultSetBuild($data[$pk], clone $this->parent)); + } + + /** + * 关联模型预查询 + * @access public + * @param array $where 关联预查询条件 + * @param string $key 关联键名 + * @param array $subRelation 子关联 + * @param Closure $closure + * @param array $cache 关联缓存 + * @return array + */ + protected function eagerlyWhere(array $where, string $key, array $subRelation = [], Closure $closure = null, array $cache = []): array + { + // 预载入关联查询 支持嵌套预载入 + $throughList = $this->through->where($where)->select(); + $keys = $throughList->column($this->throughPk, $this->throughPk); + + if ($closure) { + $this->baseQuery = true; + $closure($this->getClosureType($closure)); + } + + $throughKey = $this->throughKey; + + if ($this->baseQuery) { + $throughKey = Str::snake(class_basename($this->model)) . "." . $this->throughKey; + } + + $withLimit = $this->query->getOptions('limit'); + if ($withLimit) { + $this->query->removeOption('limit'); + } + + $list = $this->query + ->where($throughKey, 'in', $keys) + ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null) + ->select(); + + // 组装模型数据 + $data = []; + $keys = $throughList->column($this->foreignKey, $this->throughPk); + + foreach ($list as $set) { + $key = $keys[$set->{$this->throughKey}]; + + if ($withLimit && isset($data[$key]) && count($data[$key]) >= $withLimit) { + continue; + } + + $data[$key][] = $set; + } + + return $data; + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return mixed + */ + public function relationCount(Model $result, Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null) + { + $localKey = $this->localKey; + + if (!isset($result->$localKey)) { + return 0; + } + + if ($closure) { + $closure($this->getClosureType($closure), $name); + } + + $alias = Str::snake(class_basename($this->model)); + $throughTable = $this->through->getTable(); + $pk = $this->throughPk; + $throughKey = $this->throughKey; + $modelTable = $this->parent->getTable(); + + if (false === strpos($field, '.')) { + $field = $alias . '.' . $field; + } + + return $this->query + ->alias($alias) + ->join($throughTable, $throughTable . '.' . $pk . '=' . $alias . '.' . $throughKey) + ->join($modelTable, $modelTable . '.' . $this->localKey . '=' . $throughTable . '.' . $this->foreignKey) + ->where($throughTable . '.' . $this->foreignKey, $result->$localKey) + ->$aggregate($field); + } + + /** + * 创建关联统计子查询 + * @access public + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return string + */ + public function getRelationCountQuery(Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null): string + { + if ($closure) { + $closure($this->getClosureType($closure), $name); + } + + $alias = Str::snake(class_basename($this->model)); + $throughTable = $this->through->getTable(); + $pk = $this->throughPk; + $throughKey = $this->throughKey; + $modelTable = $this->parent->getTable(); + + if (false === strpos($field, '.')) { + $field = $alias . '.' . $field; + } + + return $this->query + ->alias($alias) + ->join($throughTable, $throughTable . '.' . $pk . '=' . $alias . '.' . $throughKey) + ->join($modelTable, $modelTable . '.' . $this->localKey . '=' . $throughTable . '.' . $this->foreignKey) + ->whereExp($throughTable . '.' . $this->foreignKey, '=' . $this->parent->getTable() . '.' . $this->localKey) + ->fetchSql() + ->$aggregate($field); + } + + /** + * 执行基础查询(仅执行一次) + * @access protected + * @return void + */ + protected function baseQuery(): void + { + if (empty($this->baseQuery) && $this->parent->getData()) { + $alias = Str::snake(class_basename($this->model)); + $throughTable = $this->through->getTable(); + $pk = $this->throughPk; + $throughKey = $this->throughKey; + $modelTable = $this->parent->getTable(); + + if ($this->withoutField) { + $this->query->withoutField($this->withoutField); + } + + $fields = $this->getQueryFields($alias); + + $this->query + ->field($fields) + ->alias($alias) + ->join($throughTable, $throughTable . '.' . $pk . '=' . $alias . '.' . $throughKey) + ->join($modelTable, $modelTable . '.' . $this->localKey . '=' . $throughTable . '.' . $this->foreignKey) + ->where($throughTable . '.' . $this->foreignKey, $this->parent->{$this->localKey}); + + $this->baseQuery = true; + } + } + +} diff --git a/vendor/topthink/think-orm/src/model/relation/HasOne.php b/vendor/topthink/think-orm/src/model/relation/HasOne.php new file mode 100644 index 0000000..b941f6e --- /dev/null +++ b/vendor/topthink/think-orm/src/model/relation/HasOne.php @@ -0,0 +1,304 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\model\relation; + +use Closure; +use think\db\BaseQuery as Query; +use think\Model; + +/** + * HasOne 关联类 + */ +class HasOne extends OneToOne +{ + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 当前模型主键 + */ + public function __construct(Model $parent, string $model, string $foreignKey, string $localKey) + { + $this->parent = $parent; + $this->model = $model; + $this->foreignKey = $foreignKey; + $this->localKey = $localKey; + $this->query = (new $model)->db(); + + if (get_class($parent) == $model) { + $this->selfRelation = true; + } + } + + /** + * 延迟获取关联数据 + * @access public + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包查询条件 + * @return Model + */ + public function getRelation(array $subRelation = [], Closure $closure = null) + { + $localKey = $this->localKey; + + if ($closure) { + $closure($this->getClosureType($closure)); + } + + // 判断关联类型执行查询 + $relationModel = $this->query + ->removeWhereField($this->foreignKey) + ->where($this->foreignKey, $this->parent->$localKey) + ->relation($subRelation) + ->find(); + + if ($relationModel) { + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($this->parent, $relationModel); + } + + $relationModel->setParent(clone $this->parent); + } else { + $relationModel = $this->getDefaultModel(); + } + + return $relationModel; + } + + /** + * 创建关联统计子查询 + * @access public + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return string + */ + public function getRelationCountQuery(Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null): string + { + if ($closure) { + $closure($this->getClosureType($closure), $name); + } + + return $this->query + ->whereExp($this->foreignKey, '=' . $this->parent->getTable() . '.' . $this->localKey) + ->fetchSql() + ->$aggregate($field); + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return integer + */ + public function relationCount(Model $result, Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null) + { + $localKey = $this->localKey; + + if (!isset($result->$localKey)) { + return 0; + } + + if ($closure) { + $closure($this->getClosureType($closure), $name); + } + + return $this->query + ->where($this->foreignKey, '=', $result->$localKey) + ->$aggregate($field); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '', Query $query = null): Query + { + $table = $this->query->getTable(); + $model = class_basename($this->parent); + $relation = class_basename($this->model); + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + $softDelete = $this->query->getOptions('soft_delete'); + $query = $query ?: $this->parent->db()->alias($model); + + return $query->whereExists(function ($query) use ($table, $model, $relation, $localKey, $foreignKey, $softDelete) { + $query->table([$table => $relation]) + ->field($relation . '.' . $foreignKey) + ->whereExp($model . '.' . $localKey, '=' . $relation . '.' . $foreignKey) + ->when($softDelete, function ($query) use ($softDelete, $relation) { + $query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null); + }); + }); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public function hasWhere($where = [], $fields = null, string $joinType = '', Query $query = null): Query + { + $table = $this->query->getTable(); + $model = class_basename($this->parent); + $relation = class_basename($this->model); + + if (is_array($where)) { + $this->getQueryWhere($where, $relation); + } elseif ($where instanceof Query) { + $where->via($relation); + } elseif ($where instanceof Closure) { + $where($this->query->via($relation)); + $where = $this->query; + } + + $fields = $this->getRelationQueryFields($fields, $model); + $softDelete = $this->query->getOptions('soft_delete'); + $query = $query ?: $this->parent->db(); + + return $query->alias($model) + ->field($fields) + ->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey, $joinType ?: $this->joinType) + ->when($softDelete, function ($query) use ($softDelete, $relation) { + $query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null); + }) + ->where($where); + } + + /** + * 预载入关联查询(数据集) + * @access protected + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + protected function eagerlySet(array &$resultSet, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + $range = []; + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (isset($result->$localKey)) { + $range[] = $result->$localKey; + } + } + + if (!empty($range)) { + $this->query->removeWhereField($foreignKey); + + $data = $this->eagerlyWhere([ + [$foreignKey, 'in', $range], + ], $foreignKey, $subRelation, $closure, $cache); + + // 关联数据封装 + foreach ($resultSet as $result) { + // 关联模型 + if (!isset($data[$result->$localKey])) { + $relationModel = $this->getDefaultModel(); + } else { + $relationModel = $data[$result->$localKey]; + $relationModel->setParent(clone $result); + $relationModel->exists(true); + } + // 设置关联属性 + $result->setRelation($relation, $relationModel); + + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($result, $relationModel); + $result->hidden([$relation], true); + } + } + } + } + + /** + * 预载入关联查询(数据) + * @access protected + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + protected function eagerlyOne(Model $result, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + $this->query->removeWhereField($foreignKey); + + $data = $this->eagerlyWhere([ + [$foreignKey, '=', $result->$localKey], + ], $foreignKey, $subRelation, $closure, $cache); + + // 关联模型 + if (!isset($data[$result->$localKey])) { + $relationModel = $this->getDefaultModel(); + } else { + $relationModel = $data[$result->$localKey]; + $relationModel->setParent(clone $result); + $relationModel->exists(true); + } + + // 设置关联属性 + $result->setRelation($relation, $relationModel); + + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($result, $relationModel); + $result->hidden([$relation], true); + } + } + + /** + * 执行基础查询(仅执行一次) + * @access protected + * @return void + */ + protected function baseQuery(): void + { + if (empty($this->baseQuery)) { + if (isset($this->parent->{$this->localKey})) { + // 关联查询带入关联条件 + $this->query->where($this->foreignKey, '=', $this->parent->{$this->localKey}); + } + + $this->baseQuery = true; + } + } +} diff --git a/vendor/topthink/think-orm/src/model/relation/HasOneThrough.php b/vendor/topthink/think-orm/src/model/relation/HasOneThrough.php new file mode 100644 index 0000000..2734c13 --- /dev/null +++ b/vendor/topthink/think-orm/src/model/relation/HasOneThrough.php @@ -0,0 +1,164 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use Closure; +use think\Model; + +/** + * 远程一对一关联类 + */ +class HasOneThrough extends HasManyThrough +{ + + /** + * 延迟获取关联数据 + * @access public + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包查询条件 + * @return Model + */ + public function getRelation(array $subRelation = [], Closure $closure = null) + { + if ($closure) { + $closure($this->getClosureType($closure)); + } + + $this->baseQuery(); + + $relationModel = $this->query->relation($subRelation)->find(); + + if ($relationModel) { + $relationModel->setParent(clone $this->parent); + } else { + $relationModel = $this->getDefaultModel(); + } + + return $relationModel; + } + + /** + * 预载入关联查询(数据集) + * @access protected + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + $range = []; + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (isset($result->$localKey)) { + $range[] = $result->$localKey; + } + } + + if (!empty($range)) { + $this->query->removeWhereField($foreignKey); + + $data = $this->eagerlyWhere([ + [$this->foreignKey, 'in', $range], + ], $foreignKey, $subRelation, $closure, $cache); + + // 关联数据封装 + foreach ($resultSet as $result) { + // 关联模型 + if (!isset($data[$result->$localKey])) { + $relationModel = $this->getDefaultModel(); + } else { + $relationModel = $data[$result->$localKey]; + $relationModel->setParent(clone $result); + $relationModel->exists(true); + } + + // 设置关联属性 + $result->setRelation($relation, $relationModel); + } + } + } + + /** + * 预载入关联查询(数据) + * @access protected + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResult(Model $result, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + $this->query->removeWhereField($foreignKey); + + $data = $this->eagerlyWhere([ + [$foreignKey, '=', $result->$localKey], + ], $foreignKey, $subRelation, $closure, $cache); + + // 关联模型 + if (!isset($data[$result->$localKey])) { + $relationModel = $this->getDefaultModel(); + } else { + $relationModel = $data[$result->$localKey]; + $relationModel->setParent(clone $result); + $relationModel->exists(true); + } + + $result->setRelation($relation, $relationModel); + } + + /** + * 关联模型预查询 + * @access public + * @param array $where 关联预查询条件 + * @param string $key 关联键名 + * @param array $subRelation 子关联 + * @param Closure $closure + * @param array $cache 关联缓存 + * @return array + */ + protected function eagerlyWhere(array $where, string $key, array $subRelation = [], Closure $closure = null, array $cache = []): array + { + // 预载入关联查询 支持嵌套预载入 + $keys = $this->through->where($where)->column($this->throughPk, $this->foreignKey); + + if ($closure) { + $closure($this->getClosureType($closure)); + } + + $list = $this->query + ->where($this->throughKey, 'in', $keys) + ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null) + ->select(); + + // 组装模型数据 + $data = []; + $keys = array_flip($keys); + + foreach ($list as $set) { + $data[$keys[$set->{$this->throughKey}]] = $set; + } + + return $data; + } + +} diff --git a/vendor/topthink/think-orm/src/model/relation/MorphMany.php b/vendor/topthink/think-orm/src/model/relation/MorphMany.php new file mode 100644 index 0000000..ed0dd40 --- /dev/null +++ b/vendor/topthink/think-orm/src/model/relation/MorphMany.php @@ -0,0 +1,385 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use Closure; +use think\Collection; +use think\db\BaseQuery as Query; +use think\db\exception\DbException as Exception; +use think\Model; +use think\model\Relation; + +/** + * 多态一对多关联 + */ +class MorphMany extends Relation +{ + + /** + * 多态关联外键 + * @var string + */ + protected $morphKey; + + /** + * 多态字段名 + * @var string + */ + protected $morphType; + + /** + * 多态类型 + * @var string + */ + protected $type; + + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $morphKey 关联外键 + * @param string $morphType 多态字段名 + * @param string $type 多态类型 + */ + public function __construct(Model $parent, string $model, string $morphKey, string $morphType, string $type) + { + $this->parent = $parent; + $this->model = $model; + $this->type = $type; + $this->morphKey = $morphKey; + $this->morphType = $morphType; + $this->query = (new $model)->db(); + } + + /** + * 延迟获取关联数据 + * @access public + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包查询条件 + * @return Collection + */ + public function getRelation(array $subRelation = [], Closure $closure = null): Collection + { + if ($closure) { + $closure($this->getClosureType($closure)); + } + + $this->baseQuery(); + + return $this->query->relation($subRelation) + ->select() + ->setParent(clone $this->parent); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '', Query $query = null) + { + throw new Exception('relation not support: has'); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public function hasWhere($where = [], $fields = null, string $joinType = '', Query $query = null) + { + throw new Exception('relation not support: hasWhere'); + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation, Closure $closure = null, array $cache = []): void + { + $morphType = $this->morphType; + $morphKey = $this->morphKey; + $type = $this->type; + $range = []; + + foreach ($resultSet as $result) { + $pk = $result->getPk(); + // 获取关联外键列表 + if (isset($result->$pk)) { + $range[] = $result->$pk; + } + } + + if (!empty($range)) { + $where = [ + [$morphKey, 'in', $range], + [$morphType, '=', $type], + ]; + $data = $this->eagerlyMorphToMany($where, $subRelation, $closure, $cache); + + // 关联数据封装 + foreach ($resultSet as $result) { + if (!isset($data[$result->$pk])) { + $data[$result->$pk] = []; + } + + $result->setRelation($relation, $this->resultSetBuild($data[$result->$pk], clone $this->parent)); + } + } + } + + /** + * 预载入关联查询 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResult(Model $result, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void + { + $pk = $result->getPk(); + + if (isset($result->$pk)) { + $key = $result->$pk; + $data = $this->eagerlyMorphToMany([ + [$this->morphKey, '=', $key], + [$this->morphType, '=', $this->type], + ], $subRelation, $closure, $cache); + + if (!isset($data[$key])) { + $data[$key] = []; + } + + $result->setRelation($relation, $this->resultSetBuild($data[$key], clone $this->parent)); + } + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return mixed + */ + public function relationCount(Model $result, Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null) + { + $pk = $result->getPk(); + + if (!isset($result->$pk)) { + return 0; + } + + if ($closure) { + $closure($this->getClosureType($closure), $name); + } + + return $this->query + ->where([ + [$this->morphKey, '=', $result->$pk], + [$this->morphType, '=', $this->type], + ]) + ->$aggregate($field); + } + + /** + * 获取关联统计子查询 + * @access public + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return string + */ + public function getRelationCountQuery(Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null): string + { + if ($closure) { + $closure($this->getClosureType($closure), $name); + } + + return $this->query + ->whereExp($this->morphKey, '=' . $this->parent->getTable() . '.' . $this->parent->getPk()) + ->where($this->morphType, '=', $this->type) + ->fetchSql() + ->$aggregate($field); + } + + /** + * 多态一对多 关联模型预查询 + * @access protected + * @param array $where 关联预查询条件 + * @param array $subRelation 子关联 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return array + */ + protected function eagerlyMorphToMany(array $where, array $subRelation = [], Closure $closure = null, array $cache = []): array + { + // 预载入关联查询 支持嵌套预载入 + $this->query->removeOption('where'); + + if ($closure) { + $this->baseQuery = true; + $closure($this->getClosureType($closure)); + } + + $withLimit = $this->query->getOptions('limit'); + if ($withLimit) { + $this->query->removeOption('limit'); + } + + $list = $this->query + ->where($where) + ->with($subRelation) + ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null) + ->select(); + $morphKey = $this->morphKey; + + // 组装模型数据 + $data = []; + foreach ($list as $set) { + $key = $set->$morphKey; + + if ($withLimit && isset($data[$key]) && count($data[$key]) >= $withLimit) { + continue; + } + + $data[$key][] = $set; + } + + return $data; + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 + * @param bool $replace 是否自动识别更新和写入 + * @return Model|false + */ + public function save($data, bool $replace = true) + { + if ($data instanceof Model) { + $data = $data->getData(); + } + + $model = $this->make(); + + return $model->replace($replace)->save($data) ? $model : false; + } + + /** + * 创建关联对象实例 + * @param array|Model $data + * @return Model + */ + public function make($data = []): Model + { + if ($data instanceof Model) { + $data = $data->getData(); + } + + // 保存关联表数据 + $pk = $this->parent->getPk(); + + $data[$this->morphKey] = $this->parent->$pk; + $data[$this->morphType] = $this->type; + + return new $this->model($data); + } + + /** + * 批量保存当前关联数据对象 + * @access public + * @param iterable $dataSet 数据集 + * @param boolean $replace 是否自动识别更新和写入 + * @return array|false + */ + public function saveAll(iterable $dataSet, bool $replace = true) + { + $result = []; + + foreach ($dataSet as $key => $data) { + $result[] = $this->save($data, $replace); + } + + return empty($result) ? false : $result; + } + + /** + * 获取多态关联外键 + * @return string + */ + public function getMorphKey() + { + return $this->morphKey; + } + + /** + * 获取多态字段名 + * @return string + */ + public function getMorphType() + { + return $this->morphType; + } + + /** + * 获取多态类型 + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * 执行基础查询(仅执行一次) + * @access protected + * @return void + */ + protected function baseQuery(): void + { + if (empty($this->baseQuery) && $this->parent->getData()) { + $pk = $this->parent->getPk(); + + $this->query->where([ + [$this->morphKey, '=', $this->parent->$pk], + [$this->morphType, '=', $this->type], + ]); + + $this->baseQuery = true; + } + } + +} diff --git a/vendor/topthink/think-orm/src/model/relation/MorphOne.php b/vendor/topthink/think-orm/src/model/relation/MorphOne.php new file mode 100644 index 0000000..d63966f --- /dev/null +++ b/vendor/topthink/think-orm/src/model/relation/MorphOne.php @@ -0,0 +1,352 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use Closure; +use think\db\BaseQuery as Query; +use think\db\exception\DbException as Exception; +use think\Model; +use think\model\Relation; + +/** + * 多态一对一关联类 + */ +class MorphOne extends Relation +{ + /** + * 多态关联外键 + * @var string + */ + protected $morphKey; + + /** + * 多态字段 + * @var string + */ + protected $morphType; + + /** + * 多态类型 + * @var string + */ + protected $type; + + /** + * 绑定的关联属性 + * @var array + */ + protected $bindAttr = []; + + /** + * 构造函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $morphKey 关联外键 + * @param string $morphType 多态字段名 + * @param string $type 多态类型 + */ + public function __construct(Model $parent, string $model, string $morphKey, string $morphType, string $type) + { + $this->parent = $parent; + $this->model = $model; + $this->type = $type; + $this->morphKey = $morphKey; + $this->morphType = $morphType; + $this->query = (new $model)->db(); + } + + /** + * 延迟获取关联数据 + * @access public + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包查询条件 + * @return Model + */ + public function getRelation(array $subRelation = [], Closure $closure = null) + { + if ($closure) { + $closure($this->getClosureType($closure)); + } + + $this->baseQuery(); + + $relationModel = $this->query->relation($subRelation)->find(); + + if ($relationModel) { + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($this->parent, $relationModel); + } + + $relationModel->setParent(clone $this->parent); + } else { + $relationModel = $this->getDefaultModel(); + } + + return $relationModel; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '', Query $query = null) + { + return $this->parent; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public function hasWhere($where = [], $fields = null, string $joinType = '', Query $query = null) + { + throw new Exception('relation not support: hasWhere'); + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation, Closure $closure = null, array $cache = []): void + { + $morphType = $this->morphType; + $morphKey = $this->morphKey; + $type = $this->type; + $range = []; + + foreach ($resultSet as $result) { + $pk = $result->getPk(); + // 获取关联外键列表 + if (isset($result->$pk)) { + $range[] = $result->$pk; + } + } + + if (!empty($range)) { + $data = $this->eagerlyMorphToOne([ + [$morphKey, 'in', $range], + [$morphType, '=', $type], + ], $subRelation, $closure, $cache); + + // 关联数据封装 + foreach ($resultSet as $result) { + if (!isset($data[$result->$pk])) { + $relationModel = $this->getDefaultModel(); + } else { + $relationModel = $data[$result->$pk]; + $relationModel->setParent(clone $result); + $relationModel->exists(true); + } + + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($result, $relationModel); + } else { + // 设置关联属性 + $result->setRelation($relation, $relationModel); + } + } + } + } + + /** + * 预载入关联查询 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResult(Model $result, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void + { + $pk = $result->getPk(); + + if (isset($result->$pk)) { + $pk = $result->$pk; + $data = $this->eagerlyMorphToOne([ + [$this->morphKey, '=', $pk], + [$this->morphType, '=', $this->type], + ], $subRelation, $closure, $cache); + + if (isset($data[$pk])) { + $relationModel = $data[$pk]; + $relationModel->setParent(clone $result); + $relationModel->exists(true); + } else { + $relationModel = $this->getDefaultModel(); + } + + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($result, $relationModel); + } else { + // 设置关联属性 + $result->setRelation($relation, $relationModel); + } + } + } + + /** + * 多态一对一 关联模型预查询 + * @access protected + * @param array $where 关联预查询条件 + * @param array $subRelation 子关联 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return array + */ + protected function eagerlyMorphToOne(array $where, array $subRelation = [], $closure = null, array $cache = []): array + { + // 预载入关联查询 支持嵌套预载入 + if ($closure) { + $this->baseQuery = true; + $closure($this->getClosureType($closure)); + } + + $list = $this->query + ->where($where) + ->with($subRelation) + ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null) + ->select(); + $morphKey = $this->morphKey; + + // 组装模型数据 + $data = []; + + foreach ($list as $set) { + $data[$set->$morphKey] = $set; + } + + return $data; + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 + * @param boolean $replace 是否自动识别更新和写入 + * @return Model|false + */ + public function save($data, bool $replace = true) + { + if ($data instanceof Model) { + $data = $data->getData(); + } + + $model = $this->make(); + return $model->replace($replace)->save($data) ? $model : false; + } + + /** + * 创建关联对象实例 + * @param array|Model $data + * @return Model + */ + public function make($data = []): Model + { + if ($data instanceof Model) { + $data = $data->getData(); + } + + // 保存关联表数据 + $pk = $this->parent->getPk(); + + $data[$this->morphKey] = $this->parent->$pk; + $data[$this->morphType] = $this->type; + + return new $this->model($data); + } + + /** + * 执行基础查询(进执行一次) + * @access protected + * @return void + */ + protected function baseQuery(): void + { + if (empty($this->baseQuery) && $this->parent->getData()) { + $pk = $this->parent->getPk(); + + $this->query->where([ + [$this->morphKey, '=', $this->parent->$pk], + [$this->morphType, '=', $this->type], + ]); + $this->baseQuery = true; + } + } + + /** + * 绑定关联表的属性到父模型属性 + * @access public + * @param array $attr 要绑定的属性列表 + * @return $this + */ + public function bind(array $attr) + { + $this->bindAttr = $attr; + + return $this; + } + + /** + * 获取绑定属性 + * @access public + * @return array + */ + public function getBindAttr(): array + { + return $this->bindAttr; + } + + /** + * 绑定关联属性到父模型 + * @access protected + * @param Model $result 父模型对象 + * @param Model $model 关联模型对象 + * @return void + * @throws Exception + */ + protected function bindAttr(Model $result, Model $model = null): void + { + foreach ($this->bindAttr as $key => $attr) { + $key = is_numeric($key) ? $attr : $key; + $value = $result->getOrigin($key); + + if (!is_null($value)) { + throw new Exception('bind attr has exists:' . $key); + } + + $result->setAttr($key, $model ? $model->$attr : null); + } + } +} diff --git a/vendor/topthink/think-orm/src/model/relation/MorphTo.php b/vendor/topthink/think-orm/src/model/relation/MorphTo.php new file mode 100644 index 0000000..d24c082 --- /dev/null +++ b/vendor/topthink/think-orm/src/model/relation/MorphTo.php @@ -0,0 +1,382 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use Closure; +use think\db\exception\DbException as Exception; +use think\db\Query; +use think\helper\Str; +use think\Model; +use think\model\Relation; + +/** + * 多态关联类 + */ +class MorphTo extends Relation +{ + /** + * 多态关联外键 + * @var string + */ + protected $morphKey; + + /** + * 多态字段 + * @var string + */ + protected $morphType; + + /** + * 多态别名 + * @var array + */ + protected $alias = []; + + /** + * 关联名 + * @var string + */ + protected $relation; + + protected $queryCaller = []; + + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $morphType 多态字段名 + * @param string $morphKey 外键名 + * @param array $alias 多态别名定义 + * @param ?string $relation 关联名 + */ + public function __construct(Model $parent, string $morphType, string $morphKey, array $alias = [], string $relation = null) + { + $this->parent = $parent; + $this->morphType = $morphType; + $this->morphKey = $morphKey; + $this->alias = $alias; + $this->relation = $relation; + } + + /** + * 获取当前的关联模型类的实例 + * @access public + * @return Model + */ + public function getModel(): Model + { + $morphType = $this->morphType; + $model = $this->parseModel($this->parent->$morphType); + + return (new $model); + } + + /** + * 延迟获取关联数据 + * @access public + * @param array $subRelation 子关联名 + * @param ?Closure $closure 闭包查询条件 + * @return Model + */ + public function getRelation(array $subRelation = [], Closure $closure = null) + { + $morphKey = $this->morphKey; + $morphType = $this->morphType; + + // 多态模型 + $model = $this->parseModel($this->parent->$morphType); + + // 主键数据 + $pk = $this->parent->$morphKey; + + $relationModel = $this->buildQuery((new $model)->relation($subRelation))->find($pk); + + if ($relationModel) { + $relationModel->setParent(clone $this->parent); + } + + return $relationModel; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '', Query $query = null) + { + return $this->parent; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @param string $joinType JOIN类型 + * @param ?Query $query Query对象 + * @return Query + */ + public function hasWhere($where = [], $fields = null, string $joinType = '', Query $query = null) + { + $alias = class_basename($this->parent); + + $types = $this->parent->distinct()->column($this->morphType); + + $query = $query ?: $this->parent->db(); + + return $query->alias($alias) + ->where(function (Query $query) use ($types, $where, $alias) { + foreach ($types as $type) { + if ($type) { + $query->whereExists(function (Query $query) use ($type, $where, $alias) { + $class = $this->parseModel($type); + /** @var Model $model */ + $model = new $class(); + + $table = $model->getTable(); + $query + ->table($table) + ->where($alias . '.' . $this->morphType, $type) + ->whereRaw("`{$alias}`.`{$this->morphKey}`=`{$table}`.`{$model->getPk()}`") + ->where($where); + }, 'OR'); + } + } + }); + } + + /** + * 解析模型的完整命名空间 + * @access protected + * @param string $model 模型名(或者完整类名) + * @return Model + */ + protected function parseModel(string $model): string + { + if (isset($this->alias[$model])) { + $model = $this->alias[$model]; + } + + if (false === strpos($model, '\\')) { + $path = explode('\\', get_class($this->parent)); + array_pop($path); + array_push($path, Str::studly($model)); + $model = implode('\\', $path); + } + + return $model; + } + + /** + * 设置多态别名 + * @access public + * @param array $alias 别名定义 + * @return $this + */ + public function setAlias(array $alias) + { + $this->alias = $alias; + + return $this; + } + + /** + * 移除关联查询参数 + * @access public + * @return $this + */ + public function removeOption(string $option = '') + { + return $this; + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param ?Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + * @throws Exception + */ + public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation, Closure $closure = null, array $cache = []): void + { + $morphKey = $this->morphKey; + $morphType = $this->morphType; + $range = []; + + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (!empty($result->$morphKey)) { + $range[$result->$morphType][] = $result->$morphKey; + } + } + + if (!empty($range)) { + + foreach ($range as $key => $val) { + // 多态类型映射 + $model = $this->parseModel($key); + $obj = new $model; + if (!\is_null($closure)) { + $obj = $closure($obj); + } + $pk = $obj->getPk(); + $list = $obj->with($subRelation) + ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null) + ->select($val); + $data = []; + + foreach ($list as $k => $vo) { + $data[$vo->$pk] = $vo; + } + + foreach ($resultSet as $result) { + if ($key == $result->$morphType) { + // 关联模型 + if (!isset($data[$result->$morphKey])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$morphKey]; + $relationModel->setParent(clone $result); + $relationModel->exists(true); + } + + $result->setRelation($relation, $relationModel); + } + } + } + } + } + + /** + * 预载入关联查询 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param ?Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResult(Model $result, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void + { + // 多态类型映射 + $model = $this->parseModel($result->{$this->morphType}); + + $this->eagerlyMorphToOne($model, $relation, $result, $subRelation, $cache); + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param ?Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @return integer + */ + public function relationCount(Model $result, Closure $closure = null, string $aggregate = 'count', string $field = '*') + { + } + + /** + * 多态MorphTo 关联模型预查询 + * @access protected + * @param string $model 关联模型对象 + * @param string $relation 关联名 + * @param Model $result + * @param array $subRelation 子关联 + * @param array $cache 关联缓存 + * @return void + */ + protected function eagerlyMorphToOne(string $model, string $relation, Model $result, array $subRelation = [], array $cache = []): void + { + // 预载入关联查询 支持嵌套预载入 + $pk = $this->parent->{$this->morphKey}; + + $data = null; + + if (\class_exists($model)) { + $data = (new $model)->with($subRelation) + ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null) + ->find($pk); + + if ($data) { + $data->setParent(clone $result); + $data->exists(true); + } + } + + $result->setRelation($relation, $data ?: null); + } + + /** + * 添加关联数据 + * @access public + * @param Model $model 关联模型对象 + * @param string $type 多态类型 + * @return Model + */ + public function associate(Model $model, string $type = ''): Model + { + $morphKey = $this->morphKey; + $morphType = $this->morphType; + $pk = $model->getPk(); + + $this->parent->setAttr($morphKey, $model->$pk); + $this->parent->setAttr($morphType, $type ?: get_class($model)); + $this->parent->save(); + + return $this->parent->setRelation($this->relation, $model); + } + + /** + * 注销关联数据 + * @access public + * @return Model + */ + public function dissociate(): Model + { + $morphKey = $this->morphKey; + $morphType = $this->morphType; + + $this->parent->setAttr($morphKey, null); + $this->parent->setAttr($morphType, null); + $this->parent->save(); + + return $this->parent->setRelation($this->relation, null); + } + + protected function buildQuery(Query $query) + { + foreach ($this->queryCaller as $caller) { + call_user_func_array([$query, $caller[0]], $caller[1]); + } + + return $query; + } + + public function __call($method, $args) + { + $this->queryCaller[] = [$method, $args]; + return $this; + } +} diff --git a/vendor/topthink/think-orm/src/model/relation/MorphToMany.php b/vendor/topthink/think-orm/src/model/relation/MorphToMany.php new file mode 100644 index 0000000..c9d8583 --- /dev/null +++ b/vendor/topthink/think-orm/src/model/relation/MorphToMany.php @@ -0,0 +1,490 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use Closure; +use Exception; +use think\db\BaseQuery as Query; +use think\db\Raw; +use think\Model; +use think\model\Pivot; + +/** + * 多态多对多关联 + */ +class MorphToMany extends BelongsToMany +{ + + /** + * 多态关系的模型名映射别名的数组 + * + * @var array + */ + protected static $morphMap = []; + + /** + * 多态字段名 + * @var string + */ + protected $morphType; + + /** + * 多态模型名 + * @var string + */ + protected $morphClass; + + /** + * 是否反向关联 + * @var bool + */ + protected $inverse; + + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $middle 中间表名/模型名 + * @param string $morphKey 关联外键 + * @param string $morphType 多态字段名 + * @param string $localKey 当前模型关联键 + * @param bool $inverse 反向关联 + */ + public function __construct(Model $parent, string $model, string $middle, string $morphType, string $morphKey, string $localKey, bool $inverse = false) + { + $this->morphType = $morphType; + $this->inverse = $inverse; + $this->morphClass = $inverse ? $model : get_class($parent); + if (isset(static::$morphMap[$this->morphClass])) { + $this->morphClass = static::$morphMap[$this->morphClass]; + } + + $foreignKey = $inverse ? $morphKey : $localKey; + $localKey = $inverse ? $localKey : $morphKey; + + parent::__construct($parent, $model, $middle, $foreignKey, $localKey); + } + + /** + * 预载入关联查询(数据集) + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation, Closure $closure = null, array $cache = []): void + { + $pk = $resultSet[0]->getPk(); + $range = []; + + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (isset($result->$pk)) { + $range[] = $result->$pk; + } + } + + if (!empty($range)) { + // 查询关联数据 + $data = $this->eagerlyManyToMany([ + ['pivot.' . $this->localKey, 'in', $range], + ['pivot.' . $this->morphType, '=', $this->morphClass], + ], $subRelation, $closure, $cache); + + // 关联数据封装 + foreach ($resultSet as $result) { + if (!isset($data[$result->$pk])) { + $data[$result->$pk] = []; + } + + $result->setRelation($relation, $this->resultSetBuild($data[$result->$pk], clone $this->parent)); + } + } + } + + /** + * 预载入关联查询(单个数据) + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResult(Model $result, string $relation, array $subRelation, Closure $closure = null, array $cache = []): void + { + $pk = $result->getPk(); + + if (isset($result->$pk)) { + $pk = $result->$pk; + // 查询管理数据 + $data = $this->eagerlyManyToMany([ + ['pivot.' . $this->localKey, '=', $pk], + ['pivot.' . $this->morphType, '=', $this->morphClass], + ], $subRelation, $closure, $cache); + + // 关联数据封装 + if (!isset($data[$pk])) { + $data[$pk] = []; + } + + $result->setRelation($relation, $this->resultSetBuild($data[$pk], clone $this->parent)); + } + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return integer + */ + public function relationCount(Model $result, Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null): float + { + $pk = $result->getPk(); + + if (!isset($result->$pk)) { + return 0; + } + + $pk = $result->$pk; + + if ($closure) { + $closure($this->getClosureType($closure), $name); + } + + return $this->belongsToManyQuery($this->foreignKey, $this->localKey, [ + ['pivot.' . $this->localKey, '=', $pk], + ['pivot.' . $this->morphType, '=', $this->morphClass], + ])->$aggregate($field); + } + + /** + * 获取关联统计子查询 + * @access public + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return string + */ + public function getRelationCountQuery(Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null): string + { + if ($closure) { + $closure($this->getClosureType($closure), $name); + } + + return $this->belongsToManyQuery($this->foreignKey, $this->localKey, [ + ['pivot.' . $this->localKey, 'exp', new Raw('=' . $this->parent->db(false)->getTable() . '.' . $this->parent->getPk())], + ['pivot.' . $this->morphType, '=', $this->morphClass], + ])->fetchSql()->$aggregate($field); + } + + /** + * BELONGS TO MANY 关联查询 + * @access protected + * @param string $foreignKey 关联模型关联键 + * @param string $localKey 当前模型关联键 + * @param array $condition 关联查询条件 + * @return Query + */ + protected function belongsToManyQuery(string $foreignKey, string $localKey, array $condition = []): Query + { + // 关联查询封装 + $tableName = $this->query->getTable(); + $table = $this->pivot->db()->getTable(); + + if ($this->withoutField) { + $this->query->withoutField($this->withoutField); + } + + $fields = $this->getQueryFields($tableName); + $query = $this->query + ->field($fields) + ->tableField(true, $table, 'pivot', 'pivot__'); + + if (empty($this->baseQuery)) { + $relationFk = $this->query->getPk(); + $query->join([$table => 'pivot'], 'pivot.' . $foreignKey . '=' . $tableName . '.' . $relationFk) + ->where($condition); + } + + return $query; + } + + /** + * 多对多 关联模型预查询 + * @access protected + * @param array $where 关联预查询条件 + * @param array $subRelation 子关联 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return array + */ + protected function eagerlyManyToMany(array $where, array $subRelation = [], Closure $closure = null, array $cache = []): array + { + if ($closure) { + $closure($this->getClosureType($closure)); + } + + $withLimit = $this->query->getOptions('limit'); + if ($withLimit) { + $this->query->removeOption('limit'); + } + + // 预载入关联查询 支持嵌套预载入 + $list = $this->belongsToManyQuery($this->foreignKey, $this->localKey, $where) + ->with($subRelation) + ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null) + ->select(); + + // 组装模型数据 + $data = []; + foreach ($list as $set) { + $pivot = []; + foreach ($set->getData() as $key => $val) { + if (strpos($key, '__')) { + [$name, $attr] = explode('__', $key, 2); + if ('pivot' == $name) { + $pivot[$attr] = $val; + unset($set->$key); + } + } + } + + $key = $pivot[$this->localKey]; + + if ($withLimit && isset($data[$key]) && count($data[$key]) >= $withLimit) { + continue; + } + + $set->setRelation($this->pivotDataName, $this->newPivot($pivot)); + + $data[$key][] = $set; + } + + return $data; + } + + /** + * 附加关联的一个中间表数据 + * @access public + * @param mixed $data 数据 可以使用数组、关联模型对象 或者 关联对象的主键 + * @param array $pivot 中间表额外数据 + * @return array|Pivot + */ + public function attach($data, array $pivot = []) + { + if (is_array($data)) { + if (key($data) === 0) { + $id = $data; + } else { + // 保存关联表数据 + $model = new $this->model; + $id = $model->insertGetId($data); + } + } elseif (is_numeric($data) || is_string($data)) { + // 根据关联表主键直接写入中间表 + $id = $data; + } elseif ($data instanceof Model) { + // 根据关联表主键直接写入中间表 + $id = $data->getKey(); + } + + if (!empty($id)) { + // 保存中间表数据 + $pivot[$this->localKey] = $this->parent->getKey(); + $pivot[$this->morphType] = $this->morphClass; + $ids = (array) $id; + + $result = []; + + foreach ($ids as $id) { + $pivot[$this->foreignKey] = $id; + + $this->pivot->replace() + ->exists(false) + ->data([]) + ->save($pivot); + $result[] = $this->newPivot($pivot); + } + + if (count($result) == 1) { + // 返回中间表模型对象 + $result = $result[0]; + } + + return $result; + } else { + throw new Exception('miss relation data'); + } + } + + /** + * 判断是否存在关联数据 + * @access public + * @param mixed $data 数据 可以使用关联模型对象 或者 关联对象的主键 + * @return Pivot|false + */ + public function attached($data) + { + if ($data instanceof Model) { + $id = $data->getKey(); + } else { + $id = $data; + } + + $pivot = $this->pivot + ->where($this->localKey, $this->parent->getKey()) + ->where($this->morphType, $this->morphClass) + ->where($this->foreignKey, $id) + ->find(); + + return $pivot ?: false; + } + + /** + * 解除关联的一个中间表数据 + * @access public + * @param integer|array $data 数据 可以使用关联对象的主键 + * @param bool $relationDel 是否同时删除关联表数据 + * @return integer + */ + public function detach($data = null, bool $relationDel = false): int + { + if (is_array($data)) { + $id = $data; + } elseif (is_numeric($data) || is_string($data)) { + // 根据关联表主键直接写入中间表 + $id = $data; + } elseif ($data instanceof Model) { + // 根据关联表主键直接写入中间表 + $id = $data->getKey(); + } + + // 删除中间表数据 + $pivot = [ + [$this->localKey, '=', $this->parent->getKey()], + [$this->morphType, '=', $this->morphClass], + ]; + + if (isset($id)) { + $pivot[] = [$this->foreignKey, is_array($id) ? 'in' : '=', $id]; + } + + $result = $this->pivot->where($pivot)->delete(); + + // 删除关联表数据 + if (isset($id) && $relationDel) { + $model = $this->model; + $model::destroy($id); + } + + return $result; + } + + /** + * 数据同步 + * @access public + * @param array $ids + * @param bool $detaching + * @return array + */ + public function sync(array $ids, bool $detaching = true): array + { + $changes = [ + 'attached' => [], + 'detached' => [], + 'updated' => [], + ]; + + $current = $this->pivot + ->where($this->localKey, $this->parent->getKey()) + ->where($this->morphType, $this->morphClass) + ->column($this->foreignKey); + + $records = []; + + foreach ($ids as $key => $value) { + if (!is_array($value)) { + $records[$value] = []; + } else { + $records[$key] = $value; + } + } + + $detach = array_diff($current, array_keys($records)); + + if ($detaching && count($detach) > 0) { + $this->detach($detach); + $changes['detached'] = $detach; + } + + foreach ($records as $id => $attributes) { + if (!in_array($id, $current)) { + $this->attach($id, $attributes); + $changes['attached'][] = $id; + } elseif (count($attributes) > 0 && $this->attach($id, $attributes)) { + $changes['updated'][] = $id; + } + } + + return $changes; + } + + /** + * 执行基础查询(仅执行一次) + * @access protected + * @return void + */ + protected function baseQuery(): void + { + if (empty($this->baseQuery)) { + $foreignKey = $this->foreignKey; + $localKey = $this->localKey; + + // 关联查询 + $this->belongsToManyQuery($foreignKey, $localKey, [ + ['pivot.' . $localKey, '=', $this->parent->getKey()], + ['pivot.' . $this->morphType, '=', $this->morphClass], + ]); + + $this->baseQuery = true; + } + } + + /** + * 设置或获取多态关系的模型名映射别名的数组 + * + * @param array|null $map + * @param bool $merge + * @return array + */ + public static function morphMap(array $map = null, $merge = true): array + { + if (is_array($map)) { + static::$morphMap = $merge && static::$morphMap + ? $map + static::$morphMap : $map; + } + + return static::$morphMap; + } + +} diff --git a/vendor/topthink/think-orm/src/model/relation/OneToOne.php b/vendor/topthink/think-orm/src/model/relation/OneToOne.php new file mode 100644 index 0000000..d62692a --- /dev/null +++ b/vendor/topthink/think-orm/src/model/relation/OneToOne.php @@ -0,0 +1,359 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use Closure; +use think\db\BaseQuery as Query; +use think\db\exception\DbException as Exception; +use think\helper\Str; +use think\Model; +use think\model\Relation; + +/** + * 一对一关联基础类 + * @package think\model\relation + */ +abstract class OneToOne extends Relation +{ + /** + * JOIN类型 + * @var string + */ + protected $joinType = 'INNER'; + + /** + * 绑定的关联属性 + * @var array + */ + protected $bindAttr = []; + + /** + * 关联名 + * @var string + */ + protected $relation; + + /** + * 设置join类型 + * @access public + * @param string $type JOIN类型 + * @return $this + */ + public function joinType(string $type) + { + $this->joinType = $type; + return $this; + } + + /** + * 预载入关联查询(JOIN方式) + * @access public + * @param Query $query 查询对象 + * @param string $relation 关联名 + * @param mixed $field 关联字段 + * @param string $joinType JOIN方式 + * @param Closure $closure 闭包条件 + * @param bool $first + * @return void + */ + public function eagerly(Query $query, string $relation, $field = true, string $joinType = '', Closure $closure = null, bool $first = false): void + { + $name = Str::snake(class_basename($this->parent)); + + if ($first) { + $table = $query->getTable(); + $query->table([$table => $name]); + + if ($query->getOptions('field')) { + $masterField = $query->getOptions('field'); + $query->removeOption('field'); + } else { + $masterField = true; + } + + $query->tableField($masterField, $table, $name); + } + + // 预载入封装 + $joinTable = $this->query->getTable(); + $joinAlias = $relation; + $joinType = $joinType ?: $this->joinType; + + $query->via($joinAlias); + + if ($this instanceof BelongsTo) { + + $foreignKeyExp = $this->foreignKey; + + if (strpos($foreignKeyExp, '.') === false) { + $foreignKeyExp = $name . '.' . $this->foreignKey; + } + + $joinOn = $foreignKeyExp . '=' . $joinAlias . '.' . $this->localKey; + } else { + + $foreignKeyExp = $this->foreignKey; + + if (strpos($foreignKeyExp, '.') === false) { + $foreignKeyExp = $joinAlias . '.' . $this->foreignKey; + } + + $joinOn = $name . '.' . $this->localKey . '=' . $foreignKeyExp; + } + + if ($closure) { + // 执行闭包查询 + $closure($this->getClosureType($closure, $query)); + + // 使用withField指定获取关联的字段 + if ($this->withField) { + $field = $this->withField; + } + } + + $query->join([$joinTable => $joinAlias], $joinOn, $joinType) + ->tableField($field, $joinTable, $joinAlias, $relation . '__'); + } + + /** + * 预载入关联查询(数据集) + * @access protected + * @param array $resultSet + * @param string $relation + * @param array $subRelation + * @param Closure $closure + * @return mixed + */ + abstract protected function eagerlySet(array &$resultSet, string $relation, array $subRelation = [], Closure $closure = null); + + /** + * 预载入关联查询(数据) + * @access protected + * @param Model $result + * @param string $relation + * @param array $subRelation + * @param Closure $closure + * @return mixed + */ + abstract protected function eagerlyOne(Model $result, string $relation, array $subRelation = [], Closure $closure = null); + + /** + * 预载入关联查询(数据集) + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @param bool $join 是否为JOIN方式 + * @return void + */ + public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation = [], Closure $closure = null, array $cache = [], bool $join = false): void + { + if ($join) { + // 模型JOIN关联组装 + foreach ($resultSet as $result) { + $this->match($this->model, $relation, $result); + } + } else { + // IN查询 + $this->eagerlySet($resultSet, $relation, $subRelation, $closure, $cache); + } + } + + /** + * 预载入关联查询(数据) + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @param bool $join 是否为JOIN方式 + * @return void + */ + public function eagerlyResult(Model $result, string $relation, array $subRelation = [], Closure $closure = null, array $cache = [], bool $join = false): void + { + if ($join) { + // 模型JOIN关联组装 + $this->match($this->model, $relation, $result); + } else { + // IN查询 + $this->eagerlyOne($result, $relation, $subRelation, $closure, $cache); + } + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 + * @param boolean $replace 是否自动识别更新和写入 + * @return Model|false + */ + public function save($data, bool $replace = true) + { + if ($data instanceof Model) { + $data = $data->getData(); + } + + $model = $this->make(); + + return $model->replace($replace)->save($data) ? $model : false; + } + + /** + * 创建关联对象实例 + * @param array|Model $data + * @return Model + */ + public function make($data = []): Model + { + if ($data instanceof Model) { + $data = $data->getData(); + } + + // 保存关联表数据 + $data[$this->foreignKey] = $this->parent->{$this->localKey}; + + return new $this->model($data); + } + + /** + * 绑定关联表的属性到父模型属性 + * @access public + * @param array $attr 要绑定的属性列表 + * @return $this + */ + public function bind(array $attr) + { + $this->bindAttr = $attr; + + return $this; + } + + /** + * 获取绑定属性 + * @access public + * @return array + */ + public function getBindAttr(): array + { + return $this->bindAttr; + } + + /** + * 一对一 关联模型预查询拼装 + * @access public + * @param string $model 模型名称 + * @param string $relation 关联名 + * @param Model $result 模型对象实例 + * @return void + */ + protected function match(string $model, string $relation, Model $result): void + { + // 重新组装模型数据 + foreach ($result->getData() as $key => $val) { + if (strpos($key, '__')) { + [$name, $attr] = explode('__', $key, 2); + if ($name == $relation) { + $list[$name][$attr] = $val; + unset($result->$key); + } + } + } + + if (isset($list[$relation])) { + $array = array_unique($list[$relation]); + + if (count($array) == 1 && null === current($array)) { + $relationModel = null; + } else { + $relationModel = new $model($list[$relation]); + $relationModel->setParent(clone $result); + $relationModel->exists(true); + } + + if (!empty($this->bindAttr)) { + $this->bindAttr($result, $relationModel); + } + } else { + $relationModel = null; + } + + $result->setRelation($relation, $relationModel); + } + + /** + * 绑定关联属性到父模型 + * @access protected + * @param Model $result 父模型对象 + * @param Model $model 关联模型对象 + * @return void + * @throws Exception + */ + protected function bindAttr(Model $result, Model $model = null): void + { + foreach ($this->bindAttr as $key => $attr) { + $key = is_numeric($key) ? $attr : $key; + $value = $result->getOrigin($key); + + if (!is_null($value)) { + throw new Exception('bind attr has exists:' . $key); + } + + $result->setAttr($key, $model ? $model->$attr : null); + } + } + + /** + * 一对一 关联模型预查询(IN方式) + * @access public + * @param array $where 关联预查询条件 + * @param string $key 关联键名 + * @param array $subRelation 子关联 + * @param Closure $closure + * @param array $cache 关联缓存 + * @return array + */ + protected function eagerlyWhere(array $where, string $key, array $subRelation = [], Closure $closure = null, array $cache = []) + { + // 预载入关联查询 支持嵌套预载入 + if ($closure) { + $this->baseQuery = true; + $closure($this->getClosureType($closure)); + } + + if ($this->withField) { + $this->query->field($this->withField); + } elseif ($this->withoutField) { + $this->query->withoutField($this->withoutField); + } + + $list = $this->query + ->where($where) + ->with($subRelation) + ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null) + ->select(); + + // 组装模型数据 + $data = []; + + foreach ($list as $set) { + if (!isset($data[$set->$key])) { + $data[$set->$key] = $set; + } + } + + return $data; + } + +} diff --git a/vendor/topthink/think-orm/src/paginator/driver/Bootstrap.php b/vendor/topthink/think-orm/src/paginator/driver/Bootstrap.php new file mode 100644 index 0000000..6d55c39 --- /dev/null +++ b/vendor/topthink/think-orm/src/paginator/driver/Bootstrap.php @@ -0,0 +1,209 @@ + +// +---------------------------------------------------------------------- + +namespace think\paginator\driver; + +use think\Paginator; + +/** + * Bootstrap 分页驱动 + */ +class Bootstrap extends Paginator +{ + + /** + * 上一页按钮 + * @param string $text + * @return string + */ + protected function getPreviousButton(string $text = "«"): string + { + + if ($this->currentPage() <= 1) { + return $this->getDisabledTextWrapper($text); + } + + $url = $this->url( + $this->currentPage() - 1 + ); + + return $this->getPageLinkWrapper($url, $text); + } + + /** + * 下一页按钮 + * @param string $text + * @return string + */ + protected function getNextButton(string $text = '»'): string + { + if (!$this->hasMore) { + return $this->getDisabledTextWrapper($text); + } + + $url = $this->url($this->currentPage() + 1); + + return $this->getPageLinkWrapper($url, $text); + } + + /** + * 页码按钮 + * @return string + */ + protected function getLinks(): string + { + if ($this->simple) { + return ''; + } + + $block = [ + 'first' => null, + 'slider' => null, + 'last' => null, + ]; + + $side = 3; + $window = $side * 2; + + if ($this->lastPage < $window + 6) { + $block['first'] = $this->getUrlRange(1, $this->lastPage); + } elseif ($this->currentPage <= $window) { + $block['first'] = $this->getUrlRange(1, $window + 2); + $block['last'] = $this->getUrlRange($this->lastPage - 1, $this->lastPage); + } elseif ($this->currentPage > ($this->lastPage - $window)) { + $block['first'] = $this->getUrlRange(1, 2); + $block['last'] = $this->getUrlRange($this->lastPage - ($window + 2), $this->lastPage); + } else { + $block['first'] = $this->getUrlRange(1, 2); + $block['slider'] = $this->getUrlRange($this->currentPage - $side, $this->currentPage + $side); + $block['last'] = $this->getUrlRange($this->lastPage - 1, $this->lastPage); + } + + $html = ''; + + if (is_array($block['first'])) { + $html .= $this->getUrlLinks($block['first']); + } + + if (is_array($block['slider'])) { + $html .= $this->getDots(); + $html .= $this->getUrlLinks($block['slider']); + } + + if (is_array($block['last'])) { + $html .= $this->getDots(); + $html .= $this->getUrlLinks($block['last']); + } + + return $html; + } + + /** + * 渲染分页html + * @return mixed + */ + public function render() + { + if ($this->hasPages()) { + if ($this->simple) { + return sprintf( + '
    %s %s
', + $this->getPreviousButton(), + $this->getNextButton() + ); + } else { + return sprintf( + '
    %s %s %s
', + $this->getPreviousButton(), + $this->getLinks(), + $this->getNextButton() + ); + } + } + } + + /** + * 生成一个可点击的按钮 + * + * @param string $url + * @param string $page + * @return string + */ + protected function getAvailablePageWrapper(string $url, string $page): string + { + return '
  • ' . $page . '
  • '; + } + + /** + * 生成一个禁用的按钮 + * + * @param string $text + * @return string + */ + protected function getDisabledTextWrapper(string $text): string + { + return '
  • ' . $text . '
  • '; + } + + /** + * 生成一个激活的按钮 + * + * @param string $text + * @return string + */ + protected function getActivePageWrapper(string $text): string + { + return '
  • ' . $text . '
  • '; + } + + /** + * 生成省略号按钮 + * + * @return string + */ + protected function getDots(): string + { + return $this->getDisabledTextWrapper('...'); + } + + /** + * 批量生成页码按钮. + * + * @param array $urls + * @return string + */ + protected function getUrlLinks(array $urls): string + { + $html = ''; + + foreach ($urls as $page => $url) { + $html .= $this->getPageLinkWrapper($url, $page); + } + + return $html; + } + + /** + * 生成普通页码按钮 + * + * @param string $url + * @param string $page + * @return string + */ + protected function getPageLinkWrapper(string $url, string $page): string + { + if ($this->currentPage() == $page) { + return $this->getActivePageWrapper($page); + } + + return $this->getAvailablePageWrapper($url, $page); + } +} diff --git a/vendor/topthink/think-orm/stubs/Exception.php b/vendor/topthink/think-orm/stubs/Exception.php new file mode 100644 index 0000000..0fdba9c --- /dev/null +++ b/vendor/topthink/think-orm/stubs/Exception.php @@ -0,0 +1,59 @@ + +// +---------------------------------------------------------------------- +declare (strict_types=1); + +namespace think; + +/** + * 异常基础类 + * @package think + */ +class Exception extends \Exception +{ + /** + * 保存异常页面显示的额外Debug数据 + * @var array + */ + protected $data = []; + + /** + * 设置异常额外的Debug数据 + * 数据将会显示为下面的格式 + * + * Exception Data + * -------------------------------------------------- + * Label 1 + * key1 value1 + * key2 value2 + * Label 2 + * key1 value1 + * key2 value2 + * + * @access protected + * @param string $label 数据分类,用于异常页面显示 + * @param array $data 需要显示的数据,必须为关联数组 + */ + final protected function setData(string $label, array $data) + { + $this->data[$label] = $data; + } + + /** + * 获取异常额外Debug数据 + * 主要用于输出到异常页面便于调试 + * @access public + * @return array 由setData设置的Debug数据 + */ + final public function getData() + { + return $this->data; + } +} diff --git a/vendor/topthink/think-orm/stubs/Facade.php b/vendor/topthink/think-orm/stubs/Facade.php new file mode 100644 index 0000000..d801d8b --- /dev/null +++ b/vendor/topthink/think-orm/stubs/Facade.php @@ -0,0 +1,65 @@ + +// +---------------------------------------------------------------------- +declare(strict_types=1); + +namespace think; + +class Facade +{ + /** + * 始终创建新的对象实例 + * @var bool + */ + protected static $alwaysNewInstance; + + protected static $instance; + + /** + * 获取当前Facade对应类名 + * @access protected + * @return string + */ + protected static function getFacadeClass() + {} + + /** + * 创建Facade实例 + * @static + * @access protected + * @param bool $newInstance 是否每次创建新的实例 + * @return object + */ + protected static function createFacade(bool $newInstance = false) + { + $class = static::getFacadeClass() ?: 'think\DbManager'; + + if (static::$alwaysNewInstance) { + $newInstance = true; + } + + if ($newInstance) { + return new $class(); + } + + if (!self::$instance) { + self::$instance = new $class(); + } + + return self::$instance; + + } + + // 调用实际类的方法 + public static function __callStatic($method, $params) + { + return call_user_func_array([static::createFacade(), $method], $params); + } +} diff --git a/vendor/topthink/think-orm/stubs/load_stubs.php b/vendor/topthink/think-orm/stubs/load_stubs.php new file mode 100644 index 0000000..734507e --- /dev/null +++ b/vendor/topthink/think-orm/stubs/load_stubs.php @@ -0,0 +1,9 @@ +store = $store; + $this->parser = $parser; + $this->loader = $loader; + $this->repository = $repository; + } + + /** + * Create a new dotenv instance. + * + * @param \Dotenv\Repository\RepositoryInterface $repository + * @param string|string[] $paths + * @param string|string[]|null $names + * @param bool $shortCircuit + * @param string|null $fileEncoding + * + * @return \Dotenv\Dotenv + */ + public static function create(RepositoryInterface $repository, $paths, $names = null, bool $shortCircuit = true, string $fileEncoding = null) + { + $builder = $names === null ? StoreBuilder::createWithDefaultName() : StoreBuilder::createWithNoNames(); + + foreach ((array) $paths as $path) { + $builder = $builder->addPath($path); + } + + foreach ((array) $names as $name) { + $builder = $builder->addName($name); + } + + if ($shortCircuit) { + $builder = $builder->shortCircuit(); + } + + return new self($builder->fileEncoding($fileEncoding)->make(), new Parser(), new Loader(), $repository); + } + + /** + * Create a new mutable dotenv instance with default repository. + * + * @param string|string[] $paths + * @param string|string[]|null $names + * @param bool $shortCircuit + * @param string|null $fileEncoding + * + * @return \Dotenv\Dotenv + */ + public static function createMutable($paths, $names = null, bool $shortCircuit = true, string $fileEncoding = null) + { + $repository = RepositoryBuilder::createWithDefaultAdapters()->make(); + + return self::create($repository, $paths, $names, $shortCircuit, $fileEncoding); + } + + /** + * Create a new mutable dotenv instance with default repository with the putenv adapter. + * + * @param string|string[] $paths + * @param string|string[]|null $names + * @param bool $shortCircuit + * @param string|null $fileEncoding + * + * @return \Dotenv\Dotenv + */ + public static function createUnsafeMutable($paths, $names = null, bool $shortCircuit = true, string $fileEncoding = null) + { + $repository = RepositoryBuilder::createWithDefaultAdapters() + ->addAdapter(PutenvAdapter::class) + ->make(); + + return self::create($repository, $paths, $names, $shortCircuit, $fileEncoding); + } + + /** + * Create a new immutable dotenv instance with default repository. + * + * @param string|string[] $paths + * @param string|string[]|null $names + * @param bool $shortCircuit + * @param string|null $fileEncoding + * + * @return \Dotenv\Dotenv + */ + public static function createImmutable($paths, $names = null, bool $shortCircuit = true, string $fileEncoding = null) + { + $repository = RepositoryBuilder::createWithDefaultAdapters()->immutable()->make(); + + return self::create($repository, $paths, $names, $shortCircuit, $fileEncoding); + } + + /** + * Create a new immutable dotenv instance with default repository with the putenv adapter. + * + * @param string|string[] $paths + * @param string|string[]|null $names + * @param bool $shortCircuit + * @param string|null $fileEncoding + * + * @return \Dotenv\Dotenv + */ + public static function createUnsafeImmutable($paths, $names = null, bool $shortCircuit = true, string $fileEncoding = null) + { + $repository = RepositoryBuilder::createWithDefaultAdapters() + ->addAdapter(PutenvAdapter::class) + ->immutable() + ->make(); + + return self::create($repository, $paths, $names, $shortCircuit, $fileEncoding); + } + + /** + * Create a new dotenv instance with an array backed repository. + * + * @param string|string[] $paths + * @param string|string[]|null $names + * @param bool $shortCircuit + * @param string|null $fileEncoding + * + * @return \Dotenv\Dotenv + */ + public static function createArrayBacked($paths, $names = null, bool $shortCircuit = true, string $fileEncoding = null) + { + $repository = RepositoryBuilder::createWithNoAdapters()->addAdapter(ArrayAdapter::class)->make(); + + return self::create($repository, $paths, $names, $shortCircuit, $fileEncoding); + } + + /** + * Parse the given content and resolve nested variables. + * + * This method behaves just like load(), only without mutating your actual + * environment. We do this by using an array backed repository. + * + * @param string $content + * + * @throws \Dotenv\Exception\InvalidFileException + * + * @return array + */ + public static function parse(string $content) + { + $repository = RepositoryBuilder::createWithNoAdapters()->addAdapter(ArrayAdapter::class)->make(); + + $phpdotenv = new self(new StringStore($content), new Parser(), new Loader(), $repository); + + return $phpdotenv->load(); + } + + /** + * Read and load environment file(s). + * + * @throws \Dotenv\Exception\InvalidPathException|\Dotenv\Exception\InvalidEncodingException|\Dotenv\Exception\InvalidFileException + * + * @return array + */ + public function load() + { + $entries = $this->parser->parse($this->store->read()); + + return $this->loader->load($this->repository, $entries); + } + + /** + * Read and load environment file(s), silently failing if no files can be read. + * + * @throws \Dotenv\Exception\InvalidEncodingException|\Dotenv\Exception\InvalidFileException + * + * @return array + */ + public function safeLoad() + { + try { + return $this->load(); + } catch (InvalidPathException $e) { + // suppressing exception + return []; + } + } + + /** + * Required ensures that the specified variables exist, and returns a new validator object. + * + * @param string|string[] $variables + * + * @return \Dotenv\Validator + */ + public function required($variables) + { + return (new Validator($this->repository, (array) $variables))->required(); + } + + /** + * Returns a new validator object that won't check if the specified variables exist. + * + * @param string|string[] $variables + * + * @return \Dotenv\Validator + */ + public function ifPresent($variables) + { + return new Validator($this->repository, (array) $variables); + } +} diff --git a/vendor/vlucas/phpdotenv/src/Exception/ExceptionInterface.php b/vendor/vlucas/phpdotenv/src/Exception/ExceptionInterface.php new file mode 100644 index 0000000..1e80f53 --- /dev/null +++ b/vendor/vlucas/phpdotenv/src/Exception/ExceptionInterface.php @@ -0,0 +1,12 @@ + + */ + public function load(RepositoryInterface $repository, array $entries) + { + return \array_reduce($entries, static function (array $vars, Entry $entry) use ($repository) { + $name = $entry->getName(); + + $value = $entry->getValue()->map(static function (Value $value) use ($repository) { + return Resolver::resolve($repository, $value); + }); + + if ($value->isDefined()) { + $inner = $value->get(); + if ($repository->set($name, $inner)) { + return \array_merge($vars, [$name => $inner]); + } + } else { + if ($repository->clear($name)) { + return \array_merge($vars, [$name => null]); + } + } + + return $vars; + }, []); + } +} diff --git a/vendor/vlucas/phpdotenv/src/Loader/LoaderInterface.php b/vendor/vlucas/phpdotenv/src/Loader/LoaderInterface.php new file mode 100644 index 0000000..275d98e --- /dev/null +++ b/vendor/vlucas/phpdotenv/src/Loader/LoaderInterface.php @@ -0,0 +1,20 @@ + + */ + public function load(RepositoryInterface $repository, array $entries); +} diff --git a/vendor/vlucas/phpdotenv/src/Loader/Resolver.php b/vendor/vlucas/phpdotenv/src/Loader/Resolver.php new file mode 100644 index 0000000..36d7a4b --- /dev/null +++ b/vendor/vlucas/phpdotenv/src/Loader/Resolver.php @@ -0,0 +1,65 @@ +getVars(), static function (string $s, int $i) use ($repository) { + return Str::substr($s, 0, $i).self::resolveVariable($repository, Str::substr($s, $i)); + }, $value->getChars()); + } + + /** + * Resolve a single nested variable. + * + * @param \Dotenv\Repository\RepositoryInterface $repository + * @param string $str + * + * @return string + */ + private static function resolveVariable(RepositoryInterface $repository, string $str) + { + return Regex::replaceCallback( + '/\A\${([a-zA-Z0-9_.]+)}/', + static function (array $matches) use ($repository) { + return Option::fromValue($repository->get($matches[1])) + ->getOrElse($matches[0]); + }, + $str, + 1 + )->success()->getOrElse($str); + } +} diff --git a/vendor/vlucas/phpdotenv/src/Parser/Entry.php b/vendor/vlucas/phpdotenv/src/Parser/Entry.php new file mode 100644 index 0000000..7570f58 --- /dev/null +++ b/vendor/vlucas/phpdotenv/src/Parser/Entry.php @@ -0,0 +1,59 @@ +name = $name; + $this->value = $value; + } + + /** + * Get the entry name. + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Get the entry value. + * + * @return \PhpOption\Option<\Dotenv\Parser\Value> + */ + public function getValue() + { + /** @var \PhpOption\Option<\Dotenv\Parser\Value> */ + return Option::fromValue($this->value); + } +} diff --git a/vendor/vlucas/phpdotenv/src/Parser/EntryParser.php b/vendor/vlucas/phpdotenv/src/Parser/EntryParser.php new file mode 100644 index 0000000..e286840 --- /dev/null +++ b/vendor/vlucas/phpdotenv/src/Parser/EntryParser.php @@ -0,0 +1,300 @@ + + */ + public static function parse(string $entry) + { + return self::splitStringIntoParts($entry)->flatMap(static function (array $parts) { + [$name, $value] = $parts; + + return self::parseName($name)->flatMap(static function (string $name) use ($value) { + /** @var Result */ + $parsedValue = $value === null ? Success::create(null) : self::parseValue($value); + + return $parsedValue->map(static function (?Value $value) use ($name) { + return new Entry($name, $value); + }); + }); + }); + } + + /** + * Split the compound string into parts. + * + * @param string $line + * + * @return \GrahamCampbell\ResultType\Result + */ + private static function splitStringIntoParts(string $line) + { + /** @var array{string,string|null} */ + $result = Str::pos($line, '=')->map(static function () use ($line) { + return \array_map('trim', \explode('=', $line, 2)); + })->getOrElse([$line, null]); + + if ($result[0] === '') { + /** @var \GrahamCampbell\ResultType\Result */ + return Error::create(self::getErrorMessage('an unexpected equals', $line)); + } + + /** @var \GrahamCampbell\ResultType\Result */ + return Success::create($result); + } + + /** + * Parse the given variable name. + * + * That is, strip the optional quotes and leading "export" from the + * variable name. We wrap the answer in a result type. + * + * @param string $name + * + * @return \GrahamCampbell\ResultType\Result + */ + private static function parseName(string $name) + { + if (Str::len($name) > 8 && Str::substr($name, 0, 6) === 'export' && \ctype_space(Str::substr($name, 6, 1))) { + $name = \ltrim(Str::substr($name, 6)); + } + + if (self::isQuotedName($name)) { + $name = Str::substr($name, 1, -1); + } + + if (!self::isValidName($name)) { + /** @var \GrahamCampbell\ResultType\Result */ + return Error::create(self::getErrorMessage('an invalid name', $name)); + } + + /** @var \GrahamCampbell\ResultType\Result */ + return Success::create($name); + } + + /** + * Is the given variable name quoted? + * + * @param string $name + * + * @return bool + */ + private static function isQuotedName(string $name) + { + if (Str::len($name) < 3) { + return false; + } + + $first = Str::substr($name, 0, 1); + $last = Str::substr($name, -1, 1); + + return ($first === '"' && $last === '"') || ($first === '\'' && $last === '\''); + } + + /** + * Is the given variable name valid? + * + * @param string $name + * + * @return bool + */ + private static function isValidName(string $name) + { + return Regex::matches('~(*UTF8)\A[\p{Ll}\p{Lu}\p{M}\p{N}_.]+\z~', $name)->success()->getOrElse(false); + } + + /** + * Parse the given variable value. + * + * This has the effect of stripping quotes and comments, dealing with + * special characters, and locating nested variables, but not resolving + * them. Formally, we run a finite state automaton with an output tape: a + * transducer. We wrap the answer in a result type. + * + * @param string $value + * + * @return \GrahamCampbell\ResultType\Result<\Dotenv\Parser\Value,string> + */ + private static function parseValue(string $value) + { + if (\trim($value) === '') { + /** @var \GrahamCampbell\ResultType\Result<\Dotenv\Parser\Value,string> */ + return Success::create(Value::blank()); + } + + return \array_reduce(\iterator_to_array(Lexer::lex($value)), static function (Result $data, string $token) { + return $data->flatMap(static function (array $data) use ($token) { + return self::processToken($data[1], $token)->map(static function (array $val) use ($data) { + return [$data[0]->append($val[0], $val[1]), $val[2]]; + }); + }); + }, Success::create([Value::blank(), self::INITIAL_STATE]))->flatMap(static function (array $result) { + /** @psalm-suppress DocblockTypeContradiction */ + if (in_array($result[1], self::REJECT_STATES, true)) { + /** @var \GrahamCampbell\ResultType\Result<\Dotenv\Parser\Value,string> */ + return Error::create('a missing closing quote'); + } + + /** @var \GrahamCampbell\ResultType\Result<\Dotenv\Parser\Value,string> */ + return Success::create($result[0]); + })->mapError(static function (string $err) use ($value) { + return self::getErrorMessage($err, $value); + }); + } + + /** + * Process the given token. + * + * @param int $state + * @param string $token + * + * @return \GrahamCampbell\ResultType\Result + */ + private static function processToken(int $state, string $token) + { + switch ($state) { + case self::INITIAL_STATE: + if ($token === '\'') { + /** @var \GrahamCampbell\ResultType\Result */ + return Success::create(['', false, self::SINGLE_QUOTED_STATE]); + } elseif ($token === '"') { + /** @var \GrahamCampbell\ResultType\Result */ + return Success::create(['', false, self::DOUBLE_QUOTED_STATE]); + } elseif ($token === '#') { + /** @var \GrahamCampbell\ResultType\Result */ + return Success::create(['', false, self::COMMENT_STATE]); + } elseif ($token === '$') { + /** @var \GrahamCampbell\ResultType\Result */ + return Success::create([$token, true, self::UNQUOTED_STATE]); + } else { + /** @var \GrahamCampbell\ResultType\Result */ + return Success::create([$token, false, self::UNQUOTED_STATE]); + } + case self::UNQUOTED_STATE: + if ($token === '#') { + /** @var \GrahamCampbell\ResultType\Result */ + return Success::create(['', false, self::COMMENT_STATE]); + } elseif (\ctype_space($token)) { + /** @var \GrahamCampbell\ResultType\Result */ + return Success::create(['', false, self::WHITESPACE_STATE]); + } elseif ($token === '$') { + /** @var \GrahamCampbell\ResultType\Result */ + return Success::create([$token, true, self::UNQUOTED_STATE]); + } else { + /** @var \GrahamCampbell\ResultType\Result */ + return Success::create([$token, false, self::UNQUOTED_STATE]); + } + case self::SINGLE_QUOTED_STATE: + if ($token === '\'') { + /** @var \GrahamCampbell\ResultType\Result */ + return Success::create(['', false, self::WHITESPACE_STATE]); + } else { + /** @var \GrahamCampbell\ResultType\Result */ + return Success::create([$token, false, self::SINGLE_QUOTED_STATE]); + } + case self::DOUBLE_QUOTED_STATE: + if ($token === '"') { + /** @var \GrahamCampbell\ResultType\Result */ + return Success::create(['', false, self::WHITESPACE_STATE]); + } elseif ($token === '\\') { + /** @var \GrahamCampbell\ResultType\Result */ + return Success::create(['', false, self::ESCAPE_SEQUENCE_STATE]); + } elseif ($token === '$') { + /** @var \GrahamCampbell\ResultType\Result */ + return Success::create([$token, true, self::DOUBLE_QUOTED_STATE]); + } else { + /** @var \GrahamCampbell\ResultType\Result */ + return Success::create([$token, false, self::DOUBLE_QUOTED_STATE]); + } + case self::ESCAPE_SEQUENCE_STATE: + if ($token === '"' || $token === '\\') { + /** @var \GrahamCampbell\ResultType\Result */ + return Success::create([$token, false, self::DOUBLE_QUOTED_STATE]); + } elseif ($token === '$') { + /** @var \GrahamCampbell\ResultType\Result */ + return Success::create([$token, false, self::DOUBLE_QUOTED_STATE]); + } else { + $first = Str::substr($token, 0, 1); + if (\in_array($first, ['f', 'n', 'r', 't', 'v'], true)) { + /** @var \GrahamCampbell\ResultType\Result */ + return Success::create([\stripcslashes('\\'.$first).Str::substr($token, 1), false, self::DOUBLE_QUOTED_STATE]); + } else { + /** @var \GrahamCampbell\ResultType\Result */ + return Error::create('an unexpected escape sequence'); + } + } + case self::WHITESPACE_STATE: + if ($token === '#') { + /** @var \GrahamCampbell\ResultType\Result */ + return Success::create(['', false, self::COMMENT_STATE]); + } elseif (!\ctype_space($token)) { + /** @var \GrahamCampbell\ResultType\Result */ + return Error::create('unexpected whitespace'); + } else { + /** @var \GrahamCampbell\ResultType\Result */ + return Success::create(['', false, self::WHITESPACE_STATE]); + } + case self::COMMENT_STATE: + /** @var \GrahamCampbell\ResultType\Result */ + return Success::create(['', false, self::COMMENT_STATE]); + default: + throw new \Error('Parser entered invalid state.'); + } + } + + /** + * Generate a friendly error message. + * + * @param string $cause + * @param string $subject + * + * @return string + */ + private static function getErrorMessage(string $cause, string $subject) + { + return \sprintf( + 'Encountered %s at [%s].', + $cause, + \strtok($subject, "\n") + ); + } +} diff --git a/vendor/vlucas/phpdotenv/src/Parser/Lexer.php b/vendor/vlucas/phpdotenv/src/Parser/Lexer.php new file mode 100644 index 0000000..981af24 --- /dev/null +++ b/vendor/vlucas/phpdotenv/src/Parser/Lexer.php @@ -0,0 +1,58 @@ + + */ + public static function lex(string $content) + { + static $regex; + + if ($regex === null) { + $regex = '(('.\implode(')|(', self::PATTERNS).'))A'; + } + + $offset = 0; + + while (isset($content[$offset])) { + if (!\preg_match($regex, $content, $matches, 0, $offset)) { + throw new \Error(\sprintf('Lexer encountered unexpected character [%s].', $content[$offset])); + } + + $offset += \strlen($matches[0]); + + yield $matches[0]; + } + } +} diff --git a/vendor/vlucas/phpdotenv/src/Parser/Lines.php b/vendor/vlucas/phpdotenv/src/Parser/Lines.php new file mode 100644 index 0000000..6497993 --- /dev/null +++ b/vendor/vlucas/phpdotenv/src/Parser/Lines.php @@ -0,0 +1,127 @@ +map(static function () use ($line) { + return self::looksLikeMultilineStop($line, true) === false; + })->getOrElse(false); + } + + /** + * Determine if the given line can be the start of a multiline variable. + * + * @param string $line + * @param bool $started + * + * @return bool + */ + private static function looksLikeMultilineStop(string $line, bool $started) + { + if ($line === '"') { + return true; + } + + return Regex::occurrences('/(?=([^\\\\]"))/', \str_replace('\\\\', '', $line))->map(static function (int $count) use ($started) { + return $started ? $count > 1 : $count >= 1; + })->success()->getOrElse(false); + } + + /** + * Determine if the line in the file is a comment or whitespace. + * + * @param string $line + * + * @return bool + */ + private static function isCommentOrWhitespace(string $line) + { + $line = \trim($line); + + return $line === '' || (isset($line[0]) && $line[0] === '#'); + } +} diff --git a/vendor/vlucas/phpdotenv/src/Parser/Parser.php b/vendor/vlucas/phpdotenv/src/Parser/Parser.php new file mode 100644 index 0000000..2d30dfd --- /dev/null +++ b/vendor/vlucas/phpdotenv/src/Parser/Parser.php @@ -0,0 +1,53 @@ +mapError(static function () { + return 'Could not split into separate lines.'; + })->flatMap(static function (array $lines) { + return self::process(Lines::process($lines)); + })->mapError(static function (string $error) { + throw new InvalidFileException(\sprintf('Failed to parse dotenv file. %s', $error)); + })->success()->get(); + } + + /** + * Convert the raw entries into proper entries. + * + * @param string[] $entries + * + * @return \GrahamCampbell\ResultType\Result<\Dotenv\Parser\Entry[],string> + */ + private static function process(array $entries) + { + /** @var \GrahamCampbell\ResultType\Result<\Dotenv\Parser\Entry[],string> */ + return \array_reduce($entries, static function (Result $result, string $raw) { + return $result->flatMap(static function (array $entries) use ($raw) { + return EntryParser::parse($raw)->map(static function (Entry $entry) use ($entries) { + /** @var \Dotenv\Parser\Entry[] */ + return \array_merge($entries, [$entry]); + }); + }); + }, Success::create([])); + } +} diff --git a/vendor/vlucas/phpdotenv/src/Parser/ParserInterface.php b/vendor/vlucas/phpdotenv/src/Parser/ParserInterface.php new file mode 100644 index 0000000..17cc42a --- /dev/null +++ b/vendor/vlucas/phpdotenv/src/Parser/ParserInterface.php @@ -0,0 +1,19 @@ +chars = $chars; + $this->vars = $vars; + } + + /** + * Create an empty value instance. + * + * @return \Dotenv\Parser\Value + */ + public static function blank() + { + return new self('', []); + } + + /** + * Create a new value instance, appending the characters. + * + * @param string $chars + * @param bool $var + * + * @return \Dotenv\Parser\Value + */ + public function append(string $chars, bool $var) + { + return new self( + $this->chars.$chars, + $var ? \array_merge($this->vars, [Str::len($this->chars)]) : $this->vars + ); + } + + /** + * Get the string representation of the parsed value. + * + * @return string + */ + public function getChars() + { + return $this->chars; + } + + /** + * Get the locations of the variables in the value. + * + * @return int[] + */ + public function getVars() + { + $vars = $this->vars; + + \rsort($vars); + + return $vars; + } +} diff --git a/vendor/vlucas/phpdotenv/src/Repository/Adapter/AdapterInterface.php b/vendor/vlucas/phpdotenv/src/Repository/Adapter/AdapterInterface.php new file mode 100644 index 0000000..5604398 --- /dev/null +++ b/vendor/vlucas/phpdotenv/src/Repository/Adapter/AdapterInterface.php @@ -0,0 +1,15 @@ + + */ + public static function create(); +} diff --git a/vendor/vlucas/phpdotenv/src/Repository/Adapter/ApacheAdapter.php b/vendor/vlucas/phpdotenv/src/Repository/Adapter/ApacheAdapter.php new file mode 100644 index 0000000..af0aae1 --- /dev/null +++ b/vendor/vlucas/phpdotenv/src/Repository/Adapter/ApacheAdapter.php @@ -0,0 +1,89 @@ + + */ + public static function create() + { + if (self::isSupported()) { + /** @var \PhpOption\Option */ + return Some::create(new self()); + } + + return None::create(); + } + + /** + * Determines if the adapter is supported. + * + * This happens if PHP is running as an Apache module. + * + * @return bool + */ + private static function isSupported() + { + return \function_exists('apache_getenv') && \function_exists('apache_setenv'); + } + + /** + * Read an environment variable, if it exists. + * + * @param non-empty-string $name + * + * @return \PhpOption\Option + */ + public function read(string $name) + { + /** @var \PhpOption\Option */ + return Option::fromValue(apache_getenv($name))->filter(static function ($value) { + return \is_string($value) && $value !== ''; + }); + } + + /** + * Write to an environment variable, if possible. + * + * @param non-empty-string $name + * @param string $value + * + * @return bool + */ + public function write(string $name, string $value) + { + return apache_setenv($name, $value); + } + + /** + * Delete an environment variable, if possible. + * + * @param non-empty-string $name + * + * @return bool + */ + public function delete(string $name) + { + return apache_setenv($name, ''); + } +} diff --git a/vendor/vlucas/phpdotenv/src/Repository/Adapter/ArrayAdapter.php b/vendor/vlucas/phpdotenv/src/Repository/Adapter/ArrayAdapter.php new file mode 100644 index 0000000..df64cf6 --- /dev/null +++ b/vendor/vlucas/phpdotenv/src/Repository/Adapter/ArrayAdapter.php @@ -0,0 +1,80 @@ + + */ + private $variables; + + /** + * Create a new array adapter instance. + * + * @return void + */ + private function __construct() + { + $this->variables = []; + } + + /** + * Create a new instance of the adapter, if it is available. + * + * @return \PhpOption\Option<\Dotenv\Repository\Adapter\AdapterInterface> + */ + public static function create() + { + /** @var \PhpOption\Option */ + return Some::create(new self()); + } + + /** + * Read an environment variable, if it exists. + * + * @param non-empty-string $name + * + * @return \PhpOption\Option + */ + public function read(string $name) + { + return Option::fromArraysValue($this->variables, $name); + } + + /** + * Write to an environment variable, if possible. + * + * @param non-empty-string $name + * @param string $value + * + * @return bool + */ + public function write(string $name, string $value) + { + $this->variables[$name] = $value; + + return true; + } + + /** + * Delete an environment variable, if possible. + * + * @param non-empty-string $name + * + * @return bool + */ + public function delete(string $name) + { + unset($this->variables[$name]); + + return true; + } +} diff --git a/vendor/vlucas/phpdotenv/src/Repository/Adapter/EnvConstAdapter.php b/vendor/vlucas/phpdotenv/src/Repository/Adapter/EnvConstAdapter.php new file mode 100644 index 0000000..9eb1947 --- /dev/null +++ b/vendor/vlucas/phpdotenv/src/Repository/Adapter/EnvConstAdapter.php @@ -0,0 +1,89 @@ + + */ + public static function create() + { + /** @var \PhpOption\Option */ + return Some::create(new self()); + } + + /** + * Read an environment variable, if it exists. + * + * @param non-empty-string $name + * + * @return \PhpOption\Option + */ + public function read(string $name) + { + /** @var \PhpOption\Option */ + return Option::fromArraysValue($_ENV, $name) + ->filter(static function ($value) { + return \is_scalar($value); + }) + ->map(static function ($value) { + if ($value === false) { + return 'false'; + } + + if ($value === true) { + return 'true'; + } + + /** @psalm-suppress PossiblyInvalidCast */ + return (string) $value; + }); + } + + /** + * Write to an environment variable, if possible. + * + * @param non-empty-string $name + * @param string $value + * + * @return bool + */ + public function write(string $name, string $value) + { + $_ENV[$name] = $value; + + return true; + } + + /** + * Delete an environment variable, if possible. + * + * @param non-empty-string $name + * + * @return bool + */ + public function delete(string $name) + { + unset($_ENV[$name]); + + return true; + } +} diff --git a/vendor/vlucas/phpdotenv/src/Repository/Adapter/GuardedWriter.php b/vendor/vlucas/phpdotenv/src/Repository/Adapter/GuardedWriter.php new file mode 100644 index 0000000..fed8b9b --- /dev/null +++ b/vendor/vlucas/phpdotenv/src/Repository/Adapter/GuardedWriter.php @@ -0,0 +1,85 @@ +writer = $writer; + $this->allowList = $allowList; + } + + /** + * Write to an environment variable, if possible. + * + * @param non-empty-string $name + * @param string $value + * + * @return bool + */ + public function write(string $name, string $value) + { + // Don't set non-allowed variables + if (!$this->isAllowed($name)) { + return false; + } + + // Set the value on the inner writer + return $this->writer->write($name, $value); + } + + /** + * Delete an environment variable, if possible. + * + * @param non-empty-string $name + * + * @return bool + */ + public function delete(string $name) + { + // Don't clear non-allowed variables + if (!$this->isAllowed($name)) { + return false; + } + + // Set the value on the inner writer + return $this->writer->delete($name); + } + + /** + * Determine if the given variable is allowed. + * + * @param non-empty-string $name + * + * @return bool + */ + private function isAllowed(string $name) + { + return \in_array($name, $this->allowList, true); + } +} diff --git a/vendor/vlucas/phpdotenv/src/Repository/Adapter/ImmutableWriter.php b/vendor/vlucas/phpdotenv/src/Repository/Adapter/ImmutableWriter.php new file mode 100644 index 0000000..399e6f9 --- /dev/null +++ b/vendor/vlucas/phpdotenv/src/Repository/Adapter/ImmutableWriter.php @@ -0,0 +1,110 @@ + + */ + private $loaded; + + /** + * Create a new immutable writer instance. + * + * @param \Dotenv\Repository\Adapter\WriterInterface $writer + * @param \Dotenv\Repository\Adapter\ReaderInterface $reader + * + * @return void + */ + public function __construct(WriterInterface $writer, ReaderInterface $reader) + { + $this->writer = $writer; + $this->reader = $reader; + $this->loaded = []; + } + + /** + * Write to an environment variable, if possible. + * + * @param non-empty-string $name + * @param string $value + * + * @return bool + */ + public function write(string $name, string $value) + { + // Don't overwrite existing environment variables + // Ruby's dotenv does this with `ENV[key] ||= value` + if ($this->isExternallyDefined($name)) { + return false; + } + + // Set the value on the inner writer + if (!$this->writer->write($name, $value)) { + return false; + } + + // Record that we have loaded the variable + $this->loaded[$name] = ''; + + return true; + } + + /** + * Delete an environment variable, if possible. + * + * @param non-empty-string $name + * + * @return bool + */ + public function delete(string $name) + { + // Don't clear existing environment variables + if ($this->isExternallyDefined($name)) { + return false; + } + + // Clear the value on the inner writer + if (!$this->writer->delete($name)) { + return false; + } + + // Leave the variable as fair game + unset($this->loaded[$name]); + + return true; + } + + /** + * Determine if the given variable is externally defined. + * + * That is, is it an "existing" variable. + * + * @param non-empty-string $name + * + * @return bool + */ + private function isExternallyDefined(string $name) + { + return $this->reader->read($name)->isDefined() && !isset($this->loaded[$name]); + } +} diff --git a/vendor/vlucas/phpdotenv/src/Repository/Adapter/MultiReader.php b/vendor/vlucas/phpdotenv/src/Repository/Adapter/MultiReader.php new file mode 100644 index 0000000..0cfda6f --- /dev/null +++ b/vendor/vlucas/phpdotenv/src/Repository/Adapter/MultiReader.php @@ -0,0 +1,48 @@ +readers = $readers; + } + + /** + * Read an environment variable, if it exists. + * + * @param non-empty-string $name + * + * @return \PhpOption\Option + */ + public function read(string $name) + { + foreach ($this->readers as $reader) { + $result = $reader->read($name); + if ($result->isDefined()) { + return $result; + } + } + + return None::create(); + } +} diff --git a/vendor/vlucas/phpdotenv/src/Repository/Adapter/MultiWriter.php b/vendor/vlucas/phpdotenv/src/Repository/Adapter/MultiWriter.php new file mode 100644 index 0000000..15a9d8f --- /dev/null +++ b/vendor/vlucas/phpdotenv/src/Repository/Adapter/MultiWriter.php @@ -0,0 +1,64 @@ +writers = $writers; + } + + /** + * Write to an environment variable, if possible. + * + * @param non-empty-string $name + * @param string $value + * + * @return bool + */ + public function write(string $name, string $value) + { + foreach ($this->writers as $writers) { + if (!$writers->write($name, $value)) { + return false; + } + } + + return true; + } + + /** + * Delete an environment variable, if possible. + * + * @param non-empty-string $name + * + * @return bool + */ + public function delete(string $name) + { + foreach ($this->writers as $writers) { + if (!$writers->delete($name)) { + return false; + } + } + + return true; + } +} diff --git a/vendor/vlucas/phpdotenv/src/Repository/Adapter/PutenvAdapter.php b/vendor/vlucas/phpdotenv/src/Repository/Adapter/PutenvAdapter.php new file mode 100644 index 0000000..6d017cd --- /dev/null +++ b/vendor/vlucas/phpdotenv/src/Repository/Adapter/PutenvAdapter.php @@ -0,0 +1,91 @@ + + */ + public static function create() + { + if (self::isSupported()) { + /** @var \PhpOption\Option */ + return Some::create(new self()); + } + + return None::create(); + } + + /** + * Determines if the adapter is supported. + * + * @return bool + */ + private static function isSupported() + { + return \function_exists('getenv') && \function_exists('putenv'); + } + + /** + * Read an environment variable, if it exists. + * + * @param non-empty-string $name + * + * @return \PhpOption\Option + */ + public function read(string $name) + { + /** @var \PhpOption\Option */ + return Option::fromValue(\getenv($name), false)->filter(static function ($value) { + return \is_string($value); + }); + } + + /** + * Write to an environment variable, if possible. + * + * @param non-empty-string $name + * @param string $value + * + * @return bool + */ + public function write(string $name, string $value) + { + \putenv("$name=$value"); + + return true; + } + + /** + * Delete an environment variable, if possible. + * + * @param non-empty-string $name + * + * @return bool + */ + public function delete(string $name) + { + \putenv($name); + + return true; + } +} diff --git a/vendor/vlucas/phpdotenv/src/Repository/Adapter/ReaderInterface.php b/vendor/vlucas/phpdotenv/src/Repository/Adapter/ReaderInterface.php new file mode 100644 index 0000000..306a63f --- /dev/null +++ b/vendor/vlucas/phpdotenv/src/Repository/Adapter/ReaderInterface.php @@ -0,0 +1,17 @@ + + */ + public function read(string $name); +} diff --git a/vendor/vlucas/phpdotenv/src/Repository/Adapter/ReplacingWriter.php b/vendor/vlucas/phpdotenv/src/Repository/Adapter/ReplacingWriter.php new file mode 100644 index 0000000..98c0f04 --- /dev/null +++ b/vendor/vlucas/phpdotenv/src/Repository/Adapter/ReplacingWriter.php @@ -0,0 +1,104 @@ + + */ + private $seen; + + /** + * Create a new replacement writer instance. + * + * @param \Dotenv\Repository\Adapter\WriterInterface $writer + * @param \Dotenv\Repository\Adapter\ReaderInterface $reader + * + * @return void + */ + public function __construct(WriterInterface $writer, ReaderInterface $reader) + { + $this->writer = $writer; + $this->reader = $reader; + $this->seen = []; + } + + /** + * Write to an environment variable, if possible. + * + * @param non-empty-string $name + * @param string $value + * + * @return bool + */ + public function write(string $name, string $value) + { + if ($this->exists($name)) { + return $this->writer->write($name, $value); + } + + // succeed if nothing to do + return true; + } + + /** + * Delete an environment variable, if possible. + * + * @param non-empty-string $name + * + * @return bool + */ + public function delete(string $name) + { + if ($this->exists($name)) { + return $this->writer->delete($name); + } + + // succeed if nothing to do + return true; + } + + /** + * Does the given environment variable exist. + * + * Returns true if it currently exists, or existed at any point in the past + * that we are aware of. + * + * @param non-empty-string $name + * + * @return bool + */ + private function exists(string $name) + { + if (isset($this->seen[$name])) { + return true; + } + + if ($this->reader->read($name)->isDefined()) { + $this->seen[$name] = ''; + + return true; + } + + return false; + } +} diff --git a/vendor/vlucas/phpdotenv/src/Repository/Adapter/ServerConstAdapter.php b/vendor/vlucas/phpdotenv/src/Repository/Adapter/ServerConstAdapter.php new file mode 100644 index 0000000..f93b6e5 --- /dev/null +++ b/vendor/vlucas/phpdotenv/src/Repository/Adapter/ServerConstAdapter.php @@ -0,0 +1,89 @@ + + */ + public static function create() + { + /** @var \PhpOption\Option */ + return Some::create(new self()); + } + + /** + * Read an environment variable, if it exists. + * + * @param non-empty-string $name + * + * @return \PhpOption\Option + */ + public function read(string $name) + { + /** @var \PhpOption\Option */ + return Option::fromArraysValue($_SERVER, $name) + ->filter(static function ($value) { + return \is_scalar($value); + }) + ->map(static function ($value) { + if ($value === false) { + return 'false'; + } + + if ($value === true) { + return 'true'; + } + + /** @psalm-suppress PossiblyInvalidCast */ + return (string) $value; + }); + } + + /** + * Write to an environment variable, if possible. + * + * @param non-empty-string $name + * @param string $value + * + * @return bool + */ + public function write(string $name, string $value) + { + $_SERVER[$name] = $value; + + return true; + } + + /** + * Delete an environment variable, if possible. + * + * @param non-empty-string $name + * + * @return bool + */ + public function delete(string $name) + { + unset($_SERVER[$name]); + + return true; + } +} diff --git a/vendor/vlucas/phpdotenv/src/Repository/Adapter/WriterInterface.php b/vendor/vlucas/phpdotenv/src/Repository/Adapter/WriterInterface.php new file mode 100644 index 0000000..4cb3d61 --- /dev/null +++ b/vendor/vlucas/phpdotenv/src/Repository/Adapter/WriterInterface.php @@ -0,0 +1,27 @@ +reader = $reader; + $this->writer = $writer; + } + + /** + * Determine if the given environment variable is defined. + * + * @param string $name + * + * @return bool + */ + public function has(string $name) + { + return '' !== $name && $this->reader->read($name)->isDefined(); + } + + /** + * Get an environment variable. + * + * @param string $name + * + * @throws \InvalidArgumentException + * + * @return string|null + */ + public function get(string $name) + { + if ('' === $name) { + throw new InvalidArgumentException('Expected name to be a non-empty string.'); + } + + return $this->reader->read($name)->getOrElse(null); + } + + /** + * Set an environment variable. + * + * @param string $name + * @param string $value + * + * @throws \InvalidArgumentException + * + * @return bool + */ + public function set(string $name, string $value) + { + if ('' === $name) { + throw new InvalidArgumentException('Expected name to be a non-empty string.'); + } + + return $this->writer->write($name, $value); + } + + /** + * Clear an environment variable. + * + * @param string $name + * + * @throws \InvalidArgumentException + * + * @return bool + */ + public function clear(string $name) + { + if ('' === $name) { + throw new InvalidArgumentException('Expected name to be a non-empty string.'); + } + + return $this->writer->delete($name); + } +} diff --git a/vendor/vlucas/phpdotenv/src/Repository/RepositoryBuilder.php b/vendor/vlucas/phpdotenv/src/Repository/RepositoryBuilder.php new file mode 100644 index 0000000..a042f9a --- /dev/null +++ b/vendor/vlucas/phpdotenv/src/Repository/RepositoryBuilder.php @@ -0,0 +1,272 @@ +readers = $readers; + $this->writers = $writers; + $this->immutable = $immutable; + $this->allowList = $allowList; + } + + /** + * Create a new repository builder instance with no adapters added. + * + * @return \Dotenv\Repository\RepositoryBuilder + */ + public static function createWithNoAdapters() + { + return new self(); + } + + /** + * Create a new repository builder instance with the default adapters added. + * + * @return \Dotenv\Repository\RepositoryBuilder + */ + public static function createWithDefaultAdapters() + { + $adapters = \iterator_to_array(self::defaultAdapters()); + + return new self($adapters, $adapters); + } + + /** + * Return the array of default adapters. + * + * @return \Generator<\Dotenv\Repository\Adapter\AdapterInterface> + */ + private static function defaultAdapters() + { + foreach (self::DEFAULT_ADAPTERS as $adapter) { + $instance = $adapter::create(); + if ($instance->isDefined()) { + yield $instance->get(); + } + } + } + + /** + * Determine if the given name if of an adapterclass. + * + * @param string $name + * + * @return bool + */ + private static function isAnAdapterClass(string $name) + { + if (!\class_exists($name)) { + return false; + } + + return (new ReflectionClass($name))->implementsInterface(AdapterInterface::class); + } + + /** + * Creates a repository builder with the given reader added. + * + * Accepts either a reader instance, or a class-string for an adapter. If + * the adapter is not supported, then we silently skip adding it. + * + * @param \Dotenv\Repository\Adapter\ReaderInterface|string $reader + * + * @throws \InvalidArgumentException + * + * @return \Dotenv\Repository\RepositoryBuilder + */ + public function addReader($reader) + { + if (!(\is_string($reader) && self::isAnAdapterClass($reader)) && !($reader instanceof ReaderInterface)) { + throw new InvalidArgumentException( + \sprintf( + 'Expected either an instance of %s or a class-string implementing %s', + ReaderInterface::class, + AdapterInterface::class + ) + ); + } + + $optional = Some::create($reader)->flatMap(static function ($reader) { + return \is_string($reader) ? $reader::create() : Some::create($reader); + }); + + $readers = \array_merge($this->readers, \iterator_to_array($optional)); + + return new self($readers, $this->writers, $this->immutable, $this->allowList); + } + + /** + * Creates a repository builder with the given writer added. + * + * Accepts either a writer instance, or a class-string for an adapter. If + * the adapter is not supported, then we silently skip adding it. + * + * @param \Dotenv\Repository\Adapter\WriterInterface|string $writer + * + * @throws \InvalidArgumentException + * + * @return \Dotenv\Repository\RepositoryBuilder + */ + public function addWriter($writer) + { + if (!(\is_string($writer) && self::isAnAdapterClass($writer)) && !($writer instanceof WriterInterface)) { + throw new InvalidArgumentException( + \sprintf( + 'Expected either an instance of %s or a class-string implementing %s', + WriterInterface::class, + AdapterInterface::class + ) + ); + } + + $optional = Some::create($writer)->flatMap(static function ($writer) { + return \is_string($writer) ? $writer::create() : Some::create($writer); + }); + + $writers = \array_merge($this->writers, \iterator_to_array($optional)); + + return new self($this->readers, $writers, $this->immutable, $this->allowList); + } + + /** + * Creates a repository builder with the given adapter added. + * + * Accepts either an adapter instance, or a class-string for an adapter. If + * the adapter is not supported, then we silently skip adding it. We will + * add the adapter as both a reader and a writer. + * + * @param \Dotenv\Repository\Adapter\WriterInterface|string $adapter + * + * @throws \InvalidArgumentException + * + * @return \Dotenv\Repository\RepositoryBuilder + */ + public function addAdapter($adapter) + { + if (!(\is_string($adapter) && self::isAnAdapterClass($adapter)) && !($adapter instanceof AdapterInterface)) { + throw new InvalidArgumentException( + \sprintf( + 'Expected either an instance of %s or a class-string implementing %s', + WriterInterface::class, + AdapterInterface::class + ) + ); + } + + $optional = Some::create($adapter)->flatMap(static function ($adapter) { + return \is_string($adapter) ? $adapter::create() : Some::create($adapter); + }); + + $readers = \array_merge($this->readers, \iterator_to_array($optional)); + $writers = \array_merge($this->writers, \iterator_to_array($optional)); + + return new self($readers, $writers, $this->immutable, $this->allowList); + } + + /** + * Creates a repository builder with mutability enabled. + * + * @return \Dotenv\Repository\RepositoryBuilder + */ + public function immutable() + { + return new self($this->readers, $this->writers, true, $this->allowList); + } + + /** + * Creates a repository builder with the given allow list. + * + * @param string[]|null $allowList + * + * @return \Dotenv\Repository\RepositoryBuilder + */ + public function allowList(array $allowList = null) + { + return new self($this->readers, $this->writers, $this->immutable, $allowList); + } + + /** + * Creates a new repository instance. + * + * @return \Dotenv\Repository\RepositoryInterface + */ + public function make() + { + $reader = new MultiReader($this->readers); + $writer = new MultiWriter($this->writers); + + if ($this->immutable) { + $writer = new ImmutableWriter($writer, $reader); + } + + if ($this->allowList !== null) { + $writer = new GuardedWriter($writer, $this->allowList); + } + + return new AdapterRepository($reader, $writer); + } +} diff --git a/vendor/vlucas/phpdotenv/src/Repository/RepositoryInterface.php b/vendor/vlucas/phpdotenv/src/Repository/RepositoryInterface.php new file mode 100644 index 0000000..d9b18a4 --- /dev/null +++ b/vendor/vlucas/phpdotenv/src/Repository/RepositoryInterface.php @@ -0,0 +1,51 @@ + + */ + public static function read(array $filePaths, bool $shortCircuit = true, string $fileEncoding = null) + { + $output = []; + + foreach ($filePaths as $filePath) { + $content = self::readFromFile($filePath, $fileEncoding); + if ($content->isDefined()) { + $output[$filePath] = $content->get(); + if ($shortCircuit) { + break; + } + } + } + + return $output; + } + + /** + * Read the given file. + * + * @param string $path + * @param string|null $encoding + * + * @throws \Dotenv\Exception\InvalidEncodingException + * + * @return \PhpOption\Option + */ + private static function readFromFile(string $path, string $encoding = null) + { + /** @var Option */ + $content = Option::fromValue(@\file_get_contents($path), false); + + return $content->flatMap(static function (string $content) use ($encoding) { + return Str::utf8($content, $encoding)->mapError(static function (string $error) { + throw new InvalidEncodingException($error); + })->success(); + }); + } +} diff --git a/vendor/vlucas/phpdotenv/src/Store/FileStore.php b/vendor/vlucas/phpdotenv/src/Store/FileStore.php new file mode 100644 index 0000000..43f6135 --- /dev/null +++ b/vendor/vlucas/phpdotenv/src/Store/FileStore.php @@ -0,0 +1,72 @@ +filePaths = $filePaths; + $this->shortCircuit = $shortCircuit; + $this->fileEncoding = $fileEncoding; + } + + /** + * Read the content of the environment file(s). + * + * @throws \Dotenv\Exception\InvalidEncodingException|\Dotenv\Exception\InvalidPathException + * + * @return string + */ + public function read() + { + if ($this->filePaths === []) { + throw new InvalidPathException('At least one environment file path must be provided.'); + } + + $contents = Reader::read($this->filePaths, $this->shortCircuit, $this->fileEncoding); + + if (\count($contents) > 0) { + return \implode("\n", $contents); + } + + throw new InvalidPathException( + \sprintf('Unable to read any of the environment file(s) at [%s].', \implode(', ', $this->filePaths)) + ); + } +} diff --git a/vendor/vlucas/phpdotenv/src/Store/StoreBuilder.php b/vendor/vlucas/phpdotenv/src/Store/StoreBuilder.php new file mode 100644 index 0000000..304117f --- /dev/null +++ b/vendor/vlucas/phpdotenv/src/Store/StoreBuilder.php @@ -0,0 +1,141 @@ +paths = $paths; + $this->names = $names; + $this->shortCircuit = $shortCircuit; + $this->fileEncoding = $fileEncoding; + } + + /** + * Create a new store builder instance with no names. + * + * @return \Dotenv\Store\StoreBuilder + */ + public static function createWithNoNames() + { + return new self(); + } + + /** + * Create a new store builder instance with the default name. + * + * @return \Dotenv\Store\StoreBuilder + */ + public static function createWithDefaultName() + { + return new self([], [self::DEFAULT_NAME]); + } + + /** + * Creates a store builder with the given path added. + * + * @param string $path + * + * @return \Dotenv\Store\StoreBuilder + */ + public function addPath(string $path) + { + return new self(\array_merge($this->paths, [$path]), $this->names, $this->shortCircuit, $this->fileEncoding); + } + + /** + * Creates a store builder with the given name added. + * + * @param string $name + * + * @return \Dotenv\Store\StoreBuilder + */ + public function addName(string $name) + { + return new self($this->paths, \array_merge($this->names, [$name]), $this->shortCircuit, $this->fileEncoding); + } + + /** + * Creates a store builder with short circuit mode enabled. + * + * @return \Dotenv\Store\StoreBuilder + */ + public function shortCircuit() + { + return new self($this->paths, $this->names, true, $this->fileEncoding); + } + + /** + * Creates a store builder with the specified file encoding. + * + * @param string|null $fileEncoding + * + * @return \Dotenv\Store\StoreBuilder + */ + public function fileEncoding(string $fileEncoding = null) + { + return new self($this->paths, $this->names, $this->shortCircuit, $fileEncoding); + } + + /** + * Creates a new store instance. + * + * @return \Dotenv\Store\StoreInterface + */ + public function make() + { + return new FileStore( + Paths::filePaths($this->paths, $this->names), + $this->shortCircuit, + $this->fileEncoding + ); + } +} diff --git a/vendor/vlucas/phpdotenv/src/Store/StoreInterface.php b/vendor/vlucas/phpdotenv/src/Store/StoreInterface.php new file mode 100644 index 0000000..6f5b986 --- /dev/null +++ b/vendor/vlucas/phpdotenv/src/Store/StoreInterface.php @@ -0,0 +1,17 @@ +content = $content; + } + + /** + * Read the content of the environment file(s). + * + * @return string + */ + public function read() + { + return $this->content; + } +} diff --git a/vendor/vlucas/phpdotenv/src/Util/Regex.php b/vendor/vlucas/phpdotenv/src/Util/Regex.php new file mode 100644 index 0000000..52c1578 --- /dev/null +++ b/vendor/vlucas/phpdotenv/src/Util/Regex.php @@ -0,0 +1,112 @@ + + */ + public static function matches(string $pattern, string $subject) + { + return self::pregAndWrap(static function (string $subject) use ($pattern) { + return @\preg_match($pattern, $subject) === 1; + }, $subject); + } + + /** + * Perform a preg match all, wrapping up the result. + * + * @param string $pattern + * @param string $subject + * + * @return \GrahamCampbell\ResultType\Result + */ + public static function occurrences(string $pattern, string $subject) + { + return self::pregAndWrap(static function (string $subject) use ($pattern) { + return (int) @\preg_match_all($pattern, $subject); + }, $subject); + } + + /** + * Perform a preg replace callback, wrapping up the result. + * + * @param string $pattern + * @param callable $callback + * @param string $subject + * @param int|null $limit + * + * @return \GrahamCampbell\ResultType\Result + */ + public static function replaceCallback(string $pattern, callable $callback, string $subject, int $limit = null) + { + return self::pregAndWrap(static function (string $subject) use ($pattern, $callback, $limit) { + return (string) @\preg_replace_callback($pattern, $callback, $subject, $limit ?? -1); + }, $subject); + } + + /** + * Perform a preg split, wrapping up the result. + * + * @param string $pattern + * @param string $subject + * + * @return \GrahamCampbell\ResultType\Result + */ + public static function split(string $pattern, string $subject) + { + return self::pregAndWrap(static function (string $subject) use ($pattern) { + /** @var string[] */ + return (array) @\preg_split($pattern, $subject); + }, $subject); + } + + /** + * Perform a preg operation, wrapping up the result. + * + * @template V + * + * @param callable(string):V $operation + * @param string $subject + * + * @return \GrahamCampbell\ResultType\Result + */ + private static function pregAndWrap(callable $operation, string $subject) + { + $result = $operation($subject); + + if (\preg_last_error() !== \PREG_NO_ERROR) { + /** @var \GrahamCampbell\ResultType\Result */ + return Error::create(\preg_last_error_msg()); + } + + /** @var \GrahamCampbell\ResultType\Result */ + return Success::create($result); + } +} diff --git a/vendor/vlucas/phpdotenv/src/Util/Str.php b/vendor/vlucas/phpdotenv/src/Util/Str.php new file mode 100644 index 0000000..087e236 --- /dev/null +++ b/vendor/vlucas/phpdotenv/src/Util/Str.php @@ -0,0 +1,98 @@ + + */ + public static function utf8(string $input, string $encoding = null) + { + if ($encoding !== null && !\in_array($encoding, \mb_list_encodings(), true)) { + /** @var \GrahamCampbell\ResultType\Result */ + return Error::create( + \sprintf('Illegal character encoding [%s] specified.', $encoding) + ); + } + $converted = $encoding === null ? + @\mb_convert_encoding($input, 'UTF-8') : + @\mb_convert_encoding($input, 'UTF-8', $encoding); + /** + * this is for support UTF-8 with BOM encoding + * @see https://en.wikipedia.org/wiki/Byte_order_mark + * @see https://github.com/vlucas/phpdotenv/issues/500 + */ + if (\substr($converted, 0, 3) == "\xEF\xBB\xBF") { + $converted = \substr($converted, 3); + } + /** @var \GrahamCampbell\ResultType\Result */ + return Success::create($converted); + } + + /** + * Search for a given substring of the input. + * + * @param string $haystack + * @param string $needle + * + * @return \PhpOption\Option + */ + public static function pos(string $haystack, string $needle) + { + /** @var \PhpOption\Option */ + return Option::fromValue(\mb_strpos($haystack, $needle, 0, 'UTF-8'), false); + } + + /** + * Grab the specified substring of the input. + * + * @param string $input + * @param int $start + * @param int|null $length + * + * @return string + */ + public static function substr(string $input, int $start, int $length = null) + { + return \mb_substr($input, $start, $length, 'UTF-8'); + } + + /** + * Compute the length of the given string. + * + * @param string $input + * + * @return int + */ + public static function len(string $input) + { + return \mb_strlen($input, 'UTF-8'); + } +} diff --git a/vendor/vlucas/phpdotenv/src/Validator.php b/vendor/vlucas/phpdotenv/src/Validator.php new file mode 100644 index 0000000..0c04ab6 --- /dev/null +++ b/vendor/vlucas/phpdotenv/src/Validator.php @@ -0,0 +1,209 @@ +repository = $repository; + $this->variables = $variables; + } + + /** + * Assert that each variable is present. + * + * @throws \Dotenv\Exception\ValidationException + * + * @return \Dotenv\Validator + */ + public function required() + { + return $this->assert( + static function (?string $value) { + return $value !== null; + }, + 'is missing' + ); + } + + /** + * Assert that each variable is not empty. + * + * @throws \Dotenv\Exception\ValidationException + * + * @return \Dotenv\Validator + */ + public function notEmpty() + { + return $this->assertNullable( + static function (string $value) { + return Str::len(\trim($value)) > 0; + }, + 'is empty' + ); + } + + /** + * Assert that each specified variable is an integer. + * + * @throws \Dotenv\Exception\ValidationException + * + * @return \Dotenv\Validator + */ + public function isInteger() + { + return $this->assertNullable( + static function (string $value) { + return \ctype_digit($value); + }, + 'is not an integer' + ); + } + + /** + * Assert that each specified variable is a boolean. + * + * @throws \Dotenv\Exception\ValidationException + * + * @return \Dotenv\Validator + */ + public function isBoolean() + { + return $this->assertNullable( + static function (string $value) { + if ($value === '') { + return false; + } + + return \filter_var($value, \FILTER_VALIDATE_BOOLEAN, \FILTER_NULL_ON_FAILURE) !== null; + }, + 'is not a boolean' + ); + } + + /** + * Assert that each variable is amongst the given choices. + * + * @param string[] $choices + * + * @throws \Dotenv\Exception\ValidationException + * + * @return \Dotenv\Validator + */ + public function allowedValues(array $choices) + { + return $this->assertNullable( + static function (string $value) use ($choices) { + return \in_array($value, $choices, true); + }, + \sprintf('is not one of [%s]', \implode(', ', $choices)) + ); + } + + /** + * Assert that each variable matches the given regular expression. + * + * @param string $regex + * + * @throws \Dotenv\Exception\ValidationException + * + * @return \Dotenv\Validator + */ + public function allowedRegexValues(string $regex) + { + return $this->assertNullable( + static function (string $value) use ($regex) { + return Regex::matches($regex, $value)->success()->getOrElse(false); + }, + \sprintf('does not match "%s"', $regex) + ); + } + + /** + * Assert that the callback returns true for each variable. + * + * @param callable(?string):bool $callback + * @param string $message + * + * @throws \Dotenv\Exception\ValidationException + * + * @return \Dotenv\Validator + */ + public function assert(callable $callback, string $message) + { + $failing = []; + + foreach ($this->variables as $variable) { + if ($callback($this->repository->get($variable)) === false) { + $failing[] = \sprintf('%s %s', $variable, $message); + } + } + + if (\count($failing) > 0) { + throw new ValidationException(\sprintf( + 'One or more environment variables failed assertions: %s.', + \implode(', ', $failing) + )); + } + + return $this; + } + + /** + * Assert that the callback returns true for each variable. + * + * Skip checking null variable values. + * + * @param callable(string):bool $callback + * @param string $message + * + * @throws \Dotenv\Exception\ValidationException + * + * @return \Dotenv\Validator + */ + public function assertNullable(callable $callback, string $message) + { + return $this->assert( + static function (?string $value) use ($callback) { + if ($value === null) { + return true; + } + + return $callback($value); + }, + $message + ); + } +} diff --git a/vendor/voku/portable-ascii/.whitesource b/vendor/voku/portable-ascii/.whitesource new file mode 100644 index 0000000..55b922e --- /dev/null +++ b/vendor/voku/portable-ascii/.whitesource @@ -0,0 +1,12 @@ +{ + "scanSettings": { + "baseBranches": [] + }, + "checkRunSettings": { + "vulnerableCheckRunConclusionLevel": "failure", + "displayMode": "diff" + }, + "issueSettings": { + "minSeverityLevel": "LOW" + } +} \ No newline at end of file diff --git a/vendor/voku/portable-ascii/CHANGELOG.md b/vendor/voku/portable-ascii/CHANGELOG.md new file mode 100644 index 0000000..04b64ac --- /dev/null +++ b/vendor/voku/portable-ascii/CHANGELOG.md @@ -0,0 +1,191 @@ +# Changelog + +### 1.6.1 (2021-01-24) + +- revert: prefer "Russian - Passport (2013), ICAO" instead of "Russian - GOST 7.79-2000(B)" +- revert: fix "Ukrainian" char-mapping (thanks to @Andr1yk0) +- revert: fix "Persian" char-mapping (thanks to @frost-cyber) + +### 1.6.0 (2021-01-24) + +- prefer "Russian - Passport (2013), ICAO" instead of "Russian - GOST 7.79-2000(B)" +- fix "Ukrainian" char-mapping (thanks to @Andr1yk0) +- fix "Persian" char-mapping (thanks to @frost-cyber) +- fix "ASCII::normalize_whitespace()" -> "CARRIAGE RETURN" is more like "
    " and no "\n" +- add "ASCII::to_ascii_remap()" -> this method will return broken characters and is only for special cases + +### 1.5.6 (2020-11-12) + +- "ASCII::normalize_whitespace()" -> can now also remove "control characters" if needed v2 + +### 1.5.5 (2020-11-12) + +- fix "Greeklish" char-mapping (thanks @sebdesign) +- "ASCII::normalize_whitespace()" -> can now also remove "control characters" if needed + +### 1.5.4 (2020-11-08) + +- add some missing replacements in U+23xx page (thanks @marcoffee) +- fix "Russian" char-mapping (thanks @ilyahoilik) +- running test with PHP 8.0 rc3 + +### 1.5.3 (2020-07-23) + +- fix "Georgian" char-mapping (thanks @waska14) + +### 1.5.2 (2020-06-16) + +- add "Bengali" (bn) language support (thanks @eliyas5044) +- fix "Portuguese" char-mapping +- reduce the file size (removed extra comments from "avian2/unidecode") + +### 1.5.1 (2020-05-26) + +- fix merge ASCII transliterations from "avian2/unidecode" (python) + -> https://github.com/avian2/unidecode/ + +### 1.5.0 (2020-05-24) + +- merge ASCII transliterations from "avian2/unidecode" (python) + -> https://github.com/avian2/unidecode/ + +### 1.4.11 (2020-05-23) + +- "composer.json" -> remove "autoload-dev" stuff from "autoload" +- "voku/php-readme-helper" -> auto-generate the API documentation in the README + +### 1.4.10 (2020-03-13) + +- ASCII::to_ascii() -> fix extra symbol handling in the regex +- ASCII::to_ascii() -> fix for languages with multi-length-special-char (e.g. Greek -> 'ει' => 'i') + +### 1.4.9 (2020-03-06) + +- ASCII::to_slugify() -> fix php warning from empty "separator" + +### 1.4.8 (2020-02-06) + +- small optimization for "ASCII::to_ascii()" performance + +### 1.4.7 (2020-01-27) + +- fix possible wrong type from "getDataIfExists()" -> e.g. a bug reported where "/data/" was modified +- inline variables +- do not use "=== true" for "bool"-types + +### 1.4.6 (2019-12-23) + +- optimize "ASCII::to_ascii()" performance +- add "armenian" chars +- add "ASCII:getAllLanguages()" + +### 1.4.5 (2019-12-19) + +- use "@psalm-pure" v2 + +### 1.4.4 (2019-12-19) + +- use "@psalm-pure" + +### 1.4.3 (2019-12-19) + +- use "@psalm-immutable" + +### 1.4.2 (2019-12-13) + +- optimize the performance v2 +- more fixes for non-ascii regex + +### 1.4.1 (2019-12-13) + +- fix regex for non-ascii + +### 1.4.0 (2019-12-13) + +- optimize the performance, via single char replacements + +### 1.3.6 (2019-12-13) + +- "ascii_extras" -> convert the static content into ascii + -> e.g.: instead of replacing "+" with "più" we use "piu" (Italian), because we want to use ascii anyway + +### 1.3.5 (2019-11-11) + +- fix "ASCII::remove_invisible_characters()" -> do not remove invisible encoded url strings by default + +### 1.3.4 (2019-10-14) + +- fix static cache for "ASCII::charsArrayWithOneLanguage" + +### 1.3.3 (2019-10-14) + +- fix "Turkish" mapping -> 'ä' -> 'a' + +### 1.3.2 (2019-10-14) + +- fix language parameter usage with e.g. "de_DE" +- re-add missing "extra"-mapping chars + +### 1.3.1 (2019-10-13) + +- fix "ASCII::to_slugify" -> remove unicode chars +- add more test for ascii chars in the mapping +- fix non ascii chars in the mapping + +### 1.3.0 (2019-10-12) + +- add transliteration "fr" (was supported before, but with chars from other languages) +- add transliteration "ru" - Passport (2013), ICAO +- add transliteration "ru" - GOST 7.79-2000(B) +- add transliteration "el" - greeklish +- add transliteration "zh" +- add transliteration "nl" +- add transliteration "it" +- add transliteration "mk" +- add transliteration "pt" +- add constants -> ASCII::*LANGUAGE_CODES +- add more special latin chars / (currency) symbols +- add simple tests for all supported languages +- optimize "Russian" to ASCII (via "translit.ru") +- optimize performance of string replacement +- optimize performance of array merging +- optimize phpdoc comments +- "ASCII::to_transliterate" -> use "transliterator_create" + static cache +- "ASCII::to_ascii" -> fix "remove unsupported chars" +- "ASCII::to_ascii" -> add some more special chars +- run/fix static analyse via "pslam" + "phpstan" +- auto fix code style via "php-cs-fixer" +- fix transliteration for "german" +- fix transliteration for "persian" (thanks @mardep) +- fix transliteration for "polish" (thanks @dariusz.drobisz) +- fix transliteration for "bulgarian" (thanks @mkosturkov) +- fix transliteration for "croatian" (thanks @ludifonovac) +- fix transliteration for "serbian" (thanks @ludifonovac) +- fix transliteration for "swedish" (thanks @nicholasruunu) +- fix transliteration for "france" (thanks @sharptsa) +- fix transliteration for "serbian" (thanks @nikolaposa) +- fix transliteration for "czech" (thanks @slepic) + +### 1.2.3 (2019-09-10) + +- fix language depending ASCII chars (the order matters) + +### 1.2.2 (2019-09-10) + +- fix bulgarian ASCII chars | thanks @bgphp + +### 1.2.1 (2019-09-07) + +- "charsArray()" -> add access to "ASCII::$ASCII_MAPS*"" + +### 1.2.0 (2019-09-07) + +- "to_slugify()" -> use the extra ascii array + +### 1.1.0 (2019-09-07) + +- add + split extra ascii replacements + +### 1.0.0 (2019-09-05) + +- initial commit \ No newline at end of file diff --git a/vendor/voku/portable-ascii/LICENSE.txt b/vendor/voku/portable-ascii/LICENSE.txt new file mode 100644 index 0000000..b6ba47e --- /dev/null +++ b/vendor/voku/portable-ascii/LICENSE.txt @@ -0,0 +1,19 @@ +Copyright (C) 2019 Lars Moelleken + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/voku/portable-ascii/README.md b/vendor/voku/portable-ascii/README.md new file mode 100644 index 0000000..929c21e --- /dev/null +++ b/vendor/voku/portable-ascii/README.md @@ -0,0 +1,428 @@ +[//]: # (AUTO-GENERATED BY "PHP README Helper": base file -> docs/base.md) +[![Build Status](https://github.com/voku/portable-ascii/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/voku/portable-ascii/actions) +[![Build status](https://ci.appveyor.com/api/projects/status/gnejjnk7qplr7f5t/branch/master?svg=true)](https://ci.appveyor.com/project/voku/portable-ascii/branch/master) +[![codecov.io](https://codecov.io/github/voku/portable-ascii/coverage.svg?branch=master)](https://codecov.io/github/voku/portable-ascii?branch=master) +[![Codacy Badge](https://api.codacy.com/project/badge/Grade/997c9bb10d1c4791967bdf2e42013e8e)](https://www.codacy.com/app/voku/portable-ascii) +[![Latest Stable Version](https://poser.pugx.org/voku/portable-ascii/v/stable)](https://packagist.org/packages/voku/portable-ascii) +[![Total Downloads](https://poser.pugx.org/voku/portable-ascii/downloads)](https://packagist.org/packages/voku/portable-ascii) +[![License](https://poser.pugx.org/voku/portable-ascii/license)](https://packagist.org/packages/voku/portable-ascii) +[![Donate to this project using Paypal](https://img.shields.io/badge/paypal-donate-yellow.svg)](https://www.paypal.me/moelleken) +[![Donate to this project using Patreon](https://img.shields.io/badge/patreon-donate-yellow.svg)](https://www.patreon.com/voku) + +# 🔡 Portable ASCII + +## Description + +It is written in PHP (PHP 7+) and can work without "mbstring", "iconv" or any other extra encoding php-extension on your server. + +The benefit of Portable ASCII is that it is easy to use, easy to bundle. + +The project based on ... ++ Sean M. Burke's work (https://metacpan.org/pod/Text::Unidecode) ++ Tomaz Solc's work (https://pypi.org/project/Unidecode/) ++ Portable UTF-8 work (https://github.com/voku/portable-utf8) ++ Daniel St. Jules's work (https://github.com/danielstjules/Stringy) ++ Johnny Broadway's work (https://github.com/jbroadway/urlify) ++ and many cherry-picks from "github"-gists and "Stack Overflow"-snippets ... + +## Index + +* [Alternative](#alternative) +* [Install](#install-portable-ascii-via-composer-require) +* [Why Portable ASCII?](#why-portable-ascii) +* [Requirements and Recommendations](#requirements-and-recommendations) +* [Usage](#usage) +* [Class methods](#class-methods) +* [Unit Test](#unit-test) +* [License and Copyright](#license-and-copyright) + +## Alternative + +If you like a more Object Oriented Way to edit strings, then you can take a look at [voku/Stringy](https://github.com/voku/Stringy), it's a fork of "danielstjules/Stringy" but it used the "Portable ASCII"-Class and some extra methods. + +```php +// Portable ASCII +use voku\helper\ASCII; +ASCII::to_transliterate('déjà σσς iıii'); // 'deja sss iiii' + +// voku/Stringy +use Stringy\Stringy as S; +$stringy = S::create('déjà σσς iıii'); +$stringy->toTransliterate(); // 'deja sss iiii' +``` + +## Install "Portable ASCII" via "composer require" +```shell +composer require voku/portable-ascii +``` + +## Why Portable ASCII?[]() +I need ASCII char handling in different classes and before I added this functions into "Portable UTF-8", +but this repo is more modular and portable, because it has no dependencies. + +## Requirements and Recommendations + +* No extensions are required to run this library. Portable ASCII only needs PCRE library that is available by default since PHP 4.2.0 and cannot be disabled since PHP 5.3.0. "\u" modifier support in PCRE for ASCII handling is not a must. +* PHP 7.0 is the minimum requirement +* PHP 8.0 is also supported + +## Usage + +Example: ASCII::to_ascii() +```php + echo ASCII::to_ascii('�Düsseldorf�', 'de'); + + // will output + // Duesseldorf + + echo ASCII::to_ascii('�Düsseldorf�', 'en'); + + // will output + // Dusseldorf +``` + +# Portable ASCII | API + +The API from the "ASCII"-Class is written as small static methods. + + +## Class methods + +

    charsArray +charsArrayWithMultiLanguageValues +charsArrayWithOneLanguage +charsArrayWithSingleLanguageValues +
    clean +getAllLanguages +is_ascii +normalize_msword +
    normalize_whitespace +remove_invisible_characters +to_ascii +to_filename +
    to_slugify +to_transliterate +
    + +#### charsArray(bool $replace_extra_symbols): array + +Returns an replacement array for ASCII methods. + +EXAMPLE: +$array = ASCII::charsArray(); +var_dump($array['ru']['б']); // 'b' + + +**Parameters:** +- `bool $replace_extra_symbols [optional]

    Add some more replacements e.g. "£" with " pound ".

    ` + +**Return:** +- `array` + +-------- + +#### charsArrayWithMultiLanguageValues(bool $replace_extra_symbols): array + +Returns an replacement array for ASCII methods with a mix of multiple languages. + +EXAMPLE: +$array = ASCII::charsArrayWithMultiLanguageValues(); +var_dump($array['b']); // ['β', 'б', 'ဗ', 'ბ', 'ب'] + + +**Parameters:** +- `bool $replace_extra_symbols [optional]

    Add some more replacements e.g. "£" with " pound ".

    ` + +**Return:** +- `array

    An array of replacements.

    ` + +-------- + +#### charsArrayWithOneLanguage(string $language, bool $replace_extra_symbols, bool $asOrigReplaceArray): array + +Returns an replacement array for ASCII methods with one language. + +For example, German will map 'ä' to 'ae', while other languages +will simply return e.g. 'a'. + +EXAMPLE: +$array = ASCII::charsArrayWithOneLanguage('ru'); +$tmpKey = \array_search('yo', $array['replace']); +echo $array['orig'][$tmpKey]; // 'ё' + + +**Parameters:** +- `string $language [optional]

    Language of the source string e.g.: en, de_at, or de-ch. +(default is 'en') | ASCII::*_LANGUAGE_CODE

    ` +- `bool $replace_extra_symbols [optional]

    Add some more replacements e.g. "£" with " pound ".

    ` +- `bool $asOrigReplaceArray [optional]

    TRUE === return {orig: string[], replace: string[]} +array

    ` + +**Return:** +- `array

    An array of replacements.

    ` + +-------- + +#### charsArrayWithSingleLanguageValues(bool $replace_extra_symbols, bool $asOrigReplaceArray): array + +Returns an replacement array for ASCII methods with multiple languages. + +EXAMPLE: +$array = ASCII::charsArrayWithSingleLanguageValues(); +$tmpKey = \array_search('hnaik', $array['replace']); +echo $array['orig'][$tmpKey]; // '၌' + + +**Parameters:** +- `bool $replace_extra_symbols [optional]

    Add some more replacements e.g. "£" with " pound ".

    ` +- `bool $asOrigReplaceArray [optional]

    TRUE === return {orig: string[], replace: string[]} +array

    ` + +**Return:** +- `array

    An array of replacements.

    ` + +-------- + +#### clean(string $str, bool $normalize_whitespace, bool $keep_non_breaking_space, bool $normalize_msword, bool $remove_invisible_characters): string + +Accepts a string and removes all non-UTF-8 characters from it + extras if needed. + +**Parameters:** +- `string $str

    The string to be sanitized.

    ` +- `bool $normalize_whitespace [optional]

    Set to true, if you need to normalize the +whitespace.

    ` +- `bool $keep_non_breaking_space [optional]

    Set to true, to keep non-breaking-spaces, in +combination with +$normalize_whitespace

    ` +- `bool $normalize_msword [optional]

    Set to true, if you need to normalize MS Word chars +e.g.: "…" +=> "..."

    ` +- `bool $remove_invisible_characters [optional]

    Set to false, if you not want to remove invisible +characters e.g.: "\0"

    ` + +**Return:** +- `string

    A clean UTF-8 string.

    ` + +-------- + +#### getAllLanguages(): string[] + +Get all languages from the constants "ASCII::.*LANGUAGE_CODE". + +**Parameters:** +__nothing__ + +**Return:** +- `string[]` + +-------- + +#### is_ascii(string $str): bool + +Checks if a string is 7 bit ASCII. + +EXAMPLE: +ASCII::is_ascii('白'); // false + + +**Parameters:** +- `string $str

    The string to check.

    ` + +**Return:** +- `bool

    +true if it is ASCII
    +false otherwise +

    ` + +-------- + +#### normalize_msword(string $str): string + +Returns a string with smart quotes, ellipsis characters, and dashes from +Windows-1252 (commonly used in Word documents) replaced by their ASCII +equivalents. + +EXAMPLE: +ASCII::normalize_msword('„Abcdef…”'); // '"Abcdef..."' + + +**Parameters:** +- `string $str

    The string to be normalized.

    ` + +**Return:** +- `string

    A string with normalized characters for commonly used chars in Word documents.

    ` + +-------- + +#### normalize_whitespace(string $str, bool $keepNonBreakingSpace, bool $keepBidiUnicodeControls, bool $normalize_control_characters): string + +Normalize the whitespace. + +EXAMPLE: +ASCII::normalize_whitespace("abc-\xc2\xa0-öäü-\xe2\x80\xaf-\xE2\x80\xAC", true); // "abc-\xc2\xa0-öäü- -" + + +**Parameters:** +- `string $str

    The string to be normalized.

    ` +- `bool $keepNonBreakingSpace [optional]

    Set to true, to keep non-breaking-spaces.

    ` +- `bool $keepBidiUnicodeControls [optional]

    Set to true, to keep non-printable (for the web) +bidirectional text chars.

    ` +- `bool $normalize_control_characters [optional]

    Set to true, to convert e.g. LINE-, PARAGRAPH-SEPARATOR with "\n" and LINE TABULATION with "\t".

    ` + +**Return:** +- `string

    A string with normalized whitespace.

    ` + +-------- + +#### remove_invisible_characters(string $str, bool $url_encoded, string $replacement, bool $keep_basic_control_characters): string + +Remove invisible characters from a string. + +e.g.: This prevents sandwiching null characters between ascii characters, like Java\0script. + +copy&past from https://github.com/bcit-ci/CodeIgniter/blob/develop/system/core/Common.php + +**Parameters:** +- `string $str` +- `bool $url_encoded` +- `string $replacement` +- `bool $keep_basic_control_characters` + +**Return:** +- `string` + +-------- + +#### to_ascii(string $str, string $language, bool $remove_unsupported_chars, bool $replace_extra_symbols, bool $use_transliterate, bool|null $replace_single_chars_only): string + +Returns an ASCII version of the string. A set of non-ASCII characters are +replaced with their closest ASCII counterparts, and the rest are removed +by default. The language or locale of the source string can be supplied +for language-specific transliteration in any of the following formats: +en, en_GB, or en-GB. For example, passing "de" results in "äöü" mapping +to "aeoeue" rather than "aou" as in other languages. + +EXAMPLE: +ASCII::to_ascii('�Düsseldorf�', 'en'); // Dusseldorf + + +**Parameters:** +- `string $str

    The input string.

    ` +- `string $language [optional]

    Language of the source string. +(default is 'en') | ASCII::*_LANGUAGE_CODE

    ` +- `bool $remove_unsupported_chars [optional]

    Whether or not to remove the +unsupported characters.

    ` +- `bool $replace_extra_symbols [optional]

    Add some more replacements e.g. "£" with " pound +".

    ` +- `bool $use_transliterate [optional]

    Use ASCII::to_transliterate() for unknown chars.

    ` +- `bool|null $replace_single_chars_only [optional]

    Single char replacement is better for the +performance, but some languages need to replace more then one char +at the same time. | NULL === auto-setting, depended on the +language

    ` + +**Return:** +- `string

    A string that contains only ASCII characters.

    ` + +-------- + +#### to_filename(string $str, bool $use_transliterate, string $fallback_char): string + +Convert given string to safe filename (and keep string case). + +EXAMPLE: +ASCII::to_filename('שדגשדג.png', true)); // 'shdgshdg.png' + + +**Parameters:** +- `string $str` +- `bool $use_transliterate

    ASCII::to_transliterate() is used by default - unsafe characters are +simply replaced with hyphen otherwise.

    ` +- `string $fallback_char` + +**Return:** +- `string

    A string that contains only safe characters for a filename.

    ` + +-------- + +#### to_slugify(string $str, string $separator, string $language, string[] $replacements, bool $replace_extra_symbols, bool $use_str_to_lower, bool $use_transliterate): string + +Converts the string into an URL slug. This includes replacing non-ASCII +characters with their closest ASCII equivalents, removing remaining +non-ASCII and non-alphanumeric characters, and replacing whitespace with +$separator. The separator defaults to a single dash, and the string +is also converted to lowercase. The language of the source string can +also be supplied for language-specific transliteration. + +**Parameters:** +- `string $str` +- `string $separator [optional]

    The string used to replace whitespace.

    ` +- `string $language [optional]

    Language of the source string. +(default is 'en') | ASCII::*_LANGUAGE_CODE

    ` +- `array $replacements [optional]

    A map of replaceable strings.

    ` +- `bool $replace_extra_symbols [optional]

    Add some more replacements e.g. "£" with " +pound ".

    ` +- `bool $use_str_to_lower [optional]

    Use "string to lower" for the input.

    ` +- `bool $use_transliterate [optional]

    Use ASCII::to_transliterate() for unknown +chars.

    ` + +**Return:** +- `string

    A string that has been converted to an URL slug.

    ` + +-------- + +#### to_transliterate(string $str, string|null $unknown, bool $strict): string + +Returns an ASCII version of the string. A set of non-ASCII characters are +replaced with their closest ASCII counterparts, and the rest are removed +unless instructed otherwise. + +EXAMPLE: +ASCII::to_transliterate('déjà σσς iıii'); // 'deja sss iiii' + + +**Parameters:** +- `string $str

    The input string.

    ` +- `string|null $unknown [optional]

    Character use if character unknown. (default is '?') +But you can also use NULL to keep the unknown chars.

    ` +- `bool $strict [optional]

    Use "transliterator_transliterate()" from PHP-Intl` + +**Return:** +- `string

    A String that contains only ASCII characters.

    ` + +-------- + + + +## Unit Test + +1) [Composer](https://getcomposer.org) is a prerequisite for running the tests. + +``` +composer install +``` + +2) The tests can be executed by running this command from the root directory: + +```bash +./vendor/bin/phpunit +``` + +### Support + +For support and donations please visit [Github](https://github.com/voku/portable-ascii/) | [Issues](https://github.com/voku/portable-ascii/issues) | [PayPal](https://paypal.me/moelleken) | [Patreon](https://www.patreon.com/voku). + +For status updates and release announcements please visit [Releases](https://github.com/voku/portable-ascii/releases) | [Twitter](https://twitter.com/suckup_de) | [Patreon](https://www.patreon.com/voku/posts). + +For professional support please contact [me](https://about.me/voku). + +### Thanks + +- Thanks to [GitHub](https://github.com) (Microsoft) for hosting the code and a good infrastructure including Issues-Managment, etc. +- Thanks to [IntelliJ](https://www.jetbrains.com) as they make the best IDEs for PHP and they gave me an open source license for PhpStorm! +- Thanks to [Travis CI](https://travis-ci.com/) for being the most awesome, easiest continous integration tool out there! +- Thanks to [StyleCI](https://styleci.io/) for the simple but powerful code style check. +- Thanks to [PHPStan](https://github.com/phpstan/phpstan) && [Psalm](https://github.com/vimeo/psalm) for really great Static analysis tools and for discover bugs in the code! + +### License and Copyright + +Released under the MIT License - see `LICENSE.txt` for details. diff --git a/vendor/voku/portable-ascii/build/docs/base.md b/vendor/voku/portable-ascii/build/docs/base.md new file mode 100644 index 0000000..ca949d8 --- /dev/null +++ b/vendor/voku/portable-ascii/build/docs/base.md @@ -0,0 +1,127 @@ +[![Build Status](https://github.com/voku/portable-ascii/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/voku/portable-ascii/actions) +[![Build status](https://ci.appveyor.com/api/projects/status/gnejjnk7qplr7f5t/branch/master?svg=true)](https://ci.appveyor.com/project/voku/portable-ascii/branch/master) +[![codecov.io](https://codecov.io/github/voku/portable-ascii/coverage.svg?branch=master)](https://codecov.io/github/voku/portable-ascii?branch=master) +[![Codacy Badge](https://api.codacy.com/project/badge/Grade/997c9bb10d1c4791967bdf2e42013e8e)](https://www.codacy.com/app/voku/portable-ascii) +[![Latest Stable Version](https://poser.pugx.org/voku/portable-ascii/v/stable)](https://packagist.org/packages/voku/portable-ascii) +[![Total Downloads](https://poser.pugx.org/voku/portable-ascii/downloads)](https://packagist.org/packages/voku/portable-ascii) +[![License](https://poser.pugx.org/voku/portable-ascii/license)](https://packagist.org/packages/voku/portable-ascii) +[![Donate to this project using Paypal](https://img.shields.io/badge/paypal-donate-yellow.svg)](https://www.paypal.me/moelleken) +[![Donate to this project using Patreon](https://img.shields.io/badge/patreon-donate-yellow.svg)](https://www.patreon.com/voku) + +# 🔡 Portable ASCII + +## Description + +It is written in PHP (PHP 7+) and can work without "mbstring", "iconv" or any other extra encoding php-extension on your server. + +The benefit of Portable ASCII is that it is easy to use, easy to bundle. + +The project based on ... ++ Sean M. Burke's work (https://metacpan.org/pod/Text::Unidecode) ++ Tomaz Solc's work (https://pypi.org/project/Unidecode/) ++ Portable UTF-8 work (https://github.com/voku/portable-utf8) ++ Daniel St. Jules's work (https://github.com/danielstjules/Stringy) ++ Johnny Broadway's work (https://github.com/jbroadway/urlify) ++ and many cherry-picks from "github"-gists and "Stack Overflow"-snippets ... + +## Index + +* [Alternative](#alternative) +* [Install](#install-portable-ascii-via-composer-require) +* [Why Portable ASCII?](#why-portable-ascii) +* [Requirements and Recommendations](#requirements-and-recommendations) +* [Usage](#usage) +* [Class methods](#class-methods) +* [Unit Test](#unit-test) +* [License and Copyright](#license-and-copyright) + +## Alternative + +If you like a more Object Oriented Way to edit strings, then you can take a look at [voku/Stringy](https://github.com/voku/Stringy), it's a fork of "danielstjules/Stringy" but it used the "Portable ASCII"-Class and some extra methods. + +```php +// Portable ASCII +use voku\helper\ASCII; +ASCII::to_transliterate('déjà σσς iıii'); // 'deja sss iiii' + +// voku/Stringy +use Stringy\Stringy as S; +$stringy = S::create('déjà σσς iıii'); +$stringy->toTransliterate(); // 'deja sss iiii' +``` + +## Install "Portable ASCII" via "composer require" +```shell +composer require voku/portable-ascii +``` + +## Why Portable ASCII?[]() +I need ASCII char handling in different classes and before I added this functions into "Portable UTF-8", +but this repo is more modular and portable, because it has no dependencies. + +## Requirements and Recommendations + +* No extensions are required to run this library. Portable ASCII only needs PCRE library that is available by default since PHP 4.2.0 and cannot be disabled since PHP 5.3.0. "\u" modifier support in PCRE for ASCII handling is not a must. +* PHP 7.0 is the minimum requirement +* PHP 8.0 is also supported + +## Usage + +Example: ASCII::to_ascii() +```php + echo ASCII::to_ascii('�Düsseldorf�', 'de'); + + // will output + // Duesseldorf + + echo ASCII::to_ascii('�Düsseldorf�', 'en'); + + // will output + // Dusseldorf +``` + +# Portable ASCII | API + +The API from the "ASCII"-Class is written as small static methods. + + +## Class methods + +%__functions_index__voku\helper\ASCII__% + +%__functions_list__voku\helper\ASCII__% + + +## Unit Test + +1) [Composer](https://getcomposer.org) is a prerequisite for running the tests. + +``` +composer install +``` + +2) The tests can be executed by running this command from the root directory: + +```bash +./vendor/bin/phpunit +``` + +### Support + +For support and donations please visit [Github](https://github.com/voku/portable-ascii/) | [Issues](https://github.com/voku/portable-ascii/issues) | [PayPal](https://paypal.me/moelleken) | [Patreon](https://www.patreon.com/voku). + +For status updates and release announcements please visit [Releases](https://github.com/voku/portable-ascii/releases) | [Twitter](https://twitter.com/suckup_de) | [Patreon](https://www.patreon.com/voku/posts). + +For professional support please contact [me](https://about.me/voku). + +### Thanks + +- Thanks to [GitHub](https://github.com) (Microsoft) for hosting the code and a good infrastructure including Issues-Managment, etc. +- Thanks to [IntelliJ](https://www.jetbrains.com) as they make the best IDEs for PHP and they gave me an open source license for PhpStorm! +- Thanks to [Travis CI](https://travis-ci.com/) for being the most awesome, easiest continous integration tool out there! +- Thanks to [StyleCI](https://styleci.io/) for the simple but powerful code style check. +- Thanks to [PHPStan](https://github.com/phpstan/phpstan) && [Psalm](https://github.com/vimeo/psalm) for really great Static analysis tools and for discover bugs in the code! + +### License and Copyright + +Released under the MIT License - see `LICENSE.txt` for details. diff --git a/vendor/voku/portable-ascii/build/generate_docs.php b/vendor/voku/portable-ascii/build/generate_docs.php new file mode 100644 index 0000000..c86f1f1 --- /dev/null +++ b/vendor/voku/portable-ascii/build/generate_docs.php @@ -0,0 +1,26 @@ +templateMethod = <<↑ +%description% + +**Parameters:** +%params% + +**Return:** +%return% + +-------- + +RAW; +$readmeText = ($readmeGenerator)->generate( + __DIR__ . '/../src/voku/helper/ASCII.php', + __DIR__ . '/docs/base.md' +); + +file_put_contents(__DIR__ . '/../README.md', $readmeText); diff --git a/vendor/voku/portable-ascii/build/generate_max_key_length.php b/vendor/voku/portable-ascii/build/generate_max_key_length.php new file mode 100644 index 0000000..743d389 --- /dev/null +++ b/vendor/voku/portable-ascii/build/generate_max_key_length.php @@ -0,0 +1,20 @@ +>|null + */ + private static $ASCII_MAPS; + + /** + * @var array>|null + */ + private static $ASCII_MAPS_AND_EXTRAS; + + /** + * @var array>|null + */ + private static $ASCII_EXTRAS; + + /** + * @var array|null + */ + private static $ORD; + + /** + * @var array|null + */ + private static $LANGUAGE_MAX_KEY; + + /** + * url: https://en.wikipedia.org/wiki/Wikipedia:ASCII#ASCII_printable_characters + * + * @var string + */ + private static $REGEX_ASCII = "[^\x09\x10\x13\x0A\x0D\x20-\x7E]"; + + /** + * bidirectional text chars + * + * url: https://www.w3.org/International/questions/qa-bidi-unicode-controls + * + * @var array + */ + private static $BIDI_UNI_CODE_CONTROLS_TABLE = [ + // LEFT-TO-RIGHT EMBEDDING (use -> dir = "ltr") + 8234 => "\xE2\x80\xAA", + // RIGHT-TO-LEFT EMBEDDING (use -> dir = "rtl") + 8235 => "\xE2\x80\xAB", + // POP DIRECTIONAL FORMATTING // (use -> ) + 8236 => "\xE2\x80\xAC", + // LEFT-TO-RIGHT OVERRIDE // (use -> ) + 8237 => "\xE2\x80\xAD", + // RIGHT-TO-LEFT OVERRIDE // (use -> ) + 8238 => "\xE2\x80\xAE", + // LEFT-TO-RIGHT ISOLATE // (use -> dir = "ltr") + 8294 => "\xE2\x81\xA6", + // RIGHT-TO-LEFT ISOLATE // (use -> dir = "rtl") + 8295 => "\xE2\x81\xA7", + // FIRST STRONG ISOLATE // (use -> dir = "auto") + 8296 => "\xE2\x81\xA8", + // POP DIRECTIONAL ISOLATE + 8297 => "\xE2\x81\xA9", + ]; + + /** + * Get all languages from the constants "ASCII::.*LANGUAGE_CODE". + * + * @return string[] + * + * @phpstan-return array + */ + public static function getAllLanguages(): array + { + // init + static $LANGUAGES = []; + + if ($LANGUAGES !== []) { + return $LANGUAGES; + } + + foreach ((new \ReflectionClass(__CLASS__))->getConstants() as $constant => $lang) { + if (\strpos($constant, 'EXTRA') !== false) { + $LANGUAGES[\strtolower($constant)] = $lang; + } else { + $LANGUAGES[\strtolower(\str_replace('_LANGUAGE_CODE', '', $constant))] = $lang; + } + } + + return $LANGUAGES; + } + + /** + * Returns an replacement array for ASCII methods. + * + * EXAMPLE: + * $array = ASCII::charsArray(); + * var_dump($array['ru']['б']); // 'b' + * + * + * @psalm-suppress InvalidNullableReturnType - we use the prepare* methods here, so we don't get NULL here + * + * @param bool $replace_extra_symbols [optional]

    Add some more replacements e.g. "£" with " pound ".

    + * + * @psalm-pure + * + * @return array + * + * @phpstan-return array> + */ + public static function charsArray(bool $replace_extra_symbols = false): array + { + if ($replace_extra_symbols) { + self::prepareAsciiAndExtrasMaps(); + + return self::$ASCII_MAPS_AND_EXTRAS ?? []; + } + + self::prepareAsciiMaps(); + + return self::$ASCII_MAPS ?? []; + } + + /** + * Returns an replacement array for ASCII methods with a mix of multiple languages. + * + * EXAMPLE: + * $array = ASCII::charsArrayWithMultiLanguageValues(); + * var_dump($array['b']); // ['β', 'б', 'ဗ', 'ბ', 'ب'] + * + * + * @param bool $replace_extra_symbols [optional]

    Add some more replacements e.g. "£" with " pound ".

    + * + * @psalm-pure + * + * @return array + *

    An array of replacements.

    + * + * @phpstan-return array> + */ + public static function charsArrayWithMultiLanguageValues(bool $replace_extra_symbols = false): array + { + /** + * @var array + */ + static $CHARS_ARRAY = []; + $cacheKey = '' . $replace_extra_symbols; + + if (isset($CHARS_ARRAY[$cacheKey])) { + return $CHARS_ARRAY[$cacheKey]; + } + + // init + $return = []; + $language_all_chars = self::charsArrayWithSingleLanguageValues( + $replace_extra_symbols, + false + ); + + /** @noinspection PhpSillyAssignmentInspection - hack for phpstan */ + /** @var array $language_all_chars */ + $language_all_chars = $language_all_chars; + + /** @noinspection AlterInForeachInspection */ + foreach ($language_all_chars as $key => &$value) { + $return[$value][] = $key; + } + + $CHARS_ARRAY[$cacheKey] = $return; + + /** @var array> $return - hack for phpstan */ + return $return; + } + + /** + * Returns an replacement array for ASCII methods with one language. + * + * For example, German will map 'ä' to 'ae', while other languages + * will simply return e.g. 'a'. + * + * EXAMPLE: + * $array = ASCII::charsArrayWithOneLanguage('ru'); + * $tmpKey = \array_search('yo', $array['replace']); + * echo $array['orig'][$tmpKey]; // 'ё' + * + * + * @psalm-suppress InvalidNullableReturnType - we use the prepare* methods here, so we don't get NULL here + * + * @param string $language [optional]

    Language of the source string e.g.: en, de_at, or de-ch. + * (default is 'en') | ASCII::*_LANGUAGE_CODE

    + * @param bool $replace_extra_symbols [optional]

    Add some more replacements e.g. "£" with " pound ".

    + * @param bool $asOrigReplaceArray [optional]

    TRUE === return {orig: string[], replace: string[]} + * array

    + * + * @psalm-pure + * + * @return array + *

    An array of replacements.

    + * + * @phpstan-return array{orig: string[], replace: string[]}|array + */ + public static function charsArrayWithOneLanguage( + string $language = self::ENGLISH_LANGUAGE_CODE, + bool $replace_extra_symbols = false, + bool $asOrigReplaceArray = true + ): array { + $language = self::get_language($language); + + // init + /** + * @var array + */ + static $CHARS_ARRAY = []; + $cacheKey = '' . $replace_extra_symbols . '-' . $asOrigReplaceArray; + + // check static cache + if (isset($CHARS_ARRAY[$cacheKey][$language])) { + return $CHARS_ARRAY[$cacheKey][$language]; + } + + if ($replace_extra_symbols) { + self::prepareAsciiAndExtrasMaps(); + + /** @noinspection DuplicatedCode */ + if (isset(self::$ASCII_MAPS_AND_EXTRAS[$language])) { + $tmpArray = self::$ASCII_MAPS_AND_EXTRAS[$language]; + + if ($asOrigReplaceArray) { + $CHARS_ARRAY[$cacheKey][$language] = [ + 'orig' => \array_keys($tmpArray), + 'replace' => \array_values($tmpArray), + ]; + } else { + $CHARS_ARRAY[$cacheKey][$language] = $tmpArray; + } + } else { + /** @noinspection NestedPositiveIfStatementsInspection */ + if ($asOrigReplaceArray) { + $CHARS_ARRAY[$cacheKey][$language] = [ + 'orig' => [], + 'replace' => [], + ]; + } else { + $CHARS_ARRAY[$cacheKey][$language] = []; + } + } + } else { + self::prepareAsciiMaps(); + + /** @noinspection DuplicatedCode */ + if (isset(self::$ASCII_MAPS[$language])) { + $tmpArray = self::$ASCII_MAPS[$language]; + + if ($asOrigReplaceArray) { + $CHARS_ARRAY[$cacheKey][$language] = [ + 'orig' => \array_keys($tmpArray), + 'replace' => \array_values($tmpArray), + ]; + } else { + $CHARS_ARRAY[$cacheKey][$language] = $tmpArray; + } + } else { + /** @noinspection NestedPositiveIfStatementsInspection */ + if ($asOrigReplaceArray) { + $CHARS_ARRAY[$cacheKey][$language] = [ + 'orig' => [], + 'replace' => [], + ]; + } else { + $CHARS_ARRAY[$cacheKey][$language] = []; + } + } + } + + return $CHARS_ARRAY[$cacheKey][$language] ?? ['orig' => [], 'replace' => []]; + } + + /** + * Returns an replacement array for ASCII methods with multiple languages. + * + * EXAMPLE: + * $array = ASCII::charsArrayWithSingleLanguageValues(); + * $tmpKey = \array_search('hnaik', $array['replace']); + * echo $array['orig'][$tmpKey]; // '၌' + * + * + * @param bool $replace_extra_symbols [optional]

    Add some more replacements e.g. "£" with " pound ".

    + * @param bool $asOrigReplaceArray [optional]

    TRUE === return {orig: string[], replace: string[]} + * array

    + * + * @psalm-pure + * + * @return array + *

    An array of replacements.

    + * + * @phpstan-return array{orig: string[], replace: string[]}|array + */ + public static function charsArrayWithSingleLanguageValues( + bool $replace_extra_symbols = false, + bool $asOrigReplaceArray = true + ): array { + // init + /** + * @var array + */ + static $CHARS_ARRAY = []; + $cacheKey = '' . $replace_extra_symbols . '-' . $asOrigReplaceArray; + + if (isset($CHARS_ARRAY[$cacheKey])) { + return $CHARS_ARRAY[$cacheKey]; + } + + if ($replace_extra_symbols) { + self::prepareAsciiAndExtrasMaps(); + + /** @noinspection AlterInForeachInspection */ + /** @psalm-suppress PossiblyNullIterator - we use the prepare* methods here, so we don't get NULL here */ + foreach (self::$ASCII_MAPS_AND_EXTRAS ?? [] as &$map) { + $CHARS_ARRAY[$cacheKey][] = $map; + } + } else { + self::prepareAsciiMaps(); + + /** @noinspection AlterInForeachInspection */ + /** @psalm-suppress PossiblyNullIterator - we use the prepare* methods here, so we don't get NULL here */ + foreach (self::$ASCII_MAPS ?? [] as &$map) { + $CHARS_ARRAY[$cacheKey][] = $map; + } + } + + $CHARS_ARRAY[$cacheKey] = \array_merge([], ...$CHARS_ARRAY[$cacheKey]); + + if ($asOrigReplaceArray) { + $CHARS_ARRAY[$cacheKey] = [ + 'orig' => \array_keys($CHARS_ARRAY[$cacheKey]), + 'replace' => \array_values($CHARS_ARRAY[$cacheKey]), + ]; + } + + return $CHARS_ARRAY[$cacheKey]; + } + + /** + * Accepts a string and removes all non-UTF-8 characters from it + extras if needed. + * + * @param string $str

    The string to be sanitized.

    + * @param bool $normalize_whitespace [optional]

    Set to true, if you need to normalize the + * whitespace.

    + * @param bool $normalize_msword [optional]

    Set to true, if you need to normalize MS Word chars + * e.g.: "…" + * => "..."

    + * @param bool $keep_non_breaking_space [optional]

    Set to true, to keep non-breaking-spaces, in + * combination with + * $normalize_whitespace

    + * @param bool $remove_invisible_characters [optional]

    Set to false, if you not want to remove invisible + * characters e.g.: "\0"

    + * + * @psalm-pure + * + * @return string + *

    A clean UTF-8 string.

    + */ + public static function clean( + string $str, + bool $normalize_whitespace = true, + bool $keep_non_breaking_space = false, + bool $normalize_msword = true, + bool $remove_invisible_characters = true + ): string { + // http://stackoverflow.com/questions/1401317/remove-non-utf8-characters-from-string + // caused connection reset problem on larger strings + + $regex = '/ + ( + (?: [\x00-\x7F] # single-byte sequences 0xxxxxxx + | [\xC0-\xDF][\x80-\xBF] # double-byte sequences 110xxxxx 10xxxxxx + | [\xE0-\xEF][\x80-\xBF]{2} # triple-byte sequences 1110xxxx 10xxxxxx * 2 + | [\xF0-\xF7][\x80-\xBF]{3} # quadruple-byte sequence 11110xxx 10xxxxxx * 3 + ){1,100} # ...one or more times + ) + | ( [\x80-\xBF] ) # invalid byte in range 10000000 - 10111111 + | ( [\xC0-\xFF] ) # invalid byte in range 11000000 - 11111111 + /x'; + $str = (string) \preg_replace($regex, '$1', $str); + + if ($normalize_whitespace) { + $str = self::normalize_whitespace($str, $keep_non_breaking_space); + } + + if ($normalize_msword) { + $str = self::normalize_msword($str); + } + + if ($remove_invisible_characters) { + $str = self::remove_invisible_characters($str); + } + + return $str; + } + + /** + * Checks if a string is 7 bit ASCII. + * + * EXAMPLE: + * ASCII::is_ascii('白'); // false + * + * + * @param string $str

    The string to check.

    + * + * @psalm-pure + * + * @return bool + *

    + * true if it is ASCII
    + * false otherwise + *

    + */ + public static function is_ascii(string $str): bool + { + if ($str === '') { + return true; + } + + return !\preg_match('/' . self::$REGEX_ASCII . '/', $str); + } + + /** + * Returns a string with smart quotes, ellipsis characters, and dashes from + * Windows-1252 (commonly used in Word documents) replaced by their ASCII + * equivalents. + * + * EXAMPLE: + * ASCII::normalize_msword('„Abcdef…”'); // '"Abcdef..."' + * + * + * @param string $str

    The string to be normalized.

    + * + * @psalm-pure + * + * @return string + *

    A string with normalized characters for commonly used chars in Word documents.

    + */ + public static function normalize_msword(string $str): string + { + if ($str === '') { + return ''; + } + + /** + * @var array{orig: string[], replace: string[]} + */ + static $MSWORD_CACHE = ['orig' => [], 'replace' => []]; + + if (empty($MSWORD_CACHE['orig'])) { + self::prepareAsciiMaps(); + + /** + * @psalm-suppress PossiblyNullArrayAccess - we use the prepare* methods here, so we don't get NULL here + * + * @var array + */ + $map = self::$ASCII_MAPS[self::EXTRA_MSWORD_CHARS_LANGUAGE_CODE] ?? []; + + $MSWORD_CACHE = [ + 'orig' => \array_keys($map), + 'replace' => \array_values($map), + ]; + } + + return \str_replace($MSWORD_CACHE['orig'], $MSWORD_CACHE['replace'], $str); + } + + /** + * Normalize the whitespace. + * + * EXAMPLE: + * ASCII::normalize_whitespace("abc-\xc2\xa0-öäü-\xe2\x80\xaf-\xE2\x80\xAC", true); // "abc-\xc2\xa0-öäü- -" + * + * + * @param string $str

    The string to be normalized.

    + * @param bool $keepNonBreakingSpace [optional]

    Set to true, to keep non-breaking-spaces.

    + * @param bool $keepBidiUnicodeControls [optional]

    Set to true, to keep non-printable (for the web) + * bidirectional text chars.

    + * @param bool $normalize_control_characters [optional]

    Set to true, to convert e.g. LINE-, PARAGRAPH-SEPARATOR with "\n" and LINE TABULATION with "\t".

    + * + * @psalm-pure + * + * @return string + *

    A string with normalized whitespace.

    + */ + public static function normalize_whitespace( + string $str, + bool $keepNonBreakingSpace = false, + bool $keepBidiUnicodeControls = false, + bool $normalize_control_characters = false + ): string { + if ($str === '') { + return ''; + } + + /** + * @var array> + */ + static $WHITESPACE_CACHE = []; + $cacheKey = (int) $keepNonBreakingSpace; + + if ($normalize_control_characters) { + $str = \str_replace( + [ + "\x0d\x0c", // 'END OF LINE' + "\xe2\x80\xa8", // 'LINE SEPARATOR' + "\xe2\x80\xa9", // 'PARAGRAPH SEPARATOR' + "\x0c", // 'FORM FEED' // "\f" + "\x0b", // 'VERTICAL TAB' // "\v" + ], + [ + "\n", + "\n", + "\n", + "\n", + "\t", + ], + $str + ); + } + + if (!isset($WHITESPACE_CACHE[$cacheKey])) { + self::prepareAsciiMaps(); + + $WHITESPACE_CACHE[$cacheKey] = self::$ASCII_MAPS[self::EXTRA_WHITESPACE_CHARS_LANGUAGE_CODE] ?? []; + + if ($keepNonBreakingSpace) { + unset($WHITESPACE_CACHE[$cacheKey]["\xc2\xa0"]); + } + + $WHITESPACE_CACHE[$cacheKey] = \array_keys($WHITESPACE_CACHE[$cacheKey]); + } + + if (!$keepBidiUnicodeControls) { + /** + * @var array|null + */ + static $BIDI_UNICODE_CONTROLS_CACHE = null; + + if ($BIDI_UNICODE_CONTROLS_CACHE === null) { + $BIDI_UNICODE_CONTROLS_CACHE = self::$BIDI_UNI_CODE_CONTROLS_TABLE; + } + + $str = \str_replace($BIDI_UNICODE_CONTROLS_CACHE, '', $str); + } + + return \str_replace($WHITESPACE_CACHE[$cacheKey], ' ', $str); + } + + /** + * Remove invisible characters from a string. + * + * e.g.: This prevents sandwiching null characters between ascii characters, like Java\0script. + * + * copy&past from https://github.com/bcit-ci/CodeIgniter/blob/develop/system/core/Common.php + * + * @param string $str + * @param bool $url_encoded + * @param string $replacement + * @param bool $keep_basic_control_characters + * + * @psalm-pure + * + * @return string + */ + public static function remove_invisible_characters( + string $str, + bool $url_encoded = false, + string $replacement = '', + bool $keep_basic_control_characters = true + ): string { + // init + $non_displayables = []; + + // every control character except: + // - newline (dec 10), + // - carriage return (dec 13), + // - horizontal tab (dec 09) + if ($url_encoded) { + $non_displayables[] = '/%0[0-8bcefBCEF]/'; // url encoded 00-08, 11, 12, 14, 15 + $non_displayables[] = '/%1[0-9a-fA-F]/'; // url encoded 16-31 + } + + if ($keep_basic_control_characters) { + $non_displayables[] = '/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+/S'; // 00-08, 11, 12, 14-31, 127 + } else { + $str = self::normalize_whitespace($str, false, false, true); + $non_displayables[] = '/[^\P{C}\s]/u'; + } + + do { + $str = (string) \preg_replace($non_displayables, $replacement, $str, -1, $count); + } while ($count !== 0); + + return $str; + } + + /** + * WARNING: This method will return broken characters and is only for special cases. + * + * Convert two UTF-8 encoded string to a single-byte strings suitable for + * functions that need the same string length after the conversion. + * + * The function simply uses (and updates) a tailored dynamic encoding + * (in/out map parameter) where non-ascii characters are remapped to + * the range [128-255] in order of appearance. + * + * @param string $str1 + * @param string $str2 + * + * @return string[] + * + * @phpstan-return array{0: string, 1: string} + */ + public static function to_ascii_remap(string $str1, string $str2): array + { + $charMap = []; + $str1 = self::to_ascii_remap_intern($str1, $charMap); + $str2 = self::to_ascii_remap_intern($str2, $charMap); + + return [$str1, $str2]; + } + + /** + * WARNING: This method will return broken characters and is only for special cases. + * + * Convert a UTF-8 encoded string to a single-byte string suitable for + * functions that need the same string length after the conversion. + * + * The function simply uses (and updates) a tailored dynamic encoding + * (in/out map parameter) where non-ascii characters are remapped to + * the range [128-255] in order of appearance. + * + * Thus, it supports up to 128 different multibyte code points max over + * the whole set of strings sharing this encoding. + * + * Source: https://github.com/KEINOS/mb_levenshtein + * + * @param string $str UTF-8 string to be converted to extended ASCII. + * @return string Mapped borken string. + */ + private static function to_ascii_remap_intern(string $str, array &$map): string + { + // find all utf-8 characters + $matches = []; + if (!\preg_match_all('/[\xC0-\xF7][\x80-\xBF]+/', $str, $matches)) { + return $str; // plain ascii string + } + + // update the encoding map with the characters not already met + $mapCount = \count($map); + foreach ($matches[0] as $mbc) { + if (!isset($map[$mbc])) { + $map[$mbc] = \chr(128 + $mapCount); + $mapCount++; + } + } + + // finally remap non-ascii characters + return \strtr($str, $map); + } + + /** + * Returns an ASCII version of the string. A set of non-ASCII characters are + * replaced with their closest ASCII counterparts, and the rest are removed + * by default. The language or locale of the source string can be supplied + * for language-specific transliteration in any of the following formats: + * en, en_GB, or en-GB. For example, passing "de" results in "äöü" mapping + * to "aeoeue" rather than "aou" as in other languages. + * + * EXAMPLE: + * ASCII::to_ascii('�Düsseldorf�', 'en'); // Dusseldorf + * + * + * @param string $str

    The input string.

    + * @param string $language [optional]

    Language of the source string. + * (default is 'en') | ASCII::*_LANGUAGE_CODE

    + * @param bool $remove_unsupported_chars [optional]

    Whether or not to remove the + * unsupported characters.

    + * @param bool $replace_extra_symbols [optional]

    Add some more replacements e.g. "£" with " pound + * ".

    + * @param bool $use_transliterate [optional]

    Use ASCII::to_transliterate() for unknown chars.

    + * @param bool|null $replace_single_chars_only [optional]

    Single char replacement is better for the + * performance, but some languages need to replace more then one char + * at the same time. | NULL === auto-setting, depended on the + * language

    + * + * @psalm-pure + * + * @return string + *

    A string that contains only ASCII characters.

    + */ + public static function to_ascii( + string $str, + string $language = self::ENGLISH_LANGUAGE_CODE, + bool $remove_unsupported_chars = true, + bool $replace_extra_symbols = false, + bool $use_transliterate = false, + bool $replace_single_chars_only = null + ): string { + if ($str === '') { + return ''; + } + + $language = self::get_language($language); + + static $EXTRA_SYMBOLS_CACHE = null; + + /** + * @var array> + */ + static $REPLACE_HELPER_CACHE = []; + $cacheKey = $language . '-' . $replace_extra_symbols; + + if (!isset($REPLACE_HELPER_CACHE[$cacheKey])) { + $langAll = self::charsArrayWithSingleLanguageValues($replace_extra_symbols, false); + + $langSpecific = self::charsArrayWithOneLanguage($language, $replace_extra_symbols, false); + + if ($langSpecific === []) { + $REPLACE_HELPER_CACHE[$cacheKey] = $langAll; + } else { + $REPLACE_HELPER_CACHE[$cacheKey] = \array_merge([], $langAll, $langSpecific); + } + } + + if ( + $replace_extra_symbols + && + $EXTRA_SYMBOLS_CACHE === null + ) { + $EXTRA_SYMBOLS_CACHE = []; + foreach (self::$ASCII_EXTRAS ?? [] as $extrasDataTmp) { + foreach ($extrasDataTmp as $extrasDataKeyTmp => $extrasDataValueTmp) { + $EXTRA_SYMBOLS_CACHE[$extrasDataKeyTmp] = $extrasDataKeyTmp; + } + } + $EXTRA_SYMBOLS_CACHE = \implode('', $EXTRA_SYMBOLS_CACHE); + } + + $charDone = []; + if (\preg_match_all('/' . self::$REGEX_ASCII . ($replace_extra_symbols ? '|[' . $EXTRA_SYMBOLS_CACHE . ']' : '') . '/u', $str, $matches)) { + if (!$replace_single_chars_only) { + if (self::$LANGUAGE_MAX_KEY === null) { + self::$LANGUAGE_MAX_KEY = self::getData('ascii_language_max_key'); + } + + $maxKeyLength = self::$LANGUAGE_MAX_KEY[$language] ?? 0; + + if ($maxKeyLength >= 5) { + foreach ($matches[0] as $keyTmp => $char) { + if (isset($matches[0][$keyTmp + 4])) { + $fiveChars = $matches[0][$keyTmp + 0] . $matches[0][$keyTmp + 1] . $matches[0][$keyTmp + 2] . $matches[0][$keyTmp + 3] . $matches[0][$keyTmp + 4]; + } else { + $fiveChars = null; + } + if ( + $fiveChars + && + !isset($charDone[$fiveChars]) + && + isset($REPLACE_HELPER_CACHE[$cacheKey][$fiveChars]) + && + \strpos($str, $fiveChars) !== false + ) { + // DEBUG + //\var_dump($str, $fiveChars, $REPLACE_HELPER_CACHE[$cacheKey][$fiveChars]); + + $charDone[$fiveChars] = true; + $str = \str_replace($fiveChars, $REPLACE_HELPER_CACHE[$cacheKey][$fiveChars], $str); + + // DEBUG + //\var_dump($str, "\n"); + } + } + } + + if ($maxKeyLength >= 4) { + foreach ($matches[0] as $keyTmp => $char) { + if (isset($matches[0][$keyTmp + 3])) { + $fourChars = $matches[0][$keyTmp + 0] . $matches[0][$keyTmp + 1] . $matches[0][$keyTmp + 2] . $matches[0][$keyTmp + 3]; + } else { + $fourChars = null; + } + if ( + $fourChars + && + !isset($charDone[$fourChars]) + && + isset($REPLACE_HELPER_CACHE[$cacheKey][$fourChars]) + && + \strpos($str, $fourChars) !== false + ) { + // DEBUG + //\var_dump($str, $fourChars, $REPLACE_HELPER_CACHE[$cacheKey][$fourChars]); + + $charDone[$fourChars] = true; + $str = \str_replace($fourChars, $REPLACE_HELPER_CACHE[$cacheKey][$fourChars], $str); + + // DEBUG + //\var_dump($str, "\n"); + } + } + } + + foreach ($matches[0] as $keyTmp => $char) { + if (isset($matches[0][$keyTmp + 2])) { + $threeChars = $matches[0][$keyTmp + 0] . $matches[0][$keyTmp + 1] . $matches[0][$keyTmp + 2]; + } else { + $threeChars = null; + } + if ( + $threeChars + && + !isset($charDone[$threeChars]) + && + isset($REPLACE_HELPER_CACHE[$cacheKey][$threeChars]) + && + \strpos($str, $threeChars) !== false + ) { + // DEBUG + //\var_dump($str, $threeChars, $REPLACE_HELPER_CACHE[$cacheKey][$threeChars]); + + $charDone[$threeChars] = true; + $str = \str_replace($threeChars, $REPLACE_HELPER_CACHE[$cacheKey][$threeChars], $str); + + // DEBUG + //\var_dump($str, "\n"); + } + } + + foreach ($matches[0] as $keyTmp => $char) { + if (isset($matches[0][$keyTmp + 1])) { + $twoChars = $matches[0][$keyTmp + 0] . $matches[0][$keyTmp + 1]; + } else { + $twoChars = null; + } + if ( + $twoChars + && + !isset($charDone[$twoChars]) + && + isset($REPLACE_HELPER_CACHE[$cacheKey][$twoChars]) + && + \strpos($str, $twoChars) !== false + ) { + // DEBUG + //\var_dump($str, $twoChars, $REPLACE_HELPER_CACHE[$cacheKey][$twoChars]); + + $charDone[$twoChars] = true; + $str = \str_replace($twoChars, $REPLACE_HELPER_CACHE[$cacheKey][$twoChars], $str); + + // DEBUG + //\var_dump($str, "\n"); + } + } + } + + foreach ($matches[0] as $char) { + if ( + !isset($charDone[$char]) + && + isset($REPLACE_HELPER_CACHE[$cacheKey][$char]) + && + \strpos($str, $char) !== false + ) { + // DEBUG + //\var_dump($str, $char, $REPLACE_HELPER_CACHE[$cacheKey][$char]); + + $charDone[$char] = true; + $str = \str_replace($char, $REPLACE_HELPER_CACHE[$cacheKey][$char], $str); + + // DEBUG + //\var_dump($str, "\n"); + } + } + } + + /** @psalm-suppress PossiblyNullOperand - we use the prepare* methods here, so we don't get NULL here */ + if (!isset(self::$ASCII_MAPS[$language])) { + $use_transliterate = true; + } + + if ($use_transliterate) { + /** @noinspection ArgumentEqualsDefaultValueInspection */ + $str = self::to_transliterate($str, null, false); + } + + if ($remove_unsupported_chars) { + $str = (string) \str_replace(["\n\r", "\n", "\r", "\t"], ' ', $str); + $str = (string) \preg_replace('/' . self::$REGEX_ASCII . '/', '', $str); + } + + return $str; + } + + /** + * Convert given string to safe filename (and keep string case). + * + * EXAMPLE: + * ASCII::to_filename('שדגשדג.png', true)); // 'shdgshdg.png' + * + * + * @param string $str + * @param bool $use_transliterate

    ASCII::to_transliterate() is used by default - unsafe characters are + * simply replaced with hyphen otherwise.

    + * @param string $fallback_char + * + * @psalm-pure + * + * @return string + *

    A string that contains only safe characters for a filename.

    + */ + public static function to_filename( + string $str, + bool $use_transliterate = true, + string $fallback_char = '-' + ): string { + if ($use_transliterate) { + $str = self::to_transliterate($str, $fallback_char); + } + + $fallback_char_escaped = \preg_quote($fallback_char, '/'); + + $str = (string) \preg_replace( + [ + '/[^' . $fallback_char_escaped . '.\\-a-zA-Z0-9\\s]/', // 1) remove un-needed chars + '/[\\s]+/u', // 2) convert spaces to $fallback_char + '/[' . $fallback_char_escaped . ']+/u', // 3) remove double $fallback_char's + ], + [ + '', + $fallback_char, + $fallback_char, + ], + $str + ); + + return \trim($str, $fallback_char); + } + + /** + * Converts the string into an URL slug. This includes replacing non-ASCII + * characters with their closest ASCII equivalents, removing remaining + * non-ASCII and non-alphanumeric characters, and replacing whitespace with + * $separator. The separator defaults to a single dash, and the string + * is also converted to lowercase. The language of the source string can + * also be supplied for language-specific transliteration. + * + * @param string $str + * @param string $separator [optional]

    The string used to replace whitespace.

    + * @param string $language [optional]

    Language of the source string. + * (default is 'en') | ASCII::*_LANGUAGE_CODE

    + * @param array $replacements [optional]

    A map of replaceable strings.

    + * @param bool $replace_extra_symbols [optional]

    Add some more replacements e.g. "£" with " + * pound ".

    + * @param bool $use_str_to_lower [optional]

    Use "string to lower" for the input.

    + * @param bool $use_transliterate [optional]

    Use ASCII::to_transliterate() for unknown + * chars.

    + * @psalm-pure + * + * @return string + *

    A string that has been converted to an URL slug.

    + */ + public static function to_slugify( + string $str, + string $separator = '-', + string $language = self::ENGLISH_LANGUAGE_CODE, + array $replacements = [], + bool $replace_extra_symbols = false, + bool $use_str_to_lower = true, + bool $use_transliterate = false + ): string { + if ($str === '') { + return ''; + } + + foreach ($replacements as $from => $to) { + $str = \str_replace($from, $to, $str); + } + + $str = self::to_ascii( + $str, + $language, + false, + $replace_extra_symbols, + $use_transliterate + ); + + $str = \str_replace('@', $separator, $str); + + $str = (string) \preg_replace( + '/[^a-zA-Z\\d\\s\\-_' . \preg_quote($separator, '/') . ']/', + '', + $str + ); + + if ($use_str_to_lower) { + $str = \strtolower($str); + } + + $str = (string) \preg_replace('/^[\'\\s]+|[\'\\s]+$/', '', $str); + $str = (string) \preg_replace('/\\B([A-Z])/', '-\1', $str); + $str = (string) \preg_replace('/[\\-_\\s]+/', $separator, $str); + + $l = \strlen($separator); + if ($l && \strpos($str, $separator) === 0) { + $str = (string) \substr($str, $l); + } + + if (\substr($str, -$l) === $separator) { + $str = (string) \substr($str, 0, \strlen($str) - $l); + } + + return $str; + } + + /** + * Returns an ASCII version of the string. A set of non-ASCII characters are + * replaced with their closest ASCII counterparts, and the rest are removed + * unless instructed otherwise. + * + * EXAMPLE: + * ASCII::to_transliterate('déjà σσς iıii'); // 'deja sss iiii' + * + * + * @param string $str

    The input string.

    + * @param string|null $unknown [optional]

    Character use if character unknown. (default is '?') + * But you can also use NULL to keep the unknown chars.

    + * @param bool $strict [optional]

    Use "transliterator_transliterate()" from PHP-Intl + * + * @psalm-pure + * + * @return string + *

    A String that contains only ASCII characters.

    + * + * @noinspection ParameterDefaultValueIsNotNullInspection + */ + public static function to_transliterate( + string $str, + $unknown = '?', + bool $strict = false + ): string { + /** + * @var array|null + */ + static $UTF8_TO_TRANSLIT = null; + + /** + * null|\Transliterator + */ + static $TRANSLITERATOR = null; + + /** + * @var bool|null + */ + static $SUPPORT_INTL = null; + + if ($str === '') { + return ''; + } + + if ($SUPPORT_INTL === null) { + $SUPPORT_INTL = \extension_loaded('intl'); + } + + // check if we only have ASCII, first (better performance) + $str_tmp = $str; + if (self::is_ascii($str)) { + return $str; + } + + $str = self::clean($str); + + // check again, if we only have ASCII, now ... + if ( + $str_tmp !== $str + && + self::is_ascii($str) + ) { + return $str; + } + + if ( + $strict + && + $SUPPORT_INTL === true + ) { + if (!isset($TRANSLITERATOR)) { + // INFO: see "*-Latin" rules via "transliterator_list_ids()" + /** + * @var \Transliterator + */ + $TRANSLITERATOR = \transliterator_create('NFKC; [:Nonspacing Mark:] Remove; NFKC; Any-Latin; Latin-ASCII;'); + } + + // INFO: https://unicode.org/cldr/utility/character.jsp + $str_tmp = \transliterator_transliterate($TRANSLITERATOR, $str); + + if ($str_tmp !== false) { + + // check again, if we only have ASCII, now ... + if ( + $str_tmp !== $str + && + self::is_ascii($str_tmp) + ) { + return $str_tmp; + } + + $str = $str_tmp; + } + } + + if (self::$ORD === null) { + self::$ORD = self::getData('ascii_ord'); + } + + \preg_match_all('/.|[^\x00]$/us', $str, $array_tmp); + $chars = $array_tmp[0]; + $ord = null; + $str_tmp = ''; + foreach ($chars as &$c) { + $ordC0 = self::$ORD[$c[0]]; + + if ($ordC0 >= 0 && $ordC0 <= 127) { + $str_tmp .= $c; + + continue; + } + + $ordC1 = self::$ORD[$c[1]]; + + // ASCII - next please + if ($ordC0 >= 192 && $ordC0 <= 223) { + $ord = ($ordC0 - 192) * 64 + ($ordC1 - 128); + } + + if ($ordC0 >= 224) { + $ordC2 = self::$ORD[$c[2]]; + + if ($ordC0 <= 239) { + $ord = ($ordC0 - 224) * 4096 + ($ordC1 - 128) * 64 + ($ordC2 - 128); + } + + if ($ordC0 >= 240) { + $ordC3 = self::$ORD[$c[3]]; + + if ($ordC0 <= 247) { + $ord = ($ordC0 - 240) * 262144 + ($ordC1 - 128) * 4096 + ($ordC2 - 128) * 64 + ($ordC3 - 128); + } + + // We only process valid UTF-8 chars (<= 4 byte), so we don't need this code here ... + /* + if ($ordC0 >= 248) { + $ordC4 = self::$ORD[$c[4]]; + + if ($ordC0 <= 251) { + $ord = ($ordC0 - 248) * 16777216 + ($ordC1 - 128) * 262144 + ($ordC2 - 128) * 4096 + ($ordC3 - 128) * 64 + ($ordC4 - 128); + } + + if ($ordC0 >= 252) { + $ordC5 = self::$ORD[$c[5]]; + + if ($ordC0 <= 253) { + $ord = ($ordC0 - 252) * 1073741824 + ($ordC1 - 128) * 16777216 + ($ordC2 - 128) * 262144 + ($ordC3 - 128) * 4096 + ($ordC4 - 128) * 64 + ($ordC5 - 128); + } + } + } + */ + } + } + + if ( + $ordC0 === 254 + || + $ordC0 === 255 + || + $ord === null + ) { + $str_tmp .= $unknown ?? $c; + + continue; + } + + $bank = $ord >> 8; + if (!isset($UTF8_TO_TRANSLIT[$bank])) { + $UTF8_TO_TRANSLIT[$bank] = self::getDataIfExists(\sprintf('x%03x', $bank)); + } + + $new_char = $ord & 255; + + if (isset($UTF8_TO_TRANSLIT[$bank][$new_char])) { + + // keep for debugging + /* + echo "file: " . sprintf('x%02x', $bank) . "\n"; + echo "char: " . $c . "\n"; + echo "ord: " . $ord . "\n"; + echo "new_char: " . $new_char . "\n"; + echo "new_char: " . mb_chr($new_char) . "\n"; + echo "ascii: " . $UTF8_TO_TRANSLIT[$bank][$new_char] . "\n"; + echo "bank:" . $bank . "\n\n"; + */ + + $new_char = $UTF8_TO_TRANSLIT[$bank][$new_char]; + + /** @noinspection MissingOrEmptyGroupStatementInspection */ + /** @noinspection PhpStatementHasEmptyBodyInspection */ + if ($unknown === null && $new_char === '') { + // nothing + } elseif ( + $new_char === '[?]' + || + $new_char === '[?] ' + ) { + $c = $unknown ?? $c; + } else { + $c = $new_char; + } + } else { + + // keep for debugging missing chars + /* + echo "file: " . sprintf('x%02x', $bank) . "\n"; + echo "char: " . $c . "\n"; + echo "ord: " . $ord . "\n"; + echo "new_char: " . $new_char . "\n"; + echo "new_char: " . mb_chr($new_char) . "\n"; + echo "bank:" . $bank . "\n\n"; + */ + + $c = $unknown ?? $c; + } + + $str_tmp .= $c; + } + + return $str_tmp; + } + + /** + * Get the language from a string. + * + * e.g.: de_at -> de_at + * de_DE -> de + * DE_DE -> de + * de-de -> de + * + * @noinspection ReturnTypeCanBeDeclaredInspection + * + * @param string $language + * + * @psalm-pure + * + * @return string + */ + private static function get_language(string $language) + { + if ($language === '') { + return ''; + } + + if ( + \strpos($language, '_') === false + && + \strpos($language, '-') === false + ) { + return \strtolower($language); + } + + $language = \str_replace('-', '_', \strtolower($language)); + + $regex = '/(?[a-z]+)_\g{first}/'; + + return (string) \preg_replace($regex, '$1', $language); + } + + /** + * Get data from "/data/*.php". + * + * @noinspection ReturnTypeCanBeDeclaredInspection + * + * @param string $file + * + * @psalm-pure + * + * @return array + */ + private static function getData(string $file) + { + /** @noinspection PhpIncludeInspection */ + /** @noinspection UsingInclusionReturnValueInspection */ + /** @psalm-suppress UnresolvableInclude */ + return include __DIR__ . '/data/' . $file . '.php'; + } + + /** + * Get data from "/data/*.php". + * + * @param string $file + * + * @psalm-pure + * + * @return array + */ + private static function getDataIfExists(string $file): array + { + $file = __DIR__ . '/data/' . $file . '.php'; + /** @psalm-suppress ImpureFunctionCall */ + if (\is_file($file)) { + /** @noinspection PhpIncludeInspection */ + /** @noinspection UsingInclusionReturnValueInspection */ + /** @psalm-suppress UnresolvableInclude */ + return include $file; + } + + return []; + } + + /** + * @psalm-pure + * + * @return void + */ + private static function prepareAsciiAndExtrasMaps() + { + if (self::$ASCII_MAPS_AND_EXTRAS === null) { + self::prepareAsciiMaps(); + self::prepareAsciiExtras(); + + /** @psalm-suppress PossiblyNullArgument - we use the prepare* methods here, so we don't get NULL here */ + self::$ASCII_MAPS_AND_EXTRAS = \array_merge_recursive( + self::$ASCII_MAPS ?? [], + self::$ASCII_EXTRAS ?? [] + ); + } + } + + /** + * @psalm-pure + * + * @return void + */ + private static function prepareAsciiMaps() + { + if (self::$ASCII_MAPS === null) { + self::$ASCII_MAPS = self::getData('ascii_by_languages'); + } + } + + /** + * @psalm-pure + * + * @return void + */ + private static function prepareAsciiExtras() + { + if (self::$ASCII_EXTRAS === null) { + self::$ASCII_EXTRAS = self::getData('ascii_extras_by_languages'); + } + } +} diff --git a/vendor/voku/portable-ascii/src/voku/helper/data/ascii_by_languages.php b/vendor/voku/portable-ascii/src/voku/helper/data/ascii_by_languages.php new file mode 100644 index 0000000..d51f557 --- /dev/null +++ b/vendor/voku/portable-ascii/src/voku/helper/data/ascii_by_languages.php @@ -0,0 +1,2929 @@ + [ + 'Á' => 'A', + 'á' => 'a', + 'Ä' => 'A', + 'ä' => 'a', + 'À' => 'A', + 'à' => 'a', + 'Â' => 'A', + 'â' => 'a', + 'É' => 'E', + 'é' => 'e', + 'Ë' => 'E', + 'ë' => 'e', + 'È' => 'E', + 'è' => 'e', + 'Ê' => 'E', + 'ê' => 'e', + 'Í' => 'I', + 'í' => 'i', + 'Ï' => 'I', + 'ï' => 'i', + 'Ì' => 'I', + 'ì' => 'i', + 'Î' => 'I', + 'î' => 'i', + 'Ó' => 'O', + 'ó' => 'o', + 'Ö' => 'O', + 'ö' => 'o', + 'Ò' => 'O', + 'ò' => 'o', + 'Ô' => 'O', + 'ô' => 'o', + 'Ú' => 'U', + 'ú' => 'u', + 'Ü' => 'U', + 'ü' => 'u', + 'Ù' => 'U', + 'ù' => 'u', + 'Û' => 'U', + 'û' => 'u', + 'Ý' => 'Y', + 'ý' => 'y', + 'Ÿ' => 'Y', + ], + // Italian + 'it' => [ + 'à' => 'a', + 'À' => 'A', + 'é' => 'e', + 'É' => 'E', + 'è' => 'e', + 'È' => 'E', + 'ì' => 'i', + 'Ì' => 'I', + 'Ò' => 'O', + 'ò' => 'o', + 'ù' => 'u', + 'Ù' => 'U', + ], + // Macedonian + 'mk' => [ + 'А' => 'A', + 'Б' => 'B', + 'В' => 'V', + 'Г' => 'G', + 'Д' => 'D', + 'Ѓ' => 'Gj', + 'Е' => 'E', + 'Ж' => 'Zh', + 'З' => 'Z', + 'Ѕ' => 'Dz', + 'И' => 'I', + 'Ј' => 'J', + 'К' => 'K', + 'Л' => 'L', + 'Љ' => 'Lj', + 'М' => 'M', + 'Н' => 'N', + 'Њ' => 'Nj', + 'О' => 'O', + 'П' => 'P', + 'Р' => 'R', + 'С' => 'S', + 'Т' => 'T', + 'Ќ' => 'Kj', + 'У' => 'U', + 'Ф' => 'F', + 'Х' => 'H', + 'Ц' => 'C', + 'Ч' => 'Ch', + 'Џ' => 'Dj', + 'Ш' => 'Sh', + 'а' => 'a', + 'б' => 'b', + 'в' => 'v', + 'г' => 'g', + 'д' => 'd', + 'ѓ' => 'gj', + 'е' => 'e', + 'ж' => 'zh', + 'з' => 'z', + 'ѕ' => 'dz', + 'и' => 'i', + 'ј' => 'j', + 'к' => 'k', + 'л' => 'l', + 'љ' => 'lj', + 'м' => 'm', + 'н' => 'n', + 'њ' => 'nj', + 'о' => 'o', + 'п' => 'p', + 'р' => 'r', + 'с' => 's', + 'т' => 't', + 'ќ' => 'kj', + 'у' => 'u', + 'ф' => 'f', + 'х' => 'h', + 'ц' => 'c', + 'ч' => 'ch', + 'џ' => 'dj', + 'ш' => 'sh', + ], + // Portuguese (Brazil) + 'pt' => [ + 'æ' => 'ae', + 'ǽ' => 'ae', + 'À' => 'A', + 'Á' => 'A', + 'Â' => 'A', + 'Ã' => 'A', + 'Å' => 'AA', + 'Ǻ' => 'A', + 'Ă' => 'A', + 'Ǎ' => 'A', + 'Æ' => 'AE', + 'Ǽ' => 'AE', + 'à' => 'a', + 'á' => 'a', + 'â' => 'a', + 'ã' => 'a', + 'å' => 'aa', + 'ǻ' => 'a', + 'ă' => 'a', + 'ǎ' => 'a', + 'ª' => 'a', + 'Ĉ' => 'C', + 'Ċ' => 'C', + 'Ç' => 'C', + 'ç' => 'c', + 'ĉ' => 'c', + 'ċ' => 'c', + 'Ð' => 'Dj', + 'Đ' => 'D', + 'ð' => 'dj', + 'đ' => 'd', + 'È' => 'E', + 'É' => 'E', + 'Ê' => 'E', + 'Ë' => 'E', + 'Ĕ' => 'E', + 'Ė' => 'E', + 'è' => 'e', + 'é' => 'e', + 'ê' => 'e', + 'ë' => 'e', + 'ĕ' => 'e', + 'ė' => 'e', + 'ƒ' => 'f', + 'Ĝ' => 'G', + 'Ġ' => 'G', + 'ĝ' => 'g', + 'ġ' => 'g', + 'Ĥ' => 'H', + 'Ħ' => 'H', + 'ĥ' => 'h', + 'ħ' => 'h', + 'Ì' => 'I', + 'Í' => 'I', + 'Î' => 'I', + 'Ï' => 'I', + 'Ĩ' => 'I', + 'Ĭ' => 'I', + 'Ǐ' => 'I', + 'Į' => 'I', + 'IJ' => 'IJ', + 'ì' => 'i', + 'í' => 'i', + 'î' => 'i', + 'ï' => 'i', + 'ĩ' => 'i', + 'ĭ' => 'i', + 'ǐ' => 'i', + 'į' => 'i', + 'ij' => 'ij', + 'Ĵ' => 'J', + 'ĵ' => 'j', + 'Ĺ' => 'L', + 'Ľ' => 'L', + 'Ŀ' => 'L', + 'ĺ' => 'l', + 'ľ' => 'l', + 'ŀ' => 'l', + 'Ñ' => 'N', + 'ñ' => 'n', + 'ʼn' => 'n', + 'Ò' => 'O', + 'Ó' => 'O', + 'Ô' => 'O', + 'Õ' => 'O', + 'Ō' => 'O', + 'Ŏ' => 'O', + 'Ǒ' => 'O', + 'Ő' => 'O', + 'Ơ' => 'O', + 'Ø' => 'OE', + 'Ǿ' => 'O', + 'Œ' => 'OE', + 'ò' => 'o', + 'ó' => 'o', + 'ô' => 'o', + 'õ' => 'o', + 'ō' => 'o', + 'ŏ' => 'o', + 'ǒ' => 'o', + 'ő' => 'o', + 'ơ' => 'o', + 'ø' => 'oe', + 'ǿ' => 'o', + 'º' => 'o', + 'œ' => 'oe', + 'Ŕ' => 'R', + 'Ŗ' => 'R', + 'ŕ' => 'r', + 'ŗ' => 'r', + 'Ŝ' => 'S', + 'Ș' => 'S', + 'ŝ' => 's', + 'ș' => 's', + 'ſ' => 's', + 'Ţ' => 'T', + 'Ț' => 'T', + 'Ŧ' => 'T', + 'Þ' => 'TH', + 'ţ' => 't', + 'ț' => 't', + 'ŧ' => 't', + 'þ' => 'th', + 'Ù' => 'U', + 'Ú' => 'U', + 'Û' => 'U', + 'Ü' => 'U', + 'Ũ' => 'U', + 'Ŭ' => 'U', + 'Ű' => 'U', + 'Ų' => 'U', + 'Ư' => 'U', + 'Ǔ' => 'U', + 'Ǖ' => 'U', + 'Ǘ' => 'U', + 'Ǚ' => 'U', + 'Ǜ' => 'U', + 'ù' => 'u', + 'ú' => 'u', + 'û' => 'u', + 'ü' => 'u', + 'ũ' => 'u', + 'ŭ' => 'u', + 'ű' => 'u', + 'ų' => 'u', + 'ư' => 'u', + 'ǔ' => 'u', + 'ǖ' => 'u', + 'ǘ' => 'u', + 'ǚ' => 'u', + 'ǜ' => 'u', + 'Ŵ' => 'W', + 'ŵ' => 'w', + 'Ý' => 'Y', + 'Ÿ' => 'Y', + 'Ŷ' => 'Y', + 'ý' => 'y', + 'ÿ' => 'y', + 'ŷ' => 'y', + ], + // Greek(lish) (Elláda) + 'el__greeklish' => [ + 'ΑΥ' => 'AU', + 'ΑΎ' => 'AU', + 'Αυ' => 'Au', + 'Αύ' => 'Au', + 'ΕΊ' => 'EI', + 'ΕΙ' => 'EI', + 'Ει' => 'EI', + 'ΕΥ' => 'EU', + 'ΕΎ' => 'EU', + 'Εί' => 'Ei', + 'Ευ' => 'Eu', + 'Εύ' => 'Eu', + 'ΟΙ' => 'OI', + 'ΟΊ' => 'OI', + 'ΟΥ' => 'OU', + 'ΟΎ' => 'OU', + 'Οι' => 'Oi', + 'Οί' => 'Oi', + 'Ου' => 'Ou', + 'Ού' => 'Ou', + 'ΥΙ' => 'YI', + 'ΎΙ' => 'YI', + 'Υι' => 'Yi', + 'Ύι' => 'Yi', + 'ΥΊ' => 'Yi', + 'Υί' => 'Yi', + 'αυ' => 'au', + 'αύ' => 'au', + 'εί' => 'ei', + 'ει' => 'ei', + 'ευ' => 'eu', + 'εύ' => 'eu', + 'οι' => 'oi', + 'οί' => 'oi', + 'ου' => 'ou', + 'ού' => 'ou', + 'υι' => 'yi', + 'ύι' => 'yi', + 'υί' => 'yi', + 'Α' => 'A', + 'Ά' => 'A', + 'Β' => 'B', + 'Δ' => 'D', + 'Ε' => 'E', + 'Έ' => 'E', + 'Φ' => 'F', + 'Γ' => 'G', + 'Η' => 'H', + 'Ή' => 'H', + 'Ι' => 'I', + 'Ί' => 'I', + 'Ϊ' => 'I', + 'Κ' => 'K', + 'Ξ' => 'Ks', + 'Λ' => 'L', + 'Μ' => 'M', + 'Ν' => 'N', + 'Π' => 'N', + 'Ο' => 'O', + 'Ό' => 'O', + 'Ψ' => 'Ps', + 'Ρ' => 'R', + 'Σ' => 'S', + 'Τ' => 'T', + 'Θ' => 'Th', + 'Ω' => 'W', + 'Ώ' => 'W', + 'Χ' => 'X', + 'ϒ' => 'Y', + 'Υ' => 'Y', + 'Ύ' => 'Y', + 'Ϋ' => 'Y', + 'Ζ' => 'Z', + 'α' => 'a', + 'ά' => 'a', + 'β' => 'b', + 'δ' => 'd', + 'ε' => 'e', + 'έ' => 'e', + 'φ' => 'f', + 'γ' => 'g', + 'η' => 'h', + 'ή' => 'h', + 'ι' => 'i', + 'ί' => 'i', + 'ϊ' => 'i', + 'ΐ' => 'i', + 'κ' => 'k', + 'ξ' => 'ks', + 'λ' => 'l', + 'μ' => 'm', + 'ν' => 'n', + 'ο' => 'o', + 'ό' => 'o', + 'π' => 'p', + 'ψ' => 'ps', + 'ρ' => 'r', + 'σ' => 's', + 'ς' => 's', + 'τ' => 't', + 'ϑ' => 'th', + 'θ' => 'th', + 'ϐ' => 'v', + 'ω' => 'w', + 'ώ' => 'w', + 'χ' => 'x', + 'υ' => 'y', + 'ύ' => 'y', + 'ΰ' => 'y', + 'ϋ' => 'y', + 'ζ' => 'z', + ], + // Greek (Elláda) + 'el' => [ + 'ΑΥ' => 'AU', + 'Αυ' => 'Au', + 'ΟΥ' => 'U', + 'Ου' => 'u', + 'ΕΥ' => 'EF', + 'Ευ' => 'Ef', + 'ΕΙ' => 'I', + 'Ει' => 'I', + 'ΟΙ' => 'I', + 'Οι' => 'I', + 'ΥΙ' => 'I', + 'Υι' => 'I', + 'ΑΎ' => 'AU', + 'Αύ' => 'Au', + 'ΟΎ' => 'OU', + 'Ού' => 'Ou', + 'ΕΎ' => 'EU', + 'Εύ' => 'Eu', + 'ΕΊ' => 'I', + 'Εί' => 'I', + 'ΟΊ' => 'I', + 'Οί' => 'I', + 'ΎΙ' => 'I', + 'Ύι' => 'I', + 'ΥΊ' => 'I', + 'Υί' => 'I', + 'αυ' => 'au', + 'ου' => 'u', + 'ευ' => 'ef', + 'ει' => 'i', + 'οι' => 'i', + 'υι' => 'i', + 'αύ' => 'au', + 'ού' => 'ou', + 'εύ' => 'eu', + 'εί' => 'i', + 'οί' => 'i', + 'ύι' => 'i', + 'υί' => 'i', + 'α' => 'a', + 'β' => 'v', + 'γ' => 'gh', + 'δ' => 'd', + 'ε' => 'e', + 'ζ' => 'z', + 'η' => 'i', + 'θ' => 'th', + 'ι' => 'i', + 'κ' => 'k', + 'λ' => 'l', + 'μ' => 'm', + 'ν' => 'n', + 'ξ' => 'ks', + 'ο' => 'o', + 'π' => 'p', + 'ρ' => 'r', + 'σ' => 's', + 'τ' => 't', + 'υ' => 'i', + 'φ' => 'f', + 'χ' => 'kh', + 'ψ' => 'ps', + 'ω' => 'o', + 'ά' => 'a', + 'έ' => 'e', + 'ί' => 'i', + 'ό' => 'o', + 'ϒ' => 'Y', + 'ύ' => 'y', + 'ή' => 'i', + 'ώ' => 'w', + 'ς' => 's', + 'ϊ' => 'i', + 'ΰ' => 'y', + 'ϋ' => 'y', + 'ΐ' => 'i', + 'Α' => 'A', + 'Β' => 'B', + 'Γ' => 'G', + 'Δ' => 'D', + 'Ε' => 'E', + 'Ζ' => 'Z', + 'Η' => 'H', + 'Θ' => 'Th', + 'Ι' => 'I', + 'Κ' => 'K', + 'Λ' => 'L', + 'Μ' => 'M', + 'Ν' => 'N', + 'Ξ' => 'Ks', + 'Ο' => 'O', + 'Π' => 'P', + 'Ρ' => 'R', + 'Σ' => 'S', + 'Τ' => 'T', + 'Υ' => 'Y', + 'Φ' => 'F', + 'Χ' => 'X', + 'Ψ' => 'Ps', + 'Ω' => 'O', + 'Ά' => 'A', + 'Έ' => 'E', + 'Ί' => 'I', + 'Ό' => 'O', + 'Ύ' => 'Y', + 'Ή' => 'I', + 'Ώ' => 'W', + 'Ϊ' => 'I', + 'Ϋ' => 'Y', + 'ϐ' => 'v', + 'ϑ' => 'th', + ], + // Hindi + 'hi' => [ + 'अ' => 'a', + 'आ' => 'aa', + 'ए' => 'e', + 'ई' => 'ii', + 'ऍ' => 'ei', + 'ऎ' => 'ae', + 'ऐ' => 'ai', + 'इ' => 'i', + 'ओ' => 'o', + 'ऑ' => 'oi', + 'ऒ' => 'oii', + 'ऊ' => 'uu', + 'औ' => 'ou', + 'उ' => 'u', + 'ब' => 'B', + 'भ' => 'Bha', + 'च' => 'Ca', + 'छ' => 'Chha', + 'ड' => 'Da', + 'ढ' => 'Dha', + 'फ' => 'Fa', + 'फ़' => 'Fi', + 'ग' => 'Ga', + 'घ' => 'Gha', + 'ग़' => 'Ghi', + 'ह' => 'Ha', + 'ज' => 'Ja', + 'झ' => 'Jha', + 'क' => 'Ka', + 'ख' => 'Kha', + 'ख़' => 'Khi', + 'ल' => 'L', + 'ळ' => 'Li', + 'ऌ' => 'Li', + 'ऴ' => 'Lii', + 'ॡ' => 'Lii', + 'म' => 'Ma', + 'न' => 'Na', + 'ङ' => 'Na', + 'ञ' => 'Nia', + 'ण' => 'Nae', + 'ऩ' => 'Ni', + 'ॐ' => 'oms', + 'प' => 'Pa', + 'क़' => 'Qi', + 'र' => 'Ra', + 'ऋ' => 'Ri', + 'ॠ' => 'Ri', + 'ऱ' => 'Ri', + 'स' => 'Sa', + 'श' => 'Sha', + 'ष' => 'Shha', + 'ट' => 'Ta', + 'त' => 'Ta', + 'ठ' => 'Tha', + 'द' => 'Tha', + 'थ' => 'Tha', + 'ध' => 'Thha', + 'ड़' => 'ugDha', + 'ढ़' => 'ugDhha', + 'व' => 'Va', + 'य' => 'Ya', + 'य़' => 'Yi', + 'ज़' => 'Za', + ], + // Armenian + 'hy' => [ + 'Ա' => 'A', + 'Բ' => 'B', + 'Գ' => 'G', + 'Դ' => 'D', + 'Ե' => 'E', + 'Զ' => 'Z', + 'Է' => 'E', + 'Ը' => 'Y', + 'Թ' => 'Th', + 'Ժ' => 'Zh', + 'Ի' => 'I', + 'Լ' => 'L', + 'Խ' => 'Kh', + 'Ծ' => 'Ts', + 'Կ' => 'K', + 'Հ' => 'H', + 'Ձ' => 'Dz', + 'Ղ' => 'Gh', + 'Ճ' => 'Tch', + 'Մ' => 'M', + 'Յ' => 'Y', + 'Ն' => 'N', + 'Շ' => 'Sh', + 'Ո' => 'Vo', + 'Չ' => 'Ch', + 'Պ' => 'P', + 'Ջ' => 'J', + 'Ռ' => 'R', + 'Ս' => 'S', + 'Վ' => 'V', + 'Տ' => 'T', + 'Ր' => 'R', + 'Ց' => 'C', + 'Ւ' => 'u', + 'Փ' => 'Ph', + 'Ք' => 'Q', + 'և' => 'ev', + 'Օ' => 'O', + 'Ֆ' => 'F', + 'ա' => 'a', + 'բ' => 'b', + 'գ' => 'g', + 'դ' => 'd', + 'ե' => 'e', + 'զ' => 'z', + 'է' => 'e', + 'ը' => 'y', + 'թ' => 'th', + 'ժ' => 'zh', + 'ի' => 'i', + 'լ' => 'l', + 'խ' => 'kh', + 'ծ' => 'ts', + 'կ' => 'k', + 'հ' => 'h', + 'ձ' => 'dz', + 'ղ' => 'gh', + 'ճ' => 'tch', + 'մ' => 'm', + 'յ' => 'y', + 'ն' => 'n', + 'շ' => 'sh', + 'ո' => 'vo', + 'չ' => 'ch', + 'պ' => 'p', + 'ջ' => 'j', + 'ռ' => 'r', + 'ս' => 's', + 'վ' => 'v', + 'տ' => 't', + 'ր' => 'r', + 'ց' => 'c', + 'ւ' => 'u', + 'փ' => 'ph', + 'ք' => 'q', + 'օ' => 'o', + 'ֆ' => 'f', + ], + // Swedish + 'sv' => [ + 'Ä' => 'A', + 'ä' => 'a', + 'Å' => 'A', + 'å' => 'a', + 'Ö' => 'O', + 'ö' => 'o', + ], + // Turkmen + 'tk' => [ + 'Ç' => 'C', + 'Ä' => 'A', + 'Ž' => 'Z', + 'Ň' => 'N', + 'Ö' => 'O', + 'Ş' => 'S', + 'Ü' => 'U', + 'Ý' => 'Y', + 'ç' => 'c', + 'ä' => 'a', + 'ž' => 'z', + 'ň' => 'n', + 'ö' => 'o', + 'ş' => 's', + 'ü' => 'u', + 'ý' => 'y', + ], + // Turkish + 'tr' => [ + 'ň' => 'n', + 'Ň' => 'N', + 'ş' => 's', + 'Ş' => 'S', + 'ı' => 'i', + 'İ' => 'I', + 'ç' => 'c', + 'Ç' => 'C', + 'ä' => 'a', + 'Ä' => 'A', + 'ü' => 'u', + 'Ü' => 'U', + 'ö' => 'o', + 'Ö' => 'O', + 'ğ' => 'g', + 'Ğ' => 'G', + 'ý' => 'y', + 'Ý' => 'Y', + 'ž' => 'z', + 'Ž' => 'Z', + ], + // Bulgarian + 'bg' => [ + 'ьо' => 'yo', + 'А' => 'A', + 'Б' => 'B', + 'В' => 'V', + 'Г' => 'G', + 'Д' => 'D', + 'Е' => 'E', + 'Ж' => 'Zh', + 'З' => 'Z', + 'И' => 'I', + 'Й' => 'Y', + 'К' => 'K', + 'Л' => 'L', + 'М' => 'M', + 'Н' => 'N', + 'О' => 'O', + 'П' => 'P', + 'Р' => 'R', + 'С' => 'S', + 'Т' => 'T', + 'У' => 'U', + 'Ф' => 'F', + 'Х' => 'H', + 'Ц' => 'C', + 'Ч' => 'Ch', + 'Ш' => 'Sh', + 'Щ' => 'Sht', + 'Ъ' => 'A', + 'Ь' => '', + 'Ю' => 'Yu', + 'Я' => 'Ya', + 'а' => 'a', + 'б' => 'b', + 'в' => 'v', + 'г' => 'g', + 'д' => 'd', + 'е' => 'e', + 'ж' => 'zh', + 'з' => 'z', + 'и' => 'i', + 'й' => 'y', + 'к' => 'k', + 'л' => 'l', + 'м' => 'm', + 'н' => 'n', + 'о' => 'o', + 'п' => 'p', + 'р' => 'r', + 'с' => 's', + 'т' => 't', + 'у' => 'u', + 'ф' => 'f', + 'х' => 'h', + 'ц' => 'c', + 'ч' => 'ch', + 'ш' => 'sh', + 'щ' => 'sht', + 'ъ' => 'a', + 'ь' => '', + 'ю' => 'yu', + 'я' => 'ya', + ], + // Hungarian + 'hu' => [ + 'Á' => 'A', + 'Ē' => 'E', + 'É' => 'E', + 'Í' => 'I', + 'Ó' => 'O', + 'Ö' => 'O', + 'Ő' => 'O', + 'Ú' => 'U', + 'Ü' => 'U', + 'Ű' => 'U', + 'á' => 'a', + 'ē' => 'e', + 'é' => 'e', + 'í' => 'i', + 'ó' => 'o', + 'ö' => 'o', + 'ő' => 'o', + 'ú' => 'u', + 'ü' => 'u', + 'ű' => 'u', + ], + // Myanmar (Burmese) + 'my' => [ + 'န်ုပ်' => 'nub', + 'ောင်' => 'aung', + 'ိုက်' => 'aik', + 'ိုဒ်' => 'ok', + 'ိုင်' => 'aing', + 'ိုလ်' => 'ol', + 'ေါင်' => 'aung', + 'သြော' => 'aw', + 'ောက်' => 'auk', + 'ိတ်' => 'eik', + 'ုတ်' => 'ok', + 'ုန်' => 'on', + 'ေတ်' => 'it', + 'ုဒ်' => 'ait', + 'ာန်' => 'an', + 'ိန်' => 'ein', + 'ွတ်' => 'ut', + 'ေါ်' => 'aw', + 'ွန်' => 'un', + 'ိပ်' => 'eik', + 'ုပ်' => 'ok', + 'ွပ်' => 'ut', + 'ိမ်' => 'ein', + 'ုမ်' => 'on', + 'ော်' => 'aw', + 'ွမ်' => 'un', + 'က်' => 'et', + 'ေါ' => 'aw', + 'ော' => 'aw', + 'ျွ' => 'ywa', + 'ြွ' => 'yw', + 'ို' => 'o', + 'ုံ' => 'on', + 'တ်' => 'at', + 'င်' => 'in', + 'ည်' => 'i', + 'ဒ်' => 'd', + 'န်' => 'an', + 'ပ်' => 'at', + 'မ်' => 'an', + 'စျ' => 'za', + 'ယ်' => 'e', + 'ဉ်' => 'in', + 'စ်' => 'it', + 'ိံ' => 'ein', + 'ဲ' => 'e', + 'း' => '', + 'ာ' => 'a', + 'ါ' => 'a', + 'ေ' => 'e', + 'ံ' => 'an', + 'ိ' => 'i', + 'ီ' => 'i', + 'ု' => 'u', + 'ူ' => 'u', + '်' => 'at', + '္' => '', + '့' => '', + 'က' => 'k', + '၉' => '9', + 'တ' => 't', + 'ရ' => 'ya', + 'ယ' => 'y', + 'မ' => 'm', + 'ဘ' => 'ba', + 'ဗ' => 'b', + 'ဖ' => 'pa', + 'ပ' => 'p', + 'န' => 'n', + 'ဓ' => 'da', + 'ဒ' => 'd', + 'ထ' => 'ta', + 'ဏ' => 'na', + 'ဝ' => 'w', + 'ဎ' => 'da', + 'ဍ' => 'd', + 'ဌ' => 'ta', + 'ဋ' => 't', + 'ည' => 'ny', + 'ဇ' => 'z', + 'ဆ' => 'sa', + 'စ' => 's', + 'င' => 'ng', + 'ဃ' => 'ga', + 'ဂ' => 'g', + 'လ' => 'l', + 'သ' => 'th', + '၈' => '8', + 'ဩ' => 'aw', + 'ခ' => 'kh', + '၆' => '6', + '၅' => '5', + '၄' => '4', + '၃' => '3', + '၂' => '2', + '၁' => '1', + '၀' => '0', + '၌' => 'hnaik', + '၍' => 'ywae', + 'ဪ' => 'aw', + 'ဦ' => '-u', + 'ဟ' => 'h', + 'ဉ' => 'u', + 'ဤ' => '-i', + 'ဣ' => 'i', + '၏' => '-e', + 'ဧ' => 'e', + 'ှ' => 'h', + 'ွ' => 'w', + 'ျ' => 'ya', + 'ြ' => 'y', + 'အ' => 'a', + 'ဠ' => 'la', + '၇' => '7', + ], + // Croatian (Hrvatska) + 'hr' => [ + 'DŽ' => 'DZ', + 'Dž' => 'Dz', + 'dž' => 'dz', + 'DZ' => 'DZ', + 'Dz' => 'Dz', + 'dz' => 'dz', + 'IJ' => 'IJ', + 'ij' => 'ij', + 'LJ' => 'LJ', + 'Lj' => 'Lj', + 'lj' => 'lj', + 'NJ' => 'NJ', + 'Nj' => 'Nj', + 'nj' => 'nj', + 'ž' => 'z', + 'Ž' => 'Z', + 'đ' => 'dj', + 'Đ' => 'Dj', + 'č' => 'c', + 'Č' => 'C', + 'ć' => 'c', + 'Ć' => 'C', + 'š' => 's', + 'Š' => 'S', + ], + // Finnish + 'fi' => [ + 'Ä' => 'A', + 'Ö' => 'O', + 'ä' => 'a', + 'ö' => 'o', + ], + // Georgian (Kartvelian) + 'ka' => [ + 'ა' => 'a', + 'ბ' => 'b', + 'გ' => 'g', + 'დ' => 'd', + 'ე' => 'e', + 'ვ' => 'v', + 'ზ' => 'z', + 'თ' => 't', + 'ი' => 'i', + 'კ' => 'k', + 'ლ' => 'l', + 'მ' => 'm', + 'ნ' => 'n', + 'ო' => 'o', + 'პ' => 'p', + 'ჟ' => 'zh', + 'რ' => 'r', + 'ს' => 's', + 'ტ' => 't', + 'უ' => 'u', + 'ფ' => 'f', + 'ქ' => 'q', + 'ღ' => 'gh', + 'ყ' => 'y', + 'შ' => 'sh', + 'ჩ' => 'ch', + 'ც' => 'ts', + 'ძ' => 'dz', + 'წ' => 'ts', + 'ჭ' => 'ch', + 'ხ' => 'kh', + 'ჯ' => 'j', + 'ჰ' => 'h', + ], + // Russian + 'ru' => [ + 'А' => 'A', + 'а' => 'a', + 'Б' => 'B', + 'б' => 'b', + 'В' => 'V', + 'в' => 'v', + 'Г' => 'G', + 'г' => 'g', + 'Д' => 'D', + 'д' => 'd', + 'Е' => 'E', + 'е' => 'e', + 'Ё' => 'Yo', + 'ё' => 'yo', + 'Ж' => 'Zh', + 'ж' => 'zh', + 'З' => 'Z', + 'з' => 'z', + 'И' => 'I', + 'и' => 'i', + 'Й' => 'Y', + 'й' => 'y', + 'К' => 'K', + 'к' => 'k', + 'Л' => 'L', + 'л' => 'l', + 'М' => 'M', + 'м' => 'm', + 'Н' => 'N', + 'н' => 'n', + 'О' => 'O', + 'о' => 'o', + 'П' => 'P', + 'п' => 'p', + 'Р' => 'R', + 'р' => 'r', + 'С' => 'S', + 'с' => 's', + 'Т' => 'T', + 'т' => 't', + 'У' => 'U', + 'у' => 'u', + 'Ф' => 'F', + 'ф' => 'f', + 'Х' => 'H', + 'х' => 'h', + 'Ц' => 'Ts', + 'ц' => 'ts', + 'Ч' => 'Ch', + 'ч' => 'ch', + 'ш' => 'sh', + 'Ш' => 'Sh', + 'Щ' => 'Sch', + 'щ' => 'sch', + 'Ъ' => '', + 'ъ' => '', + 'Ы' => 'Y', + 'ы' => 'y', + 'Ь' => '', + 'ь' => '', + 'Э' => 'E', + 'э' => 'e', + 'Ю' => 'Yu', + 'ю' => 'yu', + 'Я' => 'Ya', + 'я' => 'ya', + ], + // Russian - Passport (2013), ICAO + // -> https://en.m.wikipedia.org/wiki/Romanization_of_Russian#content-collapsible-block-1 + 'ru__passport_2013' => [ + 'А' => 'A', + 'а' => 'a', + 'Б' => 'B', + 'б' => 'b', + 'В' => 'V', + 'в' => 'v', + 'Г' => 'G', + 'г' => 'g', + 'Д' => 'D', + 'д' => 'd', + 'Е' => 'E', + 'е' => 'e', + 'Ё' => 'E', + 'ё' => 'e', + 'Ж' => 'Zh', + 'ж' => 'zh', + 'З' => 'Z', + 'з' => 'z', + 'И' => 'i', + 'и' => 'i', + 'Й' => 'i', + 'й' => 'i', + 'К' => 'K', + 'к' => 'k', + 'Л' => 'L', + 'л' => 'l', + 'М' => 'M', + 'м' => 'm', + 'Н' => 'N', + 'н' => 'n', + 'О' => 'O', + 'о' => 'o', + 'П' => 'P', + 'п' => 'p', + 'Р' => 'R', + 'р' => 'r', + 'С' => 'S', + 'с' => 's', + 'Т' => 'T', + 'т' => 't', + 'У' => 'U', + 'у' => 'u', + 'Ф' => 'F', + 'ф' => 'f', + 'Х' => 'Kh', + 'х' => 'kh', + 'Ц' => 'Ts', + 'ц' => 'ts', + 'Ч' => 'Ch', + 'ч' => 'ch', + 'ш' => 'sh', + 'Ш' => 'Sh', + 'Щ' => 'Shch', + 'щ' => 'shch', + 'Ъ' => 'Ie', + 'ъ' => 'ie', + 'Ы' => 'Y', + 'ы' => 'y', + 'Ь' => '', + 'ь' => '', + 'Э' => 'E', + 'э' => 'e', + 'Ю' => 'Iu', + 'ю' => 'iu', + 'Я' => 'Ia', + 'я' => 'ia', + 'І' => '', + 'і' => '', + 'Ѳ' => '', + 'ѳ' => '', + 'Ѣ' => '', + 'ѣ' => '', + 'Ѵ' => '', + 'ѵ' => '', + 'Є' => '', + 'є' => '', + 'Ѥ' => '', + 'ѥ' => '', + 'Ѕ' => '', + 'ѕ' => '', + 'Ꙋ' => '', + 'ꙋ' => '', + 'Ѡ' => '', + 'ѡ' => '', + 'Ѿ' => '', + 'ѿ' => '', + 'Ѫ' => '', + 'ѫ' => '', + 'Ѧ' => '', + 'ѧ' => '', + 'Ѭ' => '', + 'ѭ' => '', + 'Ѩ' => '', + 'ѩ' => '', + 'Ѯ' => '', + 'ѯ' => '', + 'Ѱ' => '', + 'ѱ' => '', + ], + // Russian - GOST 7.79-2000(B) + // -> https://en.m.wikipedia.org/wiki/Romanization_of_Russian#content-collapsible-block-1 + 'ru__gost_2000_b' => [ + 'А' => 'A', + 'а' => 'a', + 'Б' => 'B', + 'б' => 'b', + 'В' => 'V', + 'в' => 'v', + 'Г' => 'G', + 'г' => 'g', + 'Д' => 'D', + 'д' => 'd', + 'Е' => 'E', + 'е' => 'e', + 'Ё' => 'Yo', + 'ё' => 'yo', + 'Ж' => 'Zh', + 'ж' => 'zh', + 'З' => 'Z', + 'з' => 'z', + 'И' => 'i', + 'и' => 'i', + 'Й' => 'i', + 'й' => 'i', + 'К' => 'K', + 'к' => 'k', + 'Л' => 'L', + 'л' => 'l', + 'М' => 'M', + 'м' => 'm', + 'Н' => 'N', + 'н' => 'n', + 'О' => 'O', + 'о' => 'o', + 'П' => 'P', + 'п' => 'p', + 'Р' => 'R', + 'р' => 'r', + 'С' => 'S', + 'с' => 's', + 'Т' => 'T', + 'т' => 't', + 'У' => 'U', + 'у' => 'u', + 'Ф' => 'F', + 'ф' => 'f', + 'Х' => 'X', + 'х' => 'x', + 'Ц' => 'Cz', + 'ц' => 'cz', + 'Ч' => 'Ch', + 'ч' => 'ch', + 'ш' => 'sh', + 'Ш' => 'Sh', + 'Щ' => 'Shh', + 'щ' => 'shh', + 'Ъ' => '', + 'ъ' => '', + 'Ы' => 'Y\'', + 'ы' => 'y\'', + 'Ь' => '', + 'ь' => '', + 'Э' => 'E\'', + 'э' => 'e\'', + 'Ю' => 'Yu', + 'ю' => 'yu', + 'Я' => 'Ya', + 'я' => 'ya', + 'І' => 'I', + 'і' => 'i', + 'Ѳ' => 'Fh', + 'ѳ' => 'fh', + 'Ѣ' => 'Ye', + 'ѣ' => 'ye', + 'Ѵ' => 'Yh', + 'ѵ' => 'yh', + 'Є' => '', + 'є' => '', + 'Ѥ' => '', + 'ѥ' => '', + 'Ѕ' => 'Js', + 'ѕ' => 'js', + 'Ꙋ' => '', + 'ꙋ' => '', + 'Ѡ' => '', + 'ѡ' => '', + 'Ѿ' => '', + 'ѿ' => '', + 'Ѫ' => '', + 'ѫ' => '', + 'Ѧ' => '', + 'ѧ' => '', + 'Ѭ' => '', + 'ѭ' => '', + 'Ѩ' => '', + 'ѩ' => '', + 'Ѯ' => '', + 'ѯ' => '', + 'Ѱ' => '', + 'ѱ' => '', + ], + // Ukrainian + 'uk' => [ + 'Є' => 'Ye', + 'є' => 'ye', + 'І' => 'I', + 'і' => 'i', + 'Ї' => 'Yi', + 'ї' => 'yi', + 'Ґ' => 'G', + 'ґ' => 'g', + ], + // Kazakh + 'kk' => [ + 'Ә' => 'A', + 'Ғ' => 'G', + 'Қ' => 'Q', + 'Ң' => 'N', + 'Ө' => 'O', + 'Ұ' => 'U', + 'Ү' => 'U', + 'Һ' => 'H', + 'ә' => 'a', + 'ғ' => 'g', + 'қ' => 'q', + 'ң' => 'n', + 'ө' => 'o', + 'ұ' => 'u', + 'ү' => 'u', + 'һ' => 'h', + ], + // Czech + 'cs' => [ + 'á' => 'a', + 'Á' => 'A', + 'č' => 'c', + 'Č' => 'C', + 'ď' => 'd', + 'Ď' => 'D', + 'é' => 'e', + 'É' => 'E', + 'ě' => 'e', + 'Ě' => 'E', + 'í' => 'i', + 'Í' => 'I', + 'ň' => 'n', + 'Ň' => 'N', + 'ó' => 'o', + 'Ó' => 'O', + 'ř' => 'r', + 'Ř' => 'R', + 'š' => 's', + 'Š' => 'S', + 'ť' => 't', + 'Ť' => 'T', + 'ú' => 'u', + 'Ú' => 'U', + 'ů' => 'u', + 'Ů' => 'U', + 'ý' => 'y', + 'Ý' => 'Y', + 'ž' => 'z', + 'Ž' => 'Z', + ], + // Danish + 'da' => [ + 'Æ' => 'Ae', + 'æ' => 'ae', + 'Ø' => 'Oe', + 'ø' => 'oe', + 'Å' => 'Aa', + 'å' => 'aa', + 'É' => 'E', + 'é' => 'e', + ], + // Polish + 'pl' => [ + 'ą' => 'a', + 'ć' => 'c', + 'ę' => 'e', + 'ł' => 'l', + 'ń' => 'n', + 'ó' => 'o', + 'ś' => 's', + 'ź' => 'z', + 'ż' => 'z', + 'Ą' => 'A', + 'Ć' => 'C', + 'Ę' => 'E', + 'Ł' => 'L', + 'Ń' => 'N', + 'Ó' => 'O', + 'Ś' => 'S', + 'Ź' => 'Z', + 'Ż' => 'Z', + ], + // Romanian + 'ro' => [ + 'ă' => 'a', + 'â' => 'a', + 'Ă' => 'A', + 'Â' => 'A', + 'î' => 'i', + 'Î' => 'I', + 'ș' => 's', + 'ş' => 's', + 'Ş' => 'S', + 'Ș' => 'S', + 'ț' => 't', + 'ţ' => 't', + 'Ţ' => 'T', + 'Ț' => 'T', + ], + // Esperanto + 'eo' => [ + 'ĉ' => 'cx', + 'ĝ' => 'gx', + 'ĥ' => 'hx', + 'ĵ' => 'jx', + 'ŝ' => 'sx', + 'ŭ' => 'ux', + 'Ĉ' => 'CX', + 'Ĝ' => 'GX', + 'Ĥ' => 'HX', + 'Ĵ' => 'JX', + 'Ŝ' => 'SX', + 'Ŭ' => 'UX', + ], + // Estonian + 'et' => [ + 'Š' => 'S', + 'Ž' => 'Z', + 'Õ' => 'O', + 'Ä' => 'A', + 'Ö' => 'O', + 'Ü' => 'U', + 'š' => 's', + 'ž' => 'z', + 'õ' => 'o', + 'ä' => 'a', + 'ö' => 'o', + 'ü' => 'u', + ], + // Latvian + 'lv' => [ + 'ā' => 'a', + 'č' => 'c', + 'ē' => 'e', + 'ģ' => 'g', + 'ī' => 'i', + 'ķ' => 'k', + 'ļ' => 'l', + 'ņ' => 'n', + 'š' => 's', + 'ū' => 'u', + 'ž' => 'z', + 'Ā' => 'A', + 'Č' => 'C', + 'Ē' => 'E', + 'Ģ' => 'G', + 'Ī' => 'i', + 'Ķ' => 'k', + 'Ļ' => 'L', + 'Ņ' => 'N', + 'Š' => 'S', + 'Ū' => 'u', + 'Ž' => 'Z', + ], + // Lithuanian + 'lt' => [ + 'ą' => 'a', + 'č' => 'c', + 'ę' => 'e', + 'ė' => 'e', + 'į' => 'i', + 'š' => 's', + 'ų' => 'u', + 'ū' => 'u', + 'ž' => 'z', + 'Ą' => 'A', + 'Č' => 'C', + 'Ę' => 'E', + 'Ė' => 'E', + 'Į' => 'I', + 'Š' => 'S', + 'Ų' => 'U', + 'Ū' => 'U', + 'Ž' => 'Z', + ], + // Norwegian + 'no' => [ + 'Æ' => 'AE', + 'æ' => 'ae', + 'Ø' => 'OE', + 'ø' => 'oe', + 'Å' => 'AA', + 'å' => 'aa', + ], + // Vietnamese + 'vi' => [ + 'Á' => 'A', + 'À' => 'A', + 'Ả' => 'A', + 'Ã' => 'A', + 'Ạ' => 'A', + 'Ă' => 'A', + 'Ắ' => 'A', + 'Ằ' => 'A', + 'Ẳ' => 'A', + 'Ẵ' => 'A', + 'Ặ' => 'A', + 'Â' => 'A', + 'Ấ' => 'A', + 'Ầ' => 'A', + 'Ẩ' => 'A', + 'Ẫ' => 'A', + 'Ậ' => 'A', + 'á' => 'a', + 'à' => 'a', + 'ả' => 'a', + 'ã' => 'a', + 'ạ' => 'a', + 'ă' => 'a', + 'ắ' => 'a', + 'ằ' => 'a', + 'ẳ' => 'a', + 'ẵ' => 'a', + 'ặ' => 'a', + 'â' => 'a', + 'ấ' => 'a', + 'ầ' => 'a', + 'ẩ' => 'a', + 'ẫ' => 'a', + 'ậ' => 'a', + 'É' => 'E', + 'È' => 'E', + 'Ẻ' => 'E', + 'Ẽ' => 'E', + 'Ẹ' => 'E', + 'Ê' => 'E', + 'Ế' => 'E', + 'Ề' => 'E', + 'Ể' => 'E', + 'Ễ' => 'E', + 'Ệ' => 'E', + 'é' => 'e', + 'è' => 'e', + 'ẻ' => 'e', + 'ẽ' => 'e', + 'ẹ' => 'e', + 'ê' => 'e', + 'ế' => 'e', + 'ề' => 'e', + 'ể' => 'e', + 'ễ' => 'e', + 'ệ' => 'e', + 'Í' => 'I', + 'Ì' => 'I', + 'Ỉ' => 'I', + 'Ĩ' => 'I', + 'Ị' => 'I', + 'í' => 'i', + 'ì' => 'i', + 'ỉ' => 'i', + 'ĩ' => 'i', + 'ị' => 'i', + 'Ó' => 'O', + 'Ò' => 'O', + 'Ỏ' => 'O', + 'Õ' => 'O', + 'Ọ' => 'O', + 'Ô' => 'O', + 'Ố' => 'O', + 'Ồ' => 'O', + 'Ổ' => 'O', + 'Ỗ' => 'O', + 'Ộ' => 'O', + 'Ơ' => 'O', + 'Ớ' => 'O', + 'Ờ' => 'O', + 'Ở' => 'O', + 'Ỡ' => 'O', + 'Ợ' => 'O', + 'ó' => 'o', + 'ò' => 'o', + 'ỏ' => 'o', + 'õ' => 'o', + 'ọ' => 'o', + 'ô' => 'o', + 'ố' => 'o', + 'ồ' => 'o', + 'ổ' => 'o', + 'ỗ' => 'o', + 'ộ' => 'o', + 'ơ' => 'o', + 'ớ' => 'o', + 'ờ' => 'o', + 'ở' => 'o', + 'ỡ' => 'o', + 'ợ' => 'o', + 'Ú' => 'U', + 'Ù' => 'U', + 'Ủ' => 'U', + 'Ũ' => 'U', + 'Ụ' => 'U', + 'Ư' => 'U', + 'Ứ' => 'U', + 'Ừ' => 'U', + 'Ử' => 'U', + 'Ữ' => 'U', + 'Ự' => 'U', + 'ú' => 'u', + 'ù' => 'u', + 'ủ' => 'u', + 'ũ' => 'u', + 'ụ' => 'u', + 'ư' => 'u', + 'ứ' => 'u', + 'ừ' => 'u', + 'ử' => 'u', + 'ữ' => 'u', + 'ự' => 'u', + 'Ý' => 'Y', + 'Ỳ' => 'Y', + 'Ỷ' => 'Y', + 'Ỹ' => 'Y', + 'Ỵ' => 'Y', + 'ý' => 'y', + 'ỳ' => 'y', + 'ỷ' => 'y', + 'ỹ' => 'y', + 'ỵ' => 'y', + 'Đ' => 'D', + 'đ' => 'd', + ], + // Persian (Farsi) + 'fa' => [ + 'ا' => 'a', + 'ب' => 'b', + 'پ' => 'b', + 'ت' => 't', + 'ث' => 's', + 'ج' => 'g', + 'چ' => 'ch', + 'ح' => 'h', + 'خ' => 'kh', + 'د' => 'd', + 'ذ' => 'z', + 'ر' => 'r', + 'ز' => 'z', + 'س' => 's', + 'ش' => 'sh', + 'ص' => 's', + 'ض' => 'z', + 'ط' => 't', + 'ظ' => 'z', + 'ع' => 'a', + 'غ' => 'gh', + 'ف' => 'f', + 'ق' => 'gh', + 'ک' => 'k', + 'گ' => 'g', + 'ل' => 'l', + 'ژ' => 'zh', + 'ك' => 'k', + 'م' => 'm', + 'ن' => 'n', + 'ه' => 'h', + 'و' => 'o', + 'ی' => 'y', + 'آ' => 'a', + '٠' => '0', + '١' => '1', + '٢' => '2', + '٣' => '3', + '٤' => '4', + '٥' => '5', + '٦' => '6', + '٧' => '7', + '٨' => '8', + '٩' => '9', + ], + // Arabic + 'ar' => [ + 'أ' => 'a', + 'ب' => 'b', + 'ت' => 't', + 'ث' => 'th', + 'ج' => 'g', + 'ح' => 'h', + 'خ' => 'kh', + 'د' => 'd', + 'ذ' => 'th', + 'ر' => 'r', + 'ز' => 'z', + 'س' => 's', + 'ش' => 'sh', + 'ص' => 's', + 'ض' => 'd', + 'ط' => 't', + 'ظ' => 'th', + 'ع' => 'aa', + 'غ' => 'gh', + 'ف' => 'f', + 'ق' => 'k', + 'ك' => 'k', + 'ل' => 'l', + 'م' => 'm', + 'ن' => 'n', + 'ه' => 'h', + 'و' => 'o', + 'ي' => 'y', + 'ا' => 'a', + 'إ' => 'a', + 'آ' => 'a', + 'ؤ' => 'o', + 'ئ' => 'y', + 'ء' => 'aa', + '٠' => '0', + '١' => '1', + '٢' => '2', + '٣' => '3', + '٤' => '4', + '٥' => '5', + '٦' => '6', + '٧' => '7', + '٨' => '8', + '٩' => '9', + ], + // Serbian + 'sr' => [ + 'đ' => 'dj', + 'ž' => 'z', + 'ć' => 'c', + 'č' => 'c', + 'š' => 's', + 'Đ' => 'Dj', + 'Ž' => 'Z', + 'Ć' => 'C', + 'Č' => 'C', + 'Š' => 'S', + 'а' => 'a', + 'б' => 'b', + 'в' => 'v', + 'г' => 'g', + 'д' => 'd', + 'ђ' => 'dj', + 'е' => 'e', + 'ж' => 'z', + 'з' => 'z', + 'и' => 'i', + 'ј' => 'j', + 'к' => 'k', + 'л' => 'l', + 'љ' => 'lj', + 'м' => 'm', + 'н' => 'n', + 'њ' => 'nj', + 'о' => 'o', + 'п' => 'p', + 'р' => 'r', + 'с' => 's', + 'т' => 't', + 'ћ' => 'c', + 'у' => 'u', + 'ф' => 'f', + 'х' => 'h', + 'ц' => 'c', + 'ч' => 'c', + 'џ' => 'dz', + 'ш' => 's', + 'А' => 'A', + 'Б' => 'B', + 'В' => 'V', + 'Г' => 'G', + 'Д' => 'D', + 'Ђ' => 'Dj', + 'Е' => 'E', + 'Ж' => 'Z', + 'З' => 'Z', + 'И' => 'I', + 'Ј' => 'j', + 'К' => 'K', + 'Л' => 'L', + 'Љ' => 'Lj', + 'М' => 'M', + 'Н' => 'N', + 'Њ' => 'Nj', + 'О' => 'O', + 'П' => 'P', + 'Р' => 'R', + 'С' => 'S', + 'Т' => 'T', + 'Ћ' => 'C', + 'У' => 'U', + 'Ф' => 'F', + 'Х' => 'H', + 'Ц' => 'C', + 'Ч' => 'C', + 'Џ' => 'Dz', + 'Ш' => 'S', + ], + // Serbian - Cyrillic + 'sr__cyr' => [ + 'а' => 'a', + 'б' => 'b', + 'в' => 'v', + 'г' => 'g', + 'д' => 'd', + 'ђ' => 'dj', + 'е' => 'e', + 'ж' => 'z', + 'з' => 'z', + 'и' => 'i', + 'ј' => 'j', + 'к' => 'k', + 'л' => 'l', + 'љ' => 'lj', + 'м' => 'm', + 'н' => 'n', + 'њ' => 'nj', + 'о' => 'o', + 'п' => 'p', + 'р' => 'r', + 'с' => 's', + 'т' => 't', + 'ћ' => 'c', + 'у' => 'u', + 'ф' => 'f', + 'х' => 'h', + 'ц' => 'c', + 'ч' => 'c', + 'џ' => 'dz', + 'ш' => 's', + 'А' => 'A', + 'Б' => 'B', + 'В' => 'V', + 'Г' => 'G', + 'Д' => 'D', + 'Ђ' => 'Dj', + 'Е' => 'E', + 'Ж' => 'Z', + 'З' => 'Z', + 'И' => 'I', + 'Ј' => 'j', + 'К' => 'K', + 'Л' => 'L', + 'Љ' => 'Lj', + 'М' => 'M', + 'Н' => 'N', + 'Њ' => 'Nj', + 'О' => 'O', + 'П' => 'P', + 'Р' => 'R', + 'С' => 'S', + 'Т' => 'T', + 'Ћ' => 'C', + 'У' => 'U', + 'Ф' => 'F', + 'Х' => 'H', + 'Ц' => 'C', + 'Ч' => 'C', + 'Џ' => 'Dz', + 'Ш' => 'S', + ], + // Serbian - Latin + 'sr__lat' => [ + 'đ' => 'dj', + 'ž' => 'z', + 'ć' => 'c', + 'č' => 'c', + 'š' => 's', + 'Đ' => 'Dj', + 'Ž' => 'Z', + 'Ć' => 'C', + 'Č' => 'C', + 'Š' => 'S', + ], + // Azerbaijani + 'az' => [ + 'ç' => 'c', + 'ə' => 'e', + 'ğ' => 'g', + 'ı' => 'i', + 'ö' => 'o', + 'ş' => 's', + 'ü' => 'u', + 'Ç' => 'C', + 'Ə' => 'E', + 'Ğ' => 'G', + 'İ' => 'I', + 'Ö' => 'O', + 'Ş' => 'S', + 'Ü' => 'U', + ], + // Slovak + 'sk' => [ + 'á' => 'a', + 'ä' => 'a', + 'č' => 'c', + 'ď' => 'd', + 'é' => 'e', + 'í' => 'i', + 'ľ' => 'l', + 'ĺ' => 'l', + 'ň' => 'n', + 'ó' => 'o', + 'ô' => 'o', + 'ŕ' => 'r', + 'š' => 's', + 'ť' => 't', + 'ú' => 'u', + 'ý' => 'y', + 'ž' => 'z', + 'Á' => 'A', + 'Ä' => 'A', + 'Č' => 'C', + 'Ď' => 'D', + 'É' => 'E', + 'Í' => 'I', + 'Ľ' => 'L', + 'Ĺ' => 'L', + 'Ň' => 'N', + 'Ó' => 'O', + 'Ô' => 'O', + 'Ŕ' => 'R', + 'Š' => 'S', + 'Ť' => 'T', + 'Ú' => 'U', + 'Ý' => 'Y', + 'Ž' => 'Z', + ], + // French + 'fr' => [ + 'Æ' => 'AE', + 'æ' => 'ae', + 'Œ' => 'OE', + 'œ' => 'oe', + 'â' => 'a', + 'Â' => 'A', + 'à' => 'a', + 'À' => 'A', + 'ä' => 'a', + 'Ä' => 'A', + 'ç' => 'c', + 'Ç' => 'C', + 'é' => 'e', + 'É' => 'E', + 'ê' => 'e', + 'Ê' => 'E', + 'ë' => 'e', + 'Ë' => 'E', + 'è' => 'e', + 'È' => 'E', + 'ï' => 'i', + 'î' => 'i', + 'Ï' => 'I', + 'Î' => 'I', + 'ÿ' => 'y', + 'Ÿ' => 'Y', + 'ô' => 'o', + 'Ô' => 'O', + 'ö' => 'o', + 'Ö' => 'O', + 'û' => 'u', + 'Û' => 'U', + 'ù' => 'u', + 'Ù' => 'U', + 'ü' => 'u', + 'Ü' => 'U', + ], + // Austrian (French) + 'fr_at' => [ + 'ß' => 'sz', + 'ẞ' => 'SZ', + 'Æ' => 'AE', + 'æ' => 'ae', + 'Œ' => 'OE', + 'œ' => 'oe', + 'â' => 'a', + 'Â' => 'A', + 'à' => 'a', + 'À' => 'A', + 'ä' => 'a', + 'Ä' => 'A', + 'ç' => 'c', + 'Ç' => 'C', + 'é' => 'e', + 'É' => 'E', + 'ê' => 'e', + 'Ê' => 'E', + 'ë' => 'e', + 'Ë' => 'E', + 'è' => 'e', + 'È' => 'E', + 'ï' => 'i', + 'î' => 'i', + 'Ï' => 'I', + 'Î' => 'I', + 'ÿ' => 'y', + 'Ÿ' => 'Y', + 'ô' => 'o', + 'Ô' => 'O', + 'ö' => 'o', + 'Ö' => 'O', + 'û' => 'u', + 'Û' => 'U', + 'ù' => 'u', + 'Ù' => 'U', + 'ü' => 'u', + 'Ü' => 'U', + ], + // Switzerland (French) + 'fr_ch' => [ + 'ß' => 'ss', + 'ẞ' => 'SS', + 'Æ' => 'AE', + 'æ' => 'ae', + 'Œ' => 'OE', + 'œ' => 'oe', + 'â' => 'a', + 'Â' => 'A', + 'à' => 'a', + 'À' => 'A', + 'ä' => 'a', + 'Ä' => 'A', + 'ç' => 'c', + 'Ç' => 'C', + 'é' => 'e', + 'É' => 'E', + 'ê' => 'e', + 'Ê' => 'E', + 'ë' => 'e', + 'Ë' => 'E', + 'è' => 'e', + 'È' => 'E', + 'ï' => 'i', + 'î' => 'i', + 'Ï' => 'I', + 'Î' => 'I', + 'ÿ' => 'y', + 'Ÿ' => 'Y', + 'ô' => 'o', + 'Ô' => 'O', + 'ö' => 'o', + 'Ö' => 'O', + 'û' => 'u', + 'Û' => 'U', + 'ù' => 'u', + 'Ù' => 'U', + 'ü' => 'u', + 'Ü' => 'U', + ], + // German + 'de' => [ + 'Ä' => 'Ae', + 'Ö' => 'Oe', + 'Ü' => 'Ue', + 'ä' => 'ae', + 'ö' => 'oe', + 'ü' => 'ue', + 'ß' => 'ss', + 'ẞ' => 'SS', + ], + // Austrian (German) + 'de_at' => [ + 'Ä' => 'Ae', + 'Ö' => 'Oe', + 'Ü' => 'Ue', + 'ä' => 'ae', + 'ö' => 'oe', + 'ü' => 'ue', + 'ß' => 'sz', + 'ẞ' => 'SZ', + ], + // Switzerland (German) + 'de_ch' => [ + 'Ä' => 'Ae', + 'Ö' => 'Oe', + 'Ü' => 'Ue', + 'ä' => 'ae', + 'ö' => 'oe', + 'ü' => 'ue', + 'ß' => 'ss', + 'ẞ' => 'SS', + ], + // Bengali (Bangla) + 'bn' => [ + 'ভ্ল' => 'vl', + 'পশ' => 'psh', + 'ব্ধ' => 'bdh', + 'ব্জ' => 'bj', + 'ব্দ' => 'bd', + 'ব্ব' => 'bb', + 'ব্ল' => 'bl', + 'ভ' => 'v', + 'ব' => 'b', + 'চ্ঞ' => 'cNG', + 'চ্ছ' => 'cch', + 'চ্চ' => 'cc', + 'ছ' => 'ch', + 'চ' => 'c', + 'ধ্ন' => 'dhn', + 'ধ্ম' => 'dhm', + 'দ্ঘ' => 'dgh', + 'দ্ধ' => 'ddh', + 'দ্ভ' => 'dv', + 'দ্ম' => 'dm', + 'ড্ড' => 'DD', + 'ঢ' => 'Dh', + 'ধ' => 'dh', + 'দ্গ' => 'dg', + 'দ্দ' => 'dd', + 'ড' => 'D', + 'দ' => 'd', + '।' => '.', + 'ঘ্ন' => 'Ghn', + 'গ্ধ' => 'Gdh', + 'গ্ণ' => 'GN', + 'গ্ন' => 'Gn', + 'গ্ম' => 'Gm', + 'গ্ল' => 'Gl', + 'জ্ঞ' => 'jNG', + 'ঘ' => 'Gh', + 'গ' => 'g', + 'হ্ণ' => 'hN', + 'হ্ন' => 'hn', + 'হ্ম' => 'hm', + 'হ্ল' => 'hl', + 'হ' => 'h', + 'জ্ঝ' => 'jjh', + 'ঝ' => 'jh', + 'জ্জ' => 'jj', + 'জ' => 'j', + 'ক্ষ্ণ' => 'kxN', + 'ক্ষ্ম' => 'kxm', + 'ক্ষ' => 'ksh', + 'কশ' => 'ksh', + 'ক্ক' => 'kk', + 'ক্ট' => 'kT', + 'ক্ত' => 'kt', + 'ক্ল' => 'kl', + 'ক্স' => 'ks', + 'খ' => 'kh', + 'ক' => 'k', + 'ল্ভ' => 'lv', + 'ল্ধ' => 'ldh', + 'লখ' => 'lkh', + 'লঘ' => 'lgh', + 'লফ' => 'lph', + 'ল্ক' => 'lk', + 'ল্গ' => 'lg', + 'ল্ট' => 'lT', + 'ল্ড' => 'lD', + 'ল্প' => 'lp', + 'ল্ম' => 'lm', + 'ল্ল' => 'll', + 'ল্ব' => 'lb', + 'ল' => 'l', + 'ম্থ' => 'mth', + 'ম্ফ' => 'mf', + 'ম্ভ' => 'mv', + 'মপ্ল' => 'mpl', + 'ম্ন' => 'mn', + 'ম্প' => 'mp', + 'ম্ম' => 'mm', + 'ম্ল' => 'ml', + 'ম্ব' => 'mb', + 'ম' => 'm', + '০' => '0', + '১' => '1', + '২' => '2', + '৩' => '3', + '৪' => '4', + '৫' => '5', + '৬' => '6', + '৭' => '7', + '৮' => '8', + '৯' => '9', + 'ঙ্ক্ষ' => 'Ngkx', + 'ঞ্ছ' => 'nch', + 'ঙ্ঘ' => 'ngh', + 'ঙ্খ' => 'nkh', + 'ঞ্ঝ' => 'njh', + 'ঙ্গৌ' => 'ngOU', + 'ঙ্গৈ' => 'ngOI', + 'ঞ্চ' => 'nc', + 'ঙ্ক' => 'nk', + 'ঙ্ষ' => 'Ngx', + 'ঙ্গ' => 'ngo', + 'ঙ্ম' => 'Ngm', + 'ঞ্জ' => 'nj', + 'ন্ধ' => 'ndh', + 'ন্ঠ' => 'nTh', + 'ণ্ঠ' => 'NTh', + 'ন্থ' => 'nth', + 'ঙ্গা' => 'nga', + 'ঙ্গি' => 'ngi', + 'ঙ্গী' => 'ngI', + 'ঙ্গু' => 'ngu', + 'ঙ্গূ' => 'ngU', + 'ঙ্গে' => 'nge', + 'ঙ্গো' => 'ngO', + 'ণ্ঢ' => 'NDh', + 'নশ' => 'nsh', + 'ঙর' => 'Ngr', + 'ঞর' => 'NGr', + 'ংর' => 'ngr', + 'ঙ' => 'Ng', + 'ঞ' => 'NG', + 'ং' => 'ng', + 'ন্ন' => 'nn', + 'ণ্ণ' => 'NN', + 'ণ্ন' => 'Nn', + 'ন্ম' => 'nm', + 'ণ্ম' => 'Nm', + 'ন্দ' => 'nd', + 'ন্ট' => 'nT', + 'ণ্ট' => 'NT', + 'ন্ড' => 'nD', + 'ণ্ড' => 'ND', + 'ন্ত' => 'nt', + 'ন্স' => 'ns', + 'ন' => 'n', + 'ণ' => 'N', + 'ৈ' => 'OI', + 'ৌ' => 'OU', + 'ো' => 'O', + 'ঐ' => 'OI', + 'ঔ' => 'OU', + 'অ' => 'o', + 'ও' => 'oo', + 'ফ্ল' => 'fl', + 'প্ট' => 'pT', + 'প্ত' => 'pt', + 'প্ন' => 'pn', + 'প্প' => 'pp', + 'প্ল' => 'pl', + 'প্স' => 'ps', + 'ফ' => 'f', + 'প' => 'p', + 'ৃ' => 'rri', + 'ঋ' => 'rri', + 'রর‍্য' => 'rry', + '্র্য' => 'ry', + '্রর' => 'rr', + 'ড়্গ' => 'Rg', + 'ঢ়' => 'Rh', + 'ড়' => 'R', + 'র' => 'r', + '্র' => 'r', + 'শ্ছ' => 'Sch', + 'ষ্ঠ' => 'ShTh', + 'ষ্ফ' => 'Shf', + 'স্ক্ল' => 'skl', + 'স্খ' => 'skh', + 'স্থ' => 'sth', + 'স্ফ' => 'sf', + 'শ্চ' => 'Sc', + 'শ্ত' => 'St', + 'শ্ন' => 'Sn', + 'শ্ম' => 'Sm', + 'শ্ল' => 'Sl', + 'ষ্ক' => 'Shk', + 'ষ্ট' => 'ShT', + 'ষ্ণ' => 'ShN', + 'ষ্প' => 'Shp', + 'ষ্ম' => 'Shm', + 'স্প্ল' => 'spl', + 'স্ক' => 'sk', + 'স্ট' => 'sT', + 'স্ত' => 'st', + 'স্ন' => 'sn', + 'স্প' => 'sp', + 'স্ম' => 'sm', + 'স্ল' => 'sl', + 'শ' => 'S', + 'ষ' => 'Sh', + 'স' => 's', + 'ু' => 'u', + 'উ' => 'u', + 'অ্য' => 'oZ', + 'ত্থ' => 'tth', + 'ৎ' => 'tt', + 'ট্ট' => 'TT', + 'ট্ম' => 'Tm', + 'ঠ' => 'Th', + 'ত্ন' => 'tn', + 'ত্ম' => 'tm', + 'থ' => 'th', + 'ত্ত' => 'tt', + 'ট' => 'T', + 'ত' => 't', + 'অ্যা' => 'AZ', + 'া' => 'a', + 'আ' => 'a', + 'য়া' => 'ya', + 'য়' => 'y', + 'ি' => 'i', + 'ই' => 'i', + 'ী' => 'ee', + 'ঈ' => 'ee', + 'ূ' => 'uu', + 'ঊ' => 'uu', + 'ে' => 'e', + 'এ' => 'e', + 'য' => 'z', + '্য' => 'Z', + 'ইয়' => 'y', + 'ওয়' => 'w', + '্ব' => 'w', + 'এক্স' => 'x', + 'ঃ' => ':', + 'ঁ' => 'nn', + '্‌' => '', + ], + // English + 'en' => [ + ], + // Latin (+ Cyrillic ?) chars + // + // -> Mix of languages, but we need to keep this here, so that different languages can handle there own behavior. + 'latin' => [ + '˚' => '0', + '¹' => '1', + '²' => '2', + '³' => '3', + '⁴' => '4', + '⁵' => '5', + '⁶' => '6', + '⁷' => '7', + '⁸' => '8', + '⁹' => '9', + '₀' => '0', + '₁' => '1', + '₂' => '2', + '₃' => '3', + '₄' => '4', + '₅' => '5', + '₆' => '6', + '₇' => '7', + '₈' => '8', + '₉' => '9', + '௦' => '0', + '௧' => '1', + '௨' => '2', + '௩' => '3', + '௪' => '4', + '௫' => '5', + '௬' => '6', + '௭' => '7', + '௮' => '8', + '௯' => '9', + '௰' => '10', + '௱' => '100', + '௲' => '1000', + 'Ꜳ' => 'AA', + 'ꜳ' => 'aa', + 'Æ' => 'AE', + 'æ' => 'ae', + 'Ǽ' => 'AE', + 'ǽ' => 'ae', + 'Ꜵ' => 'AO', + 'ꜵ' => 'ao', + 'Ꜷ' => 'AU', + 'ꜷ' => 'au', + 'Ꜹ' => 'AV', + 'ꜹ' => 'av', + 'Ꜻ' => 'av', + 'ꜻ' => 'av', + 'Ꜽ' => 'AY', + 'ꜽ' => 'ay', + 'ȸ' => 'db', + 'ʣ' => 'dz', + 'ʥ' => 'dz', + 'ʤ' => 'dezh', + '🙰' => 'et', + 'ff' => 'ff', + 'ffi' => 'ffi', + 'ffl' => 'ffl', + 'fi' => 'fi', + 'fl' => 'fl', + 'ʩ' => 'feng', + 'IJ' => 'IJ', + 'ij' => 'ij', + 'ʪ' => 'ls', + 'ʫ' => 'lz', + 'ɮ' => 'lezh', + 'ȹ' => 'qp', + 'ʨ' => 'tc', + 'ʦ' => 'ts', + 'ʧ' => 'tesh', + 'Œ' => 'OE', + 'œ' => 'oe', + 'Ꝏ' => 'OO', + 'ꝏ' => 'oo', + 'ẞ' => 'SS', + 'ß' => 'ss', + 'st' => 'st', + 'ſt' => 'st', + 'Ꜩ' => 'TZ', + 'ꜩ' => 'tz', + 'ᵫ' => 'ue', + 'Aι' => 'Ai', + 'αι' => 'ai', + 'Ει' => 'Ei', + 'ει' => 'ei', + 'Οι' => 'Oi', + 'οι' => 'oi', + 'Ου' => 'Oy', + 'ου' => 'oy', + 'Υι' => 'Yi', + 'υι' => 'yi', + 'ἀ' => 'a', + 'ἁ' => 'a', + 'ἂ' => 'a', + 'ἃ' => 'a', + 'ἄ' => 'a', + 'ἅ' => 'a', + 'ἆ' => 'a', + 'ἇ' => 'a', + 'Ἀ' => 'A', + 'Ἁ' => 'A', + 'Ἂ' => 'A', + 'Ἃ' => 'A', + 'Ἄ' => 'A', + 'Ἅ' => 'A', + 'Ἆ' => 'A', + 'Ἇ' => 'A', + 'ᾰ' => 'a', + 'ᾱ' => 'a', + 'ᾲ' => 'a', + 'ᾳ' => 'a', + 'ᾴ' => 'a', + 'ᾶ' => 'a', + 'ᾷ' => 'a', + 'Ᾰ' => 'A', + 'Ᾱ' => 'A', + 'Ὰ' => 'A', + 'Ά' => 'A', + 'ᾼ' => 'A', + 'Ä' => 'A', + 'ä' => 'a', + 'À' => 'A', + 'à' => 'a', + 'Á' => 'A', + 'á' => 'a', + 'Â' => 'A', + 'â' => 'a', + 'Ã' => 'A', + 'ã' => 'a', + 'A̧' => 'A', + 'a̧' => 'a', + 'Ą' => 'A', + 'ą' => 'a', + 'Ⱥ' => 'A', + 'ⱥ' => 'a', + 'Å' => 'A', + 'å' => 'a', + 'Ǻ' => 'A', + 'ǻ' => 'a', + 'Ă' => 'A', + 'ă' => 'a', + 'Ǎ' => 'A', + 'ǎ' => 'a', + 'Ȧ' => 'A', + 'ȧ' => 'a', + 'Ạ' => 'A', + 'ạ' => 'a', + 'Ā' => 'A', + 'ā' => 'a', + 'ª' => 'a', + 'Ɓ' => 'B', + 'Ѣ' => 'E', + 'ѣ' => 'e', + 'Ç' => 'C', + 'ç' => 'c', + 'Ĉ' => 'C', + 'ĉ' => 'c', + 'C̈' => 'C', + 'c̈' => 'c', + 'C̨' => 'C', + 'c̨' => 'c', + 'Ȼ' => 'C', + 'ȼ' => 'c', + 'Č' => 'C', + 'č' => 'c', + 'Ć' => 'C', + 'ć' => 'c', + 'C̀' => 'C', + 'c̀' => 'c', + 'Ċ' => 'C', + 'ċ' => 'c', + 'C̣' => 'C', + 'c̣' => 'c', + 'C̄' => 'C', + 'c̄' => 'c', + 'C̃' => 'C', + 'c̃' => 'c', + 'Ð' => 'D', + 'Đ' => 'D', + 'ð' => 'd', + 'đ' => 'd', + 'È' => 'E', + 'É' => 'E', + 'Ê' => 'E', + 'Ë' => 'E', + 'Ĕ' => 'E', + 'Ė' => 'E', + 'Ȩ' => 'E', + 'ȩ' => 'e', + 'Ę' => 'E', + 'ę' => 'e', + 'Ɇ' => 'E', + 'ɇ' => 'e', + 'Ě' => 'E', + 'ě' => 'e', + 'Ẹ' => 'E', + 'ẹ' => 'e', + 'Ē' => 'E', + 'ē' => 'e', + 'Ẽ' => 'E', + 'ẽ' => 'e', + 'è' => 'e', + 'é' => 'e', + 'ê' => 'e', + 'ë' => 'e', + 'ĕ' => 'e', + 'ė' => 'e', + 'ƒ' => 'f', + 'Ѳ' => 'F', + 'ѳ' => 'f', + 'Ĝ' => 'G', + 'Ġ' => 'G', + 'ĝ' => 'g', + 'ġ' => 'g', + 'Ĥ' => 'H', + 'Ħ' => 'H', + 'ĥ' => 'h', + 'ħ' => 'h', + 'Ì' => 'I', + 'Í' => 'I', + 'Î' => 'I', + 'Ï' => 'I', + 'Ĩ' => 'I', + 'Ĭ' => 'I', + 'Ǐ' => 'I', + 'Į' => 'I', + 'ì' => 'i', + 'í' => 'i', + 'î' => 'i', + 'ï' => 'i', + 'ĩ' => 'i', + 'ĭ' => 'i', + 'ǐ' => 'i', + 'į' => 'i', + 'І' => 'I', + 'і' => 'i', + 'I̧' => 'I', + 'i̧' => 'i', + 'Ɨ' => 'I', + 'ɨ' => 'i', + 'İ' => 'I', + 'i' => 'i', + 'Ị' => 'I', + 'ị' => 'i', + 'Ī' => 'I', + 'ī' => 'i', + 'Ĵ' => 'J', + 'ĵ' => 'j', + 'J́́' => 'J', + 'j́' => 'j', + 'J̀̀' => 'J', + 'j̀' => 'j', + 'J̈' => 'J', + 'j̈' => 'j', + 'J̧' => 'J', + 'j̧' => 'j', + 'J̨' => 'J', + 'j̨' => 'j', + 'Ɉ' => 'J', + 'ɉ' => 'j', + 'J̌' => 'J', + 'ǰ' => 'j', + 'J̇' => 'J', + 'j' => 'j', + 'J̣' => 'J', + 'j̣' => 'j', + 'J̄' => 'J', + 'j̄' => 'j', + 'J̃' => 'J', + 'j̃' => 'j', + 'ĸ' => 'k', + 'Ĺ' => 'L', + 'Ľ' => 'L', + 'Ŀ' => 'L', + 'ĺ' => 'l', + 'ľ' => 'l', + 'ŀ' => 'l', + 'L̀' => 'L', + 'l̀' => 'l', + 'L̂' => 'L', + 'l̂' => 'l', + 'L̈' => 'L', + 'l̈' => 'l', + 'Ļ' => 'L', + 'ļ' => 'l', + 'L̨' => 'L', + 'l̨' => 'l', + 'Ł' => 'L', + 'ł' => 'l', + 'Ƚ' => 'L', + 'ƚ' => 'l', + 'L̇' => 'L', + 'l̇' => 'l', + 'Ḷ' => 'L', + 'ḷ' => 'l', + 'L̄' => 'L', + 'l̄' => 'l', + 'L̃' => 'L', + 'l̃' => 'l', + 'Ñ' => 'N', + 'ñ' => 'n', + 'Ŋ' => 'N', + 'ŋ' => 'n', + 'ʼn' => 'n', + 'Ń' => 'N', + 'ń' => 'n', + 'Ǹ' => 'N', + 'ǹ' => 'n', + 'N̂' => 'N', + 'n̂' => 'n', + 'N̈' => 'N', + 'n̈' => 'n', + 'Ņ' => 'N', + 'ņ' => 'n', + 'N̨' => 'N', + 'n̨' => 'n', + 'Ꞥ' => 'N', + 'ꞥ' => 'n', + 'Ň' => 'N', + 'ň' => 'n', + 'Ṅ' => 'N', + 'ṅ' => 'n', + 'Ṇ' => 'N', + 'ṇ' => 'n', + 'N̄' => 'N', + 'n̄' => 'n', + 'Ö' => 'O', + 'Ò' => 'O', + 'Ó' => 'O', + 'Ô' => 'O', + 'Õ' => 'O', + 'Ō' => 'O', + 'Ŏ' => 'O', + 'Ǒ' => 'O', + 'Ő' => 'O', + 'Ơ' => 'O', + 'Ø' => 'O', + 'Ǿ' => 'O', + 'ö' => 'o', + 'ò' => 'o', + 'ó' => 'o', + 'ô' => 'o', + 'õ' => 'o', + 'ō' => 'o', + 'ŏ' => 'o', + 'ǒ' => 'o', + 'ő' => 'o', + 'ơ' => 'o', + 'ø' => 'o', + 'ǿ' => 'o', + 'º' => 'o', + 'O̧' => 'O', + 'o̧' => 'o', + 'Ǫ' => 'O', + 'ǫ' => 'o', + 'Ɵ' => 'O', + 'ɵ' => 'o', + 'Ȯ' => 'O', + 'ȯ' => 'o', + 'Ọ' => 'O', + 'ọ' => 'o', + 'Ŕ' => 'R', + 'Ŗ' => 'R', + 'ŕ' => 'r', + 'ŗ' => 'r', + 'Ŝ' => 'S', + 'Ș' => 'S', + 'ș' => 's', + 'Ś' => 'S', + 'ś' => 's', + 'S̀' => 'S', + 's̀' => 's', + 'Ŝ̀' => 'S', + 'ŝ' => 's', + 'S̈' => 'S', + 's̈' => 's', + 'Ş' => 'S', + 'ş' => 's', + 'S̨' => 'S', + 's̨' => 's', + 'Ꞩ' => 'S', + 'ꞩ' => 's', + 'Š' => 'S', + 'š' => 's', + 'Ṡ' => 'S', + 'ṡ' => 's', + 'Ṣ' => 'S', + 'ṣ' => 's', + 'S̄' => 'S', + 's̄' => 's', + 'S̃' => 'S', + 's̃' => 's', + 'ſ' => 's', + 'Ţ' => 'T', + 'Ț' => 'T', + 'Ŧ' => 'T', + 'Þ' => 'TH', + 'ţ' => 't', + 'ț' => 't', + 'ŧ' => 't', + 'þ' => 'th', + 'T́' => 'T', + 't́' => 't', + 'T̀' => 'T', + 't̀' => 't', + 'T̂' => 'T', + 't̂' => 't', + 'T̈' => 'T', + 'ẗ' => 't', + 'T̨' => 'T', + 't̨' => 't', + 'Ⱦ' => 'T', + 'ⱦ' => 't', + 'Ť' => 'T', + 'ť' => 't', + 'Ṫ' => 'T', + 'ṫ' => 't', + 'Ṭ' => 'T', + 'ṭ' => 't', + 'T̄' => 'T', + 't̄' => 't', + 'T̃' => 'T', + 't̃' => 't', + 'Ü' => 'U', + 'Ù' => 'U', + 'Ú' => 'U', + 'Û' => 'U', + 'Ũ' => 'U', + 'Ŭ' => 'U', + 'Ű' => 'U', + 'Ų' => 'U', + 'Ư' => 'U', + 'Ǔ' => 'U', + 'Ǖ' => 'U', + 'Ǘ' => 'U', + 'Ǚ' => 'U', + 'Ǜ' => 'U', + 'ü' => 'u', + 'ù' => 'u', + 'ú' => 'u', + 'û' => 'u', + 'ũ' => 'u', + 'ŭ' => 'u', + 'ű' => 'u', + 'ų' => 'u', + 'ư' => 'u', + 'ǔ' => 'u', + 'ǖ' => 'u', + 'ǘ' => 'u', + 'ǚ' => 'u', + 'ǜ' => 'u', + 'U̧' => 'U', + 'u̧' => 'u', + 'Ʉ' => 'U', + 'ʉ' => 'u', + 'U̇' => 'U', + 'u̇' => 'u', + 'Ụ' => 'U', + 'ụ' => 'u', + 'Ū' => 'U', + 'ū' => 'u', + 'Ʊ' => 'U', + 'ʊ' => 'u', + 'Ŵ' => 'W', + 'ŵ' => 'w', + 'Ẁ' => 'W', + 'ẁ' => 'w', + 'Ẃ' => 'W', + 'ẃ' => 'w', + 'Ẅ' => 'W', + 'ẅ' => 'w', + 'Ѵ' => 'I', + 'ѵ' => 'i', + 'Ꙗ' => 'Ja', + 'ꙗ' => 'ja', + 'Є' => 'Je', + 'є' => 'je', + 'Ѥ' => 'Je', + 'ѥ' => 'je', + 'Ѕ' => 'Dz', + 'ѕ' => 'dz', + 'Ꙋ' => 'U', + 'ꙋ' => 'u', + 'Ѡ' => 'O', + 'ѡ' => 'o', + 'Ѿ' => 'Ot', + 'ѿ' => 'ot', + 'Ѫ' => 'U', + 'ѫ' => 'u', + 'Ѧ' => 'Ja', + 'ѧ' => 'ja', + 'Ѭ' => 'Ju', + 'ѭ' => 'ju', + 'Ѩ' => 'Ja', + 'ѩ' => 'Ja', + 'Ѯ' => 'Ks', + 'ѯ' => 'ks', + 'Ѱ' => 'Ps', + 'ѱ' => 'ps', + 'Х' => 'X', + 'х' => 'x', + 'Ý' => 'Y', + 'Ÿ' => 'Y', + 'Ŷ' => 'Y', + 'ý' => 'y', + 'ÿ' => 'y', + 'ŷ' => 'y', + 'Ỳ' => 'Y', + 'ỳ' => 'y', + 'Y̧' => 'Y', + 'y̧' => 'y', + 'Y̨' => 'Y', + 'y̨' => 'y', + 'Ɏ' => 'Y', + 'ɏ' => 'y', + 'Y̌' => 'Y', + 'y̌' => 'y', + 'Ẏ' => 'Y', + 'ẏ' => 'y', + 'Ỵ' => 'Y', + 'ỵ' => 'y', + 'Ȳ' => 'Y', + 'ȳ' => 'y', + 'Ỹ' => 'Y', + 'ỹ' => 'y', + 'Ź' => 'Z', + 'ź' => 'z', + 'Z̀' => 'Z', + 'z̀' => 'z', + 'Ẑ' => 'Z', + 'ẑ' => 'z', + 'Z̈' => 'Z', + 'z̈' => 'z', + 'Z̧' => 'Z', + 'z̧' => 'z', + 'Z̨' => 'Z', + 'z̨' => 'z', + 'Ƶ' => 'Z', + 'ƶ' => 'z', + 'Ž' => 'Z', + 'ž' => 'z', + 'Ż' => 'Z', + 'ż' => 'z', + 'Ẓ' => 'Z', + 'ẓ' => 'z', + 'Z̄' => 'Z', + 'z̄' => 'z', + 'Z̃' => 'Z', + 'z̃' => 'z', + ], + // whitespace chars + ' ' => [ + "\xc2\xa0" => ' ', // 'NO-BREAK SPACE' + "\xe1\x9a\x80" => ' ', // 'OGHAM SPACE MARK' + "\xe2\x80\x80" => ' ', // 'EN QUAD' + "\xe2\x80\x81" => ' ', // 'EM QUAD' + "\xe2\x80\x82" => ' ', // 'EN SPACE' + "\xe2\x80\x83" => ' ', // 'EM SPACE' + "\xe2\x80\x84" => ' ', // 'THREE-PER-EM SPACE' + "\xe2\x80\x85" => ' ', // 'FOUR-PER-EM SPACE' + "\xe2\x80\x86" => ' ', // 'SIX-PER-EM SPACE' + "\xe2\x80\x87" => ' ', // 'FIGURE SPACE' + "\xe2\x80\x88" => ' ', // 'PUNCTUATION SPACE' + "\xe2\x80\x89" => ' ', // 'THIN SPACE' + "\xe2\x80\x8a" => ' ', // 'HAIR SPACE' + "\xe2\x80\xa8" => ' ', // 'LINE SEPARATOR' + "\xe2\x80\xa9" => ' ', // 'PARAGRAPH SEPARATOR' + "\xe2\x80\x8b" => ' ', // 'ZERO WIDTH SPACE' + "\xe2\x80\xaf" => ' ', // 'NARROW NO-BREAK SPACE' + "\xe2\x81\x9f" => ' ', // 'MEDIUM MATHEMATICAL SPACE' + "\xe3\x80\x80" => ' ', // 'IDEOGRAPHIC SPACE' + "\xef\xbe\xa0" => ' ', // 'HALFWIDTH HANGUL FILLER' + ], + // commonly used in Word documents + 'msword' => [ + "\xc2\xab" => '<<', // « (U+00AB) in UTF-8 + "\xc2\xbb" => '>>', // » (U+00BB) in UTF-8 + "\xe2\x80\x98" => "'", // ‘ (U+2018) in UTF-8 + "\xe2\x80\x99" => "'", // ’ (U+2019) in UTF-8 + "\xe2\x80\x9a" => "'", // ‚ (U+201A) in UTF-8 + "\xe2\x80\x9b" => "'", // ‛ (U+201B) in UTF-8 + "\xe2\x80\x9c" => '"', // “ (U+201C) in UTF-8 + "\xe2\x80\x9d" => '"', // ” (U+201D) in UTF-8 + "\xe2\x80\x9e" => '"', // „ (U+201E) in UTF-8 + "\xe2\x80\x9f" => '"', // ‟ (U+201F) in UTF-8 + "\xe2\x80\xb9" => "'", // ‹ (U+2039) in UTF-8 + "\xe2\x80\xba" => "'", // › (U+203A) in UTF-8 + "\xe2\x80\x93" => '-', // – (U+2013) in UTF-8 + "\xe2\x80\x94" => '-', // — (U+2014) in UTF-8 + "\xe2\x80\xa6" => '...', // … (U+2026) in UTF-8 + ], + // Currency + // + // url => https://en.wikipedia.org/wiki/Currency_symbol + 'currency_short' => [ + '€' => 'EUR', + '$' => '$', + '₢' => 'Cr', + '₣' => 'Fr.', + '£' => 'PS', + '₤' => 'L.', + 'ℳ' => 'M', + '₥' => 'mil', + '₦' => 'N', + '₧' => 'Pts', + '₨' => 'Rs', + 'රු' => 'LKR', + 'ரூ' => 'LKR', + '௹' => 'Rs', + 'रू' => 'NPR', + '₹' => 'Rs', + '૱' => 'Rs', + '₩' => 'W', + '₪' => 'NS', + '₸' => 'KZT', + '₫' => 'D', + '֏' => 'AMD', + '₭' => 'K', + '₺' => 'TL', + '₼' => 'AZN', + '₮' => 'T', + '₯' => 'Dr', + '₲' => 'PYG', + '₾' => 'GEL', + '₳' => 'ARA', + '₴' => 'UAH', + '₽' => 'RUB', + '₵' => 'GHS', + '₡' => 'CL', + '¢' => 'c', + '¥' => 'YEN', + '円' => 'JPY', + '৳' => 'BDT', + '元' => 'CNY', + '﷼' => 'SAR', + '៛' => 'KR', + '₠' => 'ECU', + '¤' => '$?', + '฿' => 'THB', + '؋' => 'AFN', + ], +]; diff --git a/vendor/voku/portable-ascii/src/voku/helper/data/ascii_extras_by_languages.php b/vendor/voku/portable-ascii/src/voku/helper/data/ascii_extras_by_languages.php new file mode 100644 index 0000000..426d84a --- /dev/null +++ b/vendor/voku/portable-ascii/src/voku/helper/data/ascii_extras_by_languages.php @@ -0,0 +1,759 @@ + [ + '=' => ' gelijk ', + '%' => ' procent ', + '∑' => ' som ', + '∆' => ' delta ', + '∞' => ' oneindig ', + '♥' => ' love ', + '&' => ' en ', + '+' => ' plus ', + ], + // Italian + 'it' => [ + '=' => ' uguale ', + '%' => ' percent ', + '∑' => ' somma ', + '∆' => ' delta ', + '∞' => ' infinito ', + '♥' => ' amore ', + '&' => ' e ', + '+' => ' piu ', + ], + // Macedonian + 'mk' => [ + '=' => ' ednakva ', + '%' => ' procenti ', + '∑' => ' zbir ', + '∆' => ' delta ', + '∞' => ' beskonecnost ', + '♥' => ' loveubov ', + '&' => ' i ', + '+' => ' plus ', + ], + // Portuguese (Brazil) + 'pt' => [ + '=' => ' igual ', + '%' => ' por cento ', + '∑' => ' soma ', + '∆' => ' delta ', + '∞' => ' infinito ', + '♥' => ' amor ', + '&' => ' e ', + '+' => ' mais ', + ], + // Greek(lish) (Elláda) + 'el__greeklish' => [ + '=' => ' isos ', + '%' => ' tois ekato ', + '∑' => ' athroisma ', + '∆' => ' delta ', + '∞' => ' apeiro ', + '♥' => ' agape ', + '&' => ' kai ', + '+' => ' syn ', + ], + // Greek (Elláda) + 'el' => [ + '=' => ' isos ', + '%' => ' tois ekato ', + '∑' => ' athroisma ', + '∆' => ' delta ', + '∞' => ' apeiro ', + '♥' => ' agape ', + '&' => ' kai ', + '+' => ' syn ', + ], + // Hindi + 'hi' => [ + '=' => ' samana ', + '%' => ' paratisata ', + '∑' => ' yoga ', + '∆' => ' dalata ', + '∞' => ' anata ', + '♥' => ' payara ', + '&' => ' aura ', + '+' => ' palasa ', + ], + // Armenian + 'hy' => [ + '=' => ' havasar ', + '%' => ' tvokvos ', + '∑' => ' gvoumar ', + '∆' => ' delta ', + '∞' => ' ansahmanvouthyvoun ', + '♥' => ' ser ', + '&' => ' ev ', + '+' => ' gvoumarats ', + ], + // Swedish + 'sv' => [ + '=' => ' lika ', + '%' => ' procent ', + '∑' => ' summa ', + '∆' => ' delta ', + '∞' => ' oandlighet ', + '♥' => ' alskar ', + '&' => ' och ', + '+' => ' plus ', + ], + // Turkmen + 'tk' => [ + '=' => ' den ', + '%' => ' yuzde ', + '∑' => ' jem ', + '∆' => ' delta ', + '∞' => ' mudimilik ', + '♥' => ' soygi ', + '&' => ' we ', + '+' => ' yzy ', + ], + // Turkish + 'tr' => [ + '=' => ' esit ', + '%' => ' yuzde ', + '∑' => ' Toplam ', + '∆' => ' delta ', + '∞' => ' sonsuzluk ', + '♥' => ' ask ', + '&' => ' ve ', + '+' => ' arti ', + ], + // Bulgarian + 'bg' => [ + '=' => ' raven ', + '%' => ' na sto ', + '∑' => ' suma ', + '∆' => ' delta ', + '∞' => ' bezkrajnost ', + '♥' => ' obicam ', + '&' => ' i ', + '+' => ' plus ', + ], + // Hungarian + 'hu' => [ + '=' => ' Egyenlo ', + '%' => ' Szazalek ', + '∑' => ' osszeg ', + '∆' => ' delta ', + '∞' => ' vegtelenitett ', + '♥' => ' love ', + '&' => ' Es ', + '+' => ' Plusz ', + ], + // Myanmar (Burmese) + 'my' => [ + '=' => ' ttn:ttnnym? ', + '%' => ' raakhngnn:k ', + '∑' => ' ld ', + '∆' => ' m?cwk?n:pe? ', + '∞' => ' ach:m ', + '♥' => ' mettttaa ', + '&' => ' n ', + '+' => ' ape?ng: ', + ], + // Croatian (Hrvatska) + 'hr' => [ + '=' => ' Jednaki ', + '%' => ' Posto ', + '∑' => ' zbroj ', + '∆' => ' Delta ', + '∞' => ' beskonacno ', + '♥' => ' ljubav ', + '&' => ' I ', + '+' => ' Plus ', + ], + // Finnish + 'fi' => [ + '=' => ' Sama ', + '%' => ' Prosenttia ', + '∑' => ' sum ', + '∆' => ' delta ', + '∞' => ' aareton ', + '♥' => ' rakkautta ', + '&' => ' Ja ', + '+' => ' Plus ', + ], + // Georgian (Kartvelian) + 'ka' => [ + '=' => ' tanasts\'ori ', + '%' => ' p\'rotsent\'i ', + '∑' => ' tankha ', + '∆' => ' delt\'a ', + '∞' => ' usasrulo ', + '♥' => ' siq\'varuli ', + '&' => ' da ', + '+' => ' p\'lus ', + ], + // Russian + 'ru' => [ + '=' => ' ravnyj ', + '%' => ' procent ', + '∑' => ' summa ', + '∆' => ' del\'ta ', + '∞' => ' beskonecnost\' ', + '♥' => ' lublu ', + '&' => ' i ', + '+' => ' plus ', + ], + // Russian - Passport (2013), ICAO + 'ru__passport_2013' => [ + '=' => ' ravnyj ', + '%' => ' procent ', + '∑' => ' summa ', + '∆' => ' del\'ta ', + '∞' => ' beskonecnost\' ', + '♥' => ' lublu ', + '&' => ' i ', + '+' => ' plus ', + ], + // Russian - GOST 7.79-2000(B) + 'ru__gost_2000_b' => [ + '=' => ' ravnyj ', + '%' => ' procent ', + '∑' => ' summa ', + '∆' => ' del\'ta ', + '∞' => ' beskonecnost\' ', + '♥' => ' lublu ', + '&' => ' i ', + '+' => ' plus ', + ], + // Ukrainian + 'uk' => [ + '=' => ' rivnij ', + '%' => ' vidsotkiv ', + '∑' => ' suma ', + '∆' => ' del\'ta ', + '∞' => ' neskincennist\' ', + '♥' => ' lubov ', + '&' => ' i ', + '+' => ' plus ', + ], + // Kazakh + 'kk' => [ + '=' => ' ten\' ', + '%' => ' Pajyzdar ', + '∑' => ' zalpy ', + '∆' => ' ajyrmasylyk, ', + '∞' => ' seksiz ', + '♥' => ' mahabbat ', + '&' => ' z@ne ', + '+' => ' plus ', + ], + // Czech + 'cs' => [ + '=' => ' rovnat se ', + '%' => ' procento ', + '∑' => ' soucet ', + '∆' => ' delta ', + '∞' => ' nekonecno ', + '♥' => ' laska ', + '&' => ' a ', + '+' => ' plus ', + ], + // Danish + 'da' => [ + '=' => ' Lige ', + '%' => ' Prozent ', + '∑' => ' sum ', + '∆' => ' delta ', + '∞' => ' uendelig ', + '♥' => ' kaerlighed ', + '&' => ' Og ', + '+' => ' Plus ', + ], + // Polish + 'pl' => [ + '=' => ' rowny ', + '%' => ' procent ', + '∑' => ' suma ', + '∆' => ' delta ', + '∞' => ' nieskonczonosc ', + '♥' => ' milosc ', + '&' => ' i ', + '+' => ' plus ', + ], + // Romanian + 'ro' => [ + '=' => ' egal ', + '%' => ' la suta ', + '∑' => ' suma ', + '∆' => ' delta ', + '∞' => ' infinit ', + '♥' => ' dragoste ', + '&' => ' si ', + '+' => ' la care se adauga ', + ], + // Esperanto + 'eo' => [ + '=' => ' Egalaj ', + '%' => ' Procento ', + '∑' => ' sumo ', + '∆' => ' delto ', + '∞' => ' senfina ', + '♥' => ' amo ', + '&' => ' Kaj ', + '+' => ' Pli ', + ], + // Estonian + 'et' => [ + '=' => ' Vordsed ', + '%' => ' Protsenti ', + '∑' => ' summa ', + '∆' => ' o ', + '∞' => ' loputut ', + '♥' => ' armastus ', + '&' => ' Ja ', + '+' => ' Pluss ', + ], + // Latvian + 'lv' => [ + '=' => ' vienads ', + '%' => ' procents ', + '∑' => ' summa ', + '∆' => ' delta ', + '∞' => ' bezgaliba ', + '♥' => ' milestiba ', + '&' => ' un ', + '+' => ' pluss ', + ], + // Lithuanian + 'lt' => [ + '=' => ' lygus ', + '%' => ' procentu ', + '∑' => ' suma ', + '∆' => ' delta ', + '∞' => ' begalybe ', + '♥' => ' meile ', + '&' => ' ir ', + '+' => ' plius ', + ], + // Norwegian + 'no' => [ + '=' => ' Lik ', + '%' => ' Prosent ', + '∑' => ' sum ', + '∆' => ' delta ', + '∞' => ' uendelig ', + '♥' => ' kjaerlighet ', + '&' => ' Og ', + '+' => ' Pluss ', + ], + // Vietnamese + 'vi' => [ + '=' => ' cong bang ', + '%' => ' phan tram ', + '∑' => ' tong so ', + '∆' => ' dong bang ', + '∞' => ' vo cuc ', + '♥' => ' Yeu ', + '&' => ' va ', + '+' => ' them ', + ], + // Arabic + 'ar' => [ + '=' => ' mtsawy ', + '%' => ' nsbh mywyh ', + '∑' => ' mjmw\' ', + '∆' => ' dlta ', + '∞' => ' ma la nhayt ', + '♥' => ' hb ', + '&' => ' w ', + '+' => ' zayd ', + ], + // Persian (Farsi) + 'fa' => [ + '=' => ' brabr ', + '%' => ' dr sd ', + '∑' => ' mjmw\' ', + '∆' => ' dlta ', + '∞' => ' by nhayt ', + '♥' => ' \'shq ', + '&' => ' w ', + '+' => ' bh \'lawh ', + ], + // Serbian + 'sr' => [ + '=' => ' jednak ', + '%' => ' procenat ', + '∑' => ' zbir ', + '∆' => ' delta ', + '∞' => ' beskraj ', + '♥' => ' lubav ', + '&' => ' i ', + '+' => ' vise ', + ], + // Serbian - Cyrillic + 'sr__cyr' => [ + '=' => ' jednak ', + '%' => ' procenat ', + '∑' => ' zbir ', + '∆' => ' delta ', + '∞' => ' beskraj ', + '♥' => ' lubav ', + '&' => ' i ', + '+' => ' vise ', + ], + // Serbian - Latin + 'sr__lat' => [ + '=' => ' jednak ', + '%' => ' procenat ', + '∑' => ' zbir ', + '∆' => ' delta ', + '∞' => ' beskraj ', + '♥' => ' lubav ', + '&' => ' i ', + '+' => ' vise ', + ], + // Azerbaijani + 'az' => [ + '=' => ' b@rab@r ', + '%' => ' faiz ', + '∑' => ' m@bl@g ', + '∆' => ' delta ', + '∞' => ' sonsuzluq ', + '♥' => ' sevgi ', + '&' => ' v@ ', + '+' => ' plus ', + ], + // Slovak + 'sk' => [ + '=' => ' rovny ', + '%' => ' percento ', + '∑' => ' sucet ', + '∆' => ' delta ', + '∞' => ' infinity ', + '♥' => ' milovat ', + '&' => ' a ', + '+' => ' viac ', + ], + // French + 'fr' => [ + '=' => ' Egal ', + '%' => ' Pourcentage ', + '∑' => ' somme ', + '∆' => ' delta ', + '∞' => ' infini ', + '♥' => ' amour ', + '&' => ' Et ', + '+' => ' Plus ', + ], + // Austrian (French) + 'fr_at' => [ + '=' => ' Egal ', + '%' => ' Pourcentage ', + '∑' => ' somme ', + '∆' => ' delta ', + '∞' => ' infini ', + '♥' => ' amour ', + '&' => ' Et ', + '+' => ' Plus ', + ], + // Switzerland (French) + 'fr_ch' => [ + '=' => ' Egal ', + '%' => ' Pourcentage ', + '∑' => ' somme ', + '∆' => ' delta ', + '∞' => ' infini ', + '♥' => ' amour ', + '&' => ' Et ', + '+' => ' Plus ', + ], + // German + 'de' => [ + '=' => ' gleich ', + '%' => ' Prozent ', + '∑' => ' gesamt ', + '∆' => ' Unterschied ', + '∞' => ' undendlich ', + '♥' => ' liebe ', + '&' => ' und ', + '+' => ' plus ', + ], + // Austrian (German) + 'de_at' => [ + '=' => ' gleich ', + '%' => ' Prozent ', + '∑' => ' gesamt ', + '∆' => ' Unterschied ', + '∞' => ' undendlich ', + '♥' => ' liebe ', + '&' => ' und ', + '+' => ' plus ', + ], + // Switzerland (German) + 'de_ch' => [ + '=' => ' gleich ', + '%' => ' Prozent ', + '∑' => ' gesamt ', + '∆' => ' Unterschied ', + '∞' => ' undendlich ', + '♥' => ' liebe ', + '&' => ' und ', + '+' => ' plus ', + ], + // Bengali (Bangla) + 'bn' => [ + '=' => ' Saman ', + '%' => ' Satakora ', + '∑' => ' Samasti ', + '∆' => ' Badhip ', + '∞' => ' Ananta ', + '♥' => ' Valobasa ', + '&' => ' Abong ', + '+' => ' Songzojon ', + ], + // English + 'en' => [ + '=' => ' equal ', + '%' => ' percent ', + '∑' => ' sum ', + '∆' => ' delta ', + '∞' => ' infinity ', + '♥' => ' love ', + '&' => ' and ', + '+' => ' plus ', + ], + // Currency + // + // url: https://en.wikipedia.org/wiki/Currency_symbol + 'currency' => [ + '€' => ' Euro ', + '$' => ' Dollar ', + '₢' => ' cruzeiro ', + '₣' => ' French franc ', + '£' => ' pound ', + '₤' => ' lira ', // Italian + '₶' => ' livre tournois ', + 'ℳ' => ' mark ', + '₥' => ' mill ', + '₦' => ' naira ', + '₧' => ' peseta ', + '₨' => ' rupee ', + 'රු' => ' rupee ', // Sri Lankan + 'ரூ' => ' rupee ', // Sri Lankan + '௹' => ' rupee ', // Tamil + 'रू' => ' rupee ', // Nepalese + '₹' => ' rupee ', // Indian + '૱' => ' rupee ', // Gujarat + '₩' => ' won ', + '₪' => ' new shequel ', + '₸' => ' tenge ', + '₫' => ' dong ', + '֏' => ' dram ', + '₭' => ' kip ', + '₺' => ' lira ', // Turkish + '₼' => ' manat ', + '₮' => ' tugrik ', + '₯' => ' drachma ', + '₰' => ' pfennig ', + '₷' => ' spesmilo ', + '₱' => ' peso ', // Philippine + '﷼‎' => ' riyal ', + '₲' => ' guarani ', + '₾' => ' lari ', + '₳' => ' austral ', + '₴' => ' hryvnia ', + '₽' => ' ruble ', + '₵' => ' cedi ', + '₡' => ' colon ', + '¢' => ' cent ', + '¥' => ' yen ', + '円' => ' yen ', + '৳' => ' taka ', + '元' => ' yuan ', + '﷼' => ' riyal ', + '៛' => ' riel ', + '₠' => ' European Currency ', + '¤' => ' currency ', + '฿' => ' baht ', + '؋' => ' afghani ', + ], + // Temperature + // + // url: https://en.wikipedia.org/wiki/Conversion_of_units_of_temperature + 'temperature' => [ + '°De' => ' Delisle ', + '°Re' => ' Reaumur ', // Réaumur + '°Ro' => ' Romer ', // Rømer + '°R' => ' Rankine ', + '°C' => ' Celsius ', + '°F' => ' Fahrenheit ', + '°N' => ' Newton ', + ], + 'latin_symbols' => [ + '=' => '=', + '%' => '%', + '∑' => '∑', + '∆' => '∆', + '∞' => '∞', + '♥' => '♥', + '&' => '&', + '+' => '+', + // --- + '©' => ' (c) ', + '®' => ' (r) ', + '@' => ' (at) ', + '№' => ' No. ', + '℞' => ' Rx ', + '[' => '[', + '\' => '\\', + ']' => ']', + '^' => '^', + '_' => '_', + '`' => '`', + '‐' => '-', + '‑' => '-', + '‒' => '-', + '–' => '-', + '−' => '-', + '—' => '-', + '―' => '-', + '﹘' => '-', + '│' => '|', + '∖' => '\\', + '∕' => '/', + '⁄' => '/', + '←' => '<-', + '→' => '->', + '↑' => '|', + '↓' => '|', + '⁅' => '[', + '⁆' => ']', + '⁎' => '*', + '、' => ',', + '。' => '.', + '〈' => '<', + '〉' => '>', + '《' => '<<', + '》' => '>>', + '〔' => '[', + '〕' => ']', + '〘' => '[', + '〙' => ']', + '〚' => '[', + '〛' => ']', + '﹝' => '[', + '﹞' => ']', + '︹' => '[', + '︺' => ']', + '﹇' => '[', + '﹈' => ']', + '︐' => ',', + '︑' => ',', + '︒' => '.', + '︓' => ':', + '︔' => ';', + '︕' => '!', + '︖' => '?', + '︙' => '...', + '︰' => '..', + '︵' => '(', + '︶' => ')', + '﹙' => '(', + '﹚' => ')', + '︷' => '{', + '︸' => '}', + '﹛' => '{', + '﹜' => '}', + '︽' => '<<', + '︾' => '>>', + '︿' => '<', + '﹀' => '>', + '×' => '*', + '÷' => '/', + '≪' => '<<', + '≫' => '>>', + '⦅' => '((', + '⦆' => '))', + '〇' => '0', + '′' => '\'', + '〝' => '"', + '〞' => '"', + '«' => '<<', + '»' => '>>', + '‘' => "'", + '’' => "'", + '‚' => ',', + '‛' => "'", + '“' => '"', + '”' => '"', + '„' => '"', + '‟' => '"', + '‹' => '<', + '›' => '>', + '․' => '.', + '‥' => '..', + '…' => '...', + '″' => '"', + '‴' => '\'\'\'', + '‶' => '``', + '‷' => '```', + '‼' => '!!', + '⁇' => '??', + '⁈' => '?!', + '⁉' => '!?', + '⁗' => '````', + '⩴' => '::=', + '⩵' => '==', + '⩶' => '===', + '﹔' => ';', + '﹕' => ':', + '﹖' => '?', + '﹗' => '!', + '﹍' => '_', + '﹎' => '_', + '﹏' => '_', + '﹐' => ',', + '﹑' => ',', + '﹒' => '.', + '﹟' => '#', + '﹠' => '&', + '﹡' => '*', + '﹢' => '+', + '﹣' => '-', + '﹤' => '<', + '﹥' => '>', + '﹦' => '=', + '﹨' => '\\', + '﹩' => '$', + '﹪' => '%', + '﹫' => '@', + '!' => '!', + '"' => '"', + '#' => '#', + '$' => '$', + '%' => '%', + '&' => '&', + ''' => '\'', + '(' => '(', + ')' => ')', + '*' => '*', + '+' => '+', + ',' => ',', + '-' => '-', + '.' => '.', + '/' => '/', + ':' => ':', + ';' => ';', + '<' => '<', + '=' => '=', + '>' => '>', + '?' => '?', + '@' => '@', + '{' => '{', + '|' => '|', + '}' => '}', + '~' => '~', + '⦅' => '((', + '⦆' => '))', + '¬' => '!', + ' ̄' => '-', + '¦' => '|', + '■' => '#', + ], +]; diff --git a/vendor/voku/portable-ascii/src/voku/helper/data/ascii_language_max_key.php b/vendor/voku/portable-ascii/src/voku/helper/data/ascii_language_max_key.php new file mode 100644 index 0000000..a6345f2 --- /dev/null +++ b/vendor/voku/portable-ascii/src/voku/helper/data/ascii_language_max_key.php @@ -0,0 +1,65 @@ + 0, + 'tk' => 1, + 'th' => 0, + 'ps' => 0, + 'or' => 0, + 'mn' => 0, + 'ko' => 0, + 'ky' => 0, + 'hy' => 1, + 'bn' => 5, + 'be' => 0, + 'am' => 0, + 'ja' => 0, + 'zh' => 0, + 'nl' => 1, + 'it' => 1, + 'mk' => 1, + 'pt' => 1, + 'el__greeklish' => 2, + 'el' => 2, + 'hi' => 2, + 'sv' => 1, + 'tr' => 1, + 'bg' => 2, + 'hu' => 1, + 'my' => 5, + 'hr' => 2, + 'fi' => 1, + 'ka' => 1, + 'ru' => 1, + 'ru__passport_2013' => 1, + 'ru__gost_2000_b' => 1, + 'uk' => 1, + 'kk' => 1, + 'cs' => 1, + 'da' => 1, + 'pl' => 1, + 'ro' => 1, + 'eo' => 1, + 'et' => 1, + 'lv' => 1, + 'lt' => 1, + 'no' => 1, + 'vi' => 1, + 'ar' => 1, + 'fa' => 1, + 'sr' => 1, + 'sr__cyr' => 1, + 'sr__lat' => 1, + 'az' => 1, + 'sk' => 1, + 'fr' => 1, + 'fr_at' => 1, + 'fr_ch' => 1, + 'de' => 1, + 'de_at' => 1, + 'de_ch' => 1, + 'en' => 0, + 'latin' => 3, + ' ' => 1, + 'msword' => 1, +]; diff --git a/vendor/voku/portable-ascii/src/voku/helper/data/ascii_ord.php b/vendor/voku/portable-ascii/src/voku/helper/data/ascii_ord.php new file mode 100644 index 0000000..142318c --- /dev/null +++ b/vendor/voku/portable-ascii/src/voku/helper/data/ascii_ord.php @@ -0,0 +1 @@ + 0, "\x00" => 0, "\x01" => 1, "\x02" => 2, "\x03" => 3, "\x04" => 4, "\x05" => 5, "\x06" => 6, "\x07" => 7, "\x08" => 8, "\x09" => 9, "\x0A" => 10, "\x0B" => 11, "\x0C" => 12, "\x0D" => 13, "\x0E" => 14, "\x0F" => 15, "\x10" => 16, "\x11" => 17, "\x12" => 18, "\x13" => 19, "\x14" => 20, "\x15" => 21, "\x16" => 22, "\x17" => 23, "\x18" => 24, "\x19" => 25, "\x1A" => 26, "\x1B" => 27, "\x1C" => 28, "\x1D" => 29, "\x1E" => 30, "\x1F" => 31, "\x20" => 32, "\x21" => 33, "\x22" => 34, "\x23" => 35, "\x24" => 36, "\x25" => 37, "\x26" => 38, "\x27" => 39, "\x28" => 40, "\x29" => 41, "\x2A" => 42, "\x2B" => 43, "\x2C" => 44, "\x2D" => 45, "\x2E" => 46, "\x2F" => 47, "\x30" => 48, "\x31" => 49, "\x32" => 50, "\x33" => 51, "\x34" => 52, "\x35" => 53, "\x36" => 54, "\x37" => 55, "\x38" => 56, "\x39" => 57, "\x3A" => 58, "\x3B" => 59, "\x3C" => 60, "\x3D" => 61, "\x3E" => 62, "\x3F" => 63, "\x40" => 64, "\x41" => 65, "\x42" => 66, "\x43" => 67, "\x44" => 68, "\x45" => 69, "\x46" => 70, "\x47" => 71, "\x48" => 72, "\x49" => 73, "\x4A" => 74, "\x4B" => 75, "\x4C" => 76, "\x4D" => 77, "\x4E" => 78, "\x4F" => 79, "\x50" => 80, "\x51" => 81, "\x52" => 82, "\x53" => 83, "\x54" => 84, "\x55" => 85, "\x56" => 86, "\x57" => 87, "\x58" => 88, "\x59" => 89, "\x5A" => 90, "\x5B" => 91, "\x5C" => 92, "\x5D" => 93, "\x5E" => 94, "\x5F" => 95, "\x60" => 96, "\x61" => 97, "\x62" => 98, "\x63" => 99, "\x64" => 100, "\x65" => 101, "\x66" => 102, "\x67" => 103, "\x68" => 104, "\x69" => 105, "\x6A" => 106, "\x6B" => 107, "\x6C" => 108, "\x6D" => 109, "\x6E" => 110, "\x6F" => 111, "\x70" => 112, "\x71" => 113, "\x72" => 114, "\x73" => 115, "\x74" => 116, "\x75" => 117, "\x76" => 118, "\x77" => 119, "\x78" => 120, "\x79" => 121, "\x7A" => 122, "\x7B" => 123, "\x7C" => 124, "\x7D" => 125, "\x7E" => 126, "\x7F" => 127, "\x80" => 128, "\x81" => 129, "\x82" => 130, "\x83" => 131, "\x84" => 132, "\x85" => 133, "\x86" => 134, "\x87" => 135, "\x88" => 136, "\x89" => 137, "\x8A" => 138, "\x8B" => 139, "\x8C" => 140, "\x8D" => 141, "\x8E" => 142, "\x8F" => 143, "\x90" => 144, "\x91" => 145, "\x92" => 146, "\x93" => 147, "\x94" => 148, "\x95" => 149, "\x96" => 150, "\x97" => 151, "\x98" => 152, "\x99" => 153, "\x9A" => 154, "\x9B" => 155, "\x9C" => 156, "\x9D" => 157, "\x9E" => 158, "\x9F" => 159, "\xA0" => 160, "\xA1" => 161, "\xA2" => 162, "\xA3" => 163, "\xA4" => 164, "\xA5" => 165, "\xA6" => 166, "\xA7" => 167, "\xA8" => 168, "\xA9" => 169, "\xAA" => 170, "\xAB" => 171, "\xAC" => 172, "\xAD" => 173, "\xAE" => 174, "\xAF" => 175, "\xB0" => 176, "\xB1" => 177, "\xB2" => 178, "\xB3" => 179, "\xB4" => 180, "\xB5" => 181, "\xB6" => 182, "\xB7" => 183, "\xB8" => 184, "\xB9" => 185, "\xBA" => 186, "\xBB" => 187, "\xBC" => 188, "\xBD" => 189, "\xBE" => 190, "\xBF" => 191, "\xC0" => 192, "\xC1" => 193, "\xC2" => 194, "\xC3" => 195, "\xC4" => 196, "\xC5" => 197, "\xC6" => 198, "\xC7" => 199, "\xC8" => 200, "\xC9" => 201, "\xCA" => 202, "\xCB" => 203, "\xCC" => 204, "\xCD" => 205, "\xCE" => 206, "\xCF" => 207, "\xD0" => 208, "\xD1" => 209, "\xD2" => 210, "\xD3" => 211, "\xD4" => 212, "\xD5" => 213, "\xD6" => 214, "\xD7" => 215, "\xD8" => 216, "\xD9" => 217, "\xDA" => 218, "\xDB" => 219, "\xDC" => 220, "\xDD" => 221, "\xDE" => 222, "\xDF" => 223, "\xE0" => 224, "\xE1" => 225, "\xE2" => 226, "\xE3" => 227, "\xE4" => 228, "\xE5" => 229, "\xE6" => 230, "\xE7" => 231, "\xE8" => 232, "\xE9" => 233, "\xEA" => 234, "\xEB" => 235, "\xEC" => 236, "\xED" => 237, "\xEE" => 238, "\xEF" => 239, "\xF0" => 240, "\xF1" => 241, "\xF2" => 242, "\xF3" => 243, "\xF4" => 244, "\xF5" => 245, "\xF6" => 246, "\xF7" => 247, "\xF8" => 248, "\xF9" => 249, "\xFA" => 250, "\xFB" => 251, "\xFC" => 252, "\xFD" => 253, "\xFE" => 254, "\xFF" => 255]; diff --git a/vendor/voku/portable-ascii/src/voku/helper/data/x000.php b/vendor/voku/portable-ascii/src/voku/helper/data/x000.php new file mode 100644 index 0000000..6c9d81f --- /dev/null +++ b/vendor/voku/portable-ascii/src/voku/helper/data/x000.php @@ -0,0 +1,16 @@ +', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~', '', 'EUR', // "\xc2\x80" => "\xe2\x82\xac" => EURO SIGN + '', ',', 'f', ',,', // "\xc2\x84" => "\xe2\x80\x9e" => DOUBLE LOW-9 QUOTATION MARK + '...', // "\xc2\x85" => "\xe2\x80\xa6" => HORIZONTAL ELLIPSIS + '+', '++', // "\xc2\x87" => "\xe2\x80\xa1" => DOUBLE DAGGER + '^', '%0', // "\xc2\x89" => "\xe2\x80\xb0" => PER MILLE SIGN + 'S', '<', 'OE', // "\xc2\x8c" => "\xc5\x92" => LATIN CAPITAL LIGATURE OE + '', 'Z', '', '', '\'', // "\xc2\x91" => "\xe2\x80\x98" => LEFT SINGLE QUOTATION MARK + '\'', // "\xc2\x92" => "\xe2\x80\x99" => RIGHT SINGLE QUOTATION MARK + '"', '"', '*', '-', '--', // "\xc2\x97" => "\xe2\x80\x94" => EM DASH + '~', 'tm', 's', '>', 'oe', '', 'z', 'Y', ' ', '!', 'C/', 'PS', '$?', 'Y=', '|', 'SS', '"', '(c)', 'a', '<<', '!', '', '(r)', '-', 'deg', '+-', '2', '3', '\'', 'u', 'P', '*', ',', '1', 'o', '>>', '1/4', '1/2', '3/4', '?', 'A', 'A', 'A', 'A', // Not "AE" - used in languages other than German + 'A', 'A', 'AE', 'C', 'E', 'E', 'E', 'E', 'I', 'I', 'I', 'I', 'D', 'N', 'O', 'O', 'O', 'O', // Not "OE" - used in languages other than German + 'O', 'x', 'O', 'U', 'U', 'U', // Not "UE" - used in languages other than German + 'U', 'Y', 'Th', 'ss', 'a', 'a', 'a', 'a', // Not "ae" - used in languages other than German + 'a', 'a', 'ae', 'c', 'e', 'e', 'e', 'e', 'i', 'i', 'i', 'i', 'd', 'n', 'o', 'o', 'o', 'o', // Not "oe" - used in languages other than German + 'o', '/', 'o', 'u', 'u', 'u', // Not "ue" - used in languages other than German + 'u', 'y', 'th', 'y', ]; diff --git a/vendor/voku/portable-ascii/src/voku/helper/data/x001.php b/vendor/voku/portable-ascii/src/voku/helper/data/x001.php new file mode 100644 index 0000000..87fb12f --- /dev/null +++ b/vendor/voku/portable-ascii/src/voku/helper/data/x001.php @@ -0,0 +1 @@ +', '^', 'V', '^', 'V', '\'', '-', '/', '\\', ',', '_', '\\', '/', ':', '.', '`', '\'', '^', 'V', '+', '-', 'V', '.', '@', ',', '~', '"', 'R', 'X', 'G', 'l', 's', 'x', '?', '', '', '', '', '', '', '', 'V', '=', '"', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]']; diff --git a/vendor/voku/portable-ascii/src/voku/helper/data/x003.php b/vendor/voku/portable-ascii/src/voku/helper/data/x003.php new file mode 100644 index 0000000..3d02b86 --- /dev/null +++ b/vendor/voku/portable-ascii/src/voku/helper/data/x003.php @@ -0,0 +1 @@ +', '[?]', '[?]', '[?]', 'f', 'v', 'u', 'yr', 'y', 'w', 'th', 'th', 'a', 'o', 'ac', 'ae', 'o', 'o', 'o', 'oe', 'on', 'r', 'k', 'c', 'k', 'g', 'ng', 'g', 'g', 'w', 'h', 'h', 'h', 'h', 'n', 'n', 'n', 'i', 'e', 'j', 'g', 'ae', 'a', 'eo', 'p', 'z', 's', 's', 's', 'c', 'z', 't', 't', 'd', 'b', 'b', 'p', 'p', 'e', 'm', 'm', 'm', 'l', 'l', 'ng', 'ng', 'd', 'o', 'ear', 'ior', 'qu', 'qu', 'qu', 's', 'yr', 'yr', 'yr', 'q', 'x', '.', ':', '+', '17', '18', '19', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]']; diff --git a/vendor/voku/portable-ascii/src/voku/helper/data/x017.php b/vendor/voku/portable-ascii/src/voku/helper/data/x017.php new file mode 100644 index 0000000..8f2a7ca --- /dev/null +++ b/vendor/voku/portable-ascii/src/voku/helper/data/x017.php @@ -0,0 +1 @@ +', '.', '..', '...', '.', "\n", + "\n\n", + '', '', '', '', '', ' ', '%0', '%00', '\'', '\'\'', '\'\'\'', '`', '``', '```', '^', '<', '>', '*', '!!', '!?', '-', '_', '-', '^', '***', '--', '/', '-[', ']-', '??', '?!', '!?', '7', 'PP', '(]', '[)', '*', '[?]', '[?]', '[?]', '%', '~', '[?]', '[?]', '[?]', "''''", // 0x57 + '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', ' ', '', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '', '', '', '', '', '', '0', 'i', '', '', '4', '5', '6', '7', '8', '9', '+', '-', '=', '(', ')', 'n', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '-', '=', '(', ')', '[?]', 'a', 'e', 'o', 'x', '[?]', 'h', 'k', 'l', 'm', 'n', 'p', 's', 't', '[?]', '[?]', '[?]', 'ECU', 'CL', 'Cr', 'Fr.', 'L.', 'mil', 'N', 'Pts', 'Rs', 'W', 'NS', 'D', 'EUR', 'K', 'T', 'Dr', 'Pf', 'P', 'G', 'A', 'UAH', 'C|', 'L', 'Sm', 'T', 'Rs', 'L', 'M', 'm', 'R', 'l', 'BTC', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '[?]', '', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', ]; diff --git a/vendor/voku/portable-ascii/src/voku/helper/data/x021.php b/vendor/voku/portable-ascii/src/voku/helper/data/x021.php new file mode 100644 index 0000000..1643d67 --- /dev/null +++ b/vendor/voku/portable-ascii/src/voku/helper/data/x021.php @@ -0,0 +1 @@ +=', '<=', '>=', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]']; diff --git a/vendor/voku/portable-ascii/src/voku/helper/data/x023.php b/vendor/voku/portable-ascii/src/voku/helper/data/x023.php new file mode 100644 index 0000000..b8f4ca0 --- /dev/null +++ b/vendor/voku/portable-ascii/src/voku/helper/data/x023.php @@ -0,0 +1 @@ + ', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]']; diff --git a/vendor/voku/portable-ascii/src/voku/helper/data/x024.php b/vendor/voku/portable-ascii/src/voku/helper/data/x024.php new file mode 100644 index 0000000..26abcc6 --- /dev/null +++ b/vendor/voku/portable-ascii/src/voku/helper/data/x024.php @@ -0,0 +1 @@ +', '>', '>', '>', '>', '>', 'V', 'V', 'V', 'V', '<', '<', '<', '<', '<', '<', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '#', '#', '#', '#', '#', '^', '^', '^', 'O', '#', '#', '#', '#', 'O', 'O', 'O', 'O', '/', '\\\\', '\\\\', '#', '#', '#', '#', '/']; diff --git a/vendor/voku/portable-ascii/src/voku/helper/data/x026.php b/vendor/voku/portable-ascii/src/voku/helper/data/x026.php new file mode 100644 index 0000000..0c97de3 --- /dev/null +++ b/vendor/voku/portable-ascii/src/voku/helper/data/x026.php @@ -0,0 +1 @@ + ', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]']; diff --git a/vendor/voku/portable-ascii/src/voku/helper/data/x028.php b/vendor/voku/portable-ascii/src/voku/helper/data/x028.php new file mode 100644 index 0000000..9585d91 --- /dev/null +++ b/vendor/voku/portable-ascii/src/voku/helper/data/x028.php @@ -0,0 +1 @@ +', 'n', 't', 'q', ',', '*', '5', '<', '-', 'u', '8', 'v', '.', '%', '[', '$', '+', 'x', '!', '&', ';', ':', '4', '\\', '0', 'z', '7', '(', '_', '?', 'w', ']', '#', 'y', ')', '=', '[d7]', '[d17]', '[d27]', '[d127]', '[d37]', '[d137]', '[d237]', '[d1237]', '[d47]', '[d147]', '[d247]', '[d1247]', '[d347]', '[d1347]', '[d2347]', '[d12347]', '[d57]', '[d157]', '[d257]', '[d1257]', '[d357]', '[d1357]', '[d2357]', '[d12357]', '[d457]', '[d1457]', '[d2457]', '[d12457]', '[d3457]', '[d13457]', '[d23457]', '[d123457]', '[d67]', '[d167]', '[d267]', '[d1267]', '[d367]', '[d1367]', '[d2367]', '[d12367]', '[d467]', '[d1467]', '[d2467]', '[d12467]', '[d3467]', '[d13467]', '[d23467]', '[d123467]', '[d567]', '[d1567]', '[d2567]', '[d12567]', '[d3567]', '[d13567]', '[d23567]', '[d123567]', '[d4567]', '[d14567]', '[d24567]', '[d124567]', '[d34567]', '[d134567]', '[d234567]', '[d1234567]', '[d8]', '[d18]', '[d28]', '[d128]', '[d38]', '[d138]', '[d238]', '[d1238]', '[d48]', '[d148]', '[d248]', '[d1248]', '[d348]', '[d1348]', '[d2348]', '[d12348]', '[d58]', '[d158]', '[d258]', '[d1258]', '[d358]', '[d1358]', '[d2358]', '[d12358]', '[d458]', '[d1458]', '[d2458]', '[d12458]', '[d3458]', '[d13458]', '[d23458]', '[d123458]', '[d68]', '[d168]', '[d268]', '[d1268]', '[d368]', '[d1368]', '[d2368]', '[d12368]', '[d468]', '[d1468]', '[d2468]', '[d12468]', '[d3468]', '[d13468]', '[d23468]', '[d123468]', '[d568]', '[d1568]', '[d2568]', '[d12568]', '[d3568]', '[d13568]', '[d23568]', '[d123568]', '[d4568]', '[d14568]', '[d24568]', '[d124568]', '[d34568]', '[d134568]', '[d234568]', '[d1234568]', '[d78]', '[d178]', '[d278]', '[d1278]', '[d378]', '[d1378]', '[d2378]', '[d12378]', '[d478]', '[d1478]', '[d2478]', '[d12478]', '[d3478]', '[d13478]', '[d23478]', '[d123478]', '[d578]', '[d1578]', '[d2578]', '[d12578]', '[d3578]', '[d13578]', '[d23578]', '[d123578]', '[d4578]', '[d14578]', '[d24578]', '[d124578]', '[d34578]', '[d134578]', '[d234578]', '[d1234578]', '[d678]', '[d1678]', '[d2678]', '[d12678]', '[d3678]', '[d13678]', '[d23678]', '[d123678]', '[d4678]', '[d14678]', '[d24678]', '[d124678]', '[d34678]', '[d134678]', '[d234678]', '[d1234678]', '[d5678]', '[d15678]', '[d25678]', '[d125678]', '[d35678]', '[d135678]', '[d235678]', '[d1235678]', '[d45678]', '[d145678]', '[d245678]', '[d1245678]', '[d345678]', '[d1345678]', '[d2345678]', '[d12345678]']; diff --git a/vendor/voku/portable-ascii/src/voku/helper/data/x029.php b/vendor/voku/portable-ascii/src/voku/helper/data/x029.php new file mode 100644 index 0000000..5162de3 --- /dev/null +++ b/vendor/voku/portable-ascii/src/voku/helper/data/x029.php @@ -0,0 +1 @@ +', '%', '[?]', '[?]', '>', '=', '[?]', '/', '-', '~', '\\', '/', '~', '~', '|-', '-|', '[?]', '[?]', '[?]', '[?]', '<=', '=>', '((', '))', '[?]', '[?]', '::', '[?]', '?', '\'', 'o', '.', ',', '.', ',', ';', '[?]', '[?]', '[?]', '[?]', '----', '------', 'x', '|', '[?]', '[?]', '=', ',', '"', '`--', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?]', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?] ', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]']; diff --git a/vendor/voku/portable-ascii/src/voku/helper/data/x02f.php b/vendor/voku/portable-ascii/src/voku/helper/data/x02f.php new file mode 100644 index 0000000..5147b57 --- /dev/null +++ b/vendor/voku/portable-ascii/src/voku/helper/data/x02f.php @@ -0,0 +1 @@ + ', '<<', '>> ', '[', '] ', '{', '} ', '[(', ')] ', '@', 'X ', '[', '] ', '[[', ']] ', '((', ')) ', '[[', ']] ', '~ ', '``', '\'\'', ',,', '@', '1', '2', '3', '4', '5', '6', '7', '8', '9', '', '', '', '', '', '', '~', '+', '+', '+', '+', '', '@', ' // ', '+10+', '+20+', '+30+', '[?]', '[?]', '[?]', '', '', '[?]', 'a', 'a', 'i', 'i', 'u', 'u', 'e', 'e', 'o', 'o', 'ka', 'ga', 'ki', 'gi', 'ku', 'gu', 'ke', 'ge', 'ko', 'go', 'sa', 'za', 'shi', // 0x57 + 'zi', 'su', 'zu', 'se', 'ze', 'so', 'zo', 'ta', 'da', 'chi', // 0x61 + 'di', 'tsu', // 0x63 + 'tsu', // 0x64 + 'du', 'te', 'de', 'to', 'do', 'na', 'ni', 'nu', 'ne', 'no', 'ha', 'ba', 'pa', 'hi', 'bi', 'pi', 'hu', 'bu', 'pu', 'he', 'be', 'pe', 'ho', 'bo', 'po', 'ma', 'mi', 'mu', 'me', 'mo', 'ya', 'ya', 'yu', 'yu', 'yo', 'yo', 'ra', 'ri', 'ru', 're', 'ro', 'wa', 'wa', 'wi', 'we', 'wo', 'n', 'vu', '[?]', '[?]', '[?]', '[?]', '', '', '', '', '"', '"', '[?]', '[?]', 'a', 'a', 'i', 'i', 'u', 'u', 'e', 'e', 'o', 'o', 'ka', 'ga', 'ki', 'gi', 'ku', 'gu', 'ke', 'ge', 'ko', 'go', 'sa', 'za', 'shi', // 0xb7 + 'zi', 'su', 'zu', 'se', 'ze', 'so', 'zo', 'ta', 'da', 'chi', // 0xc1 + 'di', 'tsu', // 0xc3 + 'tsu', // 0xc4 + 'du', 'te', 'de', 'to', 'do', 'na', 'ni', 'nu', 'ne', 'no', 'ha', 'ba', 'pa', 'hi', 'bi', 'pi', 'hu', 'bu', 'pu', 'he', 'be', 'pe', 'ho', 'bo', 'po', 'ma', 'mi', 'mu', 'me', 'mo', 'ya', 'ya', 'yu', 'yu', 'yo', 'yo', 'ra', 'ri', 'ru', 're', 'ro', 'wa', 'wa', 'wi', 'we', 'wo', 'n', 'vu', 'ka', 'ke', 'va', 'vi', 've', 'vo', '', '', '"', '"', ]; diff --git a/vendor/voku/portable-ascii/src/voku/helper/data/x031.php b/vendor/voku/portable-ascii/src/voku/helper/data/x031.php new file mode 100644 index 0000000..72c0260 --- /dev/null +++ b/vendor/voku/portable-ascii/src/voku/helper/data/x031.php @@ -0,0 +1 @@ +>', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '21', '22', '23', '24', '25', '26', '27', '28', '29', '30', '31', '32', '33', '34', '35', '(g)', '(n)', '(d)', '(r)', '(m)', '(b)', '(s)', '()', '(j)', '(c)', '(k)', '(t)', '(p)', '(h)', '(ga)', '(na)', '(da)', '(ra)', '(ma)', '(ba)', '(sa)', '(a)', '(ja)', '(ca)', '(ka)', '(ta)', '(pa)', '(ha)', '[?]', '[?]', '[?]', 'KIS ', '(1) ', '(2) ', '(3) ', '(4) ', '(5) ', '(6) ', '(7) ', '(8) ', '(9) ', '(10) ', '(Yue) ', '(Huo) ', '(Shui) ', '(Mu) ', '(Jin) ', '(Tu) ', '(Ri) ', '(Zhu) ', '(You) ', '(She) ', '(Ming) ', '(Te) ', '(Cai) ', '(Zhu) ', '(Lao) ', '(Mi) ', '(Nan) ', '(Nu) ', '(Shi) ', '(You) ', '(Yin) ', '(Zhu) ', '(Xiang) ', '(Xiu) ', '(Xie) ', '(Zheng) ', '(Shang) ', '(Zhong) ', '(Xia) ', '(Zuo) ', '(You) ', '(Yi) ', '(Zong) ', '(Xue) ', '(Jian) ', '(Qi) ', '(Zi) ', '(Xie) ', '(Ye) ', '36', '37', '38', '39', '40', '41', '42', '43', '44', '45', '46', '47', '48', '49', '50', '1M', '2M', '3M', '4M', '5M', '6M', '7M', '8M', '9M', '10M', '11M', '12M', 'Hg', 'erg', 'eV', 'LTD', 'a', 'i', 'u', 'u', 'o', 'ka', 'ki', 'ku', 'ke', 'ko', 'sa', 'si', 'su', 'se', 'so', 'ta', 'ti', 'tu', 'te', 'to', 'na', 'ni', 'nu', 'ne', 'no', 'ha', 'hi', 'hu', 'he', 'ho', 'ma', 'mi', 'mu', 'me', 'mo', 'ya', 'yu', 'yo', 'ra', 'ri', 'ru', 're', 'ro', 'wa', 'wi', 'we', 'wo']; diff --git a/vendor/voku/portable-ascii/src/voku/helper/data/x033.php b/vendor/voku/portable-ascii/src/voku/helper/data/x033.php new file mode 100644 index 0000000..8505337 --- /dev/null +++ b/vendor/voku/portable-ascii/src/voku/helper/data/x033.php @@ -0,0 +1 @@ +> ', '<', '> ', '[', '] ', '{', '}', '[?]', '[?]', '[?]', '[?]', '', '', '', '', '', '', '', ',', ',', '.', '', ';', ':', '?', '!', '-', '(', ')', '{', '}', '{', '}', '#', '&', '*', '+', '-', '<', '>', '=', '', '\\', '$', '%', '@', '[?]', '[?]', '[?]', '[?]', '', '', '', '[?]', '', '[?]', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '[?]', '[?]', '']; diff --git a/vendor/voku/portable-ascii/src/voku/helper/data/x0ff.php b/vendor/voku/portable-ascii/src/voku/helper/data/x0ff.php new file mode 100644 index 0000000..b3a1539 --- /dev/null +++ b/vendor/voku/portable-ascii/src/voku/helper/data/x0ff.php @@ -0,0 +1 @@ +', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~', '[?]', '[?]', '.', '[', ']', ',', '*', 'wo', 'a', 'i', 'u', 'e', 'o', 'ya', 'yu', 'yo', 'tu', '+', 'a', 'i', 'u', 'e', 'o', 'ka', 'ki', 'ku', 'ke', 'ko', 'sa', 'si', 'su', 'se', 'so', 'ta', 'ti', 'tu', 'te', 'to', 'na', 'ni', 'nu', 'ne', 'no', 'ha', 'hi', 'hu', 'he', 'ho', 'ma', 'mi', 'mu', 'me', 'mo', 'ya', 'yu', 'yo', 'ra', 'ri', 'ru', 're', 'ro', 'wa', 'n', ':', ';', '', 'g', 'gg', 'gs', 'n', 'nj', 'nh', 'd', 'dd', 'r', 'lg', 'lm', 'lb', 'ls', 'lt', 'lp', 'rh', 'm', 'b', 'bb', 'bs', 's', 'ss', '', 'j', 'jj', 'c', 'k', 't', 'p', 'h', '[?]', '[?]', '[?]', 'a', 'ae', 'ya', 'yae', 'eo', 'e', '[?]', '[?]', 'yeo', 'ye', 'o', 'wa', 'wae', 'oe', '[?]', '[?]', 'yo', 'u', 'weo', 'we', 'wi', 'yu', '[?]', '[?]', 'eu', 'yi', 'i', '[?]', '[?]', '[?]', '/C', 'PS', '!', '-', '|', 'Y=', 'W=', '[?]', '|', '-', '|', '-', '|', '#', 'O', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '{', '|', '}', '', '', '', '']; diff --git a/vendor/voku/portable-ascii/src/voku/helper/data/x1d4.php b/vendor/voku/portable-ascii/src/voku/helper/data/x1d4.php new file mode 100644 index 0000000..ad8d3b2 --- /dev/null +++ b/vendor/voku/portable-ascii/src/voku/helper/data/x1d4.php @@ -0,0 +1 @@ + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 52 => 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 78 => 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 104 => 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 130 => 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 156 => 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 181 => 'Z', 182 => 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 208 => 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 234 => 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']; diff --git a/vendor/voku/portable-ascii/src/voku/helper/data/x1d5.php b/vendor/voku/portable-ascii/src/voku/helper/data/x1d5.php new file mode 100644 index 0000000..a2a9b90 --- /dev/null +++ b/vendor/voku/portable-ascii/src/voku/helper/data/x1d5.php @@ -0,0 +1,4 @@ + 'w', 'x', 'y', 'z', 4 => 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 30 => 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 56 => 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 82 => 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 108 => 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 134 => 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 160 => 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 186 => 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 212 => 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 238 => 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', ]; diff --git a/vendor/voku/portable-ascii/src/voku/helper/data/x1d6.php b/vendor/voku/portable-ascii/src/voku/helper/data/x1d6.php new file mode 100644 index 0000000..315ef5e --- /dev/null +++ b/vendor/voku/portable-ascii/src/voku/helper/data/x1d6.php @@ -0,0 +1 @@ + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 80 => 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 112 => 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 230 => 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', ]; diff --git a/vendor/webman/admin/.gitignore b/vendor/webman/admin/.gitignore new file mode 100644 index 0000000..25bceaa --- /dev/null +++ b/vendor/webman/admin/.gitignore @@ -0,0 +1,9 @@ +/.idea +/.vscode +/vendor +*.log +.env +src/plugin/admin/public/upload +src/plugin/admin/config/database.php +src/plugin/admin/config/thinkorm.php +src/plugin/admin.zip diff --git a/vendor/webman/admin/LICENSE b/vendor/webman/admin/LICENSE new file mode 100644 index 0000000..099b83e --- /dev/null +++ b/vendor/webman/admin/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 workerman.net + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/webman/admin/README.md b/vendor/webman/admin/README.md new file mode 100644 index 0000000..2b46555 --- /dev/null +++ b/vendor/webman/admin/README.md @@ -0,0 +1,4 @@ +# admin +admin plugin for webman + +https://www.workerman.net/plugin/82 diff --git a/vendor/webman/admin/src/Install.php b/vendor/webman/admin/src/Install.php new file mode 100644 index 0000000..95cb65e --- /dev/null +++ b/vendor/webman/admin/src/Install.php @@ -0,0 +1,74 @@ + 'plugin/admin', +); + + /** + * Install + * @return void + */ + public static function install() + { + static::installByRelation(); + } + + /** + * Uninstall + * @return void + */ + public static function uninstall() + { + self::uninstallByRelation(); + } + + /** + * installByRelation + * @return void + */ + public static function installByRelation() + { + foreach (static::$pathRelation as $source => $dest) { + if ($pos = strrpos($dest, '/')) { + $parent_dir = base_path().'/'.substr($dest, 0, $pos); + if (!is_dir($parent_dir)) { + mkdir($parent_dir, 0777, true); + } + } + //symlink(__DIR__ . "/$source", base_path()."/$dest"); + copy_dir(__DIR__ . "/$source", base_path()."/$dest"); + echo "Create $dest +"; + } + } + + /** + * uninstallByRelation + * @return void + */ + public static function uninstallByRelation() + { + foreach (static::$pathRelation as $source => $dest) { + $path = base_path()."/$dest"; + if (!is_dir($path) && !is_file($path)) { + continue; + } + echo "Remove $dest +"; + if (is_file($path) || is_link($path)) { + unlink($path); + continue; + } + remove_dir($path); + } + } + +} \ No newline at end of file diff --git a/vendor/webman/admin/src/plugin/admin/api/Auth.php b/vendor/webman/admin/src/plugin/admin/api/Auth.php new file mode 100644 index 0000000..afe0c80 --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/api/Auth.php @@ -0,0 +1,127 @@ +getDefaultProperties(); + $noNeedLogin = $properties['noNeedLogin'] ?? []; + $noNeedAuth = $properties['noNeedAuth'] ?? []; + + // 不需要登录 + if (in_array($action, $noNeedLogin)) { + return true; + } + + // 获取登录信息 + $admin = admin(); + if (!$admin) { + $msg = '请登录'; + // 401是未登录固定的返回码 + $code = 401; + return false; + } + + // 不需要鉴权 + if (in_array($action, $noNeedAuth)) { + return true; + } + + // 当前管理员无角色 + $roles = $admin['roles']; + if (!$roles) { + $msg = '无权限'; + $code = 2; + return false; + } + + // 角色没有规则 + $rules = Role::whereIn('id', $roles)->pluck('rules'); + $rule_ids = []; + foreach ($rules as $rule_string) { + if (!$rule_string) { + continue; + } + $rule_ids = array_merge($rule_ids, explode(',', $rule_string)); + } + if (!$rule_ids) { + $msg = '无权限'; + $code = 2; + return false; + } + + // 超级管理员 + if (in_array('*', $rule_ids)){ + return true; + } + + // 如果action为index,规则里有任意一个以$controller开头的权限即可 + if (strtolower($action) === 'index') { + $rule = Rule::where(function ($query) use ($controller, $action) { + $controller_like = str_replace('\\', '\\\\', $controller); + $query->where('key', 'like', "$controller_like@%")->orWhere('key', $controller); + })->whereIn('id', $rule_ids)->first(); + if ($rule) { + return true; + } + $msg = '无权限'; + $code = 2; + return false; + } + + // 查询是否有当前控制器的规则 + $rule = Rule::where(function ($query) use ($controller, $action) { + $query->where('key', "$controller@$action")->orWhere('key', $controller); + })->whereIn('id', $rule_ids)->first(); + + if (!$rule) { + $msg = '无权限'; + $code = 2; + return false; + } + + return true; + } + +} \ No newline at end of file diff --git a/vendor/webman/admin/src/plugin/admin/api/Install.php b/vendor/webman/admin/src/plugin/admin/api/Install.php new file mode 100644 index 0000000..4741bec --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/api/Install.php @@ -0,0 +1,93 @@ + static::getMenus()]; + } + + /** + * 获取菜单 + * + * @return array|mixed + */ + public static function getMenus() + { + clearstatcache(); + if (is_file($menu_file = __DIR__ . '/../config/menu.php')) { + $menus = include $menu_file; + return $menus ?: []; + } + return []; + } + + /** + * 删除不需要的菜单 + * + * @param $previous_menus + * @return void + */ + public static function removeUnnecessaryMenus($previous_menus) + { + $menus_to_remove = array_diff(Menu::column($previous_menus, 'name'), Menu::column(static::getMenus(), 'name')); + foreach ($menus_to_remove as $name) { + Menu::delete($name); + } + } + +} \ No newline at end of file diff --git a/vendor/webman/admin/src/plugin/admin/api/Menu.php b/vendor/webman/admin/src/plugin/admin/api/Menu.php new file mode 100644 index 0000000..c482657 --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/api/Menu.php @@ -0,0 +1,150 @@ +first(); + return $menu ? $menu->toArray() : null; + } + + /** + * 根据id获得菜单 + * @param $id + * @return array + */ + public static function find($id): array + { + return Rule::find($id)->toArray(); + } + + /** + * 添加菜单 + * @param array $menu + * @return int + */ + public static function add(array $menu) + { + $item = new Rule; + foreach ($menu as $key => $value) { + $item->$key = $value; + } + $item->save(); + return $item->id; + } + + /** + * 导入菜单 + * @param array $menu_tree + * @return void + */ + public static function import(array $menu_tree) + { + if (is_numeric(key($menu_tree)) && !isset($menu_tree['key'])) { + foreach ($menu_tree as $item) { + static::import($item); + } + return; + } + $children = $menu_tree['children'] ?? []; + unset($menu_tree['children']); + if ($old_menu = Menu::get($menu_tree['key'])) { + $pid = $old_menu['id']; + Rule::where('key', $menu_tree['key'])->update($menu_tree); + } else { + $pid = static::add($menu_tree); + } + foreach ($children as $menu) { + $menu['pid'] = $pid; + static::import($menu); + } + } + + /** + * 删除菜单 + * @param $key + * @return void + */ + public static function delete($key) + { + $item = Rule::where('key', $key)->first(); + if (!$item) { + return; + } + // 子规则一起删除 + $delete_ids = $children_ids = [$item['id']]; + while($children_ids) { + $children_ids = Rule::whereIn('pid', $children_ids)->pluck('id')->toArray(); + $delete_ids = array_merge($delete_ids, $children_ids); + } + Rule::whereIn('id', $delete_ids)->delete(); + } + + + /** + * 获取菜单中某个(些)字段的值 + * @param $menu + * @param null $column + * @param null $index + * @return array|mixed + */ + public static function column($menu, $column = null, $index = null) + { + $values = []; + if (is_numeric(key($menu)) && !isset($menu['key'])) { + foreach ($menu as $item) { + $values = array_merge($values, static::column($item, $column, $index)); + } + return $values; + } + + $children = $menu['children'] ?? []; + unset($menu['children']); + if ($column === null) { + if ($index) { + $values[$menu[$index]] = $menu; + } else { + $values[] = $menu; + } + } else { + if (is_array($column)) { + $item = []; + foreach ($column as $f) { + $item[$f] = $menu[$f] ?? null; + } + if ($index) { + $values[$menu[$index]] = $item; + } else { + $values[] = $item; + } + } else { + $value = $menu[$column] ?? null; + if ($index) { + $values[$menu[$index]] = $value; + } else { + $values[] = $value; + } + } + } + foreach ($children as $child) { + $values = array_merge($values, static::column($child, $column, $index)); + } + return $values; + } + +} \ No newline at end of file diff --git a/vendor/webman/admin/src/plugin/admin/api/Middleware.php b/vendor/webman/admin/src/plugin/admin/api/Middleware.php new file mode 100644 index 0000000..20d5540 --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/api/Middleware.php @@ -0,0 +1,55 @@ +controller; + $action = $request->action; + + $code = 0; + $msg = ''; + if (!Auth::canAccess($controller, $action, $code, $msg)) { + if ($request->expectsJson()) { + $response = json(['code' => $code, 'msg' => $msg, 'type' => 'error']); + } else { + if ($code === 401) { + $response = response(<< + if (self !== top) { + parent.location.reload(); + } + +EOF + ); + } else { + $request->app = ''; + $request->plugin = 'admin'; + $response = view('common/error/403')->withStatus(403); + } + } + } else { + $response = $request->method() == 'OPTIONS' ? response('') : $handler($request); + } + return $response; + } + +} diff --git a/vendor/webman/admin/src/plugin/admin/app/common/Auth.php b/vendor/webman/admin/src/plugin/admin/app/common/Auth.php new file mode 100644 index 0000000..c25f237 --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/app/common/Auth.php @@ -0,0 +1,63 @@ +pluck('rules')->toArray(); + if ($rules && in_array('*', $rules)) { + return Role::pluck('id')->toArray(); + } + + $roles = Role::get(); + $tree = new Tree($roles); + $descendants = $tree->getDescendant($role_ids, $with_self); + return array_column($descendants, 'id'); + } + + /** + * 获取权限范围内的所有管理员id + * @param bool $with_self + * @return array + */ + public static function getScopeAdminIds(bool $with_self = false): array + { + $role_ids = static::getScopeRoleIds($with_self); + return AdminRole::whereIn('role_id', $role_ids)->pluck('admin_id')->toArray(); + } + + /** + * 是否是超级管理员 + * @param int $admin_id + * @return bool + */ + public static function isSupperAdmin(int $admin_id = 0): bool + { + if (!$admin_id) { + if (!$roles = admin('roles')) { + return false; + } + } else { + $roles = AdminRole::where('admin_id', $admin_id)->pluck('role_id'); + } + $rules = Role::whereIn('id', $roles)->pluck('rules'); + return $rules && in_array('*', $rules->toArray()); + } + +} \ No newline at end of file diff --git a/vendor/webman/admin/src/plugin/admin/app/common/Layui.php b/vendor/webman/admin/src/plugin/admin/app/common/Layui.php new file mode 100644 index 0000000..1d771f6 --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/app/common/Layui.php @@ -0,0 +1,1107 @@ +htmlContent); + } + + /** + * 获取生成的js代码 + * @param $indent + * @return string + */ + public function js($indent = 0): string + { + return str_replace("\n", "\n" . str_repeat(' ', $indent), $this->jsContent); + } + + /** + * 获取控件及相关参数 + * @param $options + * @return array + */ + protected function options($options): array + { + array_walk_recursive($options, function(&$item, $key){ + if (is_string($item)) { + $item = htmlspecialchars($item); + if ($key === 'url') { + $item = str_replace('&', '&', $item); + } + } + }); + $field = $options['field']??''; + $props = !empty($options['props']) ? $options['props'] : []; + $verify_string = !empty($props['lay-verify']) ? ' lay-verify="'.$props['lay-verify'].'"' : ''; + $required_string = strpos($verify_string, 'required') ? ' required' : ''; + $label = !empty($options['label']) ? '' : ''; + $value = $props['value'] ?? ''; + $class = $props['class'] ?? 'layui-input-block'; + + return [$label, $field, $value, $props, $verify_string, $required_string, $class]; + } + + /** + * input输入框 + * @param $options + * @return void + */ + public function input($options) + { + [$label, $field, $value, $props, $verify_string, $required_string, $class] = $this->options($options); + + $placeholder_string = !empty($props['placeholder']) ? ' placeholder="'.$props['placeholder'].'"' : ''; + $autocomplete_string = !empty($props['autocomplete']) ? ' autocomplete="'.$props['autocomplete'].'"' : ''; + $disabled_string = !empty($props['disabled']) ? ' disabled' : ''; + $type = $props['type'] ?? 'text'; + + $this->htmlContent .= << + $label +
    + +
    +
    + +EOF; + } + + /** + * input数字输入框 + * @param $options + * @return void + */ + public function inputNumber($options) + { + $options['props']['type'] = 'number'; + $this->input($options); + } + + /** + * 输入框范围 + * @param $options + * @return void + */ + public function inputRange($options) + { + [$label, $field, $value, $props, $verify_string, $required_string, $class] = $this->options($options); + $type = $props['type'] ?? 'text'; + + $this->htmlContent .= << + $label +
    +
    + + - + +
    +
    + + +EOF; + } + + /** + * 输入框模糊查询 + * @param $options + * @return void + */ + public function inputLike($options) + { + [$label, $field, $value, $props, $verify_string, $required_string, $class] = $this->options($options); + $type = $props['type'] ?? 'text'; + + $this->htmlContent .= << + $label +
    +
    + + +
    +
    + + +EOF; + } + + /** + * 数字输入框范围 + * @param $options + * @return void + */ + public function inputNumberRange($options) + { + $options['props']['type'] = 'number'; + $this->inputRange($options); + } + + /** + * 数字输入框模糊查询 + * @param $options + * @return void + */ + public function inputNumberLike($options) + { + $options['props']['type'] = 'number'; + $this->inputLike($options); + } + + /** + * 密码输入框 + * @param $options + * @return void + */ + public function inputPassword($options) + { + $options['props']['type'] = 'password'; + $this->input($options); + } + + /** + * 文本域 + * @param $options + * @return void + */ + public function textArea($options) + { + [$label, $field, $value, $props, $verify_string, $required_string, $class] = $this->options($options); + + $placeholder_string = !empty($props['placeholder']) ? ' placeholder="'.$props['placeholder'].'"' : ''; + $disabled_string = !empty($props['disabled']) ? ' disabled' : ''; + + $this->htmlContent .= << + $label +
    + +
    + + +EOF; + } + + /** + * 富文本 + * @param $options + * @return void + */ + public function richText($options) + { + [$label, $field, $value, $props, $verify_string, $required_string, $class] = $this->options($options); + + $placeholder_string = !empty($props['placeholder']) ? ' placeholder="'.$props['placeholder'].'"' : ''; + $disabled_string = !empty($props['disabled']) ? ' disabled' : ''; + $id = $field; + + $this->htmlContent .= << + $label +
    + +
    + + +EOF; + + $options_string = ''; + if (!isset($props['images_upload_url'])) { + $props['images_upload_url'] = '/app/admin/upload/image'; + } + foreach ($props as $key => $item) { + if (is_array($item)) { + $item = json_encode($item, JSON_UNESCAPED_UNICODE); + $options_string .= "\n $key: $item,"; + } else { + $options_string .= "\n $key: \"$item\","; + } + } + $this->jsContent .= <<options($options); + + $props['accept'] = $props['accept'] ?? 'file'; + $props['url'] = $props['url'] ?? '/app/admin/upload/file'; + $id = $this->createId($field); + + $props['field'] = $props['field'] ?? '__file__'; + unset($props['lay-verify']); + $options_string = ''; + foreach ($props as $key => $item) { + if (is_array($item)) { + $item = json_encode($item, JSON_UNESCAPED_UNICODE); + $options_string .= "\n $key: $item,"; + } else { + $options_string .= "\n $key: \"$item\","; + } + } + + $this->htmlContent .= << + $label +
    + $value + + + +
    + + +EOF; + $this->jsContent .= <<options($options); + $props['acceptMime'] = $props['acceptMime'] ?? 'image/gif,image/jpeg,image/jpg,image/png'; + $props['url'] = $props['url'] ?? '/app/admin/upload/image'; + $id = $this->createId($field); + + unset($props['lay-verify']); + $props['field'] = $props['field'] ?? '__file__'; + $options_string = ''; + foreach ($props as $key => $item) { + if (is_array($item)) { + $item = json_encode($item, JSON_UNESCAPED_UNICODE); + $options_string .= "\n $key: $item,"; + } else { + $options_string .= "\n $key: \"$item\","; + } + } + + $this->htmlContent .= << + $label +
    + + + + +
    + + +EOF; + $this->jsContent .= << 0) return layui.layer.msg(res.msg); + this.item.prev().val(res.data.url).prev().attr("src", res.data.url); + } + }); +}); + +EOF; + + } + + /** + * 日期时间选择组件 + * @param $options + * @return void + */ + public function dateTimePicker($options) + { + $options['props']['type'] = 'datetime'; + $this->datePicker($options); + } + + /** + * 日期选择组件 + * @param $options + * @return void + */ + public function datePicker($options) + { + [$label, $field, $value, $props, $verify_string, $required_string, $class] = $this->options($options); + $value_string = $value ? ' value="'.$value.'"' : ''; + $options_string = ''; + unset($props['required'], $props['lay-verify'], $props['value']); + foreach ($props as $key => $item) { + if (is_array($item)) { + continue; + } + $options_string .= "\n $key: \"$item\","; + } + $id = $this->createId($field); + + $this->htmlContent .= << + $label +
    + +
    + + +EOF; + $this->jsContent .= <<datePickerRange($options); + } + + /** + * 日期范围选择组件 + * @param $options + * @return void + */ + public function datePickerRange($options) + { + [$label, $field, $value, $props, $verify_string, $required_string, $class] = $this->options($options); + if (!isset($options['props']['type'])) { + $options['props']['type'] = 'date'; + } + $options_string = ''; + unset($props['required'], $props['lay-verify'], $props['value']); + foreach ($props as $key => $item) { + if (is_array($item)) { + continue; + } + $options_string .= "\n $key: \"$item\","; + } + $id = $this->createId($field); + $id_start = "$id-date-start"; + $id_end = "$id-date-end"; + $this->htmlContent .= << + $label +
    +
    + + - + +
    +
    + + +EOF; + $this->jsContent .= <<options($options); + + $value_string = $value ? ' value="'.$value.'"' : ''; + $id = $this->createId($field); + $options_string = ''; + foreach ($props as $key => $item) { + if (is_array($item)) { + $item = json_encode($item, JSON_UNESCAPED_UNICODE); + $options_string .= "\n $key: $item,"; + } else { + $options_string .= "\n $key: \"$item\","; + } + } + + $this->htmlContent .= << + $label +
    + +
    + + +EOF; + + $this->jsContent .= <<options($options); + + $value = (int)$value; + $disabled_string = !empty($props['disabled']) ? ' disabled' : ''; + $lay_text = !empty($props['lay-text']) ? "lay-text=\"{$props['lay-text']}\"" : ''; + + $id = $this->createId($field); + + $this->htmlContent .= << + $label +
    + + +
    + + +EOF; + + $this->jsContent .= << 'hidden', + 'label' => [ + 'type' => 'text', + ] + ], $options['props']['model'] ?? []); + $options['props']['clickClose'] = $options['props']['clickClose'] ?? true; + $options['props']['radio'] = $options['props']['radio'] ?? true; + $this->apiSelect($options); + } + + /** + * 下拉多选组件 + * @return void + */ + public function selectMulti($options) + { + $options['props']['toolbar'] = array_merge_recursive([ + 'show' => true, + 'list' => [ 'ALL', 'CLEAR', 'REVERSE' ] + ], $options['props']['toolbar'] ?? []); + $this->apiSelect($options); + } + + /** + * 树单选组件 + * @return void + */ + public function treeSelect($options) + { + $options['props']['model'] = array_merge_recursive([ + 'icon' => 'hidden', + 'label' => [ + 'type' => 'text', + ] + ], $options['props']['model'] ?? []); + $options['props']['clickClose'] = $options['props']['clickClose'] ?? true; + $options['props']['radio'] = $options['props']['radio'] ?? true; + $options['props']['tree'] = array_merge_recursive([ + '$show' => true, + '$strict' => false, + '$clickCheck' => true, + '$clickExpand' => false, + '$expandedKeys' => '$initValue' + ], $options['props']['tree'] ?? []); + $this->apiSelect($options); + } + + /** + * 树多选组件 + * @return void + */ + public function treeSelectMulti($options) + { + $options['props']['tree'] = array_merge_recursive(['show' => true, + '$expandedKeys' => '$initValue'], $options['props']['tree'] ?? []); + $options['props']['toolbar'] = array_merge_recursive([ + '$show' => true, + '$list' => [ 'ALL', 'CLEAR', 'REVERSE' ] + ], $options['props']['toolbar'] ?? []); + $this->apiSelect($options); + } + + /** + * 选择框,支持单选、多选、树形选择 + * @see https://maplemei.gitee.io/xm-select/ + * @param $options + * @return void + */ + public function apiSelect($options) + { + [$select_label, $field, $value, $props, $verify_string, $required_string, $class] = $this->options($options); + + $default_value_string = isset($props['initValue']) && $props['initValue'] != '' ? $props['initValue'] : $value; + $url = $props['url'] ?? ''; + $options_string = ''; + if (isset($props['lay-verify'])) { + $props['layVerify'] = $props['lay-verify']; + } + unset($props['lay-verify'], $props['url']); + foreach ($props as $key => $item) { + if (is_array($item)) { + $item = json_encode($item, JSON_UNESCAPED_UNICODE); + $item = preg_replace('/"\$([^"]+)"/', '$1', $item); + $options_string .= "\n".($url?' ':' ')."$key: $item,"; + } else if (is_string($item)) { + $options_string .= "\n".($url?' ':' ')."$key: \"$item\","; + } else { + $options_string .= "\n".($url?' ':' ')."$key: ".var_export($item, true).","; + } + } + + $id = $this->createId($field); + if ($url) { + $this->jsContent .= <<jsContent .= <<htmlContent .= << + $select_label +
    +
    +
    + + +EOF; + + } + + /** + * 构建表单 + * @param $table + * @param string $type + * @return Layui + * @throws BusinessException + */ + public static function buildForm($table, string $type = 'insert'): Layui + { + if (!in_array($type, ['insert', 'update', 'search'])) { + $type = 'insert'; + } + $filter = $type === 'search' ? 'searchable' : 'form_show'; + $form = new Layui(); + $schema = Util::getSchema($table); + $forms = $schema['forms']; + $columns = $schema['columns']; + $primary_key = $schema['table']['primary_key'][0] ?? null; + + foreach ($forms as $key => $info) { + if (empty($info[$filter])) { + continue; + } + $field = $info['field']; + $default = $columns[$key]['default']; + $control = strtolower($info['control']); + $auto_increment = $columns[$key]['auto_increment']; + // 搜索框里上传组件替换为input + if ($type == 'search' && in_array($control, ['upload', 'uploadimg'])) { + $control = 'input'; + $info['control_args'] = ''; + } + + $props = Util::getControlProps($control, $info['control_args']); + // 增加修改记录验证必填项 + if ($filter == 'form_show' && !$columns[$key]['nullable'] && $default === null && ($field !== 'password' || $type === 'insert')) { + if (!isset($props['lay-verify'])) { + $props['lay-verify'] = 'required'; + // 非类似字符串类型不允许传空 + } elseif (!in_array($columns[$key]['type'], ['string', 'text', 'mediumText', 'longText', 'char', 'binary', 'json']) + && strpos($props['lay-verify'], 'required') === false) { + $props['lay-verify'] = 'required|' . $props['lay-verify']; + } + } + // 增加记录显示默认值 + if ($type === 'insert' && !isset($props['value']) && $default !== null) { + $props['value'] = $default; + } + // 主键是自增字段或者表单是更新类型不显示主键 + if ($primary_key && $field == $primary_key && (($type == 'insert' && $auto_increment) || $type == 'update')) { + continue; + } + // 查询类型 + if ($type == 'search') { + if ($info['search_type'] == 'between' && method_exists($form, "{$control}Range")) { + $control = "{$control}Range"; + } elseif ($info['search_type'] == 'like' && method_exists($form, "{$control}Like")) { + $control = "{$control}Like"; + } + } + // 查询类型移除lay-verify + if ($type == 'search' && !empty($props['lay-verify'])) { + $props['lay-verify'] = ''; + } + + $options = [ + 'label' => $info['comment'] ?: $field, + 'field' => $field, + 'props' => $props, + ]; + + $form->{$control}($options); + } + return $form; + } + + /** + * 构建表格 + * @param $table + * @param int $indent + * @return array|string|string[] + * @throws BusinessException + */ + public static function buildTable($table, int $indent = 0) + { + $schema = Util::getSchema($table); + $forms = $schema['forms']; + + $codes = ''; + $cols = ''; + $api = ''; + $api_result = ''; + + foreach ($forms as $info) { + $title = $info['comment'] ?: $info['field']; + $hide_str = $info['list_show'] ? '' : "\n hide: true,"; + $sort_str = $info['enable_sort'] ? "\n sort: true," : ''; + $field = $info['field']; + $templet = ''; + $schema = <<'; + } +EOF; + break; + + case 'iconpicker': + $templet = <<'; + } +EOF; + break; + case 'upload': + $templet = <<' + util.escape(d['$field']) + ''; + } +EOF; + break; + case 'uploadimage': + $templet = <<' + } +EOF; + break; + + } + + if (in_array($control, ['select', 'selectmulti', 'treeselect', 'treeselectmulti'])) { + $props = Util::getControlProps($info['control'], $info['control_args']); + + if (isset($props['url'])) { + $api .= "\napis.push([\"$field\", \"{$props['url']}\"]);"; + $api_result .= "\napiResults[\"$field\"] = [];"; + } else if (!empty($props['data'])) { + $options = []; + foreach ($props['data'] as $option) { + if (isset($option['value']) && isset($option['name'])) { + $options[$option['value']] = $option['name']; + } + } + $api_result .= "\napiResults[\"$field\"] = " . json_encode($options, JSON_UNESCAPED_UNICODE) . ";"; + } else { + $api_result .= "\napiResults[\"$field\"] = [];"; + } + + $templet = <<pidName = $pid_name; + if (is_object($data) && method_exists($data, 'toArray')) { + $this->data = $data->toArray(); + } else { + $this->data = (array)$data; + } + $this->hashTree = $this->getHashTree(); + } + + /** + * 获取子孙节点 + * @param array $include + * @param bool $with_self + * @return array + */ + public function getDescendant(array $include, bool $with_self = false): array + { + $items = []; + foreach ($include as $id) { + if (!isset($this->hashTree[$id])) { + return []; + } + if ($with_self) { + $item = $this->hashTree[$id]; + unset($item['children']); + $items[$item['id']] = $item; + } + foreach ($this->hashTree[$id]['children'] ?? [] as $item) { + unset($item['children']); + $items[$item['id']] = $item; + foreach ($this->getDescendant([$item['id']]) as $it) { + $items[$it['id']] = $it; + } + } + } + return array_values($items); + } + + /** + * 获取哈希树 + * @param array $data + * @return array + */ + protected function getHashTree(array $data = []): array + { + $data = $data ?: $this->data; + $hash_tree = []; + foreach ($data as $item) { + $hash_tree[$item['id']] = $item; + } + foreach ($hash_tree as $index => $item) { + if ($item[$this->pidName] && isset($hash_tree[$item[$this->pidName]])) { + $hash_tree[$item[$this->pidName]]['children'][$hash_tree[$index]['id']] = &$hash_tree[$index]; + } + } + return $hash_tree; + } + + /** + * 获取树 + * @param array $include + * @param int $type + * @return array|null + */ + public function getTree(array $include = [], int $type = 1): ?array + { + // $type === static::EXCLUDE_ANCESTORS + if ($type === static::EXCLUDE_ANCESTORS) { + $items = []; + $include = array_unique($include); + foreach ($include as $id) { + if (!isset($this->hashTree[$id])) { + return []; + } + $items[] = $this->hashTree[$id]; + } + return static::arrayValues($items); + } + + // $type === static::INCLUDE_ANCESTORS + $hash_tree = $this->hashTree; + $items = []; + if ($include) { + $map = []; + foreach ($include as $id) { + if (!isset($hash_tree[$id])) { + continue; + } + $item = $hash_tree[$id]; + $max_depth = 100; + while ($max_depth-- > 0 && $item[$this->pidName] && isset($hash_tree[$item[$this->pidName]])) { + $last_item = $item; + $pid = $item[$this->pidName]; + $item = $hash_tree[$pid]; + $item_id = $item['id']; + if (empty($map[$item_id])) { + $map[$item_id] = 1; + $hash_tree[$pid]['children'] = []; + } + $hash_tree[$pid]['children'][$last_item['id']] = $last_item; + $item = $hash_tree[$pid]; + } + $items[$item['id']] = $item; + } + } else { + $items = $hash_tree; + } + $formatted_items = []; + foreach ($items as $item) { + if (!$item[$this->pidName] || !isset($hash_tree[$item[$this->pidName]])) { + $formatted_items[] = $item; + } + } + + return static::arrayValues($formatted_items); + } + + /** + * 递归重建数组下标 + * @param $array + * @return array + */ + public static function arrayValues($array): array + { + if (!$array) { + return []; + } + if (!isset($array['children'])) { + $current = current($array); + if (!is_array($current) || !isset($current['children'])) { + return $array; + } + $tree = array_values($array); + foreach ($tree as $index => $item) { + $tree[$index] = static::arrayValues($item); + } + return $tree; + } + $array['children'] = array_values($array['children']); + foreach ($array['children'] as $index => $child) { + $array['children'][$index] = static::arrayValues($child); + } + return $array; + } + +} \ No newline at end of file diff --git a/vendor/webman/admin/src/plugin/admin/app/common/Util.php b/vendor/webman/admin/src/plugin/admin/app/common/Util.php new file mode 100644 index 0000000..cacabfe --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/app/common/Util.php @@ -0,0 +1,567 @@ +getPdo()->quote($var, \PDO::PARAM_STR); + } + + /** + * 检查表名是否合法 + * @param string $table + * @return string + * @throws BusinessException + */ + public static function checkTableName(string $table): string + { + if (!preg_match('/^[a-zA-Z_0-9]+$/', $table)) { + throw new BusinessException('表名不合法'); + } + return $table; + } + + /** + * 变量或数组中的元素只能是字母数字下划线组合 + * @param $var + * @return mixed + * @throws BusinessException + */ + public static function filterAlphaNum($var) + { + $vars = (array)$var; + array_walk_recursive($vars, function ($item) { + if (is_string($item) && !preg_match('/^[a-zA-Z_0-9]+$/', $item)) { + throw new BusinessException('参数不合法'); + } + }); + return $var; + } + + /** + * 变量或数组中的元素只能是字母数字 + * @param $var + * @return mixed + * @throws BusinessException + */ + public static function filterNum($var) + { + $vars = (array)$var; + array_walk_recursive($vars, function ($item) { + if (is_string($item) && !preg_match('/^[0-9]+$/', $item)) { + throw new BusinessException('参数不合法'); + } + }); + return $var; + } + + /** + * 检测是否是合法URL Path + * @param $var + * @return string + * @throws BusinessException + */ + public static function filterUrlPath($var): string + { + if (!is_string($var) || !preg_match('/^[a-zA-Z0-9_\-\/&?.]+$/', $var)) { + throw new BusinessException('参数不合法'); + } + return $var; + } + + /** + * 检测是否是合法Path + * @param $var + * @return string + * @throws BusinessException + */ + public static function filterPath($var): string + { + if (!is_string($var) || !preg_match('/^[a-zA-Z0-9_\-\/]+$/', $var)) { + throw new BusinessException('参数不合法'); + } + return $var; + } + + /** + * 类转换为url path + * @param $controller_class + * @return false|string + */ + static function controllerToUrlPath($controller_class) + { + $key = strtolower($controller_class); + $action = ''; + if (strpos($key, '@')) { + [$key, $action] = explode( '@', $key, 2); + } + $prefix = 'plugin'; + $paths = explode('\\', $key); + if (count($paths) < 2) { + return false; + } + $base = ''; + if (strpos($key, "$prefix\\") === 0) { + if (count($paths) < 4) { + return false; + } + array_shift($paths); + $plugin = array_shift($paths); + $base = "/app/$plugin/"; + } + array_shift($paths); + foreach ($paths as $index => $path) { + if ($path === 'controller') { + unset($paths[$index]); + } + } + $suffix = 'controller'; + $code = $base . implode('/', $paths); + if (substr($code, -strlen($suffix)) === $suffix) { + $code = substr($code, 0, -strlen($suffix)); + } + return $action ? "$code/$action" : $code; + } + + /** + * 转换为驼峰 + * @param string $value + * @return string + */ + public static function camel(string $value): string + { + static $cache = []; + $key = $value; + + if (isset($cache[$key])) { + return $cache[$key]; + } + + $value = ucwords(str_replace(['-', '_'], ' ', $value)); + + return $cache[$key] = str_replace(' ', '', $value); + } + + /** + * 转换为小驼峰 + * @param $value + * @return string + */ + public static function smCamel($value): string + { + return lcfirst(static::camel($value)); + } + + /** + * 获取注释中第一行 + * @param $comment + * @return false|mixed|string + */ + public static function getCommentFirstLine($comment) + { + if ($comment === false) { + return false; + } + foreach (explode("\n", $comment) as $str) { + if ($s = trim($str, "*/\ \t\n\r\0\x0B")) { + return $s; + } + } + return $comment; + } + + /** + * 表单类型到插件的映射 + * @return \string[][] + */ + public static function methodControlMap(): array + { + return [ + //method=>[控件] + 'integer' => ['InputNumber'], + 'string' => ['Input'], + 'text' => ['TextArea'], + 'date' => ['DatePicker'], + 'enum' => ['Select'], + 'float' => ['Input'], + + 'tinyInteger' => ['InputNumber'], + 'smallInteger' => ['InputNumber'], + 'mediumInteger' => ['InputNumber'], + 'bigInteger' => ['InputNumber'], + + 'unsignedInteger' => ['InputNumber'], + 'unsignedTinyInteger' => ['InputNumber'], + 'unsignedSmallInteger' => ['InputNumber'], + 'unsignedMediumInteger' => ['InputNumber'], + 'unsignedBigInteger' => ['InputNumber'], + + 'decimal' => ['Input'], + 'double' => ['Input'], + + 'mediumText' => ['TextArea'], + 'longText' => ['TextArea'], + + 'dateTime' => ['DateTimePicker'], + + 'time' => ['DateTimePicker'], + 'timestamp' => ['DateTimePicker'], + + 'char' => ['Input'], + + 'binary' => ['Input'], + + 'json' => ['input'] + ]; + } + + /** + * 数据库类型到插件的转换 + * @param $type + * @return string + */ + public static function typeToControl($type): string + { + if (stripos($type, 'int') !== false) { + return 'inputNumber'; + } + if (stripos($type, 'time') !== false || stripos($type, 'date') !== false) { + return 'dateTimePicker'; + } + if (stripos($type, 'text') !== false) { + return 'textArea'; + } + if ($type === 'enum') { + return 'select'; + } + return 'input'; + } + + /** + * 数据库类型到表单类型的转换 + * @param $type + * @param $unsigned + * @return string + */ + public static function typeToMethod($type, $unsigned = false) + { + if (stripos($type, 'int') !== false) { + $type = str_replace('int', 'Integer', $type); + return $unsigned ? "unsigned" . ucfirst($type) : lcfirst($type); + } + $map = [ + 'int' => 'integer', + 'varchar' => 'string', + 'mediumtext' => 'mediumText', + 'longtext' => 'longText', + 'datetime' => 'dateTime', + ]; + return $map[$type] ?? $type; + } + + /** + * 按表获取摘要 + * @param $table + * @param null $section + * @return array|mixed + * @throws BusinessException + */ + public static function getSchema($table, $section = null) + { + Util::checkTableName($table); + $database = config('database.connections')['plugin.admin.mysql']['database']; + $schema_raw = $section !== 'table' ? Util::db()->select("select * from information_schema.COLUMNS where TABLE_SCHEMA = '$database' and table_name = '$table' order by ORDINAL_POSITION") : []; + $forms = []; + $columns = []; + foreach ($schema_raw as $item) { + $field = $item->COLUMN_NAME; + $columns[$field] = [ + 'field' => $field, + 'type' => Util::typeToMethod($item->DATA_TYPE, (bool)strpos($item->COLUMN_TYPE, 'unsigned')), + 'comment' => $item->COLUMN_COMMENT, + 'default' => $item->COLUMN_DEFAULT, + 'length' => static::getLengthValue($item), + 'nullable' => $item->IS_NULLABLE !== 'NO', + 'primary_key' => $item->COLUMN_KEY === 'PRI', + 'auto_increment' => strpos($item->EXTRA, 'auto_increment') !== false + ]; + + $forms[$field] = [ + 'field' => $field, + 'comment' => $item->COLUMN_COMMENT, + 'control' => static::typeToControl($item->DATA_TYPE), + 'form_show' => $item->COLUMN_KEY !== 'PRI', + 'list_show' => true, + 'enable_sort' => false, + 'searchable' => false, + 'search_type' => 'normal', + 'control_args' => '', + ]; + } + $table_schema = $section == 'table' || !$section ? Util::db()->select("SELECT TABLE_COMMENT FROM information_schema.`TABLES` WHERE TABLE_SCHEMA='$database' and TABLE_NAME='$table'") : []; + $indexes = !$section || in_array($section, ['keys', 'table']) ? Util::db()->select("SHOW INDEX FROM `$table`") : []; + $keys = []; + $primary_key = []; + foreach ($indexes as $index) { + $key_name = $index->Key_name; + if ($key_name == 'PRIMARY') { + $primary_key[] = $index->Column_name; + continue; + } + if (!isset($keys[$key_name])) { + $keys[$key_name] = [ + 'name' => $key_name, + 'columns' => [], + 'type' => $index->Non_unique == 0 ? 'unique' : 'normal' + ]; + } + $keys[$key_name]['columns'][] = $index->Column_name; + } + + $data = [ + 'table' => ['name' => $table, 'comment' => $table_schema[0]->TABLE_COMMENT ?? '', 'primary_key' => $primary_key], + 'columns' => $columns, + 'forms' => $forms, + 'keys' => array_reverse($keys, true) + ]; + + $schema = Option::where('name', "table_form_schema_$table")->value('value'); + $form_schema_map = $schema ? json_decode($schema, true) : []; + + foreach ($data['forms'] as $field => $item) { + if (isset($form_schema_map[$field])) { + $data['forms'][$field] = $form_schema_map[$field]; + } + } + + return $section ? $data[$section] : $data; + } + + /** + * 获取字段长度或默认值 + * @param $schema + * @return mixed|string + */ + public static function getLengthValue($schema) + { + $type = $schema->DATA_TYPE; + if (in_array($type, ['float', 'decimal', 'double'])) { + return "{$schema->NUMERIC_PRECISION},{$schema->NUMERIC_SCALE}"; + } + if ($type === 'enum') { + return implode(',', array_map(function($item){ + return trim($item, "'"); + }, explode(',', substr($schema->COLUMN_TYPE, 5, -1)))); + } + if (in_array($type, ['varchar', 'text', 'char'])) { + return $schema->CHARACTER_MAXIMUM_LENGTH; + } + if (in_array($type, ['time', 'datetime', 'timestamp'])) { + return $schema->CHARACTER_MAXIMUM_LENGTH; + } + return ''; + } + + /** + * 获取控件参数 + * @param $control + * @param $control_args + * @return array + */ + public static function getControlProps($control, $control_args): array + { + if (!$control_args) { + return []; + } + $control = strtolower($control); + $props = []; + $split = explode(';', $control_args); + foreach ($split as $item) { + $pos = strpos($item, ':'); + if ($pos === false) { + continue; + } + $name = trim(substr($item, 0, $pos)); + $values = trim(substr($item, $pos + 1)); + // values = a:v,c:d + $pos = strpos($values, ':'); + if ($pos !== false) { + $options = explode(',', $values); + $values = []; + foreach ($options as $option) { + [$v, $n] = explode(':', $option); + if (in_array($control, ['select', 'selectmulti', 'treeselect', 'treemultiselect']) && $name == 'data') { + $values[] = ['value' => $v, 'name' => $n]; + } else { + $values[$v] = $n; + } + } + } + $props[$name] = $values; + } + return $props; + + } + + /** + * 获取某个composer包的版本 + * @param string $package + * @return mixed|string + */ + public static function getPackageVersion(string $package) + { + $installed_php = base_path('vendor/composer/installed.php'); + if (is_file($installed_php)) { + $packages = include $installed_php; + } + return substr($packages['versions'][$package]['version'] ?? 'unknown ', 0, -2); + } + + + /** + * Reload webman + * @return bool + */ + public static function reloadWebman() + { + if (function_exists('posix_kill')) { + try { + posix_kill(posix_getppid(), SIGUSR1); + return true; + } catch (Throwable $e) {} + } else { + Timer::add(1, function () { + Worker::stopAll(); + }); + } + return false; + } + + /** + * Pause file monitor + * @return void + */ + public static function pauseFileMonitor() + { + if (method_exists(Monitor::class, 'pause')) { + Monitor::pause(); + } + } + + /** + * Resume file monitor + * @return void + */ + public static function resumeFileMonitor() + { + if (method_exists(Monitor::class, 'resume')) { + Monitor::resume(); + } + } + +} diff --git a/vendor/webman/admin/src/plugin/admin/app/controller/AccountController.php b/vendor/webman/admin/src/plugin/admin/app/controller/AccountController.php new file mode 100644 index 0000000..bf1184d --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/app/controller/AccountController.php @@ -0,0 +1,258 @@ +model = new Admin; + } + + /** + * 账户设置 + * @return Response + */ + public function index() + { + return view('account/index'); + } + + /** + * 登录 + * @param Request $request + * @return Response + * @throws BusinessException + */ + public function login(Request $request): Response + { + $this->checkDatabaseAvailable(); + $captcha = $request->post('captcha'); + if (strtolower($captcha) !== session('captcha-login')) { + return $this->json(1, '验证码错误'); + } + $request->session()->forget('captcha-login'); + $username = $request->post('username', ''); + $password = $request->post('password', ''); + if (!$username) { + return $this->json(1, '用户名不能为空'); + } + $this->checkLoginLimit($username); + $admin = Admin::where('username', $username)->first(); + if (!$admin || !Util::passwordVerify($password, $admin->password)) { + return $this->json(1, '账户不存在或密码错误'); + } + if ($admin->status != 0) { + return $this->json(1, '当前账户暂时无法登录'); + } + $admin->login_at = date('Y-m-d H:i:s'); + $admin->save(); + $this->removeLoginLimit($username); + $admin = $admin->toArray(); + $session = $request->session(); + unset($admin['password']); + $session->set('admin', $admin); + return $this->json(0, '登录成功', [ + 'nickname' => $admin['nickname'], + 'token' => $request->sessionId(), + ]); + } + + /** + * 退出 + * @param Request $request + * @return Response + */ + public function logout(Request $request): Response + { + $request->session()->delete('admin'); + return $this->json(0); + } + + /** + * 获取登录信息 + * @param Request $request + * @return Response + */ + public function info(Request $request): Response + { + $admin = admin(); + if (!$admin) { + return $this->json(1); + } + $info = [ + 'id' => $admin['id'], + 'username' => $admin['username'], + 'nickname' => $admin['nickname'], + 'avatar' => $admin['avatar'], + 'email' => $admin['email'], + 'mobile' => $admin['mobile'], + 'isSupperAdmin' => Auth::isSupperAdmin(), + 'token' => $request->sessionId(), + ]; + return $this->json(0, 'ok', $info); + } + + /** + * 更新 + * @param Request $request + * @return Response + */ + public function update(Request $request): Response + { + $allow_column = [ + 'nickname' => 'nickname', + 'avatar' => 'avatar', + 'email' => 'email', + 'mobile' => 'mobile', + ]; + + $data = $request->post(); + $update_data = []; + foreach ($allow_column as $key => $column) { + if (isset($data[$key])) { + $update_data[$column] = $data[$key]; + } + } + if (isset($update_data['password'])) { + $update_data['password'] = Util::passwordHash($update_data['password']); + } + Admin::where('id', admin_id())->update($update_data); + $admin = admin(); + unset($update_data['password']); + foreach ($update_data as $key => $value) { + $admin[$key] = $value; + } + $request->session()->set('admin', $admin); + return $this->json(0); + } + + /** + * 修改密码 + * @param Request $request + * @return Response + */ + public function password(Request $request): Response + { + $hash = Admin::find(admin_id())['password']; + $password = $request->post('password'); + if (!$password) { + return $this->json(2, '密码不能为空'); + } + if ($request->post('password_confirm') !== $password) { + return $this->json(3, '两次密码输入不一致'); + } + if (!Util::passwordVerify($request->post('old_password'), $hash)) { + return $this->json(1, '原始密码不正确'); + } + $update_data = [ + 'password' => Util::passwordHash($password) + ]; + Admin::where('id', admin_id())->update($update_data); + return $this->json(0); + } + + /** + * 验证码 + * @param Request $request + * @param string $type + * @return Response + */ + public function captcha(Request $request, string $type = 'login'): Response + { + $builder = new PhraseBuilder(4, 'abcdefghjkmnpqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ'); + $captcha = new CaptchaBuilder(null, $builder); + $captcha->build(120); + $request->session()->set("captcha-$type", strtolower($captcha->getPhrase())); + $img_content = $captcha->get(); + return response($img_content, 200, ['Content-Type' => 'image/jpeg']); + } + + /** + * 检查登录频率限制 + * @param $username + * @return void + * @throws BusinessException + */ + protected function checkLoginLimit($username) + { + $limit_log_path = runtime_path() . '/login'; + if (!is_dir($limit_log_path)) { + mkdir($limit_log_path, 0777, true); + } + $limit_file = $limit_log_path . '/' . md5($username) . '.limit'; + $time = date('YmdH') . ceil(date('i')/5); + $limit_info = []; + if (is_file($limit_file)) { + $json_str = file_get_contents($limit_file); + $limit_info = json_decode($json_str, true); + } + + if (!$limit_info || $limit_info['time'] != $time) { + $limit_info = [ + 'username' => $username, + 'count' => 0, + 'time' => $time + ]; + } + $limit_info['count']++; + file_put_contents($limit_file, json_encode($limit_info)); + if ($limit_info['count'] >= 5) { + throw new BusinessException('登录失败次数过多,请5分钟后再试'); + } + } + + /** + * 解除登录频率限制 + * @param $username + * @return void + */ + protected function removeLoginLimit($username) + { + $limit_log_path = runtime_path() . '/login'; + $limit_file = $limit_log_path . '/' . md5($username) . '.limit'; + if (is_file($limit_file)) { + unlink($limit_file); + } + } + + protected function checkDatabaseAvailable() + { + if (!config('plugin.admin.database')) { + throw new BusinessException('请重启webman'); + } + } + +} diff --git a/vendor/webman/admin/src/plugin/admin/app/controller/AdminController.php b/vendor/webman/admin/src/plugin/admin/app/controller/AdminController.php new file mode 100644 index 0000000..125116b --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/app/controller/AdminController.php @@ -0,0 +1,219 @@ +model = new Admin; + } + + /** + * 浏览 + * @return Response + */ + public function index(): Response + { + return view('admin/index'); + } + + /** + * 查询 + * @param Request $request + * @return Response + * @throws BusinessException + */ + public function select(Request $request): Response + { + [$where, $format, $limit, $field, $order] = $this->selectInput($request); + $query = $this->doSelect($where, $field, $order); + if ($format === 'select') { + return $this->formatSelect($query->get()); + } + $paginator = $query->paginate($limit); + $items = $paginator->items(); + $admin_ids = array_column($items, 'id'); + $roles = AdminRole::whereIn('admin_id', $admin_ids)->get(); + $roles_map = []; + foreach ($roles as $role) { + $roles_map[$role['admin_id']][] = $role['role_id']; + } + $login_admin_id = admin_id(); + foreach ($items as $index => $item) { + $admin_id = $item['id']; + $items[$index]['roles'] = isset($roles_map[$admin_id]) ? implode(',', $roles_map[$admin_id]) : ''; + $items[$index]['show_toolbar'] = $admin_id != $login_admin_id; + } + return json(['code' => 0, 'msg' => 'ok', 'count' => $paginator->total(), 'data' => $items]); + } + + /** + * 插入 + * @param Request $request + * @return Response + * @throws BusinessException + */ + public function insert(Request $request): Response + { + if ($request->method() === 'POST') { + $data = $this->insertInput($request); + $admin_id = $this->doInsert($data); + $role_ids = $request->post('roles'); + $role_ids = $role_ids ? explode(',', $role_ids) : []; + if (!$role_ids) { + return $this->json(1, '至少选择一个角色组'); + } + if (!Auth::isSupperAdmin() && array_diff($role_ids, Auth::getScopeRoleIds())) { + return $this->json(1, '角色超出权限范围'); + } + AdminRole::where('admin_id', $admin_id)->delete(); + foreach ($role_ids as $id) { + $admin_role = new AdminRole; + $admin_role->admin_id = $admin_id; + $admin_role->role_id = $id; + $admin_role->save(); + } + return $this->json(0, 'ok', ['id' => $admin_id]); + } + return view('admin/insert'); + } + + /** + * 更新 + * @param Request $request + * @return Response + * @throws BusinessException + */ + public function update(Request $request): Response + { + if ($request->method() === 'POST') { + + [$id, $data] = $this->updateInput($request); + $admin_id = $request->post('id'); + if (!$admin_id) { + return $this->json(1, '缺少参数'); + } + + // 不能禁用自己 + if (isset($data['status']) && $data['status'] == 1 && $id == admin_id()) { + return $this->json(1, '不能禁用自己'); + } + + // 需要更新角色 + $role_ids = $request->post('roles'); + if ($role_ids !== null) { + if (!$role_ids) { + return $this->json(1, '至少选择一个角色组'); + } + $role_ids = explode(',', $role_ids); + + $is_supper_admin = Auth::isSupperAdmin(); + $exist_role_ids = AdminRole::where('admin_id', $admin_id)->pluck('role_id')->toArray(); + $scope_role_ids = Auth::getScopeRoleIds(); + if (!$is_supper_admin && !array_intersect($exist_role_ids, $scope_role_ids)) { + return $this->json(1, '无权限更改该记录'); + } + if (!$is_supper_admin && array_diff($role_ids, $scope_role_ids)) { + return $this->json(1, '角色超出权限范围'); + } + + // 删除账户角色 + $delete_ids = array_diff($exist_role_ids, $role_ids); + AdminRole::whereIn('role_id', $delete_ids)->where('admin_id', $admin_id)->delete(); + // 添加账户角色 + $add_ids = array_diff($role_ids, $exist_role_ids); + foreach ($add_ids as $role_id) { + $admin_role = new AdminRole; + $admin_role->admin_id = $admin_id; + $admin_role->role_id = $role_id; + $admin_role->save(); + } + } + + $this->doUpdate($id, $data); + return $this->json(0); + } + + return view('admin/update'); + } + + /** + * 删除 + * @param Request $request + * @return Response + */ + public function delete(Request $request): Response + { + $primary_key = $this->model->getKeyName(); + $ids = $request->post($primary_key); + if (!$ids) { + return $this->json(0); + } + $ids = (array)$ids; + if (in_array(admin_id(), $ids)) { + return $this->json(1, '不能删除自己'); + } + if (!Auth::isSupperAdmin() && array_diff($ids, Auth::getScopeAdminIds())) { + return $this->json(1, '无数据权限'); + } + $this->model->whereIn($primary_key, $ids)->delete(); + AdminRole::whereIn('admin_id', $ids)->delete(); + return $this->json(0); + } + + /** + * 格式化下拉列表 + * @param $items + * @return Response + */ + protected function formatSelect($items): Response + { + $formatted_items = []; + foreach ($items as $item) { + $formatted_items[] = [ + 'name' => $item->nickname, + 'value' => $item->id + ]; + } + return $this->json(0, 'ok', $formatted_items); + } + +} diff --git a/vendor/webman/admin/src/plugin/admin/app/controller/Base.php b/vendor/webman/admin/src/plugin/admin/app/controller/Base.php new file mode 100644 index 0000000..c9182be --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/app/controller/Base.php @@ -0,0 +1,56 @@ + $code, 'data' => $data, 'msg' => $msg]); + } + +} diff --git a/vendor/webman/admin/src/plugin/admin/app/controller/ConfigController.php b/vendor/webman/admin/src/plugin/admin/app/controller/ConfigController.php new file mode 100644 index 0000000..7e66781 --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/app/controller/ConfigController.php @@ -0,0 +1,143 @@ +getByDefault()); + } + + /** + * 基于配置文件获取默认权限 + * @return mixed + */ + protected function getByDefault() + { + $name = 'system_config'; + $config = Option::where('name', $name)->value('value'); + if (empty($config)) { + $config = file_get_contents(base_path('plugin/admin/public/config/pear.config.json')); + if ($config) { + $option = new Option(); + $option->name = $name; + $option->value = $config; + $option->save(); + } + } + return json_decode($config, true); + } + + /** + * 更改 + * @param Request $request + * @return Response + * @throws BusinessException + */ + public function update(Request $request): Response + { + $post = $request->post(); + $config = $this->getByDefault(); + $data = []; + foreach ($post as $section => $items) { + if (!isset($config[$section])) { + continue; + } + switch ($section) { + case 'logo': + $data[$section]['title'] = htmlspecialchars($items['title'] ?? ''); + $data[$section]['image'] = Util::filterUrlPath($items['image'] ?? ''); + break; + case 'menu': + $data[$section]['data'] = Util::filterUrlPath($items['data'] ?? ''); + $data[$section]['accordion'] = !empty($items['accordion']); + $data[$section]['collapse'] = !empty($items['collapse']); + $data[$section]['control'] = !empty($items['control']); + $data[$section]['controlWidth'] = (int)$items['controlWidth'] ?? 500; + $data[$section]['select'] = (int)$items['select'] ?? 0; + $data[$section]['async'] = true; + break; + case 'tab': + $data[$section]['enable'] = true; + $data[$section]['keepState'] = !empty($items['keepState']); + $data[$section]['preload'] = !empty($items['preload']); + $data[$section]['session'] = !empty($items['session']); + $data[$section]['max'] = Util::filterNum($items['max'] ?? '30'); + $data[$section]['index']['id'] = Util::filterNum($items['index']['id'] ?? '0'); + $data[$section]['index']['href'] = Util::filterUrlPath($items['index']['href'] ?? ''); + $data[$section]['index']['title'] = htmlspecialchars($items['index']['title'] ?? '首页'); + break; + case 'theme': + $data[$section]['defaultColor'] = Util::filterNum($items['defaultColor'] ?? '2'); + $data[$section]['defaultMenu'] = $items['defaultMenu'] ?? '' == 'dark-theme' ? 'dark-theme' : 'light-theme'; + $data[$section]['defaultHeader'] = $items['defaultHeader'] ?? '' == 'dark-theme' ? 'dark-theme' : 'light-theme'; + $data[$section]['allowCustom'] = !empty($items['allowCustom']); + $data[$section]['banner'] = !empty($items['banner']); + break; + case 'colors': + foreach ($config['colors'] as $index => $item) { + if (!isset($items[$index])) { + $config['colors'][$index] = $item; + continue; + } + $data_item = $items[$index]; + $data[$section][$index]['id'] = $index + 1; + $data[$section][$index]['color'] = $this->filterColor($data_item['color'] ?? ''); + $data[$section][$index]['second'] = $this->filterColor($data_item['second'] ?? ''); + } + break; + + } + } + $config = array_merge($config, $data); + $name = 'system_config'; + Option::where('name', $name)->update([ + 'value' => json_encode($config) + ]); + return $this->json(0); + } + + /** + * 颜色检查 + * @param string $color + * @return string + * @throws BusinessException + */ + protected function filterColor(string $color): string + { + if (!preg_match('/\#[a-zA-Z]6/', $color)) { + throw new BusinessException('参数错误'); + } + return $color; + } + +} diff --git a/vendor/webman/admin/src/plugin/admin/app/controller/Crud.php b/vendor/webman/admin/src/plugin/admin/app/controller/Crud.php new file mode 100644 index 0000000..a61d6fe --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/app/controller/Crud.php @@ -0,0 +1,401 @@ +selectInput($request); + $query = $this->doSelect($where, $field, $order); + return $this->doFormat($query, $format, $limit); + } + + /** + * 添加 + * @param Request $request + * @return Response + * @throws BusinessException + */ + public function insert(Request $request): Response + { + $data = $this->insertInput($request); + $id = $this->doInsert($data); + return $this->json(0, 'ok', ['id' => $id]); + } + + /** + * 更新 + * @param Request $request + * @return Response + * @throws BusinessException + */ + public function update(Request $request): Response + { + [$id, $data] = $this->updateInput($request); + $this->doUpdate($id, $data); + return $this->json(0); + } + + /** + * 删除 + * @param Request $request + * @return Response + * @throws BusinessException + */ + public function delete(Request $request): Response + { + $ids = $this->deleteInput($request); + $this->doDelete($ids); + return $this->json(0); + } + + /** + * 查询前置 + * @param Request $request + * @return array + * @throws BusinessException + */ + protected function selectInput(Request $request): array + { + $field = $request->get('field'); + $order = $request->get('order', 'asc'); + $format = $request->get('format', 'normal'); + $limit = (int)$request->get('limit', $format === 'tree' ? 1000 : 10); + $limit = $limit <= 0 ? 10 : $limit; + $order = $order === 'asc' ? 'asc' : 'desc'; + $where = $request->get(); + $page = (int)$request->get('page'); + $page = $page > 0 ? $page : 1; + $table = config('plugin.admin.database.connections.mysql.prefix') . $this->model->getTable(); + + $allow_column = Util::db()->select("desc `$table`"); + if (!$allow_column) { + throw new BusinessException('表不存在'); + } + $allow_column = array_column($allow_column, 'Field', 'Field'); + if (!in_array($field, $allow_column)) { + $field = null; + } + foreach ($where as $column => $value) { + if ($value === '' || !isset($allow_column[$column]) || + (is_array($value) && (in_array($value[0], ['', 'undefined']) || in_array($value[1], ['', 'undefined'])))) { + unset($where[$column]); + } + } + // 按照数据限制字段返回数据 + if ($this->dataLimit === 'personal') { + $where[$this->dataLimitField] = admin_id(); + } elseif ($this->dataLimit === 'auth') { + $primary_key = $this->model->getKeyName(); + if (!Auth::isSupperAdmin() && (!isset($where[$primary_key]) || $this->dataLimitField != $primary_key)) { + $where[$this->dataLimitField] = ['in', Auth::getScopeAdminIds(true)]; + } + } + return [$where, $format, $limit, $field, $order, $page]; + } + + /** + * 执行查询 + * @param array $where + * @param string|null $field + * @param string $order + * @return EloquentBuilder|QueryBuilder|Model + */ + protected function doSelect(array $where, string $field = null, string $order= 'desc') + { + $model = $this->model; + foreach ($where as $column => $value) { + if (is_array($value)) { + if ($value[0] === 'like') { + $model = $model->where($column, 'like', "%$value[1]%"); + } elseif (in_array($value[0], ['>', '=', '<', '<>', 'not like'])) { + $model = $model->where($column, $value[0], $value[1]); + } elseif ($value[0] == 'in') { + $model = $model->whereIn($column, $value[1]); + } elseif ($value[0] == 'not in') { + $model = $model->whereNotIn($column, $value[1]); + } elseif ($value[0] == 'null') { + $model = $model->whereNull($column, $value[1]); + } elseif ($value[0] == 'not null') { + $model = $model->whereNotNull($column, $value[1]); + } elseif ($value[0] !== '' || $value[1] !== '') { + $model = $model->whereBetween($column, $value); + } + } else { + $model = $model->where($column, $value); + } + } + if ($field) { + $model = $model->orderBy($field, $order); + } + return $model; + } + + /** + * 格式化数据 + * @param $query + * @param $format + * @param $limit + * @return Response + */ + protected function doFormat($query, $format, $limit): Response + { + $methods = [ + 'select' => 'formatSelect', + 'tree' => 'formatTree', + 'table_tree' => 'formatTableTree', + 'normal' => 'formatNormal', + ]; + $paginator = $query->paginate($limit); + $total = $paginator->total(); + $items = $paginator->items(); + $format_function = $methods[$format] ?? 'formatNormal'; + return call_user_func([$this, $format_function], $items, $total); + } + + /** + * 插入前置方法 + * @param Request $request + * @return array + * @throws BusinessException + */ + protected function insertInput(Request $request): array + { + $data = $this->inputFilter($request->post()); + $password_filed = 'password'; + if (isset($data[$password_filed])) { + $data[$password_filed] = Util::passwordHash($data[$password_filed]); + } + + if (!Auth::isSupperAdmin() && $this->dataLimit) { + if (!empty($data[$this->dataLimitField])) { + $admin_id = $data[$this->dataLimitField]; + if (!in_array($admin_id, Auth::getScopeAdminIds(true))) { + throw new BusinessException('无数据权限'); + } + } + } + return $data; + } + + /** + * 执行插入 + * @param array $data + * @return mixed|null + */ + protected function doInsert(array $data) + { + $primary_key = $this->model->getKeyName(); + $model_class = get_class($this->model); + $model = new $model_class; + foreach ($data as $key => $val) { + $model->{$key} = $val; + } + $model->save(); + return $primary_key ? $model->$primary_key : null; + } + + /** + * 更新前置方法 + * @param Request $request + * @return array + * @throws BusinessException + */ + protected function updateInput(Request $request): array + { + $primary_key = $this->model->getKeyName(); + $id = $request->post($primary_key); + $data = $this->inputFilter($request->post()); + if (!Auth::isSupperAdmin() && $this->dataLimit && !empty($data[$this->dataLimitField])) { + $admin_id = $data[$this->dataLimitField]; + if (!in_array($admin_id, Auth::getScopeAdminIds(true))) { + throw new BusinessException('无数据权限'); + } + } + $password_filed = 'password'; + if (isset($data[$password_filed])) { + // 密码为空,则不更新密码 + if ($data[$password_filed] === '') { + unset($data[$password_filed]); + } else { + $data[$password_filed] = Util::passwordHash($data[$password_filed]); + } + } + unset($data[$primary_key]); + return [$id, $data]; + } + + /** + * 执行更新 + * @param $id + * @param $data + * @return void + * @throws BusinessException + */ + protected function doUpdate($id, $data) + { + $model = $this->model->find($id); + if (!$model) { + throw new BusinessException('记录不存在', 2); + } + foreach ($data as $key => $val) { + $model->{$key} = $val; + } + $model->save(); + } + + /** + * 对用户输入表单过滤 + * @param array $data + * @return array + * @throws BusinessException + */ + protected function inputFilter(array $data): array + { + $table = config('plugin.admin.database.connections.mysql.prefix') . $this->model->getTable(); + $allow_column = $this->model->getConnection()->select("desc `$table`"); + if (!$allow_column) { + throw new BusinessException('表不存在', 2); + } + $columns = array_column($allow_column, 'Type', 'Field'); + foreach ($data as $col => $item) { + if (!isset($columns[$col])) { + unset($data[$col]); + continue; + } + // 非字符串类型传空则为null + if ($item === '' && strpos(strtolower($columns[$col]), 'varchar') === false && strpos(strtolower($columns[$col]), 'text') === false) { + $data[$col] = null; + } + if (is_array($item)) { + $data[$col] = implode(',', $item); + } + } + if (empty($data['created_at'])) { + unset($data['created_at']); + } + if (empty($data['updated_at'])) { + unset($data['updated_at']); + } + return $data; + } + + /** + * 删除前置方法 + * @param Request $request + * @return array + * @throws BusinessException + */ + protected function deleteInput(Request $request): array + { + $primary_key = $this->model->getKeyName(); + if (!$primary_key) { + throw new BusinessException('该表无主键,不支持删除'); + } + $ids = (array)$request->post($primary_key, []); + if (!Auth::isSupperAdmin() && $this->dataLimit) { + $admin_ids = $this->model->where($primary_key, $ids)->pluck($this->dataLimitField)->toArray(); + if (array_diff($admin_ids, Auth::getScopeAdminIds(true))) { + throw new BusinessException('无数据权限'); + } + } + return $ids; + } + + /** + * 执行删除 + * @param array $ids + * @return void + */ + protected function doDelete(array $ids) + { + if (!$ids) { + return; + } + $primary_key = $this->model->getKeyName(); + $this->model->whereIn($primary_key, $ids)->delete(); + } + + /** + * 格式化树 + * @param $items + * @return Response + */ + protected function formatTree($items): Response + { + $format_items = []; + foreach ($items as $item) { + $format_items[] = [ + 'name' => $item->title ?? $item->name ?? $item->id, + 'value' => (string)$item->id, + 'id' => $item->id, + 'pid' => $item->pid, + ]; + } + $tree = new Tree($format_items); + return $this->json(0, 'ok', $tree->getTree()); + } + + /** + * 格式化表格树 + * @param $items + * @return Response + */ + protected function formatTableTree($items): Response + { + $tree = new Tree($items); + return $this->json(0, 'ok', $tree->getTree()); + } + + /** + * 格式化下拉列表 + * @param $items + * @return Response + */ + protected function formatSelect($items): Response + { + $formatted_items = []; + foreach ($items as $item) { + $formatted_items[] = [ + 'name' => $item->title ?? $item->name ?? $item->id, + 'value' => $item->id + ]; + } + return $this->json(0, 'ok', $formatted_items); + } + + /** + * 通用格式化 + * @param $items + * @param $total + * @return Response + */ + protected function formatNormal($items, $total): Response + { + return json(['code' => 0, 'msg' => 'ok', 'count' => $total, 'data' => $items]); + } + +} diff --git a/vendor/webman/admin/src/plugin/admin/app/controller/DevController.php b/vendor/webman/admin/src/plugin/admin/app/controller/DevController.php new file mode 100644 index 0000000..da9c3b3 --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/app/controller/DevController.php @@ -0,0 +1,22 @@ +get('name', ''); + if ($name && is_string($name)) { + $items = Option::where('name', 'like', "dict_$name%")->get()->toArray(); + } else { + $items = Option::where('name', 'like', 'dict_%')->get()->toArray(); + } + foreach ($items as &$item) { + $item['name'] = Dict::optionNameTodictName($item['name']); + } + return $this->json(0, 'ok', $items); + } + + /** + * 插入 + * @param Request $request + * @return Response + * @throws BusinessException + */ + public function insert(Request $request): Response + { + if ($request->method() === 'POST') { + $name = $request->post('name'); + if (Dict::get($name)) { + return $this->json(1, '字典已经存在'); + } + $values = (array)$request->post('value', []); + Dict::save($name, $values); + } + return view('dict/insert'); + } + + /** + * 更新 + * @param Request $request + * @return Response + * @throws BusinessException + */ + public function update(Request $request): Response + { + if ($request->method() === 'POST') { + $name = $request->post('name'); + if (!Dict::get($name)) { + return $this->json(1, '字典不存在'); + } + Dict::save($name, $request->post('value')); + } + return view('dict/update'); + } + + /** + * 删除 + * @param Request $request + * @return Response + */ + public function delete(Request $request): Response + { + $names = (array)$request->post('name'); + Dict::delete($names); + return $this->json(0); + } + + /** + * 获取 + * @param Request $request + * @param $name + * @return Response + */ + public function get(Request $request, $name): Response + { + return $this->json(0, 'ok', Dict::get($name)); + } + +} diff --git a/vendor/webman/admin/src/plugin/admin/app/controller/IndexController.php b/vendor/webman/admin/src/plugin/admin/app/controller/IndexController.php new file mode 100644 index 0000000..14f42d5 --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/app/controller/IndexController.php @@ -0,0 +1,89 @@ +', date('Y-m-d') . ' 00:00:00')->count(); + // 7天内新增用户数 + $day7_user_count = User::where('created_at', '>', date('Y-m-d H:i:s', time() - 7 * 24 * 60 * 60))->count(); + // 30天内新增用户数 + $day30_user_count = User::where('created_at', '>', date('Y-m-d H:i:s', time() - 30 * 24 * 60 * 60))->count(); + // 总用户数 + $user_count = User::count(); + // mysql版本 + $version = Util::db()->select('select VERSION() as version'); + $mysql_version = $version[0]->version ?? 'unknown'; + + $day7_detail = []; + $now = time(); + for ($i = 0; $i < 7; $i++) { + $date = date('Y-m-d', $now - 24 * 60 * 60 * $i); + $day7_detail[substr($date, 5)] = User::where('created_at', '>', "$date 00:00:00") + ->where('created_at', '<', "$date 23:59:59")->count(); + } + + return view('index/dashboard', [ + 'today_user_count' => $today_user_count, + 'day7_user_count' => $day7_user_count, + 'day30_user_count' => $day30_user_count, + 'user_count' => $user_count, + 'php_version' => PHP_VERSION, + 'workerman_version' => Worker::VERSION, + 'webman_version' => Util::getPackageVersion('workerman/webman-framework'), + 'admin_version' => config('plugin.admin.app.version'), + 'mysql_version' => $mysql_version, + 'os' => PHP_OS, + 'day7_detail' => array_reverse($day7_detail), + ]); + } + +} diff --git a/vendor/webman/admin/src/plugin/admin/app/controller/InstallController.php b/vendor/webman/admin/src/plugin/admin/app/controller/InstallController.php new file mode 100644 index 0000000..3a31a66 --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/app/controller/InstallController.php @@ -0,0 +1,392 @@ +json(1, '管理后台已经安装!如需重新安装,请删除该插件数据库配置文件并重启'); + } + + if (!class_exists(CaptchaBuilder::class) || !class_exists(Manager::class)) { + return $this->json(1, '请先restart重启webman后再进行此页面的设置'); + } + + $user = $request->post('user'); + $password = $request->post('password'); + $database = $request->post('database'); + $host = $request->post('host'); + $port = (int)$request->post('port') ?: 3306; + $overwrite = $request->post('overwrite'); + + try { + $db = $this->getPdo($host, $user, $password, $port); + $smt = $db->query("show databases like '$database'"); + if (empty($smt->fetchAll())) { + $db->exec("create database $database"); + } + $db->exec("use $database"); + $smt = $db->query("show tables"); + $tables = $smt->fetchAll(); + } catch (\Throwable $e) { + if (stripos($e, 'Access denied for user')) { + return $this->json(1, '数据库用户名或密码错误'); + } + if (stripos($e, 'Connection refused')) { + return $this->json(1, 'Connection refused. 请确认数据库IP端口是否正确,数据库已经启动'); + } + if (stripos($e, 'timed out')) { + return $this->json(1, '数据库连接超时,请确认数据库IP端口是否正确,安全组及防火墙已经放行端口'); + } + throw $e; + } + + $tables_to_install = [ + 'wa_admins', + 'wa_admin_roles', + 'wa_roles', + 'wa_rules', + 'wa_options', + 'wa_users', + 'wa_uploads', + ]; + + $tables_exist = []; + foreach ($tables as $table) { + $tables_exist[] = current($table); + } + $tables_conflict = array_intersect($tables_to_install, $tables_exist); + if (!$overwrite) { + if ($tables_conflict) { + return $this->json(1, '以下表' . implode(',', $tables_conflict) . '已经存在,如需覆盖请选择强制覆盖'); + } + } else { + foreach ($tables_conflict as $table) { + $db->exec("DROP TABLE `$table`"); + } + } + + $sql_file = base_path() . '/plugin/admin/install.sql'; + if (!is_file($sql_file)) { + return $this->json(1, '数据库SQL文件不存在'); + } + + $sql_query = file_get_contents($sql_file); + $sql_query = $this->removeComments($sql_query); + $sql_query = $this->splitSqlFile($sql_query, ';'); + foreach ($sql_query as $sql) { + $db->exec($sql); + } + + // 导入菜单 + $menus = include base_path() . '/plugin/admin/config/menu.php'; + // 安装过程中没有数据库配置,无法使用api\Menu::import()方法 + $this->importMenu($menus, $db); + + $config_content = << 'mysql', + 'connections' => [ + 'mysql' => [ + 'driver' => 'mysql', + 'host' => '$host', + 'port' => '$port', + 'database' => '$database', + 'username' => '$user', + 'password' => '$password', + 'charset' => 'utf8mb4', + 'collation' => 'utf8mb4_general_ci', + 'prefix' => '', + 'strict' => true, + 'engine' => null, + ], + ], +]; +EOF; + + file_put_contents($database_config_file, $config_content); + + $think_orm_config = << 'mysql', + 'connections' => [ + 'mysql' => [ + // 数据库类型 + 'type' => 'mysql', + // 服务器地址 + 'hostname' => '$host', + // 数据库名 + 'database' => '$database', + // 数据库用户名 + 'username' => '$user', + // 数据库密码 + 'password' => '$password', + // 数据库连接端口 + 'hostport' => $port, + // 数据库连接参数 + 'params' => [ + // 连接超时3秒 + \PDO::ATTR_TIMEOUT => 3, + ], + // 数据库编码默认采用utf8 + 'charset' => 'utf8mb4', + // 数据库表前缀 + 'prefix' => '', + // 断线重连 + 'break_reconnect' => true, + // 关闭SQL监听日志 + 'trigger_sql' => true, + // 自定义分页类 + 'bootstrap' => '' + ], + ], +]; +EOF; + file_put_contents(base_path() . '/plugin/admin/config/thinkorm.php', $think_orm_config); + + + // 尝试reload + if (function_exists('posix_kill')) { + set_error_handler(function () {}); + posix_kill(posix_getppid(), SIGUSR1); + restore_error_handler(); + } + + return $this->json(0); + } + + /** + * 设置管理员 + * @param Request $request + * @return Response + * @throws BusinessException + */ + public function step2(Request $request): Response + { + $username = $request->post('username'); + $password = $request->post('password'); + $password_confirm = $request->post('password_confirm'); + if ($password != $password_confirm) { + return $this->json(1, '两次密码不一致'); + } + if (!is_file($config_file = base_path() . '/plugin/admin/config/database.php')) { + return $this->json(1, '请先完成第一步数据库配置'); + } + $config = include $config_file; + $connection = $config['connections']['mysql']; + $pdo = $this->getPdo($connection['host'], $connection['username'], $connection['password'], $connection['port'], $connection['database']); + + if ($pdo->query('select * from `wa_admins`')->fetchAll()) { + return $this->json(1, '后台已经安装完毕,无法通过此页面创建管理员'); + } + + $smt = $pdo->prepare("insert into `wa_admins` (`username`, `password`, `nickname`, `created_at`, `updated_at`) values (:username, :password, :nickname, :created_at, :updated_at)"); + $time = date('Y-m-d H:i:s'); + $data = [ + 'username' => $username, + 'password' => Util::passwordHash($password), + 'nickname' => '超级管理员', + 'created_at' => $time, + 'updated_at' => $time + ]; + foreach ($data as $key => $value) { + $smt->bindValue($key, $value); + } + $smt->execute(); + $admin_id = $pdo->lastInsertId(); + + $smt = $pdo->prepare("insert into `wa_admin_roles` (`role_id`, `admin_id`) values (:role_id, :admin_id)"); + $smt->bindValue('role_id', 1); + $smt->bindValue('admin_id', $admin_id); + $smt->execute(); + + $request->session()->flush(); + return $this->json(0); + } + + /** + * 添加菜单 + * @param array $menu + * @param \PDO $pdo + * @return int + */ + protected function addMenu(array $menu, \PDO $pdo): int + { + $allow_columns = ['title', 'key', 'icon', 'href', 'pid', 'weight', 'type']; + $data = []; + foreach ($allow_columns as $column) { + if (isset($menu[$column])) { + $data[$column] = $menu[$column]; + } + } + $time = date('Y-m-d H:i:s'); + $data['created_at'] = $data['updated_at'] = $time; + $values = []; + foreach ($data as $k => $v) { + $values[] = ":$k"; + } + $columns = array_keys($data); + foreach ($columns as $k => $column) { + $columns[$k] = "`$column`"; + } + $sql = "insert into wa_rules (" .implode(',', $columns). ") values (" . implode(',', $values) . ")"; + $smt = $pdo->prepare($sql); + foreach ($data as $key => $value) { + $smt->bindValue($key, $value); + } + $smt->execute(); + return $pdo->lastInsertId(); + } + + /** + * 导入菜单 + * @param array $menu_tree + * @param \PDO $pdo + * @return void + */ + protected function importMenu(array $menu_tree, \PDO $pdo) + { + if (is_numeric(key($menu_tree)) && !isset($menu_tree['key'])) { + foreach ($menu_tree as $item) { + $this->importMenu($item, $pdo); + } + return; + } + $children = $menu_tree['children'] ?? []; + unset($menu_tree['children']); + $smt = $pdo->prepare("select * from wa_rules where `key`=:key limit 1"); + $smt->execute(['key' => $menu_tree['key']]); + $old_menu = $smt->fetch(); + if ($old_menu) { + $pid = $old_menu['id']; + $params = [ + 'title' => $menu_tree['title'], + 'icon' => $menu_tree['icon'] ?? '', + 'key' => $menu_tree['key'], + ]; + $sql = "update wa_rules set title=:title, icon=:icon where `key`=:key"; + $smt = $pdo->prepare($sql); + $smt->execute($params); + } else { + $pid = $this->addMenu($menu_tree, $pdo); + } + foreach ($children as $menu) { + $menu['pid'] = $pid; + $this->importMenu($menu, $pdo); + } + } + + /** + * 去除sql文件中的注释 + * @param $sql + * @return string + */ + protected function removeComments($sql): string + { + return preg_replace("/(\n--[^\n]*)/","", $sql); + } + + /** + * 分割sql文件 + * @param $sql + * @param $delimiter + * @return array + */ + function splitSqlFile($sql, $delimiter): array + { + $tokens = explode($delimiter, $sql); + $output = array(); + $matches = array(); + $token_count = count($tokens); + for ($i = 0; $i < $token_count; $i++) { + if (($i != ($token_count - 1)) || (strlen($tokens[$i] > 0))) { + $total_quotes = preg_match_all("/'/", $tokens[$i], $matches); + $escaped_quotes = preg_match_all("/(? "set names utf8mb4", + \PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true, + \PDO::ATTR_EMULATE_PREPARES => false, + \PDO::ATTR_TIMEOUT => 5, + \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION, + ]; + return new \PDO($dsn, $username, $password, $params); + } + +} diff --git a/vendor/webman/admin/src/plugin/admin/app/controller/PluginController.php b/vendor/webman/admin/src/plugin/admin/app/controller/PluginController.php new file mode 100644 index 0000000..459b26e --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/app/controller/PluginController.php @@ -0,0 +1,549 @@ +httpClient(); + $response = $client->get("/webman-admin/apps"); + return (string)$response->getBody(); + } + + /** + * 列表 + * @param Request $request + * @return Response + * @throws GuzzleException + */ + public function list(Request $request): Response + { + $installed = $this->getLocalPlugins(); + + $client = $this->httpClient(); + $query = $request->get(); + $query['version'] = $this->getAdminVersion(); + $response = $client->get('/api/app/list', ['query' => $query]); + $content = $response->getBody()->getContents(); + $data = json_decode($content, true); + if (!$data) { + $msg = "/api/app/list return $content"; + echo "msg\r\n"; + Log::error($msg); + return $this->json(1, '获取数据出错'); + } + $disabled = is_phar(); + foreach ($data['data']['items'] as $key => $item) { + $name = $item['name']; + $data['data']['items'][$key]['installed'] = $installed[$name] ?? 0; + $data['data']['items'][$key]['disabled'] = $disabled; + } + $items = $data['data']['items']; + $count = $data['data']['total']; + return json(['code' => 0, 'msg' => 'ok', 'data' => $items, 'count' => $count]); + } + + /** + * 安装 + * @param Request $request + * @return Response + * @throws GuzzleException|BusinessException + */ + public function install(Request $request): Response + { + $name = $request->post('name'); + $version = $request->post('version'); + $installed_version = $this->getPluginVersion($name); + if (!$name || !$version) { + return $this->json(1, '缺少参数'); + } + + $user = session('app-plugin-user'); + if (!$user) { + return $this->json(-1, '请登录'); + } + + // 获取下载zip文件url + $data = $this->getDownloadUrl($name, $version); + if ($data['code'] != 0) { + return $this->json($data['code'], $data['msg'], $data['data'] ?? []); + } + + // 下载zip文件 + $base_path = base_path() . "/plugin/$name"; + $zip_file = "$base_path.zip"; + $extract_to = base_path() . '/plugin/'; + $this->downloadZipFile($data['data']['url'], $zip_file); + + $has_zip_archive = class_exists(ZipArchive::class, false); + if (!$has_zip_archive) { + $cmd = $this->getUnzipCmd($zip_file, $extract_to); + if (!$cmd) { + throw new BusinessException('请给php安装zip模块或者给系统安装unzip命令'); + } + if (!function_exists('proc_open')) { + throw new BusinessException('请解除proc_open函数的禁用或者给php安装zip模块'); + } + } + + Util::pauseFileMonitor(); + try { + // 解压zip到plugin目录 + if ($has_zip_archive) { + $zip = new ZipArchive; + $zip->open($zip_file, ZIPARCHIVE::CHECKCONS); + } + + $context = null; + $install_class = "\\plugin\\$name\\api\\Install"; + if ($installed_version) { + // 执行beforeUpdate + if (class_exists($install_class) && method_exists($install_class, 'beforeUpdate')) { + $context = call_user_func([$install_class, 'beforeUpdate'], $installed_version, $version); + } + } + + if (!empty($zip)) { + $zip->extractTo(base_path() . '/plugin/'); + unset($zip); + } else { + $this->unzipWithCmd($cmd); + } + + unlink($zip_file); + + if ($installed_version) { + // 执行update更新 + if (class_exists($install_class) && method_exists($install_class, 'update')) { + call_user_func([$install_class, 'update'], $installed_version, $version, $context); + } + } else { + // 执行install安装 + if (class_exists($install_class) && method_exists($install_class, 'install')) { + call_user_func([$install_class, 'install'], $version); + } + } + } finally { + Util::resumeFileMonitor(); + } + + Util::reloadWebman(); + + return $this->json(0); + } + + /** + * 卸载 + * @param Request $request + * @return Response + */ + public function uninstall(Request $request): Response + { + $name = $request->post('name'); + $version = $request->post('version'); + if (!$name || !preg_match('/^[a-zA-Z0-9_]+$/', $name)) { + return $this->json(1, '参数错误'); + } + + // 获得插件路径 + clearstatcache(); + $path = get_realpath(base_path() . "/plugin/$name"); + if (!$path || !is_dir($path)) { + return $this->json(1, '已经删除'); + } + + // 执行uninstall卸载 + $install_class = "\\plugin\\$name\\api\\Install"; + if (class_exists($install_class) && method_exists($install_class, 'uninstall')) { + call_user_func([$install_class, 'uninstall'], $version); + } + + // 删除目录 + clearstatcache(); + if (is_dir($path)) { + $monitor_support_pause = method_exists(Monitor::class, 'pause'); + if ($monitor_support_pause) { + Monitor::pause(); + } + try { + $this->rmDir($path); + } finally { + if ($monitor_support_pause) { + Monitor::resume(); + } + } + } + clearstatcache(); + + Util::reloadWebman(); + + return $this->json(0); + } + + /** + * 支付 + * @param Request $request + * @return string|Response + * @throws GuzzleException + */ + public function pay(Request $request) + { + $app = $request->get('app'); + if (!$app) { + return response('app not found'); + } + $token = session('app-plugin-token'); + if (!$token) { + return 'Please login workerman.net'; + } + $client = $this->httpClient(); + $response = $client->get("/payment/app/$app/$token"); + return (string)$response->getBody(); + } + + /** + * 登录验证码 + * @param Request $request + * @return Response + * @throws GuzzleException + */ + public function captcha(Request $request): Response + { + $client = $this->httpClient(); + $response = $client->get('/user/captcha?type=login'); + $sid_str = $response->getHeaderLine('Set-Cookie'); + if (preg_match('/PHPSID=([a-zA-Z_0-9]+?);/', $sid_str, $match)) { + $sid = $match[1]; + session()->set('app-plugin-token', $sid); + } + return response($response->getBody()->getContents())->withHeader('Content-Type', 'image/jpeg'); + } + + /** + * 登录官网 + * @param Request $request + * @return Response|string + * @throws GuzzleException + */ + public function login(Request $request) + { + $client = $this->httpClient(); + if ($request->method() === 'GET') { + $response = $client->get("/webman-admin/login"); + return (string)$response->getBody(); + } + + $response = $client->post('/api/user/login', [ + 'form_params' => [ + 'email' => $request->post('username'), + 'password' => $request->post('password'), + 'captcha' => $request->post('captcha') + ] + ]); + $content = $response->getBody()->getContents(); + $data = json_decode($content, true); + if (!$data) { + $msg = "/api/user/login return $content"; + echo "msg\r\n"; + Log::error($msg); + return $this->json(1, '发生错误'); + } + if ($data['code'] != 0) { + return $this->json($data['code'], $data['msg']); + } + session()->set('app-plugin-user', [ + 'uid' => $data['data']['uid'] + ]); + return $this->json(0); + } + + /** + * 获取zip下载url + * @param $name + * @param $version + * @return mixed + * @throws BusinessException + * @throws GuzzleException + */ + protected function getDownloadUrl($name, $version) + { + $client = $this->httpClient(); + $response = $client->get("/app/download/$name?version=$version"); + + $content = $response->getBody()->getContents(); + $data = json_decode($content, true); + if (!$data) { + $msg = "/api/app/download return $content"; + Log::error($msg); + throw new BusinessException('访问官方接口失败 ' . $response->getStatusCode() . ' ' . $response->getReasonPhrase()); + } + if ($data['code'] && $data['code'] != -1 && $data['code'] != -2) { + throw new BusinessException($data['msg']); + } + if ($data['code'] == 0 && !isset($data['data']['url'])) { + throw new BusinessException('官方接口返回数据错误'); + } + return $data; + } + + /** + * 下载zip + * @param $url + * @param $file + * @return void + * @throws BusinessException + * @throws GuzzleException + */ + protected function downloadZipFile($url, $file) + { + $client = $this->downloadClient(); + $response = $client->get($url); + $body = $response->getBody(); + $status = $response->getStatusCode(); + if ($status == 404) { + throw new BusinessException('安装包不存在'); + } + $zip_content = $body->getContents(); + if (empty($zip_content)) { + throw new BusinessException('安装包不存在'); + } + file_put_contents($file, $zip_content); + } + + /** + * 获取系统支持的解压命令 + * @param $zip_file + * @param $extract_to + * @return mixed|string|null + */ + protected function getUnzipCmd($zip_file, $extract_to) + { + if ($cmd = $this->findCmd('unzip')) { + $cmd = "$cmd -o -qq $zip_file -d $extract_to"; + } else if ($cmd = $this->findCmd('7z')) { + $cmd = "$cmd x -bb0 -y $zip_file -o$extract_to"; + } else if ($cmd = $this->findCmd('7zz')) { + $cmd = "$cmd x -bb0 -y $zip_file -o$extract_to"; + } + return $cmd; + } + + /** + * 使用解压命令解压 + * @param $cmd + * @return void + * @throws BusinessException + */ + protected function unzipWithCmd($cmd) + { + $desc = [ + 0 => ["pipe", "r"], + 1 => ["pipe", "w"], + 2 => ["pipe", "w"], + ]; + $handler = proc_open($cmd, $desc, $pipes); + if (!is_resource($handler)) { + throw new BusinessException("解压zip时出错:proc_open调用失败"); + } + $err = fread($pipes[2], 1024); + fclose($pipes[2]); + proc_close($handler); + if ($err) { + throw new BusinessException("解压zip时出错:$err"); + } + } + + /** + * 获取已安装的插件列表 + * @return array + */ + protected function getLocalPlugins(): array + { + clearstatcache(); + $installed = []; + $plugin_names = array_diff(scandir(base_path() . '/plugin/'), array('.', '..')) ?: []; + foreach ($plugin_names as $plugin_name) { + if (is_dir(base_path() . "/plugin/$plugin_name") && $version = $this->getPluginVersion($plugin_name)) { + $installed[$plugin_name] = $version; + } + } + return $installed; + } + + /** + * 获取已安装的插件列表 + * @param Request $request + * @return Response + */ + public function getInstalledPlugins(Request $request): Response + { + return $this->json(0, 'ok', $this->getLocalPlugins()); + } + + + /** + * 获取本地插件版本 + * @param $name + * @return array|mixed|null + */ + protected function getPluginVersion($name) + { + if (!is_file($file = base_path() . "/plugin/$name/config/app.php")) { + return null; + } + $config = include $file; + return $config['version'] ?? null; + } + + /** + * 获取webman/admin版本 + * @return string + */ + protected function getAdminVersion(): string + { + return config('plugin.admin.app.version', ''); + } + + /** + * 删除目录 + * @param $src + * @return void + */ + protected function rmDir($src) + { + $dir = opendir($src); + while (false !== ($file = readdir($dir))) { + if (($file != '.') && ($file != '..')) { + $full = $src . '/' . $file; + if (is_dir($full)) { + $this->rmDir($full); + } else { + unlink($full); + } + } + } + closedir($dir); + rmdir($src); + } + + /** + * 获取httpclient + * @return Client + */ + protected function httpClient(): Client + { + // 下载zip + $options = [ + 'base_uri' => config('plugin.admin.app.plugin_market_host'), + 'timeout' => 30, + 'connect_timeout' => 5, + 'verify' => false, + 'http_errors' => false, + 'headers' => [ + 'Referer' => \request()->fullUrl(), + 'User-Agent' => 'webman-app-plugin', + 'Accept' => 'application/json;charset=UTF-8', + ] + ]; + if ($token = session('app-plugin-token')) { + $options['headers']['Cookie'] = "PHPSID=$token;"; + } + return new Client($options); + } + + /** + * 获取下载httpclient + * @return Client + */ + protected function downloadClient(): Client + { + // 下载zip + $options = [ + 'timeout' => 30, + 'connect_timeout' => 5, + 'verify' => false, + 'http_errors' => false, + 'headers' => [ + 'Referer' => \request()->fullUrl(), + 'User-Agent' => 'webman-app-plugin', + ] + ]; + if ($token = session('app-plugin-token')) { + $options['headers']['Cookie'] = "PHPSID=$token;"; + } + return new Client($options); + } + + /** + * 查找系统命令 + * @param string $name + * @param string|null $default + * @param array $extraDirs + * @return mixed|string|null + */ + protected function findCmd(string $name, string $default = null, array $extraDirs = []) + { + if (ini_get('open_basedir')) { + $searchPath = array_merge(explode(PATH_SEPARATOR, ini_get('open_basedir')), $extraDirs); + $dirs = []; + foreach ($searchPath as $path) { + if (@is_dir($path)) { + $dirs[] = $path; + } else { + if (basename($path) == $name && @is_executable($path)) { + return $path; + } + } + } + } else { + $dirs = array_merge( + explode(PATH_SEPARATOR, getenv('PATH') ?: getenv('Path')), + $extraDirs + ); + } + + $suffixes = ['']; + if ('\\' === DIRECTORY_SEPARATOR) { + $pathExt = getenv('PATHEXT'); + $suffixes = array_merge($pathExt ? explode(PATH_SEPARATOR, $pathExt) : ['.exe', '.bat', '.cmd', '.com'], $suffixes); + } + foreach ($suffixes as $suffix) { + foreach ($dirs as $dir) { + if (@is_file($file = $dir . DIRECTORY_SEPARATOR . $name . $suffix) && ('\\' === DIRECTORY_SEPARATOR || @is_executable($file))) { + return $file; + } + } + } + + return $default; + } + +} diff --git a/vendor/webman/admin/src/plugin/admin/app/controller/RoleController.php b/vendor/webman/admin/src/plugin/admin/app/controller/RoleController.php new file mode 100644 index 0000000..2c466ed --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/app/controller/RoleController.php @@ -0,0 +1,252 @@ +model = new Role; + } + + /** + * 浏览 + * @return Response + */ + public function index(): Response + { + return view('role/index'); + } + + /** + * 查询 + * @param Request $request + * @return Response + * @throws BusinessException + */ + public function select(Request $request): Response + { + $id = $request->get('id'); + [$where, $format, $limit, $field, $order] = $this->selectInput($request); + $role_ids = Auth::getScopeRoleIds(true); + if (!$id) { + $where['id'] = ['in', $role_ids]; + } elseif (!in_array($id, $role_ids)) { + throw new BusinessException('无权限'); + } + $query = $this->doSelect($where, $field, $order); + return $this->doFormat($query, $format, $limit); + } + + /** + * 插入 + * @param Request $request + * @return Response + * @throws BusinessException + */ + public function insert(Request $request): Response + { + if ($request->method() === 'POST') { + $data = $this->insertInput($request); + $pid = $data['pid'] ?? null; + if (!$pid) { + return $this->json(1, '请选择父级角色组'); + } + if (!Auth::isSupperAdmin() && !in_array($pid, Auth::getScopeRoleIds(true))) { + return $this->json(1, '父级角色组超出权限范围'); + } + $this->checkRules($pid, $data['rules'] ?? ''); + + $id = $this->doInsert($data); + return $this->json(0, 'ok', ['id' => $id]); + } + return view('role/insert'); + } + + /** + * 更新 + * @param Request $request + * @return Response + * @throws BusinessException + */ + public function update(Request $request): Response + { + if ($request->method() === 'GET') { + return view('role/update'); + } + [$id, $data] = $this->updateInput($request); + $is_supper_admin = Auth::isSupperAdmin(); + $descendant_role_ids = Auth::getScopeRoleIds(); + if (!$is_supper_admin && !in_array($id, $descendant_role_ids)) { + return $this->json(1, '无数据权限'); + } + + $role = Role::find($id); + if (!$role) { + return $this->json(1, '数据不存在'); + } + $is_supper_role = $role->rules === '*'; + + // 超级角色组不允许更改rules pid 字段 + if ($is_supper_role) { + unset($data['rules'], $data['pid']); + } + + if (key_exists('pid', $data)) { + $pid = $data['pid']; + if (!$pid) { + return $this->json(1, '请选择父级角色组'); + } + if ($pid == $id) { + return $this->json(1, '父级不能是自己'); + } + if (!$is_supper_admin && !in_array($pid, Auth::getScopeRoleIds(true))) { + return $this->json(1, '父级超出权限范围'); + } + } else { + $pid = $role->pid; + } + + if (!$is_supper_role) { + $this->checkRules($pid, $data['rules'] ?? ''); + } + + $this->doUpdate($id, $data); + + // 删除所有子角色组中已经不存在的权限 + if (!$is_supper_role) { + $tree = new Tree(Role::select(['id', 'pid'])->get()); + $descendant_roles = $tree->getDescendant([$id]); + $descendant_role_ids = array_column($descendant_roles, 'id'); + $rule_ids = $data['rules'] ? explode(',', $data['rules']) : []; + foreach ($descendant_role_ids as $role_id) { + $tmp_role = Role::find($role_id); + $tmp_rule_ids = $role->getRuleIds(); + $tmp_rule_ids = array_intersect($rule_ids, $tmp_rule_ids); + $tmp_role->rules = implode(',', $tmp_rule_ids); + $tmp_role->save(); + } + } + + return $this->json(0); + } + + /** + * 删除 + * @param Request $request + * @return Response + * @throws BusinessException + */ + public function delete(Request $request): Response + { + $ids = $this->deleteInput($request); + if (in_array(1, $ids)) { + return $this->json(1, '无法删除超级管理员角色'); + } + if (!Auth::isSupperAdmin() && array_diff($ids, Auth::getScopeRoleIds())) { + return $this->json(1, '无删除权限'); + } + $tree = new Tree(Role::get()); + $descendants = $tree->getDescendant($ids); + if ($descendants) { + $ids = array_merge($ids, array_column($descendants, 'id')); + } + $this->doDelete($ids); + return $this->json(0); + } + + /** + * 获取角色权限 + * @param Request $request + * @return Response + */ + public function rules(Request $request): Response + { + $role_id = $request->get('id'); + if (empty($role_id)) { + return $this->json(0, 'ok', []); + } + if (!Auth::isSupperAdmin() && !in_array($role_id, Auth::getScopeRoleIds(true))) { + return $this->json(1, '角色组超出权限范围'); + } + $rule_id_string = Role::where('id', $role_id)->value('rules'); + if ($rule_id_string === '') { + return $this->json(0, 'ok', []); + } + $rules = Rule::get(); + $include = []; + if ($rule_id_string !== '*') { + $include = explode(',', $rule_id_string); + } + $items = []; + foreach ($rules as $item) { + $items[] = [ + 'name' => $item->title ?? $item->name ?? $item->id, + 'value' => (string)$item->id, + 'id' => $item->id, + 'pid' => $item->pid, + ]; + } + $tree = new Tree($items); + return $this->json(0, 'ok', $tree->getTree($include)); + } + + /** + * 检查权限字典是否合法 + * @param int $role_id + * @param $rule_ids + * @return void + * @throws BusinessException + */ + protected function checkRules(int $role_id, $rule_ids) + { + if ($rule_ids) { + $rule_ids = explode(',', $rule_ids); + if (in_array('*', $rule_ids)) { + throw new BusinessException('非法数据'); + } + $rule_exists = Rule::whereIn('id', $rule_ids)->pluck('id'); + if (count($rule_exists) != count($rule_ids)) { + throw new BusinessException('权限不存在'); + } + $rule_id_string = Role::where('id', $role_id)->value('rules'); + if ($rule_id_string === '') { + throw new BusinessException('数据超出权限范围'); + } + if ($rule_id_string === '*') { + return; + } + $legal_rule_ids = explode(',', $rule_id_string); + if (array_diff($rule_ids, $legal_rule_ids)) { + throw new BusinessException('数据超出权限范围'); + } + } + } + + +} diff --git a/vendor/webman/admin/src/plugin/admin/app/controller/RuleController.php b/vendor/webman/admin/src/plugin/admin/app/controller/RuleController.php new file mode 100644 index 0000000..5015546 --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/app/controller/RuleController.php @@ -0,0 +1,329 @@ +model = new Rule; + } + + /** + * 浏览 + * @return Response + */ + public function index(): Response + { + return view('rule/index'); + } + + /** + * 查询 + * @param Request $request + * @return Response + * @throws BusinessException + */ + public function select(Request $request): Response + { + $this->syncRules(); + return parent::select($request); + } + + /** + * 获取菜单 + * @param Request $request + * @return Response + */ + function get(Request $request): Response + { + $rules = $this->getRules(admin('roles')); + $types = $request->get('type', '0,1'); + $types = is_string($types) ? explode(',', $types) : [0, 1]; + $items = Rule::orderBy('weight', 'desc')->get()->toArray(); + + $formatted_items = []; + foreach ($items as $item) { + $item['pid'] = (int)$item['pid']; + $item['name'] = $item['title']; + $item['value'] = $item['id']; + $item['icon'] = $item['icon'] ? "layui-icon {$item['icon']}" : ''; + $formatted_items[] = $item; + } + + $tree = new Tree($formatted_items); + $tree_items = $tree->getTree(); + // 超级管理员权限为 * + if (!in_array('*', $rules)) { + $this->removeNotContain($tree_items, 'id', $rules); + } + $this->removeNotContain($tree_items, 'type', $types); + return $this->json(0, 'ok', Tree::arrayValues($tree_items)); + } + + /** + * 获取权限 + * @param Request $request + * @return Response + */ + public function permission(Request $request): Response + { + $rules = $this->getRules(admin('roles')); + // 超级管理员 + if (in_array('*', $rules)) { + return $this->json(0, 'ok', ['*']); + } + $keys = Rule::whereIn('id', $rules)->pluck('key'); + $permissions = []; + foreach ($keys as $key) { + if (!$key = Util::controllerToUrlPath($key)) { + continue; + } + $code = str_replace('/', '.', trim($key, '/')); + $permissions[] = $code; + } + return $this->json(0, 'ok', $permissions); + } + + /** + * 根据类同步规则到数据库 + * @return void + */ + protected function syncRules() + { + $items = $this->model->where('key', 'like', '%\\\\%')->get()->keyBy('key'); + $methods_in_db = []; + $methods_in_files = []; + foreach ($items as $item) { + $class = $item->key; + if (strpos($class, '@')) { + $methods_in_db[$class] = $class; + continue; + } + if (class_exists($class)) { + $reflection = new \ReflectionClass($class); + $properties = $reflection->getDefaultProperties(); + $no_need_auth = array_merge($properties['noNeedLogin'] ?? [], $properties['noNeedAuth'] ?? []); + $class = $reflection->getName(); + $pid = $item->id; + $methods = $reflection->getMethods(\ReflectionMethod::IS_PUBLIC); + foreach ($methods as $method) { + $method_name = $method->getName(); + if (strtolower($method_name) === 'index' || strpos($method_name, '__') === 0 || in_array($method_name, $no_need_auth)) { + continue; + } + $name = "$class@$method_name"; + + $methods_in_files[$name] = $name; + $title = Util::getCommentFirstLine($method->getDocComment()) ?: $method_name; + $menu = $items[$name] ?? []; + if ($menu) { + if ($menu->title != $title) { + Rule::where('key', $name)->update(['title' => $title]); + } + continue; + } + $menu = new Rule; + $menu->pid = $pid; + $menu->key = $name; + $menu->title = $title; + $menu->type = 2; + $menu->save(); + } + } + } + // 从数据库中删除已经不存在的方法 + $menu_names_to_del = array_diff($methods_in_db, $methods_in_files); + if ($menu_names_to_del) { + //Rule::whereIn('key', $menu_names_to_del)->delete(); + } + } + + /** + * 查询前置方法 + * @param Request $request + * @return array + * @throws BusinessException + */ + protected function selectInput(Request $request): array + { + [$where, $format, $limit, $field, $order] = parent::selectInput($request); + // 允许通过type=0,1格式传递菜单类型 + $types = $request->get('type'); + if ($types && is_string($types)) { + $where['type'] = ['in', explode(',', $types)]; + } + // 默认weight排序 + if (!$field) { + $field = 'weight'; + $order = 'desc'; + } + return [$where, $format, $limit, $field, $order]; + } + + /** + * 添加 + * @param Request $request + * @return Response + * @throws BusinessException + */ + public function insert(Request $request): Response + { + if ($request->method() === 'GET') { + return view('rule/insert'); + } + $data = $this->insertInput($request); + if (empty($data['type'])) { + $data['type'] = strpos($data['key'], '\\') ? 1 : 0; + } + $data['key'] = str_replace('\\\\', '\\', $data['key']); + $key = $data['key'] ?? ''; + if ($this->model->where('key', $key)->first()) { + return $this->json(1, "菜单标识 $key 已经存在"); + } + $data['pid'] = empty($data['pid']) ? 0 : $data['pid']; + $this->doInsert($data); + return $this->json(0); + } + + /** + * 更新 + * @param Request $request + * @return Response + * @throws BusinessException + */ + public function update(Request $request): Response + { + if ($request->method() === 'GET') { + return view('rule/update'); + } + [$id, $data] = $this->updateInput($request); + if (!$row = $this->model->find($id)) { + return $this->json(2, '记录不存在'); + } + if (isset($data['pid'])) { + $data['pid'] = $data['pid'] ?: 0; + if ($data['pid'] == $row['id']) { + return $this->json(2, '不能将自己设置为上级菜单'); + } + } + if (isset($data['key'])) { + $data['key'] = str_replace('\\\\', '\\', $data['key']); + } + $this->doUpdate($id, $data); + return $this->json(0); + } + + /** + * 删除 + * @param Request $request + * @return Response + */ + public function delete(Request $request): Response + { + $ids = $this->deleteInput($request); + // 子规则一起删除 + $delete_ids = $children_ids = $ids; + while($children_ids) { + $children_ids = $this->model->whereIn('pid', $children_ids)->pluck('id')->toArray(); + $delete_ids = array_merge($delete_ids, $children_ids); + } + $this->doDelete($delete_ids); + return $this->json(0); + } + + /** + * 移除不包含某些数据的数组 + * @param $array + * @param $key + * @param $values + * @return void + */ + protected function removeNotContain(&$array, $key, $values) + { + foreach ($array as $k => &$item) { + if (!is_array($item)) { + continue; + } + if (!$this->arrayContain($item, $key, $values)) { + unset($array[$k]); + } else { + if (!isset($item['children'])) { + continue; + } + $this->removeNotContain($item['children'], $key, $values); + } + } + } + + /** + * 判断数组是否包含某些数据 + * @param $array + * @param $key + * @param $values + * @return bool + */ + protected function arrayContain(&$array, $key, $values): bool + { + if (!is_array($array)) { + return false; + } + if (isset($array[$key]) && in_array($array[$key], $values)) { + return true; + } + if (!isset($array['children'])) { + return false; + } + foreach ($array['children'] as $item) { + if ($this->arrayContain($item, $key, $values)) { + return true; + } + } + return false; + } + + /** + * 获取权限规则 + * @param $roles + * @return array + */ + protected function getRules($roles): array + { + $rules_strings = $roles ? Role::whereIn('id', $roles)->pluck('rules') : []; + $rules = []; + foreach ($rules_strings as $rule_string) { + if (!$rule_string) { + continue; + } + $rules = array_merge($rules, explode(',', $rule_string)); + } + return $rules; + } + +} diff --git a/vendor/webman/admin/src/plugin/admin/app/controller/TableController.php b/vendor/webman/admin/src/plugin/admin/app/controller/TableController.php new file mode 100644 index 0000000..0c0b155 --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/app/controller/TableController.php @@ -0,0 +1,1661 @@ +get('table'); + $table = Util::filterAlphaNum($table); + $form = Layui::buildForm($table, 'search'); + $table_info = Util::getSchema($table, 'table'); + $primary_key = $table_info['primary_key'][0] ?? null; + return view("table/view", [ + 'form' => $form, + 'table' => $table, + 'primary_key' => $primary_key, + ]); + } + + /** + * 查询表 + * @param Request $request + * @return Response + * @throws BusinessException + */ + public function show(Request $request): Response + { + $limit = (int)$request->get('limit', 10); + $page = (int)$request->get('page', 1); + $offset = ($page - 1) * $limit; + $database = config('database.connections')['plugin.admin.mysql']['database']; + $field = $request->get('field', 'TABLE_NAME'); + $field = Util::filterAlphaNum($field); + $order = $request->get('order', 'asc'); + $allow_column = ['TABLE_NAME', 'TABLE_COMMENT', 'ENGINE', 'TABLE_ROWS', 'CREATE_TIME', 'UPDATE_TIME', 'TABLE_COLLATION']; + if (!in_array($field, $allow_column)) { + $field = 'TABLE_NAME'; + } + $order = $order === 'asc' ? 'asc' : 'desc'; + $total = Util::db()->select("SELECT count(*)total FROM information_schema.`TABLES` WHERE TABLE_SCHEMA='$database'")[0]->total ?? 0; + $tables = Util::db()->select("SELECT TABLE_NAME,TABLE_COMMENT,ENGINE,TABLE_ROWS,CREATE_TIME,UPDATE_TIME,TABLE_COLLATION FROM information_schema.`TABLES` WHERE TABLE_SCHEMA='$database' order by $field $order limit $offset,$limit"); + + if ($tables) { + $table_names = array_column($tables, 'TABLE_NAME'); + $table_rows_count = []; + foreach ($table_names as $table_name) { + $table_rows_count[$table_name] = Util::db()->table($table_name)->count(); + } + foreach ($tables as $key => $table) { + $tables[$key]->TABLE_ROWS = $table_rows_count[$table->TABLE_NAME] ?? $table->TABLE_ROWS; + } + } + + return json(['code' => 0, 'msg' => 'ok', 'count' => $total, 'data' => $tables]); + } + + /** + * 创建表 + * @param Request $request + * @return Response + * @throws BusinessException + */ + public function create(Request $request): Response + { + if ($request->method() === 'GET') { + return view("table/create", []); + } + $data = $request->post(); + $table_name = Util::filterAlphaNum($data['table']); + $table_comment = Util::pdoQuote($data['table_comment']); + $columns = $data['columns']; + $forms = $data['forms']; + $keys = $data['keys']; + + $primary_key_count = 0; + foreach ($columns as $index => $item) { + $columns[$index]['field'] = trim($item['field']); + if (!$item['field']) { + unset($columns[$index]); + continue; + } + $columns[$index]['primary_key'] = !empty($item['primary_key']); + if ($columns[$index]['primary_key']) { + $primary_key_count++; + } + $columns[$index]['auto_increment'] = !empty($item['auto_increment']); + $columns[$index]['nullable'] = !empty($item['nullable']); + if ($item['default'] === '') { + $columns[$index]['default'] = null; + } else if ($item['default'] === "''") { + $columns[$index]['default'] = ''; + } + } + + if ($primary_key_count > 1) { + throw new BusinessException('不支持复合主键'); + } + + foreach ($forms as $index => $item) { + if (!$item['field']) { + unset($forms[$index]); + continue; + } + $forms[$index]['form_show'] = !empty($item['form_show']); + $forms[$index]['list_show'] = !empty($item['list_show']); + $forms[$index]['enable_sort'] = !empty($item['enable_sort']); + $forms[$index]['searchable'] = !empty($item['searchable']); + } + + foreach ($keys as $index => $item) { + if (!$item['name'] || !$item['columns']) { + unset($keys[$index]); + } + } + + Util::schema()->create($table_name, function (Blueprint $table) use ($columns) { + $type_method_map = Util::methodControlMap(); + foreach ($columns as $column) { + if (!isset($column['type'])) { + throw new BusinessException("请为{$column['field']}选择类型"); + } + if (!isset($type_method_map[$column['type']])) { + throw new BusinessException("不支持的类型{$column['type']}"); + } + $this->createColumn($column, $table); + } + $table->charset = 'utf8mb4'; + $table->collation = 'utf8mb4_general_ci'; + $table->engine = 'InnoDB'; + }); + + Util::db()->statement("ALTER TABLE `$table_name` COMMENT $table_comment"); + + // 索引 + Util::schema()->table($table_name, function (Blueprint $table) use ($keys) { + foreach ($keys as $key) { + $name = $key['name']; + $columns = is_array($key['columns']) ? $key['columns'] : explode(',', $key['columns']); + $type = $key['type']; + if ($type == 'unique') { + $table->unique($columns, $name); + continue; + } + $table->index($columns, $name); + } + }); + $form_schema_map = []; + foreach ($forms as $item) { + $form_schema_map[$item['field']] = $item; + } + $form_schema_map = json_encode($form_schema_map, JSON_UNESCAPED_UNICODE); + $this->updateSchemaOption($table_name, $form_schema_map); + return $this->json(0, 'ok'); + } + + /** + * 修改表 + * @param Request $request + * @return Response + * @throws BusinessException + */ + public function modify(Request $request): Response + { + if ($request->method() === 'GET') { + return view("table/modify", ['table' => $request->get('table')]); + } + $data = $request->post(); + $old_table_name = Util::filterAlphaNum($data['old_table']); + $table_name = Util::filterAlphaNum($data['table']); + $table_comment = $data['table_comment']; + $columns = $data['columns']; + $forms = $data['forms']; + $keys = $data['keys']; + $primary_key = null; + $auto_increment_column = null; + $schema = Util::getSchema($old_table_name); + $old_columns = $schema['columns']; + $old_primary_key = $schema['table']['primary_key'][0] ?? null; + + $primary_key_count = $auto_increment_count = 0; + foreach ($columns as $index => $item) { + $columns[$index]['field'] = trim($item['field']); + if (!$item['field']) { + unset($columns[$index]); + continue; + } + $field = $item['field']; + $columns[$index]['auto_increment'] = !empty($item['auto_increment']); + $columns[$index]['nullable'] = !empty($item['nullable']); + $columns[$index]['primary_key'] = !empty($item['primary_key']); + if ($columns[$index]['primary_key']) { + $primary_key = $item['field']; + $columns[$index]['nullable'] = false; + $primary_key_count++; + } + if ($item['default'] === '') { + $columns[$index]['default'] = null; + } else if ($item['default'] === "''") { + $columns[$index]['default'] = ''; + } + if ($columns[$index]['auto_increment']) { + $auto_increment_count++; + if (!isset($old_columns[$field]) || !$old_columns[$field]['auto_increment']) { + $auto_increment_column = $columns[$index]; + unset($auto_increment_column['old_field']); + $columns[$index]['auto_increment'] = false; + } + } + } + + if ($primary_key_count > 1) { + throw new BusinessException('不支持复合主键'); + } + + if ($auto_increment_count > 1) { + throw new BusinessException('一个表只能有一个自增字段,并且必须为key'); + } + + foreach ($forms as $index => $item) { + if (!$item['field']) { + unset($forms[$index]); + continue; + } + $forms[$index]['form_show'] = !empty($item['form_show']); + $forms[$index]['list_show'] = !empty($item['list_show']); + $forms[$index]['enable_sort'] = !empty($item['enable_sort']); + $forms[$index]['searchable'] = !empty($item['searchable']); + } + + foreach ($keys as $index => $item) { + if (!$item['name'] || !$item['columns']) { + unset($keys[$index]); + } + } + + // 改表名 + if ($table_name != $old_table_name) { + Util::schema()->rename($old_table_name, $table_name); + } + + $type_method_map = Util::methodControlMap(); + + foreach ($columns as $column) { + if (!isset($type_method_map[$column['type']])) { + throw new BusinessException("不支持的类型{$column['type']}"); + } + $field = $column['old_field'] ?? $column['field'] ; + $old_column = $old_columns[$field] ?? []; + // 类型更改 + foreach ($old_column as $key => $value) { + if (key_exists($key, $column) && ($column[$key] != $value || ($key === 'default' && $column[$key] !== $value))) { + $this->modifyColumn($column, $table_name); + break; + } + } + } + + $table = Util::getSchema($table_name, 'table'); + if ($table_comment !== $table['comment']) { + $table_comment = Util::pdoQuote($table_comment); + Util::db()->statement("ALTER TABLE `$table_name` COMMENT $table_comment"); + } + + $old_columns = Util::getSchema($table_name, 'columns'); + Util::schema()->table($table_name, function (Blueprint $table) use ($columns, $old_columns, $keys, $table_name) { + foreach ($columns as $column) { + $field = $column['field']; + // 新字段 + if (!isset($old_columns[$field])) { + $this->createColumn($column, $table); + } + } + // 更新索引名字 + foreach ($keys as $key) { + if (!empty($key['old_name']) && $key['old_name'] !== $key['name']) { + $table->renameIndex($key['old_name'], $key['name']); + } + } + }); + + // 找到删除的字段 + $old_columns = Util::getSchema($table_name, 'columns'); + $exists_column_names = array_column($columns, 'field', 'field'); + $old_columns_names = array_column($old_columns, 'field'); + $drop_column_names = array_diff($old_columns_names, $exists_column_names); + $drop_column_names = Util::filterAlphaNum($drop_column_names); + foreach ($drop_column_names as $drop_column_name) { + Util::db()->statement("ALTER TABLE $table_name DROP COLUMN `$drop_column_name`"); + } + + $old_keys = Util::getSchema($table_name, 'keys'); + Util::schema()->table($table_name, function (Blueprint $table) use ($keys, $old_keys, $table_name) { + foreach ($keys as $key) { + $key_name = $key['name']; + $old_key = $old_keys[$key_name] ?? []; + // 如果索引有变动,则删除索引,重新建立索引 + if ($old_key && ($key['type'] != $old_key['type'] || $key['columns'] != implode(',', $old_key['columns']))) { + $old_key = []; + unset($old_keys[$key_name]); + echo "Drop Index $key_name\n"; + $table->dropIndex($key_name); + } + // 重新建立索引 + if (!$old_key) { + $name = $key['name']; + $columns = is_array($key['columns']) ? $key['columns'] : explode(',', $key['columns']); + $type = $key['type']; + if ($type == 'unique') { + $table->unique($columns, $name); + continue; + } + echo "Create Index $key_name\n"; + $table->index($columns, $name); + } + } + + // 找到删除的索引 + $exists_key_names = array_column($keys, 'name', 'name'); + $old_keys_names = array_column($old_keys, 'name'); + $drop_keys_names = array_diff($old_keys_names, $exists_key_names); + foreach ($drop_keys_names as $name) { + echo "Drop Index $name\n"; + $table->dropIndex($name); + } + }); + + // 变更主键 + if ($old_primary_key != $primary_key) { + if ($old_primary_key) { + Util::db()->statement("ALTER TABLE `$table_name` DROP PRIMARY KEY"); + } + if ($primary_key) { + $primary_key = Util::filterAlphaNum($primary_key); + Util::db()->statement("ALTER TABLE `$table_name` ADD PRIMARY KEY(`$primary_key`)"); + } + } + + // 一个表只能有一个 auto_increment 字段,并且是key,所以需要在最后设置 + if ($auto_increment_column) { + $this->modifyColumn($auto_increment_column, $table_name); + } + + $form_schema_map = []; + foreach ($forms as $item) { + $form_schema_map[$item['field']] = $item; + } + $form_schema_map = json_encode($form_schema_map, JSON_UNESCAPED_UNICODE); + $option_name = $this->updateSchemaOption($table_name, $form_schema_map); + + return $this->json(0,$option_name); + } + + + + /** + * 一键菜单 + * @param Request $request + * @return Response + * @throws BusinessException + */ + public function crud(Request $request): Response + { + $table_name = $request->input('table'); + Util::checkTableName($table_name); + $prefix = 'wa_'; + $table_basename = strpos($table_name, $prefix) === 0 ? substr($table_name, strlen($prefix)) : $table_name; + $inflector = InflectorFactory::create()->build(); + $model_class = $inflector->classify($inflector->singularize($table_basename)); + $base_path = '/plugin/admin/app'; + if ($request->method() === 'GET') { + return view("table/crud", [ + 'table' => $table_name, + 'model' => "$base_path/model/$model_class.php", + 'controller' => "$base_path/controller/{$model_class}Controller.php", + ]); + } + $title = $request->post('title'); + $pid = $request->post('pid', 0); + $icon = $request->post('icon', ''); + $controller_file = '/' . trim($request->post('controller', ''), '/'); + $model_file = '/' . trim($request->post('model', ''), '/'); + $overwrite = $request->post('overwrite'); + if ($controller_file === '/' || $model_file === '/') { + return $this->json(1, '控制器和model不能为空'); + } + + $controller_info = pathinfo($controller_file); + $model_info = pathinfo($model_file); + $controller_path = Util::filterPath($controller_info['dirname'] ?? ''); + $model_path = Util::filterPath($model_info['dirname'] ?? ''); + + $controller_file_name = Util::filterAlphaNum($controller_info['filename'] ?? ''); + $model_file_name = Util::filterAlphaNum($model_info['filename'] ?? ''); + + if ($controller_info['extension'] !== 'php' || $model_info['extension'] !== 'php' ) { + return $this->json(1, '控制器和model必须以.php为后缀'); + } + + $pid = (int)$pid; + if ($pid) { + $parent_menu = Rule::find($pid); + if (!$parent_menu) { + return $this->json(1, '父菜单不存在'); + } + } + + if (!$overwrite) { + if (is_file(base_path($controller_file))) { + return $this->json(1, "$controller_file 已经存在"); + } + if (is_file(base_path($model_file))) { + return $this->json(1, "$model_file 已经存在"); + } + } + + $explode = explode('/', trim($controller_path, '/')); + $plugin = ''; + if (strpos(strtolower($controller_file), '/controller/') === false) { + return $this->json(2, '控制器必须在controller目录下'); + } + if ($explode[0] === 'plugin') { + if (count($explode) < 4) { + return $this->json(2, '控制器参数非法'); + } + $plugin = $explode[1]; + if (strtolower($explode[2]) !== 'app') { + return $this->json(2, '控制器必须在app目录'); + } + $app = strtolower($explode[3]) !== 'controller' ? $explode[3] : ''; + } else { + if (count($explode) < 2) { + return $this->json(3, '控制器参数非法'); + } + if (strtolower($explode[0]) !== 'app') { + return $this->json(3, '控制器必须在app目录'); + } + $app = strtolower($explode[1]) !== 'controller' ? $explode[1] : ''; + } + + Util::pauseFileMonitor(); + try { + $model_class = $model_file_name; + $model_namespace = str_replace('/', '\\', trim($model_path, '/')); + + // 创建model + $this->createModel($model_class, $model_namespace, base_path($model_file), $table_name); + + $controller_suffix = $plugin ? config("plugin.$plugin.app.controller_suffix") : config('app.controller_suffix'); + $controller_class = $controller_file_name; + $controller_namespace = str_replace('/', '\\', trim($controller_path, '/')); + // 创建controller + $controller_url_name = $controller_suffix && substr($controller_class, -strlen($controller_suffix)) === $controller_suffix ? substr($controller_class, 0, -strlen($controller_suffix)) : $controller_class; + $controller_url_name = str_replace('_', '-', $inflector->tableize($controller_url_name)); + + if ($plugin) { + array_splice($explode, 0, 2); + } + array_shift($explode); + if ($app) { + array_shift($explode); + } + foreach ($explode as $index => $item) { + if (strtolower($item) === 'controller') { + unset($explode[$index]); + } + } + + $controller_base = implode('/', $explode); + $controller_class_with_namespace = "$controller_namespace\\$controller_class"; + $template_path = $controller_base ? "$controller_base/$controller_url_name" : $controller_url_name; + $this->createController($controller_class, $controller_namespace, base_path($controller_file), $model_class, $model_namespace, $title, $template_path); + + // 创建模版 + $template_file_path = ($plugin ? "/plugin/$plugin" : '') . '/app/' . ($app ? "$app/" : '') . 'view/' . $template_path; + + $model_class_with_namespace = "$model_namespace\\$model_class"; + $primary_key = (new $model_class_with_namespace)->getKeyName(); + $url_path_base = ($plugin ? "/app/$plugin/" : '/') . ($app ? "$app/" : '') . $template_path; + $this->createTemplate(base_path($template_file_path), $table_name, $url_path_base, $primary_key, "$controller_namespace\\$controller_class"); + } finally { + Util::resumeFileMonitor(); + } + + $menu = Rule::where('key', $controller_class_with_namespace)->first(); + if (!$menu) { + $menu = new Rule; + } + $menu->pid = $pid; + $menu->key = $controller_class_with_namespace; + $menu->title = $title; + $menu->icon = $icon; + $menu->href = "$url_path_base/index"; + $menu->save(); + + $roles = admin('roles'); + $rules = Role::whereIn('id', $roles)->pluck('rules'); + $rule_ids = []; + foreach ($rules as $rule_string) { + if (!$rule_string) { + continue; + } + $rule_ids = array_merge($rule_ids, explode(',', $rule_string)); + } + + // 不是超级管理员,则需要给当前管理员这个菜单的权限 + if (!in_array('*', $rule_ids) && $roles){ + $role = Role::find(current($roles)); + if ($role) { + $role->rules .= ",{$menu->id}"; + } + $role->save(); + } + + return $this->json(0); + } + + /** + * 创建model + * @param $class + * @param $namespace + * @param $file + * @param $table + * @return void + */ + protected function createModel($class, $namespace, $file, $table) + { + $this->mkdir($file); + $table_val = "'$table'"; + $pk = 'id'; + $properties = ''; + $timestamps = ''; + $incrementing = ''; + $columns = []; + try { + $database = config('database.connections')['plugin.admin.mysql']['database']; + //plugin.admin.mysql + foreach (Util::db()->select("select COLUMN_NAME,DATA_TYPE,COLUMN_KEY,COLUMN_COMMENT from INFORMATION_SCHEMA.COLUMNS where table_name = '$table' and table_schema = '$database' order by ORDINAL_POSITION") as $item) { + if ($item->COLUMN_KEY === 'PRI') { + $pk = $item->COLUMN_NAME; + $item->COLUMN_COMMENT .= "(主键)"; + if (strpos(strtolower($item->DATA_TYPE), 'int') === false) { + $incrementing = <<getType($item->DATA_TYPE); + $properties .= " * @property $type \${$item->COLUMN_NAME} {$item->COLUMN_COMMENT}\n"; + $columns[$item->COLUMN_NAME] = $item->COLUMN_NAME; + } + } catch (Throwable $e) {echo $e;} + if (!isset($columns['created_at']) || !isset($columns['updated_at'])) { + $timestamps = <<mkdir($file); + $controller_content = <<model = new $model_class; + } + + /** + * 浏览 + * @return Response + */ + public function index(): Response + { + return view('$template_path/index'); + } + + /** + * 插入 + * @param Request \$request + * @return Response + * @throws BusinessException + */ + public function insert(Request \$request): Response + { + if (\$request->method() === 'POST') { + return parent::insert(\$request); + } + return view('$template_path/insert'); + } + + /** + * 更新 + * @param Request \$request + * @return Response + * @throws BusinessException + */ + public function update(Request \$request): Response + { + if (\$request->method() === 'POST') { + return parent::update(\$request); + } + return view('$template_path/update'); + } + +} + +EOF; + file_put_contents($file, $controller_content); + } + + /** + * 创建控制器 + * @param $template_file_path + * @param $table + * @param $template_path + * @param $url_path_base + * @param $primary_key + * @param $controller_class_with_namespace + * @return void + */ + protected function createTemplate($template_file_path, $table, $url_path_base, $primary_key, $controller_class_with_namespace) + { + $this->mkdir($template_file_path . '/index.html'); + $code_base = Util::controllerToUrlPath($controller_class_with_namespace); + $code_base = str_replace('/', '.', trim($code_base, '/')); + $form = Layui::buildForm($table, 'search'); + $html = $form->html(3); + $html = $html ? << +
    +
    + $html +
    + + + +
    +
    + 展开 + 收起 +
    +
    +
    + +EOF + : ''; + $html = str_replace("\n", "\n" . str_repeat(' ', 2), $html); + $js = $form->js(3); + $table_js = Layui::buildTable($table, 4); + $template_content = << + + + + 浏览页面 + + + + + + + $html + + +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + +EOF; + file_put_contents("$template_file_path/index.html", $template_content); + + $form = Layui::buildForm($table); + $html = $form->html(5); + $js = $form->js(3); + $template_content = << + + + + 新增页面 + + + + + +
    + +
    +
    + $html +
    +
    + +
    +
    + + +
    +
    + +
    + + + + + + + + + + +EOF; + + file_put_contents("$template_file_path/insert.html", $template_content); + + $form = Layui::buildForm($table, 'update'); + $html = $form->html(5); + $js = $form->js(6); + $template_content = << + + + + 更新页面 + + + + + +
    + +
    +
    + $html +
    +
    + +
    +
    + + +
    +
    + +
    + + + + + + + + + + + +EOF; + + file_put_contents("$template_file_path/update.html", $template_content); + + } + + /** + * 创建目录 + * @param $file + * @return void + */ + protected function mkdir($file) + { + $path = pathinfo($file, PATHINFO_DIRNAME); + if (!is_dir($path)) { + mkdir($path, 0777, true); + } + } + + + /** + * 查询记录 + * @param Request $request + * @return Response + * @throws BusinessException + */ + public function select(Request $request): Response + { + $page = $request->get('page', 1); + $field = $request->get('field'); + $order = $request->get('order', 'asc'); + $table = Util::filterAlphaNum($request->get('table', '')); + $format = $request->get('format', 'normal'); + $limit = $request->get('limit', $format === 'tree' ? 5000 : 10); + + $allow_column = Util::db()->select("desc $table"); + if (!$allow_column) { + return $this->json(2, '表不存在'); + } + $allow_column = array_column($allow_column, 'Field', 'Field'); + if (!in_array($field, $allow_column)) { + $field = current($allow_column); + } + $order = $order === 'asc' ? 'asc' : 'desc'; + $paginator = Util::db()->table($table); + foreach ($request->get() as $column => $value) { + if ($value === '') { + continue; + } + if (isset($allow_column[$column])) { + if (is_array($value)) { + if ($value[0] === 'like') { + $paginator = $paginator->where($column, 'like', "%$value[1]%"); + } elseif (in_array($value[0], ['>', '=', '<', '<>', 'not like'])) { + $paginator = $paginator->where($column, $value[0], $value[1]); + } else { + if($value[0] !== '' || $value[1] !== '') { + $paginator = $paginator->whereBetween($column, $value); + } + } + } else { + $paginator = $paginator->where($column, $value); + } + } + } + $paginator = $paginator->orderBy($field, $order)->paginate($limit, '*', 'page', $page); + $items = $paginator->items(); + if ($format == 'tree') { + $items_map = []; + foreach ($items as $item) { + $items_map[$item->id] = (array)$item; + } + $formatted_items = []; + foreach ($items_map as $index => $item) { + if ($item['pid'] && isset($items_map[$item['pid']])) { + $items_map[$item['pid']]['children'][] = &$items_map[$index]; + } + } + foreach ($items_map as $item) { + if (!$item['pid']) { + $formatted_items[] = $item; + } + } + $items = $formatted_items; + } + + return json(['code' => 0, 'msg' => 'ok', 'count' => $paginator->total(), 'data' => $items]); + + } + + /** + * 插入记录 + * @param Request $request + * @return Response + * @throws BusinessException + */ + public function insert(Request $request): Response + { + if ($request->method() === 'GET') { + $table = $request->get('table'); + $form = Layui::buildForm($table); + return view("table/insert", [ + 'form' => $form, + 'table' => $table + ]); + } + $table = Util::filterAlphaNum($request->input('table', '')); + $data = $request->post(); + $allow_column = Util::db()->select("desc `$table`"); + if (!$allow_column) { + throw new BusinessException('表不存在', 2); + } + $columns = array_column($allow_column, 'Type', 'Field'); + foreach ($data as $col => $item) { + if (!isset($columns[$col])) { + unset($data[$col]); + continue; + } + // 非字符串类型传空则为null + if ($item === '' && strpos(strtolower($columns[$col]), 'varchar') === false && strpos(strtolower($columns[$col]), 'text') === false) { + $data[$col] = null; + } + if (is_array($item)) { + $data[$col] = implode(',', $item); + continue; + } + if ($col === 'password') { + $data[$col] = Util::passwordHash($item); + } + } + $datetime = date('Y-m-d H:i:s'); + if (isset($columns['created_at']) && empty($data['created_at'])) { + $data['created_at'] = $datetime; + } + if (isset($columns['updated_at']) && empty($data['updated_at'])) { + $data['updated_at'] = $datetime; + } + $id = Util::db()->table($table)->insertGetId($data); + return $this->json(0, $id); + } + + /** + * 更新记录 + * @param Request $request + * @return Response + * @throws BusinessException + */ + public function update(Request $request): Response + { + if ($request->method() === 'GET') { + $table = $request->get('table'); + $table_info = Util::getSchema($table, 'table'); + $primary_key = $table_info['primary_key'][0] ?? null; + $value = htmlspecialchars($request->get($primary_key, '')); + $form = Layui::buildForm($table,'update'); + return view("table/update", [ + 'primary_key' => $primary_key, + 'value' => $value, + 'form' => $form, + 'table' => $table + ]); + } + $table = Util::filterAlphaNum($request->post('table')); + $table_info = Util::getSchema($table, 'table'); + $primary_keys = $table_info['primary_key']; + if (empty($primary_keys)) { + return $this->json(1, '该表没有主键,无法执行更新操作'); + } + if (count($primary_keys) > 1) { + return $this->json(1, '不支持复合主键更新'); + } + $primary_key = $primary_keys[0]; + $value = $request->post($primary_key); + $data = $request->post(); + $allow_column = Util::db()->select("desc `$table`"); + if (!$allow_column) { + throw new BusinessException('表不存在', 2); + } + $columns = array_column($allow_column, 'Type', 'Field'); + foreach ($data as $col => $item) { + if (!isset($columns[$col])) { + unset($data[$col]); + continue; + } + // 非字符串类型传空则为null + if ($item === '' && strpos(strtolower($columns[$col]), 'varchar') === false && strpos(strtolower($columns[$col]), 'text') === false) { + $data[$col] = null; + } + if (is_array($item)) { + $data[$col] = implode(',', $item); + } + if ($col === 'password') { + // 密码为空,则不更新密码 + if ($item == '') { + unset($data[$col]); + continue; + } + $data[$col] = Util::passwordHash($item); + } + } + $datetime = date('Y-m-d H:i:s'); + if (isset($columns['updated_at']) && empty($data['updated_at'])) { + $data['updated_at'] = $datetime; + } + Util::db()->table($table)->where($primary_key, $value)->update($data); + return $this->json(0); + } + + /** + * 删除记录 + * @param Request $request + * @return Response + * @throws BusinessException + */ + public function delete(Request $request): Response + { + $table = $request->post('table'); + $table_info = Util::getSchema($table, 'table'); + $primary_keys = $table_info['primary_key']; + if (empty($primary_keys)) { + return $this->json(1, '该表没有主键,无法执行删除操作'); + } + if (count($primary_keys) > 1) { + return $this->json(1, '不支持复合主键删除'); + } + $primary_key = $primary_keys[0]; + $value = (array)$request->post($primary_key); + Util::db()->table($table)->whereIn($primary_key, $value)->delete(); + return $this->json(0); + } + + + /** + * 删除表 + * @param Request $request + * @return Response + */ + public function drop(Request $request): Response + { + $tables = $request->post('tables'); + if (!$tables) { + return $this->json(0, 'not found'); + } + $prefix = 'wa_'; + $table_not_allow_drop = ["{$prefix}admins", "{$prefix}users", "{$prefix}options", "{$prefix}roles", "{$prefix}rules", "{$prefix}admin_roles", "{$prefix}uploads"]; + if ($found = array_intersect($tables, $table_not_allow_drop)) { + return $this->json(400, implode(',', $found) . '不允许删除'); + } + foreach ($tables as $table) { + Util::schema()->drop($table); + // 删除schema + Util::db()->table('wa_options')->where('name', "table_form_schema_$table")->delete(); + } + return $this->json(0, 'ok'); + } + + /** + * 表摘要 + * @param Request $request + * @return Response + */ + public function schema(Request $request): Response + { + $table = $request->get('table'); + $data = Util::getSchema($table); + + return $this->json(0, 'ok', [ + 'table' => $data['table'], + 'columns' => array_values($data['columns']), + 'forms' => array_values($data['forms']), + 'keys' => array_values($data['keys']), + ]); + } + + /** + * 创建字段 + * @param $column + * @param Blueprint $table + * @return mixed + */ + protected function createColumn($column, Blueprint $table) + { + $method = $column['type']; + $args = [$column['field']]; + if (stripos($method, 'int') !== false) { + // auto_increment 会自动成为主键 + if ($column['auto_increment']) { + $column['nullable'] = false; + $column['default'] = null; + $args[] = true; + } + } elseif (in_array($method, ['string', 'char']) || stripos($method, 'time') !== false) { + if ($column['length']) { + $args[] = $column['length']; + } + } elseif ($method === 'enum') { + $args[] = array_map('trim', explode(',', $column['length'])); + } elseif (in_array($method, ['float', 'decimal', 'double'])) { + if ($column['length']) { + $args = array_merge($args, array_map('trim', explode(',', $column['length']))); + } + } else { + $column['auto_increment'] = false; + } + + $column_def = [$table, $method](...$args); + if (!empty($column['comment'])) { + $column_def = $column_def->comment($column['comment']); + } + + if (!$column['auto_increment'] && $column['primary_key']) { + $column_def = $column_def->primary(true); + } + + if ($column['auto_increment'] && !$column['primary_key']) { + $column_def = $column_def->primary(false); + } + $column_def = $column_def->nullable($column['nullable']); + + if ($column['primary_key']) { + $column_def = $column_def->nullable(false); + } + + if ($method != 'text' && $column['default'] !== null) { + $column_def->default($column['default']); + } + return $column_def; + } + + /** + * 更改字段 + * @param $column + * @param $table + * @return mixed + * @throws BusinessException + */ + protected function modifyColumn($column, $table) + { + $table = Util::filterAlphaNum($table); + $method = Util::filterAlphaNum($column['type']); + $field = Util::filterAlphaNum($column['field']); + $old_field = Util::filterAlphaNum($column['old_field'] ?? null); + $nullable = $column['nullable']; + $default = $column['default'] !== null ? Util::pdoQuote($column['default']) : null; + $comment = Util::pdoQuote($column['comment']); + $auto_increment = $column['auto_increment']; + $length = (int)$column['length']; + + if ($column['primary_key']) { + $default = null; + } + + if ($old_field && $old_field !== $field) { + $sql = "ALTER TABLE `$table` CHANGE COLUMN `$old_field` `$field` "; + } else { + $sql = "ALTER TABLE `$table` MODIFY `$field` "; + } + + if (stripos($method, 'integer') !== false) { + $type = str_ireplace('integer', 'int', $method); + if (stripos($method, 'unsigned') !== false) { + $type = str_ireplace('unsigned', '', $type); + $sql .= "$type "; + $sql .= 'unsigned '; + } else { + $sql .= "$type "; + } + if ($auto_increment) { + $column['nullable'] = false; + $column['default'] = null; + $sql .= 'AUTO_INCREMENT '; + } + } else { + switch ($method) { + case 'string': + $length = $length ?: 255; + $sql .= "varchar($length) "; + break; + case 'char': + case 'time': + $sql .= $length ? "$method($length) " : "$method "; + break; + case 'enum': + $args = array_map('trim', explode(',', (string)$column['length'])); + foreach ($args as $key => $value) { + $args[$key] = Util::pdoQuote($value); + } + $sql .= 'enum(' . implode(',', $args) . ') '; + break; + case 'double': + case 'float': + case 'decimal': + if (trim($column['length'])) { + $args = array_map('intval', explode(',', $column['length'])); + $args[1] = $args[1] ?? $args[0]; + $sql .= "$method($args[0], $args[1]) "; + break; + } + $sql .= "$method "; + break; + default : + $sql .= "$method "; + + } + } + + if (!$nullable) { + $sql .= "NOT NULL "; + } + + if ($method != 'text' && $default !== null) { + $sql .= "DEFAULT $default "; + } + + if ($comment !== null) { + $sql .= "COMMENT $comment "; + } + + echo "$sql\n"; + Util::db()->statement($sql); + } + + /** + * 字段类型列表 + * @param Request $request + * @return Response + */ + public function types(Request $request): Response + { + $types = Util::methodControlMap(); + return $this->json(0, 'ok', $types); + } + + + /** + * 更新表的form schema信息 + * @param $table_name + * @param $data + * @return string + */ + protected function updateSchemaOption($table_name, $data): string + { + $option_name = "table_form_schema_$table_name"; + $option = Option::where('name', $option_name)->first(); + if ($option) { + Option::where('name', $option_name)->update(['value' => $data]); + } else { + Option::insert(['name' => $option_name, 'value' => $data]); + } + return $option_name; + } + + /** + * 字段类型到php类型映射 + * @param string $type + * @return string + */ + protected function getType(string $type): string + { + if (strpos($type, 'int') !== false) { + return 'integer'; + } + switch ($type) { + case 'varchar': + case 'string': + case 'text': + case 'date': + case 'time': + case 'guid': + case 'datetimetz': + case 'datetime': + case 'decimal': + case 'enum': + return 'string'; + case 'boolean': + return 'integer'; + case 'float': + return 'float'; + default: + return 'mixed'; + } + } + +} diff --git a/vendor/webman/admin/src/plugin/admin/app/controller/UploadController.php b/vendor/webman/admin/src/plugin/admin/app/controller/UploadController.php new file mode 100644 index 0000000..fdb5733 --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/app/controller/UploadController.php @@ -0,0 +1,309 @@ +model = new Upload; + } + + /** + * 浏览 + * @return Response + */ + public function index(): Response + { + return view('upload/index'); + } + + /** + * 浏览附件 + * @return Response + */ + public function attachment(): Response + { + return view('upload/attachment'); + } + + /** + * 查询附件 + * @param Request $request + * @return Response + * @throws BusinessException + */ + public function select(Request $request): Response + { + [$where, $format, $limit, $field, $order] = $this->selectInput($request); + if (!empty($where['ext']) && is_string($where['ext'])) { + $where['ext'] = ['in', explode(',', $where['ext'])]; + } + if (!empty($where['name']) && is_string($where['name'])) { + $where['name'] = ['like', "%{$where['name']}%"]; + } + $query = $this->doSelect($where, $field, $order); + return $this->doFormat($query, $format, $limit); + } + + /** + * 更新附件 + * @param Request $request + * @return Response + * @throws BusinessException + */ + public function update(Request $request): Response + { + if ($request->method() === 'GET') { + return view('upload/update'); + } + return parent::update($request); + } + + /** + * 添加附件 + * @param Request $request + * @return Response + * @throws Exception + */ + public function insert(Request $request): Response + { + if ($request->method() === 'GET') { + return view('upload/insert'); + } + $file = current($request->file()); + if (!$file || !$file->isValid()) { + return $this->json(1, '未找到文件'); + } + $data = $this->base($request, '/upload/files/'.date('Ymd')); + $upload = new Upload; + $upload->admin_id = admin_id(); + $upload->name = $data['name']; + [ + $upload->url, + $upload->name, + $_, + $upload->file_size, + $upload->mime_type, + $upload->image_width, + $upload->image_height, + $upload->ext + ] = array_values($data); + $upload->category = $request->post('category'); + $upload->save(); + return $this->json(0, '上传成功', [ + 'url' => $data['url'], + 'name' => $data['name'], + 'size' => $data['size'], + ]); + } + + /** + * 上传文件 + * @param Request $request + * @return Response + * @throws Exception + */ + public function file(Request $request): Response + { + $file = current($request->file()); + if (!$file || !$file->isValid()) { + return $this->json(1, '未找到文件'); + } + $img_exts = [ + 'jpg', + 'jpeg', + 'png', + 'gif' + ]; + if (in_array($file->getUploadExtension(), $img_exts)) { + return $this->image($request); + } + $data = $this->base($request, '/upload/files/'.date('Ymd')); + return $this->json(0, '上传成功', [ + 'url' => $data['url'], + 'name' => $data['name'], + 'size' => $data['size'], + ]); + } + + /** + * 上传图片 + * @param Request $request + * @return Response + * @throws Exception + */ + public function image(Request $request): Response + { + $data = $this->base($request, '/upload/img/'.date('Ymd')); + $realpath = $data['realpath']; + try { + $img = Image::make($realpath); + $max_height = 1170; + $max_width = 1170; + $width = $img->width(); + $height = $img->height(); + $ratio = 1; + if ($height > $max_height || $width > $max_width) { + $ratio = $width > $height ? $max_width / $width : $max_height / $height; + } + $img->resize($width*$ratio, $height*$ratio)->save($realpath); + } catch (Exception $e) { + unlink($realpath); + return json( [ + 'code' => 500, + 'msg' => '处理图片发生错误' + ]); + } + return json( [ + 'code' => 0, + 'msg' => '上传成功', + 'data' => [ + 'url' => $data['url'], + 'name' => $data['name'], + 'size' => $data['size'], + ] + ]); + } + + /** + * 上传头像 + * @param Request $request + * @return Response + * @throws Exception + */ + public function avatar(Request $request): Response + { + $file = current($request->file()); + if ($file && $file->isValid()) { + $ext = strtolower($file->getUploadExtension()); + if (!in_array($ext, ['jpg', 'jpeg', 'gif', 'png'])) { + return json(['code' => 2, 'msg' => '仅支持 jpg jpeg gif png格式']); + } + $image = Image::make($file); + $width = $image->width(); + $height = $image->height(); + $size = min($width, $height); + $relative_path = 'upload/avatar/' . date('Ym'); + $real_path = base_path() . "/plugin/admin/public/$relative_path"; + if (!is_dir($real_path)) { + mkdir($real_path, 0777, true); + } + $name = bin2hex(pack('Nn',time(), random_int(1, 65535))); + $ext = $file->getUploadExtension(); + + $image->crop($size, $size)->resize(300, 300); + $path = base_path() . "/plugin/admin/public/$relative_path/$name.lg.$ext"; + $image->save($path); + + $image->resize(120, 120); + $path = base_path() . "/plugin/admin/public/$relative_path/$name.md.$ext"; + $image->save($path); + + $image->resize(60, 60); + $path = base_path() . "/plugin/admin/public/$relative_path/$name.$ext"; + $image->save($path); + + $image->resize(30, 30); + $path = base_path() . "/plugin/admin/public/$relative_path/$name.sm.$ext"; + $image->save($path); + + return json([ + 'code' => 0, + 'msg' => '上传成功', + 'data' => [ + 'url' => "/app/admin/$relative_path/$name.md.$ext" + ] + ]); + } + return json(['code' => 1, 'msg' => 'file not found']); + } + + /** + * 删除附件 + * @param Request $request + * @return Response + */ + public function delete(Request $request): Response + { + return parent::delete($request); + } + + /** + * 获取上传数据 + * @param Request $request + * @param $relative_dir + * @return array + * @throws BusinessException + */ + protected function base(Request $request, $relative_dir): array + { + $relative_dir = ltrim($relative_dir, '/'); + $file = current($request->file()); + if (!$file || !$file->isValid()) { + throw new BusinessException('未找到上传文件', 400); + } + + $base_dir = base_path() . '/plugin/admin/public/'; + $full_dir = $base_dir . $relative_dir; + if (!is_dir($full_dir)) { + mkdir($full_dir, 0777, true); + } + + $ext = strtolower($file->getUploadExtension()); + $ext_forbidden_map = ['php', 'php3', 'php5', 'css', 'js', 'html', 'htm', 'asp', 'jsp']; + if (in_array($ext, $ext_forbidden_map)) { + throw new BusinessException('不支持该格式的文件上传', 400); + } + + $relative_path = $relative_dir . '/' . bin2hex(pack('Nn',time(), random_int(1, 65535))) . ".$ext"; + $full_path = $base_dir . $relative_path; + $file_size = $file->getSize(); + $file_name = $file->getUploadName(); + $mime_type = $file->getUploadMimeType(); + $file->move($full_path); + $image_with = $image_height = 0; + if ($img_info = getimagesize($full_path)) { + [$image_with, $image_height] = $img_info; + $mime_type = $img_info['mime']; + } + return [ + 'url' => "/app/admin/$relative_path", + 'name' => $file_name, + 'realpath' => $full_path, + 'size' => $file_size, + 'mime_type' => $mime_type, + 'image_with' => $image_with, + 'image_height' => $image_height, + 'ext' => $ext, + ]; + } + +} diff --git a/vendor/webman/admin/src/plugin/admin/app/controller/UserController.php b/vendor/webman/admin/src/plugin/admin/app/controller/UserController.php new file mode 100644 index 0000000..91de4d9 --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/app/controller/UserController.php @@ -0,0 +1,69 @@ +model = new User; + } + + /** + * 浏览 + * @return Response + */ + public function index(): Response + { + return view('user/index'); + } + + /** + * 插入 + * @param Request $request + * @return Response + * @throws BusinessException + */ + public function insert(Request $request): Response + { + if ($request->method() === 'POST') { + return parent::insert($request); + } + return view('user/insert'); + } + + /** + * 更新 + * @param Request $request + * @return Response + * @throws BusinessException + */ + public function update(Request $request): Response + { + if ($request->method() === 'POST') { + return parent::update($request); + } + return view('user/update'); + } + +} diff --git a/vendor/webman/admin/src/plugin/admin/app/exception/Handler.php b/vendor/webman/admin/src/plugin/admin/app/exception/Handler.php new file mode 100644 index 0000000..63da273 --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/app/exception/Handler.php @@ -0,0 +1,40 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace plugin\admin\app\exception; + +use Throwable; +use Webman\Http\Request; +use Webman\Http\Response; + +/** + * Class Handler + * @package support\exception + */ +class Handler extends \support\exception\Handler +{ + public function render(Request $request, Throwable $exception): Response + { + $code = $exception->getCode(); + $debug = $this->_debug ?? $this->debug; + if ($request->expectsJson()) { + $json = ['code' => $code ?: 500, 'msg' => $debug ? $exception->getMessage() : 'Server internal error', 'type' => 'failed']; + $debug && $json['traces'] = (string)$exception; + return new Response(200, ['Content-Type' => 'application/json'], + \json_encode($json, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); + } + $error = $debug ? \nl2br((string)$exception) : 'Server internal error'; + return new Response(500, [], $error); + } +} diff --git a/vendor/webman/admin/src/plugin/admin/app/functions.php b/vendor/webman/admin/src/plugin/admin/app/functions.php new file mode 100644 index 0000000..1fed316 --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/app/functions.php @@ -0,0 +1,139 @@ +session(); + $admin = Admin::find($admin_id); + if (!$admin) { + $session->forget('admin'); + return null; + } + $admin = $admin->toArray(); + unset($admin['password']); + // 账户被禁用 + if ($admin['status'] != 0) { + $session->forget('admin'); + return; + } + $admin['roles'] = AdminRole::where('admin_id', $admin_id)->pluck('role_id')->toArray(); + $admin['session_last_update_time'] = $time_now; + $session->set('admin', $admin); +} + + +/** + * 刷新当前用户session + * @param bool $force + * @return void + */ +function refresh_user_session(bool $force = false) +{ + if (!$user_id = user_id()) { + return null; + } + $time_now = time(); + // session在2秒内不刷新 + $session_ttl = 2; + $session_last_update_time = session('user.session_last_update_time', 0); + if (!$force && $time_now - $session_last_update_time < $session_ttl) { + return null; + } + $session = request()->session(); + $user = User::find($user_id); + if (!$user) { + $session->forget('user'); + return null; + } + $user = $user->toArray(); + unset($user['password']); + $user['session_last_update_time'] = $time_now; + $session->set('user', $user); +} diff --git a/vendor/webman/admin/src/plugin/admin/app/middleware/AccessControl.php b/vendor/webman/admin/src/plugin/admin/app/middleware/AccessControl.php new file mode 100644 index 0000000..081d0ee --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/app/middleware/AccessControl.php @@ -0,0 +1,54 @@ +controller; + $action = $request->action; + + $code = 0; + $msg = ''; + if (!Auth::canAccess($controller, $action, $code, $msg)) { + if ($request->expectsJson()) { + $response = json(['code' => $code, 'msg' => $msg, 'data' => []]); + } else { + if ($code === 401) { + $response = response(<< + if (self !== top) { + parent.location.reload(); + } + +EOF + ); + } else { + $request->app = ''; + $request->plugin = 'admin'; + $response = view('common/error/403')->withStatus(403); + } + } + + } else { + $response = $request->method() == 'OPTIONS' ? response('') : $handler($request); + } + + return $response; + + } + +} diff --git a/vendor/webman/admin/src/plugin/admin/app/model/Admin.php b/vendor/webman/admin/src/plugin/admin/app/model/Admin.php new file mode 100644 index 0000000..f55e932 --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/app/model/Admin.php @@ -0,0 +1,40 @@ +format('Y-m-d H:i:s'); + } +} diff --git a/vendor/webman/admin/src/plugin/admin/app/model/Dict.php b/vendor/webman/admin/src/plugin/admin/app/model/Dict.php new file mode 100644 index 0000000..8178868 --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/app/model/Dict.php @@ -0,0 +1,96 @@ +value('value'); + return $value ? json_decode($value, true) : null; + } + + /** + * 保存字典 + * @param $name + * @param $values + * @return void + * @throws BusinessException + */ + public static function save($name, $values) + { + if (!preg_match('/[a-zA-Z]/', $name)) { + throw new BusinessException('字典名只能包含字母'); + } + $option_name = static::dictNameToOptionName($name); + if (!$option = Option::where('name', $option_name)->first()) { + $option = new Option; + } + $format_values = static::filterValue($values); + $option->name = $option_name; + $option->value = json_encode($format_values, JSON_UNESCAPED_UNICODE); + $option->save(); + } + + /** + * 删除字典 + * @param array $names + * @return void + */ + public static function delete(array $names) + { + foreach ($names as $index => $name) { + $names[$index] = static::dictNameToOptionName($name); + } + Option::whereIn('name', $names)->delete(); + } + + /** + * 字典名到option名转换 + * @param string $name + * @return string + */ + public static function dictNameToOptionName(string $name): string + { + return "dict_$name"; + } + + /** + * option名到字典名转换 + * @param string $name + * @return string + */ + public static function optionNameToDictName(string $name): string + { + return substr($name, 5); + } + + /** + * 过滤值 + * @param array $values + * @return array + * @throws BusinessException + */ + public static function filterValue(array $values): array + { + $format_values = []; + foreach ($values as $item) { + if (!isset($item['value']) || !isset($item['name'])) { + throw new BusinessException('字典格式错误', 1); + } + $format_values[] = ['value' => $item['value'], 'name' => $item['name']]; + } + return $format_values; + } +} diff --git a/vendor/webman/admin/src/plugin/admin/app/model/Option.php b/vendor/webman/admin/src/plugin/admin/app/model/Option.php new file mode 100644 index 0000000..97018b0 --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/app/model/Option.php @@ -0,0 +1,29 @@ +rules ? explode(',', $this->rules) : []; + } + +} diff --git a/vendor/webman/admin/src/plugin/admin/app/model/Rule.php b/vendor/webman/admin/src/plugin/admin/app/model/Rule.php new file mode 100644 index 0000000..ebc7dd1 --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/app/model/Rule.php @@ -0,0 +1,38 @@ + + + + + + + + + + + + +
    +
    + +
    +
      +
    • 基本信息
    • +
    • 安全设置
    • +
    +
    + + +
    + +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    +
    + + +
    +
    +
    + +
    + +
    + +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    +
    + + +
    +
    +
    + +
    + +
    +
    + +
    +
    + + + + + + + + + diff --git a/vendor/webman/admin/src/plugin/admin/app/view/account/login.html b/vendor/webman/admin/src/plugin/admin/app/view/account/login.html new file mode 100644 index 0000000..414dba6 --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/app/view/account/login.html @@ -0,0 +1,81 @@ + + + + + + 登录 + + + + + + +
    +
    + +
    webman admin
    +
    +
    + +
    +
    + +
    +
    + + +
    +
    + +
    +
    + + + + + + + \ No newline at end of file diff --git a/vendor/webman/admin/src/plugin/admin/app/view/admin/index.html b/vendor/webman/admin/src/plugin/admin/app/view/admin/index.html new file mode 100644 index 0000000..ed17bd6 --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/app/view/admin/index.html @@ -0,0 +1,404 @@ + + + + + + 浏览页面 + + + + + + +
    +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    +
    + + - + +
    +
    +
    + +
    + + + +
    +
    + 展开 + 收起 +
    +
    +
    +
    + + +
    +
    +
    +
    +
    + + + + + + + + + + + + + + diff --git a/vendor/webman/admin/src/plugin/admin/app/view/admin/insert.html b/vendor/webman/admin/src/plugin/admin/app/view/admin/insert.html new file mode 100644 index 0000000..99e160a --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/app/view/admin/insert.html @@ -0,0 +1,181 @@ + + + + + 新增页面 + + + + + +
    + +
    +
    + +
    + +
    +
    +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + + + + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    +
    + +
    +
    + + +
    +
    + +
    + + + + + + + + diff --git a/vendor/webman/admin/src/plugin/admin/app/view/admin/update.html b/vendor/webman/admin/src/plugin/admin/app/view/admin/update.html new file mode 100644 index 0000000..b1888df --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/app/view/admin/update.html @@ -0,0 +1,215 @@ + + + + + 更新页面 + + + + + +
    + +
    +
    + +
    + +
    +
    +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + + + + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    +
    + +
    +
    + + +
    +
    + +
    + + + + + + + + + diff --git a/vendor/webman/admin/src/plugin/admin/app/view/common/error/403.html b/vendor/webman/admin/src/plugin/admin/app/view/common/error/403.html new file mode 100644 index 0000000..7b6aa5b --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/app/view/common/error/403.html @@ -0,0 +1,21 @@ + + + + + + + + + +
    + +
    +

    403

    +

    抱歉,你无权访问该页面

    +
    +
    + + + + + \ No newline at end of file diff --git a/vendor/webman/admin/src/plugin/admin/app/view/config/index.html b/vendor/webman/admin/src/plugin/admin/app/view/config/index.html new file mode 100644 index 0000000..f01c519 --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/app/view/config/index.html @@ -0,0 +1,314 @@ + + + + + + + + + + + + +
    +
    + +
    +
      +
    • 基本信息
    • +
    • 菜单配置
    • +
    • 页面配置
    • +
    +
    + + +
    + +
    +
    + +
    + +
    +
    +
    + +
    + + + + +
    +
    + +
    +
    + +
    +
    +
    + +
    + + +
    + +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    + +
    +
    + +
    +
    +
    + +
    + + +
    + +
    + +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    + +
    +
    + +
    +
    +
    + +
    + +
    +
    + +
    +
    + + + + + + + + + diff --git a/vendor/webman/admin/src/plugin/admin/app/view/dev/form-build.html b/vendor/webman/admin/src/plugin/admin/app/view/dev/form-build.html new file mode 100644 index 0000000..75e6e94 --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/app/view/dev/form-build.html @@ -0,0 +1,113 @@ + + + + + + + 表单生成器 + + + + +
    +
    +
    + + + + +
    +
    +
    +
    + view +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    html
    +
    + +
    +
    +
    +
    code
    +
    + +
    +
    +
    +
    +
    + + + + + + diff --git a/vendor/webman/admin/src/plugin/admin/app/view/dict/index.html b/vendor/webman/admin/src/plugin/admin/app/view/dict/index.html new file mode 100644 index 0000000..e738db7 --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/app/view/dict/index.html @@ -0,0 +1,260 @@ + + + + + + 浏览页面 + + + + + + +
    +
    +
    + +
    + +
    + +
    +
    + +
    + + + +
    +
    + 展开 + 收起 +
    +
    +
    +
    + + +
    +
    +
    +
    +
    + + + + + + + + + + + + + + diff --git a/vendor/webman/admin/src/plugin/admin/app/view/dict/insert.html b/vendor/webman/admin/src/plugin/admin/app/view/dict/insert.html new file mode 100644 index 0000000..a7365d3 --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/app/view/dict/insert.html @@ -0,0 +1,224 @@ + + + + + 新增字典 + + + + + + + +
    + +
    +
    + +
    +
    + +
    + +
    +
    +
    + +
    + + +
    + + + + + + + +
    + + +
    +
    + +
    +
    + + +
    +
    +
    + + + + + + + + diff --git a/vendor/webman/admin/src/plugin/admin/app/view/dict/update.html b/vendor/webman/admin/src/plugin/admin/app/view/dict/update.html new file mode 100644 index 0000000..43ccbe4 --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/app/view/dict/update.html @@ -0,0 +1,240 @@ + + + + + 更新字典 + + + + + + + +
    + +
    +
    + +
    +
    + +
    + +
    +
    +
    + +
    + + +
    + + + + + + + +
    + + +
    +
    + +
    +
    + + +
    +
    +
    + + + + + + + + diff --git a/vendor/webman/admin/src/plugin/admin/app/view/index/dashboard.html b/vendor/webman/admin/src/plugin/admin/app/view/index/dashboard.html new file mode 100644 index 0000000..17e2c27 --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/app/view/index/dashboard.html @@ -0,0 +1,358 @@ + + + + + 控制后台 + + + + + + + +
    +
    +
    +
    +
    今日注册
    +
    +
    +
    + 0 +
    +
    + + + + + + + + + +
    +
    +
    +
    +
    +
    +
    +
    7日内注册
    +
    +
    +
    + 0 +
    +
    + + + + + + + + + + + + + +
    +
    +
    +
    +
    +
    +
    +
    30日内注册
    +
    +
    +
    + 0 +
    +
    + + + + + + + + + + +
    +
    +
    +
    +
    +
    +
    +
    总注册
    +
    +
    +
    + 0 +
    +
    + + + + + + + + + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    系统信息
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Workerman版本
    Webman版本
    WebmanAdmin版本
    PHP版本
    MYSQL版本
    操作系统
    +
    +
    +
    +
    +
    + + + + + + diff --git a/vendor/webman/admin/src/plugin/admin/app/view/index/index.html b/vendor/webman/admin/src/plugin/admin/app/view/index/index.html new file mode 100644 index 0000000..10a188e --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/app/view/index/index.html @@ -0,0 +1,144 @@ + + + + + + 主页 + + + + + + + + + + + + +
    + +
    + + + +
      +
    • +
    • +
    + +
    + + +
    + +
    + + + +
    +
    +
    +
    + +
    + +
    +
    + + + +
    + +
    + +
    +
    +
    + +
    + +
    + + + + + + + \ No newline at end of file diff --git a/vendor/webman/admin/src/plugin/admin/app/view/index/install.html b/vendor/webman/admin/src/plugin/admin/app/view/index/install.html new file mode 100644 index 0000000..d372a50 --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/app/view/index/install.html @@ -0,0 +1,214 @@ + + + + + Webman Admin 安装 + + + + +
    +
    +
    +
    +
    +

    Webman Admin 安装

    + + +
    +
    +
    +
    + +
    + + + + + + + + + diff --git a/vendor/webman/admin/src/plugin/admin/app/view/role/index.html b/vendor/webman/admin/src/plugin/admin/app/view/role/index.html new file mode 100644 index 0000000..e8b4a7d --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/app/view/role/index.html @@ -0,0 +1,298 @@ + + + + + + 浏览页面 + + + + + + + + + +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + diff --git a/vendor/webman/admin/src/plugin/admin/app/view/role/insert.html b/vendor/webman/admin/src/plugin/admin/app/view/role/insert.html new file mode 100644 index 0000000..8706574 --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/app/view/role/insert.html @@ -0,0 +1,149 @@ + + + + + 新增页面 + + + + + +
    + +
    +
    + +
    + +
    +
    +
    +
    + +
    + +
    + +
    +
    + +
    + +
    +
    +
    +
    + +
    +
    + +
    +
    + + +
    +
    + +
    + + + + + + + + + diff --git a/vendor/webman/admin/src/plugin/admin/app/view/role/update.html b/vendor/webman/admin/src/plugin/admin/app/view/role/update.html new file mode 100644 index 0000000..59d2ae7 --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/app/view/role/update.html @@ -0,0 +1,188 @@ + + + + + 更新页面 + + + + + +
    + +
    +
    + +
    + +
    +
    +
    +
    + +
    + +
    + +
    +
    + +
    + +
    +
    +
    +
    + +
    +
    + +
    +
    + + +
    +
    + +
    + + + + + + + + + + diff --git a/vendor/webman/admin/src/plugin/admin/app/view/rule/index.html b/vendor/webman/admin/src/plugin/admin/app/view/rule/index.html new file mode 100644 index 0000000..1dd60fa --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/app/view/rule/index.html @@ -0,0 +1,280 @@ + + + + + + + + + + + + +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + diff --git a/vendor/webman/admin/src/plugin/admin/app/view/rule/insert.html b/vendor/webman/admin/src/plugin/admin/app/view/rule/insert.html new file mode 100644 index 0000000..6f1bdee --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/app/view/rule/insert.html @@ -0,0 +1,174 @@ + + + + + + 新增页面 + + + + + + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    +
    +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    +
    +
    +
    + +
    + +
    + +
    +
    + +
    +
    + +
    +
    + + +
    +
    +
    + + + + + + + + diff --git a/vendor/webman/admin/src/plugin/admin/app/view/rule/update.html b/vendor/webman/admin/src/plugin/admin/app/view/rule/update.html new file mode 100644 index 0000000..0b911ac --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/app/view/rule/update.html @@ -0,0 +1,207 @@ + + + + + + 更新页面 + + + + + + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    +
    +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    +
    +
    +
    + +
    + +
    + +
    +
    + +
    +
    + +
    +
    + + +
    +
    +
    + + + + + + + + + diff --git a/vendor/webman/admin/src/plugin/admin/app/view/table/create.html b/vendor/webman/admin/src/plugin/admin/app/view/table/create.html new file mode 100644 index 0000000..847e2f2 --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/app/view/table/create.html @@ -0,0 +1,744 @@ + + + + + 新增页面 + + + + + + + +
    + +
    +
    + +
    +
    + +
    + +
    + +
    + +
    +
    +
    + +
    +
      +
    • 字段属性
    • +
    • 表单属性
    • +
    • 索引
    • +
    +
    + +
    + + +
    + + + + + + + + + + + + + + + + + + + +
    + + +
    + +
    + + + + + + + + + + + + + + + + + + + +
    + + +
    +
    + +
    + + + + + + + +
    +
    + +
    +
    + + +
    +
    + +
    +
    + + +
    +
    +
    + + + + + + + + diff --git a/vendor/webman/admin/src/plugin/admin/app/view/table/crud.html b/vendor/webman/admin/src/plugin/admin/app/view/table/crud.html new file mode 100644 index 0000000..1156813 --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/app/view/table/crud.html @@ -0,0 +1,139 @@ + + + + + 一键菜单 + + + + + + +
    + +
    +
    + +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    +
    + +
    +
    + + +
    +
    +
    + + + + + + + + diff --git a/vendor/webman/admin/src/plugin/admin/app/view/table/index.html b/vendor/webman/admin/src/plugin/admin/app/view/table/index.html new file mode 100644 index 0000000..8ccd418 --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/app/view/table/index.html @@ -0,0 +1,251 @@ + + + + + admin管理 + + + + + +
    +
    +
    +
    +
    + + + + + + + + + + + + + diff --git a/vendor/webman/admin/src/plugin/admin/app/view/table/insert.html b/vendor/webman/admin/src/plugin/admin/app/view/table/insert.html new file mode 100644 index 0000000..16eb27b --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/app/view/table/insert.html @@ -0,0 +1,78 @@ + + + + + 新增页面 + + + + + +
    + +
    +
    + + html(5)?> +
    +
    + +
    +
    + + +
    +
    +
    + + + + + + + + diff --git a/vendor/webman/admin/src/plugin/admin/app/view/table/modify.html b/vendor/webman/admin/src/plugin/admin/app/view/table/modify.html new file mode 100644 index 0000000..df2409e --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/app/view/table/modify.html @@ -0,0 +1,713 @@ + + + + + 新增页面 + + + + + + + +
    + +
    +
    + +
    +
    + +
    + + +
    + +
    + +
    +
    +
    + +
    +
      +
    • 字段属性
    • +
    • 表单属性
    • +
    • 索引
    • +
    +
    + +
    + + +
    + + + + + + + + + + + + + + + + + + + +
    + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + +
    + + +
    +
    + +
    + + + + + + + +
    +
    + +
    +
    + + +
    +
    + +
    +
    + + +
    +
    +
    + + + + + + + + diff --git a/vendor/webman/admin/src/plugin/admin/app/view/table/update.html b/vendor/webman/admin/src/plugin/admin/app/view/table/update.html new file mode 100644 index 0000000..bc40505 --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/app/view/table/update.html @@ -0,0 +1,113 @@ + + + + + 新增页面 + + + + + +
    + +
    +
    + + + + + html(5)?> + +
    +
    + +
    +
    + + +
    +
    +
    + + + + + + + + + diff --git a/vendor/webman/admin/src/plugin/admin/app/view/table/view.html b/vendor/webman/admin/src/plugin/admin/app/view/table/view.html new file mode 100644 index 0000000..0216a86 --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/app/view/table/view.html @@ -0,0 +1,379 @@ + + + + + 表详情 + + + + +
    +
    +
    + + html(5)?> + +
    + + + +
    + +
    + 展开 + 收起 +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + diff --git a/vendor/webman/admin/src/plugin/admin/app/view/upload/attachment.html b/vendor/webman/admin/src/plugin/admin/app/view/upload/attachment.html new file mode 100644 index 0000000..ba7ac5b --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/app/view/upload/attachment.html @@ -0,0 +1,442 @@ + + + + + + 浏览页面 + + + + + + +
    +
    +
    + +
    + +
    +
    +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    +
    + + - + +
    +
    +
    + +
    + + + +
    +
    + 展开 + 收起 +
    +
    +
    +
    + + +
    +
    +
    +
    +
    + + + + + + + + + + + + + + diff --git a/vendor/webman/admin/src/plugin/admin/app/view/upload/index.html b/vendor/webman/admin/src/plugin/admin/app/view/upload/index.html new file mode 100644 index 0000000..38dcdf3 --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/app/view/upload/index.html @@ -0,0 +1,424 @@ + + + + + + 浏览页面 + + + + + + +
    +
    +
    + +
    + +
    +
    +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    +
    + + - + +
    +
    +
    + +
    + + + +
    +
    + 展开 + 收起 +
    +
    +
    +
    + + +
    +
    +
    +
    +
    + + + + + + + + + + + + + + diff --git a/vendor/webman/admin/src/plugin/admin/app/view/upload/insert.html b/vendor/webman/admin/src/plugin/admin/app/view/upload/insert.html new file mode 100644 index 0000000..50f29b6 --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/app/view/upload/insert.html @@ -0,0 +1,91 @@ + + + + + 新增页面 + + + + + +
    + +
    +
    + +
    + +
    +
    +
    +
    + +
    + +
    + + + +
    +
    + +
    +
    + +
    + + + + + + + + diff --git a/vendor/webman/admin/src/plugin/admin/app/view/upload/update.html b/vendor/webman/admin/src/plugin/admin/app/view/upload/update.html new file mode 100644 index 0000000..c67d99f --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/app/view/upload/update.html @@ -0,0 +1,139 @@ + + + + + 更新页面 + + + + + +
    + +
    +
    + +
    + +
    +
    +
    +
    + +
    +
    + +
    +
    + + +
    +
    + +
    + + + + + + + + + diff --git a/vendor/webman/admin/src/plugin/admin/app/view/user/index.html b/vendor/webman/admin/src/plugin/admin/app/view/user/index.html new file mode 100644 index 0000000..ab42cd7 --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/app/view/user/index.html @@ -0,0 +1,546 @@ + + + + + + 浏览页面 + + + + + + +
    +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    +
    +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    +
    + + - + +
    +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    +
    + + - + +
    +
    +
    + +
    + +
    + +
    +
    + +
    + +
    +
    + + - + +
    +
    +
    + +
    + +
    + +
    +
    + +
    + + + +
    +
    + 展开 + 收起 +
    +
    +
    +
    + + +
    +
    +
    +
    +
    + + + + + + + + + + + + + + diff --git a/vendor/webman/admin/src/plugin/admin/app/view/user/insert.html b/vendor/webman/admin/src/plugin/admin/app/view/user/insert.html new file mode 100644 index 0000000..a9fe6f3 --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/app/view/user/insert.html @@ -0,0 +1,272 @@ + + + + + 新增页面 + + + + + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    +
    +
    +
    + +
    + +
    + + + + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + + +
    +
    + +
    +
    + +
    +
    + + +
    +
    + +
    + + + + + + + + diff --git a/vendor/webman/admin/src/plugin/admin/app/view/user/update.html b/vendor/webman/admin/src/plugin/admin/app/view/user/update.html new file mode 100644 index 0000000..8c3e74d --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/app/view/user/update.html @@ -0,0 +1,306 @@ + + + + + 更新页面 + + + + + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    +
    +
    +
    + +
    + +
    + + + + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + + +
    +
    + +
    +
    + +
    +
    + + +
    +
    + +
    + + + + + + + + + diff --git a/vendor/webman/admin/src/plugin/admin/config/app.php b/vendor/webman/admin/src/plugin/admin/config/app.php new file mode 100644 index 0000000..8c7056c --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/config/app.php @@ -0,0 +1,21 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return [ + 'debug' => true, + 'controller_suffix' => 'Controller', + 'controller_reuse' => false, + 'plugin_market_host' => 'https://www.workerman.net', + 'version' => '0.6.13' +]; diff --git a/vendor/webman/admin/src/plugin/admin/config/autoload.php b/vendor/webman/admin/src/plugin/admin/config/autoload.php new file mode 100644 index 0000000..23b4980 --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/config/autoload.php @@ -0,0 +1,19 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return [ + 'files' => [ + base_path() . '/plugin/admin/app/functions.php', + ] +]; diff --git a/vendor/webman/admin/src/plugin/admin/config/bootstrap.php b/vendor/webman/admin/src/plugin/admin/config/bootstrap.php new file mode 100644 index 0000000..7dc463a --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/config/bootstrap.php @@ -0,0 +1,15 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return []; diff --git a/vendor/webman/admin/src/plugin/admin/config/container.php b/vendor/webman/admin/src/plugin/admin/config/container.php new file mode 100644 index 0000000..7857701 --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/config/container.php @@ -0,0 +1,15 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return new Webman\Container; diff --git a/vendor/webman/admin/src/plugin/admin/config/dependence.php b/vendor/webman/admin/src/plugin/admin/config/dependence.php new file mode 100644 index 0000000..8e964ed --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/config/dependence.php @@ -0,0 +1,15 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return []; \ No newline at end of file diff --git a/vendor/webman/admin/src/plugin/admin/config/event.php b/vendor/webman/admin/src/plugin/admin/config/event.php new file mode 100644 index 0000000..639e435 --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/config/event.php @@ -0,0 +1,5 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +use plugin\admin\app\exception\Handler; + +return [ + '' => Handler::class, +]; \ No newline at end of file diff --git a/vendor/webman/admin/src/plugin/admin/config/log.php b/vendor/webman/admin/src/plugin/admin/config/log.php new file mode 100644 index 0000000..7f05de5 --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/config/log.php @@ -0,0 +1,32 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return [ + 'default' => [ + 'handlers' => [ + [ + 'class' => Monolog\Handler\RotatingFileHandler::class, + 'constructor' => [ + runtime_path() . '/logs/webman.log', + 7, //$maxFiles + Monolog\Logger::DEBUG, + ], + 'formatter' => [ + 'class' => Monolog\Formatter\LineFormatter::class, + 'constructor' => [null, 'Y-m-d H:i:s', true], + ], + ] + ], + ], +]; diff --git a/vendor/webman/admin/src/plugin/admin/config/menu.php b/vendor/webman/admin/src/plugin/admin/config/menu.php new file mode 100644 index 0000000..9bdc7e1 --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/config/menu.php @@ -0,0 +1,433 @@ + '数据库', + 'key' => 'database', + 'icon' => 'layui-icon-template-1', + 'weight' => 1000, + 'type' => 0, + 'children' => [ + [ + 'title' => '所有表', + 'key' => 'plugin\\admin\\app\\controller\\TableController', + 'href' => '/app/admin/table/index', + 'type' => 1, + 'weight' => 800, + ] + ] + ], + [ + 'title' => '权限管理', + 'key' => 'auth', + 'icon' => 'layui-icon-vercode', + 'weight' => 900, + 'type' => 0, + 'children' => [ + [ + 'title' => '账户管理', + 'key' => 'plugin\\admin\\app\\controller\\AdminController', + 'href' => '/app/admin/admin/index', + 'type' => 1, + 'weight' => 1000, + ], + [ + 'title' => '角色管理', + 'key' => 'plugin\\admin\\app\\controller\\RoleController', + 'href' => '/app/admin/role/index', + 'type' => 1, + 'weight' => 900, + ], + [ + 'title' => '菜单管理', + 'key' => 'plugin\\admin\\app\\controller\\RuleController', + 'href' => '/app/admin/rule/index', + 'type' => 1, + 'weight' => 800, + ], + ] + ], + [ + 'title' => '会员管理', + 'key' => 'user', + 'icon' => 'layui-icon-username', + 'weight' => 800, + 'type' => 0, + 'children' => [ + [ + 'title' => '用户', + 'key' => 'plugin\\admin\\app\\controller\\UserController', + 'href' => '/app/admin/user/index', + 'type' => 1, + 'weight' => 800, + ] + ] + ], + [ + 'title' => '通用设置', + 'key' => 'common', + 'icon' => 'layui-icon-set', + 'weight' => 700, + 'type' => 0, + 'children' => [ + [ + 'title' => '个人资料', + 'key' => 'plugin\\admin\\app\\controller\\AccountController', + 'href' => '/app/admin/account/index', + 'type' => 1, + 'weight' => 800, + ], + [ + 'title' => '附件管理', + 'key' => 'plugin\\admin\\app\\controller\\UploadController', + 'href' => '/app/admin/upload/index', + 'type' => 1, + 'weight' => 700, + ], + [ + 'title' => '字典设置', + 'key' => 'plugin\\admin\\app\\controller\\DictController', + 'href' => '/app/admin/dict/index', + 'type' => 1, + 'weight' => 600, + ], + [ + 'title' => '系统设置', + 'key' => 'plugin\\admin\\app\\controller\\ConfigController', + 'href' => '/app/admin/config/index', + 'type' => 1, + 'weight' => 500, + ], + ] + ], + [ + 'title' => '插件管理', + 'key' => 'plugin', + 'icon' => 'layui-icon-app', + 'weight' => 600, + 'type' => 0, + 'children' => [ + [ + 'title' => '应用插件', + 'key' => 'plugin\\admin\\app\\controller\\PluginController', + 'href' => '/app/admin/plugin/index', + 'weight' => 800, + 'type' => 1, + ] + ] + ], + [ + 'title' => '开发辅助', + 'key' => 'dev', + 'icon' => 'layui-icon-fonts-code', + 'weight' => 500, + 'type' => 0, + 'children' => [ + [ + 'title' => '表单构建', + 'key' => 'plugin\\admin\\app\\controller\\DevController', + 'href' => '/app/admin/dev/form-build', + 'weight' => 800, + 'type' => 1, + ], + ] + ], + [ + 'title' => '示例页面', + 'key' => 'demos', + 'icon' => 'layui-icon-templeate-1', + 'weight' => 400, + 'type' => 0, + 'children' => [ + [ + 'key' => 'demo1', + 'title' => '工作空间', + 'type' => 0, + 'icon' => 'layui-icon-console', + 'href' => '', + 'children' => [ + [ + 'key' => 'demo10', + 'title' => '控制后台', + 'icon' => 'layui-icon-console', + 'type' => 1, + 'href' => '/app/admin/demos/console/console1.html' + ], [ + 'key' => 'demo13', + 'title' => '数据分析', + 'icon' => 'layui-icon-console', + 'type' => 1, + 'href' => '/app/admin/demos/console/console2.html' + ], [ + 'key' => 'demo14', + 'title' => '百度一下', + 'icon' => 'layui-icon-console', + 'type' => 1, + 'href' => 'http://www.baidu.com' + ], [ + 'key' => 'demo15', + 'title' => '主题预览', + 'icon' => 'layui-icon-console', + 'type' => 1, + 'href' => '/app/admin/demos/system/theme.html' + ] + ] + ], + [ + 'key' => 'demo20', + 'title' => '常用组件', + 'icon' => 'layui-icon-component', + 'type' => 0, + 'href' => '', + 'children' => [ + [ + 'key' => 'demo2011', + 'title' => '功能按钮', + 'icon' => 'layui-icon-face-smile', + 'type' => 1, + 'href' => '/app/admin/demos/document/button.html' + ], [ + 'key' => 'demo2014', + 'title' => '表单集合', + 'icon' => 'layui-icon-face-cry', + 'type' => 1, + 'href' => '/app/admin/demos/document/form.html' + ], [ + 'key' => 'demo2010', + 'title' => '字体图标', + 'icon' => 'layui-icon-face-cry', + 'type' => 1, + 'href' => '/app/admin/demos/document/icon.html' + ], [ + 'key' => 'demo2012', + 'title' => '多选下拉', + 'icon' => 'layui-icon-face-cry', + 'type' => 1, + 'href' => '/app/admin/demos/document/select.html' + ], [ + 'key' => 'demo2013', + 'title' => '动态标签', + 'icon' => 'layui-icon-face-cry', + 'type' => 1, + 'href' => '/app/admin/demos/document/tag.html' + ], [ + 'key' => 'demo2031', + 'title' => '数据表格', + 'icon' => 'layui-icon-face-cry', + 'type' => 1, + 'href' => '/app/admin/demos/document/table.html' + ], [ + 'key' => 'demo2032', + 'title' => '分布表单', + 'icon' => 'layui-icon-face-cry', + 'type' => 1, + 'href' => '/app/admin/demos/document/step.html' + ], [ + 'key' => 'demo2033', + 'title' => '树形表格', + 'icon' => 'layui-icon-face-cry', + 'type' => 1, + 'href' => '/app/admin/demos/document/treetable.html' + ], [ + 'key' => 'demo2034', + 'title' => '树状结构', + 'icon' => 'layui-icon-face-cry', + 'type' => 1, + 'href' => '/app/admin/demos/document/dtree.html' + ], [ + 'key' => 'demo2035', + 'title' => '文本编辑', + 'icon' => 'layui-icon-face-cry', + 'type' => 1, + 'href' => '/app/admin/demos/document/tinymce.html' + ], [ + 'key' => 'demo2036', + 'title' => '卡片组件', + 'icon' => 'layui-icon-face-cry', + 'type' => 1, + 'href' => '/app/admin/demos/document/card.html' + ], [ + 'key' => 'demo2021', + 'title' => '抽屉组件', + 'icon' => 'layui-icon-face-cry', + 'type' => 1, + 'href' => '/app/admin/demos/document/drawer.html' + ], [ + 'key' => 'demo2022', + 'title' => '消息通知', + 'icon' => 'layui-icon-face-cry', + 'type' => 1, + 'href' => '/app/admin/demos/document/notice.html' + ], [ + 'key' => 'demo2024', + 'title' => '加载组件', + 'icon' => 'layui-icon-face-cry', + 'type' => 1, + 'href' => '/app/admin/demos/document/loading.html' + ], [ + 'key' => 'demo2023', + 'title' => '弹层组件', + 'icon' => 'layui-icon-face-cry', + 'type' => 1, + 'href' => '/app/admin/demos/document/popup.html' + ], [ + 'key' => 'demo60131', + 'title' => '多选项卡', + 'icon' => 'layui-icon-face-cry', + 'type' => 1, + 'href' => '/app/admin/demos/document/tab.html' + ], [ + 'key' => 'demo60132', + 'title' => '数据菜单', + 'icon' => 'layui-icon-face-cry', + 'type' => 1, + 'href' => '/app/admin/demos/document/menu.html' + ], [ + 'key' => 'demo2041', + 'title' => '哈希加密', + 'icon' => 'layui-icon-face-cry', + 'type' => 1, + 'href' => '/app/admin/demos/document/encrypt.html' + ], [ + 'key' => 'demo2042', + 'title' => '图标选择', + 'icon' => 'layui-icon-face-cry', + 'type' => 1, + 'href' => '/app/admin/demos/document/iconPicker.html' + ], [ + 'key' => 'demo2043', + 'title' => '省市级联', + 'icon' => 'layui-icon-face-cry', + 'type' => 1, + 'href' => '/app/admin/demos/document/area.html' + ], [ + 'key' => 'demo2044', + 'title' => '数字滚动', + 'icon' => 'layui-icon-face-cry', + 'type' => 1, + 'href' => '/app/admin/demos/document/count.html' + ], [ + 'key' => 'demo2045', + 'title' => '顶部返回', + 'icon' => 'layui-icon-face-cry', + 'type' => 1, + 'href' => '/app/admin/demos/document/topBar.html' + ] + ] + ], + [ + 'key' => 'demo666', + 'title' => '结果页面', + 'icon' => 'layui-icon-auz', + 'type' => 0, + 'href' => '', + 'children' => [ + [ + 'key' => 'demo667', + 'title' => '成功', + 'icon' => 'layui-icon-face-smile', + 'type' => 1, + 'href' => '/app/admin/demos/result/success.html' + ], [ + 'key' => 'demo668', + 'title' => '失败', + 'icon' => 'layui-icon-face-cry', + 'type' => 1, + 'href' => '/app/admin/demos/result/error.html' + ] + ] + ], + [ + 'key' => 'demo-error', + 'title' => '错误页面', + 'icon' => 'layui-icon-face-cry', + 'type' => 0, + 'href' => '', + 'children' => [ + [ + 'key' => 'demo403', + 'title' => '403', + 'icon' => 'layui-icon-face-smile', + 'type' => 1, + 'href' => '/app/admin/demos/error/403.html' + ], [ + 'key' => 'demo404', + 'title' => '404', + 'icon' => 'layui-icon-face-cry', + 'type' => 1, + 'href' => '/app/admin/demos/error/404.html' + ], [ + 'key' => 'demo500', + 'title' => '500', + 'icon' => 'layui-icon-face-cry', + 'type' => 1, + 'href' => '/app/admin/demos/error/500.html' + ] + + ] + ], + [ + 'key' => 'demo-system', + 'title' => '系统管理', + 'icon' => 'layui-icon-set-fill', + 'type' => 0, + 'href' => '', + 'children' => [ + [ + 'key' => 'demo601', + 'title' => '用户管理', + 'icon' => 'layui-icon-face-smile', + 'type' => 1, + 'href' => '/app/admin/demos/system/user.html' + ], [ + 'key' => 'demo602', + 'title' => '角色管理', + 'icon' => 'layui-icon-face-cry', + 'type' => 1, + 'href' => '/app/admin/demos/system/role.html' + ], [ + 'key' => 'demo603', + 'title' => '权限管理', + 'icon' => 'layui-icon-face-cry', + 'type' => 1, + 'href' => '/app/admin/demos/system/power.html' + ], [ + 'key' => 'demo604', + 'title' => '部门管理', + 'icon' => 'layui-icon-face-cry', + 'type' => 1, + 'href' => '/app/admin/demos/system/deptment.html' + ], [ + 'key' => 'demo605', + 'title' => '行为日志', + 'icon' => 'layui-icon-face-cry', + 'type' => 1, + 'href' => '/app/admin/demos/system/log.html' + ], [ + 'key' => 'demo606', + 'title' => '数据字典', + 'icon' => 'layui-icon-face-cry', + 'type' => 1, + 'href' => '/app/admin/demos/system/dict.html' + ] + ] + ], + [ + 'key' => 'demo-common', + 'title' => '常用页面', + 'icon' => 'layui-icon-template-1', + 'type' => 0, + 'href' => '', + 'children' => [ + [ + 'key' => 'demo702', + 'title' => '空白页面', + 'icon' => 'layui-icon-face-smile', + 'type' => 1, + 'href' => '/app/admin/demos/system/space.html' + ] + ] + ] + ] + ] +]; diff --git a/vendor/webman/admin/src/plugin/admin/config/middleware.php b/vendor/webman/admin/src/plugin/admin/config/middleware.php new file mode 100644 index 0000000..7be60ac --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/config/middleware.php @@ -0,0 +1,21 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +use plugin\admin\app\middleware\AccessControl; + +return [ + '' => [ + AccessControl::class, + ] +]; \ No newline at end of file diff --git a/vendor/webman/admin/src/plugin/admin/config/redis.php b/vendor/webman/admin/src/plugin/admin/config/redis.php new file mode 100644 index 0000000..2f9757a --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/config/redis.php @@ -0,0 +1,22 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return [ + 'default' => [ + 'host' => '127.0.0.1', + 'password' => null, + 'port' => 6379, + 'database' => 0, + ], +]; diff --git a/vendor/webman/admin/src/plugin/admin/config/route.php b/vendor/webman/admin/src/plugin/admin/config/route.php new file mode 100644 index 0000000..ca57fd9 --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/config/route.php @@ -0,0 +1,26 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +use plugin\admin\app\controller\AccountController; +use plugin\admin\app\controller\DictController; +use Webman\Route; +use support\Request; + +Route::any('/app/admin/account/captcha/{type}', [AccountController::class, 'captcha']); + +Route::any('/app/admin/dict/get/{name}', [DictController::class, 'get']); + +Route::fallback(function (Request $request) { + return response($request->uri() . ' not found' , 404); +}, 'admin'); diff --git a/vendor/webman/admin/src/plugin/admin/config/static.php b/vendor/webman/admin/src/plugin/admin/config/static.php new file mode 100644 index 0000000..2f76cf3 --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/config/static.php @@ -0,0 +1,23 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +/** + * Static file settings + */ +return [ + 'enable' => true, + 'middleware' => [ // Static file Middleware + //app\middleware\StaticFile::class, + ], +]; \ No newline at end of file diff --git a/vendor/webman/admin/src/plugin/admin/config/translation.php b/vendor/webman/admin/src/plugin/admin/config/translation.php new file mode 100644 index 0000000..800ec0a --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/config/translation.php @@ -0,0 +1,25 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +/** + * Multilingual configuration + */ +return [ + // Default language + 'locale' => 'zh_CN', + // Fallback language + 'fallback_locale' => ['zh_CN', 'en'], + // Folder where language files are stored + 'path' => base_path() . '/plugin/admin/resource/translations' +]; diff --git a/vendor/webman/admin/src/plugin/admin/config/view.php b/vendor/webman/admin/src/plugin/admin/config/view.php new file mode 100644 index 0000000..e3a7b85 --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/config/view.php @@ -0,0 +1,22 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +use support\view\Raw; +use support\view\Twig; +use support\view\Blade; +use support\view\ThinkPHP; + +return [ + 'handler' => Raw::class +]; diff --git a/vendor/webman/admin/src/plugin/admin/install.sql b/vendor/webman/admin/src/plugin/admin/install.sql new file mode 100644 index 0000000..739ee72 --- /dev/null +++ b/vendor/webman/admin/src/plugin/admin/install.sql @@ -0,0 +1,117 @@ + +CREATE TABLE IF NOT EXISTS `wa_admin_roles` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', + `role_id` int(11) NOT NULL COMMENT '角色id', + `admin_id` int(11) NOT NULL COMMENT '管理员id', + PRIMARY KEY (`id`), + UNIQUE KEY `role_admin_id` (`role_id`,`admin_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='管理员角色表'; + +CREATE TABLE IF NOT EXISTS `wa_admins` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID', + `username` varchar(32) NOT NULL COMMENT '用户名', + `nickname` varchar(40) NOT NULL COMMENT '昵称', + `password` varchar(255) NOT NULL COMMENT '密码', + `avatar` varchar(255) DEFAULT '/app/admin/avatar.png' COMMENT '头像', + `email` varchar(100) DEFAULT NULL COMMENT '邮箱', + `mobile` varchar(16) DEFAULT NULL COMMENT '手机', + `created_at` datetime DEFAULT NULL COMMENT '创建时间', + `updated_at` datetime DEFAULT NULL COMMENT '更新时间', + `login_at` datetime DEFAULT NULL COMMENT '登录时间', + `status` tinyint(4) DEFAULT NULL COMMENT '禁用', + PRIMARY KEY (`id`), + UNIQUE KEY `username` (`username`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='管理员表'; + +CREATE TABLE IF NOT EXISTS `wa_options` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(255) NOT NULL COMMENT '键', + `value` longtext NOT NULL COMMENT '值', + `created_at` datetime NOT NULL DEFAULT '2022-08-15 00:00:00' COMMENT '创建时间', + `updated_at` datetime NOT NULL DEFAULT '2022-08-15 00:00:00' COMMENT '更新时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='选项表'; + + +LOCK TABLES `wa_options` WRITE; +INSERT INTO `wa_options` VALUES (1,'system_config','{\"logo\":{\"title\":\"Webman Admin\",\"image\":\"\\/app\\/admin\\/admin\\/images\\/logo.png\"},\"menu\":{\"data\":\"\\/app\\/admin\\/rule\\/get\",\"method\":\"GET\",\"accordion\":true,\"collapse\":false,\"control\":false,\"controlWidth\":500,\"select\":\"0\",\"async\":true},\"tab\":{\"enable\":true,\"keepState\":true,\"preload\":false,\"session\":true,\"max\":\"30\",\"index\":{\"id\":\"0\",\"href\":\"\\/app\\/admin\\/index\\/dashboard\",\"title\":\"\\u4eea\\u8868\\u76d8\"}},\"theme\":{\"defaultColor\":\"2\",\"defaultMenu\":\"light-theme\",\"defaultHeader\":\"light-theme\",\"allowCustom\":true,\"banner\":false},\"colors\":[{\"id\":\"1\",\"color\":\"#36b368\",\"second\":\"#f0f9eb\"},{\"id\":\"2\",\"color\":\"#2d8cf0\",\"second\":\"#ecf5ff\"},{\"id\":\"3\",\"color\":\"#f6ad55\",\"second\":\"#fdf6ec\"},{\"id\":\"4\",\"color\":\"#f56c6c\",\"second\":\"#fef0f0\"},{\"id\":\"5\",\"color\":\"#3963bc\",\"second\":\"#ecf5ff\"}],\"other\":{\"keepLoad\":\"500\",\"autoHead\":false,\"footer\":false},\"header\":{\"message\":false}}','2022-12-05 14:49:01','2022-12-08 20:20:28'),(2,'table_form_schema_wa_users','{\"id\":{\"field\":\"id\",\"_field_id\":\"0\",\"comment\":\"主键\",\"control\":\"inputNumber\",\"control_args\":\"\",\"list_show\":true,\"enable_sort\":true,\"searchable\":true,\"search_type\":\"normal\",\"form_show\":false},\"username\":{\"field\":\"username\",\"_field_id\":\"1\",\"comment\":\"用户名\",\"control\":\"input\",\"control_args\":\"\",\"form_show\":true,\"list_show\":true,\"searchable\":true,\"search_type\":\"normal\",\"enable_sort\":false},\"nickname\":{\"field\":\"nickname\",\"_field_id\":\"2\",\"comment\":\"昵称\",\"control\":\"input\",\"control_args\":\"\",\"form_show\":true,\"list_show\":true,\"searchable\":true,\"search_type\":\"normal\",\"enable_sort\":false},\"password\":{\"field\":\"password\",\"_field_id\":\"3\",\"comment\":\"密码\",\"control\":\"input\",\"control_args\":\"\",\"form_show\":true,\"search_type\":\"normal\",\"list_show\":false,\"enable_sort\":false,\"searchable\":false},\"sex\":{\"field\":\"sex\",\"_field_id\":\"4\",\"comment\":\"性别\",\"control\":\"select\",\"control_args\":\"url:\\/app\\/admin\\/dict\\/get\\/sex\",\"form_show\":true,\"list_show\":true,\"searchable\":true,\"search_type\":\"normal\",\"enable_sort\":false},\"avatar\":{\"field\":\"avatar\",\"_field_id\":\"5\",\"comment\":\"头像\",\"control\":\"uploadImage\",\"control_args\":\"url:\\/app\\/admin\\/upload\\/avatar\",\"form_show\":true,\"list_show\":true,\"search_type\":\"normal\",\"enable_sort\":false,\"searchable\":false},\"email\":{\"field\":\"email\",\"_field_id\":\"6\",\"comment\":\"邮箱\",\"control\":\"input\",\"control_args\":\"\",\"form_show\":true,\"list_show\":true,\"searchable\":true,\"search_type\":\"normal\",\"enable_sort\":false},\"mobile\":{\"field\":\"mobile\",\"_field_id\":\"7\",\"comment\":\"手机\",\"control\":\"input\",\"control_args\":\"\",\"form_show\":true,\"list_show\":true,\"searchable\":true,\"search_type\":\"normal\",\"enable_sort\":false},\"level\":{\"field\":\"level\",\"_field_id\":\"8\",\"comment\":\"等级\",\"control\":\"inputNumber\",\"control_args\":\"\",\"form_show\":true,\"searchable\":true,\"search_type\":\"normal\",\"list_show\":false,\"enable_sort\":false},\"birthday\":{\"field\":\"birthday\",\"_field_id\":\"9\",\"comment\":\"生日\",\"control\":\"datePicker\",\"control_args\":\"\",\"form_show\":true,\"searchable\":true,\"search_type\":\"between\",\"list_show\":false,\"enable_sort\":false},\"money\":{\"field\":\"money\",\"_field_id\":\"10\",\"comment\":\"余额(元)\",\"control\":\"inputNumber\",\"control_args\":\"\",\"form_show\":true,\"searchable\":true,\"search_type\":\"normal\",\"list_show\":false,\"enable_sort\":false},\"score\":{\"field\":\"score\",\"_field_id\":\"11\",\"comment\":\"积分\",\"control\":\"inputNumber\",\"control_args\":\"\",\"form_show\":true,\"searchable\":true,\"search_type\":\"normal\",\"list_show\":false,\"enable_sort\":false},\"last_time\":{\"field\":\"last_time\",\"_field_id\":\"12\",\"comment\":\"登录时间\",\"control\":\"dateTimePicker\",\"control_args\":\"\",\"form_show\":true,\"searchable\":true,\"search_type\":\"between\",\"list_show\":false,\"enable_sort\":false},\"last_ip\":{\"field\":\"last_ip\",\"_field_id\":\"13\",\"comment\":\"登录ip\",\"control\":\"input\",\"control_args\":\"\",\"form_show\":true,\"searchable\":true,\"search_type\":\"normal\",\"list_show\":false,\"enable_sort\":false},\"join_time\":{\"field\":\"join_time\",\"_field_id\":\"14\",\"comment\":\"注册时间\",\"control\":\"dateTimePicker\",\"control_args\":\"\",\"form_show\":true,\"searchable\":true,\"search_type\":\"between\",\"list_show\":false,\"enable_sort\":false},\"join_ip\":{\"field\":\"join_ip\",\"_field_id\":\"15\",\"comment\":\"注册ip\",\"control\":\"input\",\"control_args\":\"\",\"form_show\":true,\"searchable\":true,\"search_type\":\"normal\",\"list_show\":false,\"enable_sort\":false},\"token\":{\"field\":\"token\",\"_field_id\":\"16\",\"comment\":\"token\",\"control\":\"input\",\"control_args\":\"\",\"search_type\":\"normal\",\"form_show\":false,\"list_show\":false,\"enable_sort\":false,\"searchable\":false},\"created_at\":{\"field\":\"created_at\",\"_field_id\":\"17\",\"comment\":\"创建时间\",\"control\":\"dateTimePicker\",\"control_args\":\"\",\"form_show\":true,\"search_type\":\"between\",\"list_show\":false,\"enable_sort\":false,\"searchable\":false},\"updated_at\":{\"field\":\"updated_at\",\"_field_id\":\"18\",\"comment\":\"更新时间\",\"control\":\"dateTimePicker\",\"control_args\":\"\",\"search_type\":\"between\",\"form_show\":false,\"list_show\":false,\"enable_sort\":false,\"searchable\":false},\"role\":{\"field\":\"role\",\"_field_id\":\"19\",\"comment\":\"角色\",\"control\":\"inputNumber\",\"control_args\":\"\",\"search_type\":\"normal\",\"form_show\":false,\"list_show\":false,\"enable_sort\":false,\"searchable\":false},\"status\":{\"field\":\"status\",\"_field_id\":\"20\",\"comment\":\"禁用\",\"control\":\"switch\",\"control_args\":\"\",\"form_show\":true,\"list_show\":true,\"search_type\":\"normal\",\"enable_sort\":false,\"searchable\":false}}','2022-08-15 00:00:00','2022-12-23 15:28:13'),(3,'table_form_schema_wa_roles','{\"id\":{\"field\":\"id\",\"_field_id\":\"0\",\"comment\":\"主键\",\"control\":\"inputNumber\",\"control_args\":\"\",\"list_show\":true,\"search_type\":\"normal\",\"form_show\":false,\"enable_sort\":false,\"searchable\":false},\"name\":{\"field\":\"name\",\"_field_id\":\"1\",\"comment\":\"角色组\",\"control\":\"input\",\"control_args\":\"\",\"form_show\":true,\"list_show\":true,\"search_type\":\"normal\",\"enable_sort\":false,\"searchable\":false},\"rules\":{\"field\":\"rules\",\"_field_id\":\"2\",\"comment\":\"权限\",\"control\":\"treeSelectMulti\",\"control_args\":\"url:\\/app\\/admin\\/rule\\/get?type=0,1,2\",\"form_show\":true,\"list_show\":true,\"search_type\":\"normal\",\"enable_sort\":false,\"searchable\":false},\"created_at\":{\"field\":\"created_at\",\"_field_id\":\"3\",\"comment\":\"创建时间\",\"control\":\"dateTimePicker\",\"control_args\":\"\",\"search_type\":\"normal\",\"form_show\":false,\"list_show\":false,\"enable_sort\":false,\"searchable\":false},\"updated_at\":{\"field\":\"updated_at\",\"_field_id\":\"4\",\"comment\":\"更新时间\",\"control\":\"dateTimePicker\",\"control_args\":\"\",\"search_type\":\"normal\",\"form_show\":false,\"list_show\":false,\"enable_sort\":false,\"searchable\":false},\"pid\":{\"field\":\"pid\",\"_field_id\":\"5\",\"comment\":\"父级\",\"control\":\"select\",\"control_args\":\"url:\\/app\\/admin\\/role\\/select?format=tree\",\"form_show\":true,\"list_show\":true,\"search_type\":\"normal\",\"enable_sort\":false,\"searchable\":false}}','2022-08-15 00:00:00','2022-12-19 14:24:25'),(4,'table_form_schema_wa_rules','{\"id\":{\"field\":\"id\",\"_field_id\":\"0\",\"comment\":\"主键\",\"control\":\"inputNumber\",\"control_args\":\"\",\"search_type\":\"normal\",\"form_show\":false,\"list_show\":false,\"enable_sort\":false,\"searchable\":false},\"title\":{\"field\":\"title\",\"_field_id\":\"1\",\"comment\":\"标题\",\"control\":\"input\",\"control_args\":\"\",\"form_show\":true,\"list_show\":true,\"searchable\":true,\"search_type\":\"normal\",\"enable_sort\":false},\"icon\":{\"field\":\"icon\",\"_field_id\":\"2\",\"comment\":\"图标\",\"control\":\"iconPicker\",\"control_args\":\"\",\"form_show\":true,\"list_show\":true,\"search_type\":\"normal\",\"enable_sort\":false,\"searchable\":false},\"key\":{\"field\":\"key\",\"_field_id\":\"3\",\"comment\":\"标识\",\"control\":\"input\",\"control_args\":\"\",\"form_show\":true,\"list_show\":true,\"searchable\":true,\"search_type\":\"normal\",\"enable_sort\":false},\"pid\":{\"field\":\"pid\",\"_field_id\":\"4\",\"comment\":\"上级菜单\",\"control\":\"treeSelect\",\"control_args\":\"\\/app\\/admin\\/rule\\/select?format=tree&type=0,1\",\"form_show\":true,\"list_show\":true,\"search_type\":\"normal\",\"enable_sort\":false,\"searchable\":false},\"created_at\":{\"field\":\"created_at\",\"_field_id\":\"5\",\"comment\":\"创建时间\",\"control\":\"dateTimePicker\",\"control_args\":\"\",\"search_type\":\"normal\",\"form_show\":false,\"list_show\":false,\"enable_sort\":false,\"searchable\":false},\"updated_at\":{\"field\":\"updated_at\",\"_field_id\":\"6\",\"comment\":\"更新时间\",\"control\":\"dateTimePicker\",\"control_args\":\"\",\"search_type\":\"normal\",\"form_show\":false,\"list_show\":false,\"enable_sort\":false,\"searchable\":false},\"href\":{\"field\":\"href\",\"_field_id\":\"7\",\"comment\":\"url\",\"control\":\"input\",\"control_args\":\"\",\"form_show\":true,\"list_show\":true,\"search_type\":\"normal\",\"enable_sort\":false,\"searchable\":false},\"type\":{\"field\":\"type\",\"_field_id\":\"8\",\"comment\":\"类型\",\"control\":\"select\",\"control_args\":\"data:0:目录,1:菜单,2:权限\",\"form_show\":true,\"list_show\":true,\"searchable\":true,\"search_type\":\"normal\",\"enable_sort\":false},\"weight\":{\"field\":\"weight\",\"_field_id\":\"9\",\"comment\":\"排序\",\"control\":\"inputNumber\",\"control_args\":\"\",\"form_show\":true,\"list_show\":true,\"search_type\":\"normal\",\"enable_sort\":false,\"searchable\":false}}','2022-08-15 00:00:00','2022-12-08 11:44:45'),(5,'table_form_schema_wa_admins','{\"id\":{\"field\":\"id\",\"_field_id\":\"0\",\"comment\":\"ID\",\"control\":\"inputNumber\",\"control_args\":\"\",\"list_show\":true,\"enable_sort\":true,\"search_type\":\"between\",\"form_show\":false,\"searchable\":false},\"username\":{\"field\":\"username\",\"_field_id\":\"1\",\"comment\":\"用户名\",\"control\":\"input\",\"control_args\":\"\",\"form_show\":true,\"list_show\":true,\"searchable\":true,\"search_type\":\"normal\",\"enable_sort\":false},\"nickname\":{\"field\":\"nickname\",\"_field_id\":\"2\",\"comment\":\"昵称\",\"control\":\"input\",\"control_args\":\"\",\"form_show\":true,\"list_show\":true,\"searchable\":true,\"search_type\":\"normal\",\"enable_sort\":false},\"password\":{\"field\":\"password\",\"_field_id\":\"3\",\"comment\":\"密码\",\"control\":\"input\",\"control_args\":\"\",\"form_show\":true,\"search_type\":\"normal\",\"list_show\":false,\"enable_sort\":false,\"searchable\":false},\"avatar\":{\"field\":\"avatar\",\"_field_id\":\"4\",\"comment\":\"头像\",\"control\":\"uploadImage\",\"control_args\":\"url:\\/app\\/admin\\/upload\\/avatar\",\"form_show\":true,\"list_show\":true,\"search_type\":\"normal\",\"enable_sort\":false,\"searchable\":false},\"email\":{\"field\":\"email\",\"_field_id\":\"5\",\"comment\":\"邮箱\",\"control\":\"input\",\"control_args\":\"\",\"form_show\":true,\"list_show\":true,\"searchable\":true,\"search_type\":\"normal\",\"enable_sort\":false},\"mobile\":{\"field\":\"mobile\",\"_field_id\":\"6\",\"comment\":\"手机\",\"control\":\"input\",\"control_args\":\"\",\"form_show\":true,\"list_show\":true,\"searchable\":true,\"search_type\":\"normal\",\"enable_sort\":false},\"created_at\":{\"field\":\"created_at\",\"_field_id\":\"7\",\"comment\":\"创建时间\",\"control\":\"dateTimePicker\",\"control_args\":\"\",\"form_show\":true,\"searchable\":true,\"search_type\":\"between\",\"list_show\":false,\"enable_sort\":false},\"updated_at\":{\"field\":\"updated_at\",\"_field_id\":\"8\",\"comment\":\"更新时间\",\"control\":\"dateTimePicker\",\"control_args\":\"\",\"form_show\":true,\"search_type\":\"normal\",\"list_show\":false,\"enable_sort\":false,\"searchable\":false},\"login_at\":{\"field\":\"login_at\",\"_field_id\":\"9\",\"comment\":\"登录时间\",\"control\":\"dateTimePicker\",\"control_args\":\"\",\"form_show\":true,\"list_show\":true,\"search_type\":\"between\",\"enable_sort\":false,\"searchable\":false},\"status\":{\"field\":\"status\",\"_field_id\":\"10\",\"comment\":\"禁用\",\"control\":\"switch\",\"control_args\":\"\",\"form_show\":true,\"list_show\":true,\"search_type\":\"normal\",\"enable_sort\":false,\"searchable\":false}}','2022-08-15 00:00:00','2022-12-23 15:36:48'),(6,'table_form_schema_wa_options','{\"id\":{\"field\":\"id\",\"_field_id\":\"0\",\"comment\":\"\",\"control\":\"inputNumber\",\"control_args\":\"\",\"list_show\":true,\"search_type\":\"normal\",\"form_show\":false,\"enable_sort\":false,\"searchable\":false},\"name\":{\"field\":\"name\",\"_field_id\":\"1\",\"comment\":\"键\",\"control\":\"input\",\"control_args\":\"\",\"form_show\":true,\"list_show\":true,\"search_type\":\"normal\",\"enable_sort\":false,\"searchable\":false},\"value\":{\"field\":\"value\",\"_field_id\":\"2\",\"comment\":\"值\",\"control\":\"textArea\",\"control_args\":\"\",\"form_show\":true,\"list_show\":true,\"search_type\":\"normal\",\"enable_sort\":false,\"searchable\":false},\"created_at\":{\"field\":\"created_at\",\"_field_id\":\"3\",\"comment\":\"创建时间\",\"control\":\"dateTimePicker\",\"control_args\":\"\",\"search_type\":\"normal\",\"form_show\":false,\"list_show\":false,\"enable_sort\":false,\"searchable\":false},\"updated_at\":{\"field\":\"updated_at\",\"_field_id\":\"4\",\"comment\":\"更新时间\",\"control\":\"dateTimePicker\",\"control_args\":\"\",\"search_type\":\"normal\",\"form_show\":false,\"list_show\":false,\"enable_sort\":false,\"searchable\":false}}','2022-08-15 00:00:00','2022-12-08 11:36:57'),(7,'table_form_schema_wa_uploads','{\"id\":{\"field\":\"id\",\"_field_id\":\"0\",\"comment\":\"主键\",\"control\":\"inputNumber\",\"control_args\":\"\",\"list_show\":true,\"enable_sort\":true,\"search_type\":\"normal\",\"form_show\":false,\"searchable\":false},\"name\":{\"field\":\"name\",\"_field_id\":\"1\",\"comment\":\"名称\",\"control\":\"input\",\"control_args\":\"\",\"list_show\":true,\"searchable\":true,\"search_type\":\"normal\",\"form_show\":false,\"enable_sort\":false},\"url\":{\"field\":\"url\",\"_field_id\":\"2\",\"comment\":\"文件\",\"control\":\"upload\",\"control_args\":\"url:\\/app\\/admin\\/upload\\/file\",\"form_show\":true,\"list_show\":true,\"search_type\":\"normal\",\"enable_sort\":false,\"searchable\":false},\"admin_id\":{\"field\":\"admin_id\",\"_field_id\":\"3\",\"comment\":\"管理员\",\"control\":\"select\",\"control_args\":\"url:\\/app\\/admin\\/admin\\/select?format=select\",\"search_type\":\"normal\",\"form_show\":false,\"list_show\":false,\"enable_sort\":false,\"searchable\":false},\"file_size\":{\"field\":\"file_size\",\"_field_id\":\"4\",\"comment\":\"文件大小\",\"control\":\"inputNumber\",\"control_args\":\"\",\"list_show\":true,\"search_type\":\"between\",\"form_show\":false,\"enable_sort\":false,\"searchable\":false},\"mime_type\":{\"field\":\"mime_type\",\"_field_id\":\"5\",\"comment\":\"mime类型\",\"control\":\"input\",\"control_args\":\"\",\"list_show\":true,\"search_type\":\"normal\",\"form_show\":false,\"enable_sort\":false,\"searchable\":false},\"image_width\":{\"field\":\"image_width\",\"_field_id\":\"6\",\"comment\":\"图片宽度\",\"control\":\"inputNumber\",\"control_args\":\"\",\"list_show\":true,\"search_type\":\"normal\",\"form_show\":false,\"enable_sort\":false,\"searchable\":false},\"image_height\":{\"field\":\"image_height\",\"_field_id\":\"7\",\"comment\":\"图片高度\",\"control\":\"inputNumber\",\"control_args\":\"\",\"list_show\":true,\"search_type\":\"normal\",\"form_show\":false,\"enable_sort\":false,\"searchable\":false},\"ext\":{\"field\":\"ext\",\"_field_id\":\"8\",\"comment\":\"扩展名\",\"control\":\"input\",\"control_args\":\"\",\"list_show\":true,\"searchable\":true,\"search_type\":\"normal\",\"form_show\":false,\"enable_sort\":false},\"storage\":{\"field\":\"storage\",\"_field_id\":\"9\",\"comment\":\"存储位置\",\"control\":\"input\",\"control_args\":\"\",\"search_type\":\"normal\",\"form_show\":false,\"list_show\":false,\"enable_sort\":false,\"searchable\":false},\"created_at\":{\"field\":\"created_at\",\"_field_id\":\"10\",\"comment\":\"上传时间\",\"control\":\"dateTimePicker\",\"control_args\":\"\",\"searchable\":true,\"search_type\":\"between\",\"form_show\":false,\"list_show\":false,\"enable_sort\":false},\"category\":{\"field\":\"category\",\"_field_id\":\"11\",\"comment\":\"类别\",\"control\":\"select\",\"control_args\":\"url:\\/app\\/admin\\/dict\\/get\\/upload\",\"form_show\":true,\"list_show\":true,\"searchable\":true,\"search_type\":\"normal\",\"enable_sort\":false},\"updated_at\":{\"field\":\"updated_at\",\"_field_id\":\"12\",\"comment\":\"更新时间\",\"control\":\"dateTimePicker\",\"control_args\":\"\",\"form_show\":true,\"list_show\":true,\"search_type\":\"normal\",\"enable_sort\":false,\"searchable\":false}}','2022-08-15 00:00:00','2022-12-08 11:47:45'),(8,'table_form_schema_wa_uploads','{\"id\":{\"field\":\"id\",\"_field_id\":\"0\",\"comment\":\"主键\",\"control\":\"inputNumber\",\"control_args\":\"\",\"list_show\":true,\"enable_sort\":true,\"search_type\":\"normal\",\"form_show\":false,\"searchable\":false},\"name\":{\"field\":\"name\",\"_field_id\":\"1\",\"comment\":\"名称\",\"control\":\"input\",\"control_args\":\"\",\"list_show\":true,\"searchable\":true,\"search_type\":\"normal\",\"form_show\":false,\"enable_sort\":false},\"url\":{\"field\":\"url\",\"_field_id\":\"2\",\"comment\":\"文件\",\"control\":\"upload\",\"control_args\":\"url:\\/app\\/admin\\/upload\\/file\",\"form_show\":true,\"list_show\":true,\"search_type\":\"normal\",\"enable_sort\":false,\"searchable\":false},\"admin_id\":{\"field\":\"admin_id\",\"_field_id\":\"3\",\"comment\":\"管理员\",\"control\":\"select\",\"control_args\":\"url:\\/app\\/admin\\/admin\\/select?format=select\",\"search_type\":\"normal\",\"form_show\":false,\"list_show\":false,\"enable_sort\":false,\"searchable\":false},\"file_size\":{\"field\":\"file_size\",\"_field_id\":\"4\",\"comment\":\"文件大小\",\"control\":\"inputNumber\",\"control_args\":\"\",\"list_show\":true,\"search_type\":\"between\",\"form_show\":false,\"enable_sort\":false,\"searchable\":false},\"mime_type\":{\"field\":\"mime_type\",\"_field_id\":\"5\",\"comment\":\"mime类型\",\"control\":\"input\",\"control_args\":\"\",\"list_show\":true,\"search_type\":\"normal\",\"form_show\":false,\"enable_sort\":false,\"searchable\":false},\"image_width\":{\"field\":\"image_width\",\"_field_id\":\"6\",\"comment\":\"图片宽度\",\"control\":\"inputNumber\",\"control_args\":\"\",\"list_show\":true,\"search_type\":\"normal\",\"form_show\":false,\"enable_sort\":false,\"searchable\":false},\"image_height\":{\"field\":\"image_height\",\"_field_id\":\"7\",\"comment\":\"图片高度\",\"control\":\"inputNumber\",\"control_args\":\"\",\"list_show\":true,\"search_type\":\"normal\",\"form_show\":false,\"enable_sort\":false,\"searchable\":false},\"ext\":{\"field\":\"ext\",\"_field_id\":\"8\",\"comment\":\"扩展名\",\"control\":\"input\",\"control_args\":\"\",\"list_show\":true,\"searchable\":true,\"search_type\":\"normal\",\"form_show\":false,\"enable_sort\":false},\"storage\":{\"field\":\"storage\",\"_field_id\":\"9\",\"comment\":\"存储位置\",\"control\":\"input\",\"control_args\":\"\",\"search_type\":\"normal\",\"form_show\":false,\"list_show\":false,\"enable_sort\":false,\"searchable\":false},\"created_at\":{\"field\":\"created_at\",\"_field_id\":\"10\",\"comment\":\"上传时间\",\"control\":\"dateTimePicker\",\"control_args\":\"\",\"searchable\":true,\"search_type\":\"between\",\"form_show\":false,\"list_show\":false,\"enable_sort\":false},\"category\":{\"field\":\"category\",\"_field_id\":\"11\",\"comment\":\"类别\",\"control\":\"select\",\"control_args\":\"url:\\/app\\/admin\\/dict\\/get\\/upload\",\"form_show\":true,\"list_show\":true,\"searchable\":true,\"search_type\":\"normal\",\"enable_sort\":false},\"updated_at\":{\"field\":\"updated_at\",\"_field_id\":\"12\",\"comment\":\"更新时间\",\"control\":\"dateTimePicker\",\"control_args\":\"\",\"form_show\":true,\"list_show\":true,\"search_type\":\"normal\",\"enable_sort\":false,\"searchable\":false}}','2022-08-15 00:00:00','2022-12-08 11:47:45'),(9,'dict_upload','[{\"value\":\"1\",\"name\":\"分类1\"},{\"value\":\"2\",\"name\":\"分类2\"},{\"value\":\"3\",\"name\":\"分类3\"}]','2022-12-04 16:24:13','2022-12-04 16:24:13'),(10,'dict_sex','[{\"value\":\"0\",\"name\":\"女\"},{\"value\":\"1\",\"name\":\"男\"}]','2022-12-04 15:04:40','2022-12-04 15:04:40'),(11,'dict_status','[{\"value\":\"0\",\"name\":\"正常\"},{\"value\":\"1\",\"name\":\"禁用\"}]','2022-12-04 15:05:09','2022-12-04 15:05:09'),(17,'table_form_schema_wa_admin_roles','{\"id\":{\"field\":\"id\",\"_field_id\":\"0\",\"comment\":\"主键\",\"control\":\"inputNumber\",\"control_args\":\"\",\"list_show\":true,\"enable_sort\":true,\"searchable\":true,\"search_type\":\"normal\",\"form_show\":false},\"role_id\":{\"field\":\"role_id\",\"_field_id\":\"1\",\"comment\":\"角色id\",\"control\":\"inputNumber\",\"control_args\":\"\",\"form_show\":true,\"list_show\":true,\"search_type\":\"normal\",\"enable_sort\":false,\"searchable\":false},\"admin_id\":{\"field\":\"admin_id\",\"_field_id\":\"2\",\"comment\":\"管理员id\",\"control\":\"inputNumber\",\"control_args\":\"\",\"form_show\":true,\"list_show\":true,\"search_type\":\"normal\",\"enable_sort\":false,\"searchable\":false}}','2022-08-15 00:00:00','2022-12-20 19:42:51'); +UNLOCK TABLES; + +CREATE TABLE IF NOT EXISTS `wa_roles` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `name` varchar(80) NOT NULL COMMENT '角色组', + `rules` text COMMENT '权限', + `created_at` datetime NOT NULL COMMENT '创建时间', + `updated_at` datetime NOT NULL COMMENT '更新时间', + `pid` int(10) unsigned DEFAULT NULL COMMENT '父级', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='管理员角色'; + +LOCK TABLES `wa_roles` WRITE; +INSERT INTO `wa_roles` VALUES (1,'超级管理员','*','2022-08-13 16:15:01','2022-12-23 12:05:07',NULL); +UNLOCK TABLES; + +CREATE TABLE IF NOT EXISTS `wa_rules` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `title` varchar(255) NOT NULL COMMENT '标题', + `icon` varchar(255) DEFAULT NULL COMMENT '图标', + `key` varchar(255) NOT NULL COMMENT '标识', + `pid` int(10) unsigned DEFAULT '0' COMMENT '上级菜单', + `created_at` datetime NOT NULL COMMENT '创建时间', + `updated_at` datetime NOT NULL COMMENT '更新时间', + `href` varchar(255) DEFAULT NULL COMMENT 'url', + `type` int(11) NOT NULL DEFAULT '1' COMMENT '类型', + `weight` int(11) DEFAULT '0' COMMENT '排序', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='权限规则'; + + +CREATE TABLE IF NOT EXISTS `wa_uploads` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', + `name` varchar(128) NOT NULL COMMENT '名称', + `url` varchar(255) NOT NULL COMMENT '文件', + `admin_id` int(11) DEFAULT NULL COMMENT '管理员', + `file_size` int(11) NOT NULL COMMENT '文件大小', + `mime_type` varchar(255) NOT NULL COMMENT 'mime类型', + `image_width` int(11) DEFAULT NULL COMMENT '图片宽度', + `image_height` int(11) DEFAULT NULL COMMENT '图片高度', + `ext` varchar(128) NOT NULL COMMENT '扩展名', + `storage` varchar(255) NOT NULL DEFAULT 'local' COMMENT '存储位置', + `created_at` date DEFAULT NULL COMMENT '上传时间', + `category` varchar(128) DEFAULT NULL COMMENT '类别', + `updated_at` date DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `category` (`category`), + KEY `admin_id` (`admin_id`), + KEY `name` (`name`), + KEY `ext` (`ext`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='附件'; + +CREATE TABLE IF NOT EXISTS `wa_users` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `username` varchar(32) NOT NULL COMMENT '用户名', + `nickname` varchar(40) NOT NULL COMMENT '昵称', + `password` varchar(255) NOT NULL COMMENT '密码', + `sex` enum('0','1') NOT NULL DEFAULT '1' COMMENT '性别', + `avatar` varchar(255) DEFAULT NULL COMMENT '头像', + `email` varchar(128) DEFAULT NULL COMMENT '邮箱', + `mobile` varchar(16) DEFAULT NULL COMMENT '手机', + `level` tinyint(4) NOT NULL DEFAULT '0' COMMENT '等级', + `birthday` date DEFAULT NULL COMMENT '生日', + `money` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '余额(元)', + `score` int(11) NOT NULL DEFAULT '0' COMMENT '积分', + `last_time` datetime DEFAULT NULL COMMENT '登录时间', + `last_ip` varchar(50) DEFAULT NULL COMMENT '登录ip', + `join_time` datetime DEFAULT NULL COMMENT '注册时间', + `join_ip` varchar(50) DEFAULT NULL COMMENT '注册ip', + `token` varchar(50) DEFAULT NULL COMMENT 'token', + `created_at` datetime DEFAULT NULL COMMENT '创建时间', + `updated_at` datetime DEFAULT NULL COMMENT '更新时间', + `role` int(11) NOT NULL DEFAULT '1' COMMENT '角色', + `status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '禁用', + PRIMARY KEY (`id`), + UNIQUE KEY `username` (`username`), + KEY `join_time` (`join_time`), + KEY `mobile` (`mobile`), + KEY `email` (`email`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户表'; diff --git a/vendor/webman/captcha/.gitignore b/vendor/webman/captcha/.gitignore new file mode 100644 index 0000000..4383716 --- /dev/null +++ b/vendor/webman/captcha/.gitignore @@ -0,0 +1,5 @@ +.idea/ +demo/*.jpg +demo/*.pgm +demo/temp/ +vendor/ diff --git a/vendor/webman/captcha/.travis.yml b/vendor/webman/captcha/.travis.yml new file mode 100644 index 0000000..5d2dbf9 --- /dev/null +++ b/vendor/webman/captcha/.travis.yml @@ -0,0 +1,16 @@ +language: php + +php: + - 5.3.3 + - 5.3 + - 5.4 + - 5.5 + - 5.6 + - 7.0 + - 7.1 + - 7.2 + - hhvm + +script: + - composer install + - phpunit diff --git a/vendor/webman/captcha/LICENSE b/vendor/webman/captcha/LICENSE new file mode 100644 index 0000000..62f991a --- /dev/null +++ b/vendor/webman/captcha/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) <2012-2017> Grégoire Passault + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/webman/captcha/README.md b/vendor/webman/captcha/README.md new file mode 100644 index 0000000..58e1544 --- /dev/null +++ b/vendor/webman/captcha/README.md @@ -0,0 +1,142 @@ +Captcha +======= + +![Captchas examples](http://gregwar.com/captchas.png) + +This is a fork of [Gregwar/Captcha](https://github.com/Gregwar/Captcha) + +Installation +============ + +With composer : + +``` +composer require webman/captcha +``` + +Usage +===== + +You can create a captcha with the `CaptchaBuilder` : + +```php +build(); +``` + +You can then save it to a file : + +```php +save('out.jpg'); +``` + +Or output it directly : + +```php +output(); +``` + +Or inline it directly in the HTML page: + +```php + +``` + +You'll be able to get the code and compare it with a user input : + +```php +getPhrase(); +``` + +You can compare the phrase with user input: +```php +if($builder->testPhrase($userInput)) { + // instructions if user phrase is good +} +else { + // user phrase is wrong +} +``` + +API +=== + +You can use theses functions : + +* **__construct($phrase = null)**, constructs the builder with the given phrase, if the phrase is null, a random one will be generated +* **getPhrase()**, allow you to get the phrase contents +* **setDistortion($distortion)**, enable or disable the distortion, call it before `build()` +* **isOCRReadable()**, returns `true` if the OCR can be read using the `ocrad` software, you'll need to have shell_exec enabled, imagemagick and ocrad installed +* **buildAgainstOCR($width = 150, $height = 40, $font = null)**, builds a code until it is not readable by `ocrad` +* **build($width = 150, $height = 40, $font = null)**, builds a code with the given $width, $height and $font. By default, a random font will be used from the library +* **save($filename, $quality = 80)**, saves the captcha into a jpeg in the $filename, with the given quality +* **get($quality = 80)**, returns the jpeg data +* **output($quality = 80)**, directly outputs the jpeg code to a browser +* **setBackgroundColor($r, $g, $b)**, sets the background color to force it (this will disable many effects and is not recommended) +* **setBackgroundImages(array($imagepath1, $imagePath2))**, Sets custom background images to be used as captcha background. It is recommended to disable image effects when passing custom images for background (ignore_all_effects). A random image is selected from the list passed, the full paths to the image files must be passed. +* **setInterpolation($interpolate)**, enable or disable the interpolation (enabled by default), disabling it will be quicker but the images will look uglier +* **setIgnoreAllEffects($ignoreAllEffects)**, disable all effects on the captcha image. Recommended to use when passing custom background images for the captcha. +* **testPhrase($phrase)**, returns true if the given phrase is good +* **setMaxBehindLines($lines)**, sets the maximum number of lines behind the code +* **setMaxFrontLines($lines)**, sets the maximum number of lines on the front of the code + +If you want to change the number of character, you can call the phrase builder directly using +extra parameters: + +```php +use Webman\Captcha\CaptchaBuilder; +use Webman\Captcha\PhraseBuilder; + +// Will build phrases of 3 characters +$phraseBuilder = new PhraseBuilder(4); + +// Will build phrases of 5 characters, only digits +$phraseBuilder = new PhraseBuilder(5, '0123456789'); + +// Pass it as first argument of CaptchaBuilder, passing it the phrase +// builder +$captcha = new CaptchaBuilder(null, $phraseBuilder); +``` + +You can also pass directly the wanted phrase to the builder: + +```php +// Building a Captcha with the "hello" phrase +$captcha = new CaptchaBuilder('hello'); +``` + +Complete example +================ + +If you want to see an example you can have a look at the ``demo/form.php``, which uses ``demo/session.php`` to +render a captcha and check it after the submission + +Symfony Bundle +================ + +You can have a look at the following repository to enjoy the Symfony 2 bundle packaging this captcha generator : +https://github.com/Gregwar/CaptchaBundle + +Yii2 Extension +=============== + +You can use the following extension for integrating with Yii2 Framework : +https://github.com/juliardi/yii2-captcha + +License +======= + +This library is under MIT license, have a look to the `LICENSE` file diff --git a/vendor/webman/captcha/demo/demo.php b/vendor/webman/captcha/demo/demo.php new file mode 100644 index 0000000..51e71ce --- /dev/null +++ b/vendor/webman/captcha/demo/demo.php @@ -0,0 +1,11 @@ +build() + ->save('out.jpg') +; diff --git a/vendor/webman/captcha/demo/fingerprint.php b/vendor/webman/captcha/demo/fingerprint.php new file mode 100644 index 0000000..17570d3 --- /dev/null +++ b/vendor/webman/captcha/demo/fingerprint.php @@ -0,0 +1,12 @@ +build() + ->getFingerprint() +); + +echo "\n"; diff --git a/vendor/webman/captcha/demo/form.php b/vendor/webman/captcha/demo/form.php new file mode 100644 index 0000000..2957b7c --- /dev/null +++ b/vendor/webman/captcha/demo/form.php @@ -0,0 +1,32 @@ + + + +Captcha is valid !"; + } else { + echo "

    Captcha is not valid!

    "; + } + // The phrase can't be used twice + unset($_SESSION['phrase']); + } +?> +
    + Copy the CAPTCHA: + + + + +
    + diff --git a/vendor/webman/captcha/demo/index.php b/vendor/webman/captcha/demo/index.php new file mode 100644 index 0000000..e543883 --- /dev/null +++ b/vendor/webman/captcha/demo/index.php @@ -0,0 +1,15 @@ + + + + + + +

    Captchas gallery

    + + + + +
    + + + diff --git a/vendor/webman/captcha/demo/inline.php b/vendor/webman/captcha/demo/inline.php new file mode 100644 index 0000000..75d9382 --- /dev/null +++ b/vendor/webman/captcha/demo/inline.php @@ -0,0 +1,22 @@ +build(); + +?> + + + + + + +

    Inline Captcha

    + +
    + Phrase: getPhrase(); ?> + + + diff --git a/vendor/webman/captcha/demo/ocr.php b/vendor/webman/captcha/demo/ocr.php new file mode 100644 index 0000000..3f766dc --- /dev/null +++ b/vendor/webman/captcha/demo/ocr.php @@ -0,0 +1,39 @@ +setDistortion(false) + ->build() + ; + + if ($captcha->isOCRReadable()) { + $passed++; + $captcha->save("passed$passed.jpg"); + echo "passed at ocr... "; + } else { + echo "failed... "; + } + + echo "pass rate: ".round(100*$passed/($i+1),2)."%\n"; +} + +echo "\n"; +echo "Over, $passed/$tests readed with OCR\n"; diff --git a/vendor/webman/captcha/demo/output.php b/vendor/webman/captcha/demo/output.php new file mode 100644 index 0000000..b6d628a --- /dev/null +++ b/vendor/webman/captcha/demo/output.php @@ -0,0 +1,12 @@ +build() + ->output() +; diff --git a/vendor/webman/captcha/demo/session.php b/vendor/webman/captcha/demo/session.php new file mode 100644 index 0000000..187a404 --- /dev/null +++ b/vendor/webman/captcha/demo/session.php @@ -0,0 +1,22 @@ +getPhrase(); + +// Setting the header to image jpeg because we here render an image +header('Content-Type: image/jpeg'); + +// Running the actual rendering of the captcha image +$captcha + ->build() + ->output() +; diff --git a/vendor/webman/captcha/phpunit.xml.dist b/vendor/webman/captcha/phpunit.xml.dist new file mode 100644 index 0000000..f17e3b4 --- /dev/null +++ b/vendor/webman/captcha/phpunit.xml.dist @@ -0,0 +1,15 @@ + + + + + + ./tests/ + + + + + + ./src + + + diff --git a/vendor/webman/captcha/src/CaptchaBuilder.php b/vendor/webman/captcha/src/CaptchaBuilder.php new file mode 100644 index 0000000..c92f0e1 --- /dev/null +++ b/vendor/webman/captcha/src/CaptchaBuilder.php @@ -0,0 +1,767 @@ + + * @author Jeremy Livingston + */ +#[AllowDynamicProperties] +class CaptchaBuilder implements CaptchaBuilderInterface +{ + /** + * @var int|bool + */ + public $background; + + /** + * @var array + */ + protected $fingerprint = array(); + + /** + * @var bool + */ + protected $useFingerprint = false; + + /** + * @var array + */ + protected $textColor = array(); + + /** + * @var array + */ + protected $lineColor = null; + + /** + * @var array + */ + protected $backgroundColor = null; + + /** + * @var array + */ + protected $backgroundImages = array(); + + /** + * @var resource + */ + protected $contents = null; + + /** + * @var string + */ + protected $phrase = null; + + /** + * @var PhraseBuilderInterface + */ + protected $builder; + + /** + * @var bool + */ + protected $distortion = true; + + /** + * The maximum number of lines to draw in front of + * the image. null - use default algorithm + */ + protected $maxFrontLines = null; + + /** + * The maximum number of lines to draw behind + * the image. null - use default algorithm + */ + protected $maxBehindLines = null; + + /** + * The maximum angle of char + */ + protected $maxAngle = 8; + + /** + * The maximum offset of char + */ + protected $maxOffset = 5; + + /** + * Is the interpolation enabled ? + * + * @var bool + */ + protected $interpolation = true; + + /** + * Ignore all effects + * + * @var bool + */ + protected $ignoreAllEffects = false; + + /** + * Allowed image types for the background images + * + * @var array + */ + protected $allowedBackgroundImageTypes = array('image/png', 'image/jpeg', 'image/gif'); + + /** + * The image contents + */ + public function getContents() + { + return $this->contents; + } + + /** + * Enable/Disables the interpolation + * + * @param $interpolate bool True to enable, false to disable + * + * @return CaptchaBuilder + */ + public function setInterpolation($interpolate = true) + { + $this->interpolation = $interpolate; + + return $this; + } + + /** + * Temporary dir, for OCR check + */ + public $tempDir = 'temp/'; + + public function __construct($phrase = null, PhraseBuilderInterface $builder = null) + { + if ($builder === null) { + $this->builder = new PhraseBuilder; + } else { + $this->builder = $builder; + } + + $this->phrase = is_string($phrase) ? $phrase : $this->builder->build($phrase); + } + + /** + * Setting the phrase + */ + public function setPhrase($phrase) + { + $this->phrase = (string) $phrase; + } + + /** + * Enables/disable distortion + */ + public function setDistortion($distortion) + { + $this->distortion = (bool) $distortion; + + return $this; + } + + public function setMaxBehindLines($maxBehindLines) + { + $this->maxBehindLines = $maxBehindLines; + + return $this; + } + + public function setMaxFrontLines($maxFrontLines) + { + $this->maxFrontLines = $maxFrontLines; + + return $this; + } + + public function setMaxAngle($maxAngle) + { + $this->maxAngle = $maxAngle; + + return $this; + } + + public function setMaxOffset($maxOffset) + { + $this->maxOffset = $maxOffset; + + return $this; + } + + /** + * Gets the captcha phrase + */ + public function getPhrase() + { + return $this->phrase; + } + + /** + * Returns true if the given phrase is good + */ + public function testPhrase($phrase) + { + return ($this->builder->niceize($phrase) == $this->builder->niceize($this->getPhrase())); + } + + /** + * Instantiation + */ + public static function create($phrase = null) + { + return new self($phrase); + } + + /** + * Sets the text color to use + */ + public function setTextColor($r, $g, $b) + { + $this->textColor = array($r, $g, $b); + + return $this; + } + + /** + * Sets the background color to use + */ + public function setBackgroundColor($r, $g, $b) + { + $this->backgroundColor = array($r, $g, $b); + + return $this; + } + + public function setLineColor($r, $g, $b) + { + $this->lineColor = array($r, $g, $b); + + return $this; + } + + /** + * Sets the ignoreAllEffects value + * + * @param bool $ignoreAllEffects + * @return CaptchaBuilder + */ + public function setIgnoreAllEffects($ignoreAllEffects) + { + $this->ignoreAllEffects = $ignoreAllEffects; + + return $this; + } + + /** + * Sets the list of background images to use (one image is randomly selected) + */ + public function setBackgroundImages(array $backgroundImages) + { + $this->backgroundImages = $backgroundImages; + + return $this; + } + + /** + * Draw lines over the image + */ + protected function drawLine($image, $width, $height, $tcol = null) + { + if ($this->lineColor === null) { + $red = $this->rand(100, 255); + $green = $this->rand(100, 255); + $blue = $this->rand(100, 255); + } else { + $red = $this->lineColor[0]; + $green = $this->lineColor[1]; + $blue = $this->lineColor[2]; + } + + if ($tcol === null) { + $tcol = imagecolorallocate($image, $red, $green, $blue); + } + + if ($this->rand(0, 1)) { // Horizontal + $Xa = $this->rand(0, $width/2); + $Ya = $this->rand(0, $height); + $Xb = $this->rand($width/2, $width); + $Yb = $this->rand(0, $height); + } else { // Vertical + $Xa = $this->rand(0, $width); + $Ya = $this->rand(0, $height/2); + $Xb = $this->rand(0, $width); + $Yb = $this->rand($height/2, $height); + } + imagesetthickness($image, $this->rand(1, 3)); + imageline($image, $Xa, $Ya, $Xb, $Yb, $tcol); + } + + /** + * Apply some post effects + */ + protected function postEffect($image) + { + if (!function_exists('imagefilter')) { + return; + } + + if ($this->backgroundColor != null || $this->textColor != null) { + return; + } + + // Negate ? + if ($this->rand(0, 1) == 0) { + imagefilter($image, IMG_FILTER_NEGATE); + } + + // Edge ? + if ($this->rand(0, 10) == 0) { + imagefilter($image, IMG_FILTER_EDGEDETECT); + } + + // Contrast + imagefilter($image, IMG_FILTER_CONTRAST, $this->rand(-50, 10)); + + // Colorize + if ($this->rand(0, 5) == 0) { + imagefilter($image, IMG_FILTER_COLORIZE, $this->rand(-80, 50), $this->rand(-80, 50), $this->rand(-80, 50)); + } + } + + /** + * Writes the phrase on the image + */ + protected function writePhrase($image, $phrase, $font, $width, $height) + { + $length = mb_strlen($phrase); + if ($length === 0) { + return \imagecolorallocate($image, 0, 0, 0); + } + + // Gets the text size and start position + $size = intval($width / $length) - $this->rand(0, 3) - 1; + $box = \imagettfbbox($size, 0, $font, $phrase); + $textWidth = $box[2] - $box[0]; + $textHeight = $box[1] - $box[7]; + $x = intval(($width - $textWidth) / 2); + $y = intval(($height - $textHeight) / 2) + $size; + + if (!$this->textColor) { + $textColor = array($this->rand(0, 150), $this->rand(0, 150), $this->rand(0, 150)); + } else { + $textColor = $this->textColor; + } + $col = \imagecolorallocate($image, $textColor[0], $textColor[1], $textColor[2]); + + // Write the letters one by one, with random angle + for ($i=0; $i<$length; $i++) { + $symbol = mb_substr($phrase, $i, 1); + $box = \imagettfbbox($size, 0, $font, $symbol); + $w = $box[2] - $box[0]; + $angle = $this->rand(-$this->maxAngle, $this->maxAngle); + $offset = $this->rand(-$this->maxOffset, $this->maxOffset); + \imagettftext($image, $size, $angle, $x, $y + $offset, $col, $font, $symbol); + $x += $w; + } + + return $col; + } + + /** + * Try to read the code against an OCR + */ + public function isOCRReadable() + { + if (!is_dir($this->tempDir)) { + @mkdir($this->tempDir, 0755, true); + } + + $tempj = $this->tempDir . uniqid('captcha', true) . '.jpg'; + $tempp = $this->tempDir . uniqid('captcha', true) . '.pgm'; + + $this->save($tempj); + shell_exec("convert $tempj $tempp"); + $value = trim(strtolower(shell_exec("ocrad $tempp"))); + + @unlink($tempj); + @unlink($tempp); + + return $this->testPhrase($value); + } + + /** + * Builds while the code is readable against an OCR + */ + public function buildAgainstOCR($width = 150, $height = 40, $font = null, $fingerprint = null) + { + do { + $this->build($width, $height, $font, $fingerprint); + } while ($this->isOCRReadable()); + } + + /** + * Generate the image + */ + public function build($width = 150, $height = 40, $font = null, $fingerprint = null) + { + if (null !== $fingerprint) { + $this->fingerprint = $fingerprint; + $this->useFingerprint = true; + } else { + $this->fingerprint = array(); + $this->useFingerprint = false; + } + + if ($font === null) { + $font = $this->getFontPath(__DIR__ . '/Font/captcha'.$this->rand(0, 5).'.ttf'); + } + + if (empty($this->backgroundImages)) { + // if background images list is not set, use a color fill as a background + $image = imagecreatetruecolor($width, $height); + if ($this->backgroundColor == null) { + $bg = imagecolorallocate($image, $this->rand(200, 255), $this->rand(200, 255), $this->rand(200, 255)); + } else { + $color = $this->backgroundColor; + $bg = imagecolorallocate($image, $color[0], $color[1], $color[2]); + } + $this->background = $bg; + imagefill($image, 0, 0, $bg); + } else { + // use a random background image + $randomBackgroundImage = $this->backgroundImages[rand(0, count($this->backgroundImages)-1)]; + + $imageType = $this->validateBackgroundImage($randomBackgroundImage); + + $image = $this->createBackgroundImageFromType($randomBackgroundImage, $imageType); + } + + // Apply effects + if (!$this->ignoreAllEffects) { + $square = $width * $height; + $effects = $this->rand($square/3000, $square/2000); + + // set the maximum number of lines to draw in front of the text + if ($this->maxBehindLines != null && $this->maxBehindLines > 0) { + $effects = min($this->maxBehindLines, $effects); + } + + if ($this->maxBehindLines !== 0) { + for ($e = 0; $e < $effects; $e++) { + $this->drawLine($image, $width, $height); + } + } + } + + // Write CAPTCHA text + $color = $this->writePhrase($image, $this->phrase, $font, $width, $height); + + // Apply effects + if (!$this->ignoreAllEffects) { + $square = $width * $height; + $effects = $this->rand($square/3000, $square/2000); + + // set the maximum number of lines to draw in front of the text + if ($this->maxFrontLines != null && $this->maxFrontLines > 0) { + $effects = min($this->maxFrontLines, $effects); + } + + if ($this->maxFrontLines !== 0) { + for ($e = 0; $e < $effects; $e++) { + $this->drawLine($image, $width, $height, $color); + } + } + } + + // Distort the image + if ($this->distortion && !$this->ignoreAllEffects) { + $image = $this->distort($image, $width, $height, $bg); + } + + // Post effects + if (!$this->ignoreAllEffects) { + $this->postEffect($image); + } + + $this->contents = $image; + + return $this; + } + + /** + * @param $font + * @return string + */ + protected function getFontPath($font) + { + static $fontPathMap = []; + if (!\class_exists(\Phar::class, false) || !\Phar::running()) { + return $font; + } + + $tmpPath = sys_get_temp_dir() ?: '/tmp'; + $filePath = "$tmpPath/" . basename($font); + clearstatcache(); + if (!isset($fontPathMap[$font]) || !is_file($filePath)) { + file_put_contents($filePath, file_get_contents($font)); + $fontPathMap[$font] = $filePath; + } + return $fontPathMap[$font]; + } + + /** + * Distorts the image + */ + public function distort($image, $width, $height, $bg) + { + $contents = imagecreatetruecolor($width, $height); + $X = $this->rand(0, $width); + $Y = $this->rand(0, $height); + $phase = $this->rand(0, 10); + $scale = 1.1 + $this->rand(0, 10000) / 30000; + for ($x = 0; $x < $width; $x++) { + for ($y = 0; $y < $height; $y++) { + $Vx = $x - $X; + $Vy = $y - $Y; + $Vn = sqrt($Vx * $Vx + $Vy * $Vy); + + if ($Vn != 0) { + $Vn2 = $Vn + 4 * sin($Vn / 30); + $nX = $X + ($Vx * $Vn2 / $Vn); + $nY = $Y + ($Vy * $Vn2 / $Vn); + } else { + $nX = $X; + $nY = $Y; + } + $nY = $nY + $scale * sin($phase + $nX * 0.2); + + if ($this->interpolation) { + $p = $this->interpolate( + $nX - floor($nX), + $nY - floor($nY), + $this->getCol($image, floor($nX), floor($nY), $bg), + $this->getCol($image, ceil($nX), floor($nY), $bg), + $this->getCol($image, floor($nX), ceil($nY), $bg), + $this->getCol($image, ceil($nX), ceil($nY), $bg) + ); + } else { + $p = $this->getCol($image, round($nX), round($nY), $bg); + } + + if ($p == 0) { + $p = $bg; + } + + imagesetpixel($contents, $x, $y, $p); + } + } + + return $contents; + } + + /** + * Saves the Captcha to a jpeg file + */ + public function save($filename, $quality = 90) + { + imagejpeg($this->contents, $filename, $quality); + } + + /** + * Gets the image GD + */ + public function getGd() + { + return $this->contents; + } + + /** + * Gets the image contents + */ + public function get($quality = 90) + { + ob_start(); + $this->output($quality); + + return ob_get_clean(); + } + + /** + * Gets the HTML inline base64 + */ + public function inline($quality = 90) + { + return 'data:image/jpeg;base64,' . base64_encode($this->get($quality)); + } + + /** + * Outputs the image + */ + public function output($quality = 90) + { + imagejpeg($this->contents, null, $quality); + } + + /** + * @return array + */ + public function getFingerprint() + { + return $this->fingerprint; + } + + /** + * Returns a random number or the next number in the + * fingerprint + */ + protected function rand($min, $max) + { + if (!is_array($this->fingerprint)) { + $this->fingerprint = array(); + } + + if ($this->useFingerprint) { + $value = current($this->fingerprint); + next($this->fingerprint); + } else { + $value = mt_rand(intval($min), intval($max)); + $this->fingerprint[] = $value; + } + + return $value; + } + + /** + * @param $x + * @param $y + * @param $nw + * @param $ne + * @param $sw + * @param $se + * + * @return int + */ + protected function interpolate($x, $y, $nw, $ne, $sw, $se) + { + list($r0, $g0, $b0) = $this->getRGB($nw); + list($r1, $g1, $b1) = $this->getRGB($ne); + list($r2, $g2, $b2) = $this->getRGB($sw); + list($r3, $g3, $b3) = $this->getRGB($se); + + $cx = 1.0 - $x; + $cy = 1.0 - $y; + + $m0 = $cx * $r0 + $x * $r1; + $m1 = $cx * $r2 + $x * $r3; + $r = (int) ($cy * $m0 + $y * $m1); + + $m0 = $cx * $g0 + $x * $g1; + $m1 = $cx * $g2 + $x * $g3; + $g = (int) ($cy * $m0 + $y * $m1); + + $m0 = $cx * $b0 + $x * $b1; + $m1 = $cx * $b2 + $x * $b3; + $b = (int) ($cy * $m0 + $y * $m1); + + return ($r << 16) | ($g << 8) | $b; + } + + /** + * @param $image + * @param $x + * @param $y + * + * @return int + */ + protected function getCol($image, $x, $y, $background) + { + $L = imagesx($image); + $H = imagesy($image); + if ($x < 0 || $x >= $L || $y < 0 || $y >= $H) { + return $background; + } + + return imagecolorat($image, $x, $y); + } + + /** + * @param $col + * + * @return array + */ + protected function getRGB($col) + { + return array( + (int) ($col >> 16) & 0xff, + (int) ($col >> 8) & 0xff, + (int) ($col) & 0xff, + ); + } + + /** + * Validate the background image path. Return the image type if valid + * + * @param string $backgroundImage + * @return string + * @throws Exception + */ + protected function validateBackgroundImage($backgroundImage) + { + // check if file exists + if (!file_exists($backgroundImage)) { + $backgroundImageExploded = explode('/', $backgroundImage); + $imageFileName = count($backgroundImageExploded) > 1? $backgroundImageExploded[count($backgroundImageExploded)-1] : $backgroundImage; + + throw new Exception('Invalid background image: ' . $imageFileName); + } + + // check image type + $finfo = finfo_open(FILEINFO_MIME_TYPE); // return mime type ala mimetype extension + $imageType = finfo_file($finfo, $backgroundImage); + finfo_close($finfo); + + if (!in_array($imageType, $this->allowedBackgroundImageTypes)) { + throw new Exception('Invalid background image type! Allowed types are: ' . join(', ', $this->allowedBackgroundImageTypes)); + } + + return $imageType; + } + + /** + * Create background image from type + * + * @param string $backgroundImage + * @param string $imageType + * @return resource + * @throws Exception + */ + protected function createBackgroundImageFromType($backgroundImage, $imageType) + { + switch ($imageType) { + case 'image/jpeg': + $image = imagecreatefromjpeg($backgroundImage); + break; + case 'image/png': + $image = imagecreatefrompng($backgroundImage); + break; + case 'image/gif': + $image = imagecreatefromgif($backgroundImage); + break; + + default: + throw new Exception('Not supported file type for background image!'); + break; + } + + return $image; + } +} diff --git a/vendor/webman/captcha/src/CaptchaBuilderInterface.php b/vendor/webman/captcha/src/CaptchaBuilderInterface.php new file mode 100644 index 0000000..420dbbb --- /dev/null +++ b/vendor/webman/captcha/src/CaptchaBuilderInterface.php @@ -0,0 +1,29 @@ + + * @author Jeremy Livingston + */ +class ImageFileHandler +{ + /** + * Name of folder for captcha images + * @var string + */ + protected $imageFolder; + + /** + * Absolute path to public web folder + * @var string + */ + protected $webPath; + + /** + * Frequency of garbage collection in fractions of 1 + * @var int + */ + protected $gcFreq; + + /** + * Maximum age of images in minutes + * @var int + */ + protected $expiration; + + /** + * @param $imageFolder + * @param $webPath + * @param $gcFreq + * @param $expiration + */ + public function __construct($imageFolder, $webPath, $gcFreq, $expiration) + { + $this->imageFolder = $imageFolder; + $this->webPath = $webPath; + $this->gcFreq = $gcFreq; + $this->expiration = $expiration; + } + + /** + * Saves the provided image content as a file + * + * @param string $contents + * + * @return string + */ + public function saveAsFile($contents) + { + $this->createFolderIfMissing(); + + $filename = md5(uniqid()) . '.jpg'; + $filePath = $this->webPath . '/' . $this->imageFolder . '/' . $filename; + imagejpeg($contents, $filePath, 15); + + return '/' . $this->imageFolder . '/' . $filename; + } + + /** + * Creates the folder if it doesn't exist + */ + protected function createFolderIfMissing() + { + if (!file_exists($this->webPath . '/' . $this->imageFolder)) { + mkdir($this->webPath . '/' . $this->imageFolder, 0755); + } + } +} diff --git a/vendor/webman/captcha/src/PhraseBuilder.php b/vendor/webman/captcha/src/PhraseBuilder.php new file mode 100644 index 0000000..b96f6c5 --- /dev/null +++ b/vendor/webman/captcha/src/PhraseBuilder.php @@ -0,0 +1,75 @@ + + */ +class PhraseBuilder implements PhraseBuilderInterface +{ + /** + * @var int + */ + public $length; + + /** + * @var string + */ + public $charset; + /** + * Constructs a PhraseBuilder with given parameters + */ + public function __construct($length = 5, $charset = 'abcdefghijklmnpqrstuvwxyz123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ') + { + $this->length = $length; + $this->charset = $charset; + } + + /** + * Generates random phrase of given length with given charset + */ + public function build($length = null, $charset = null) + { + if ($length !== null) { + $this->length = $length; + } + if ($charset !== null) { + $this->charset = $charset; + } + + $phrase = ''; + $chars = str_split($this->charset); + + for ($i = 0; $i < $this->length; $i++) { + $phrase .= $chars[array_rand($chars)]; + } + + return $phrase; + } + + /** + * "Niceize" a code + */ + public function niceize($str) + { + return self::doNiceize($str); + } + + /** + * A static helper to niceize + */ + public static function doNiceize($str) + { + return strtr(strtolower($str), '01', 'ol'); + } + + /** + * A static helper to compare + */ + public static function comparePhrases($str1, $str2) + { + return self::doNiceize($str1) === self::doNiceize($str2); + } +} diff --git a/vendor/webman/captcha/src/PhraseBuilderInterface.php b/vendor/webman/captcha/src/PhraseBuilderInterface.php new file mode 100644 index 0000000..e6ca680 --- /dev/null +++ b/vendor/webman/captcha/src/PhraseBuilderInterface.php @@ -0,0 +1,21 @@ + + */ +interface PhraseBuilderInterface +{ + /** + * Generates random phrase of given length with given charset + */ + public function build(); + + /** + * "Niceize" a code + */ + public function niceize($str); +} diff --git a/vendor/webman/captcha/tests/CaptchaBuilderTest.php b/vendor/webman/captcha/tests/CaptchaBuilderTest.php new file mode 100644 index 0000000..c6eaeb2 --- /dev/null +++ b/vendor/webman/captcha/tests/CaptchaBuilderTest.php @@ -0,0 +1,30 @@ +build() + ->save('out.jpg') + ; + + $this->assertTrue(file_exists(__DIR__.'/../out.jpg')); + } + + public function testFingerPrint() + { + $int = count(CaptchaBuilder::create() + ->build() + ->getFingerprint() + ); + + $this->assertTrue(is_int($int)); + } +} \ No newline at end of file diff --git a/vendor/webman/event/LICENSE b/vendor/webman/event/LICENSE new file mode 100644 index 0000000..e0b0695 --- /dev/null +++ b/vendor/webman/event/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 webman + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/webman/event/README.md b/vendor/webman/event/README.md new file mode 100644 index 0000000..b042b12 --- /dev/null +++ b/vendor/webman/event/README.md @@ -0,0 +1,4 @@ +# event +webman event plugin + +https://www.workerman.net/plugin/64 diff --git a/vendor/webman/event/src/BootStrap.php b/vendor/webman/event/src/BootStrap.php new file mode 100644 index 0000000..3b1aba5 --- /dev/null +++ b/vendor/webman/event/src/BootStrap.php @@ -0,0 +1,90 @@ + $events) { + // 支持排序,1 2 3 ... 9 a b c...z + ksort($events, SORT_NATURAL); + foreach ($events as $callbacks) { + foreach ($callbacks as $callback) { + Event::on($name, $callback); + } + } + } + } + + /** + * @param $callbacks + * @return array|mixed + */ + protected static function convertCallable($callbacks) + { + if (\is_array($callbacks)) { + $callback = \array_values($callbacks); + if (isset($callback[1]) && \is_string($callback[0]) && \class_exists($callback[0])) { + return [Container::get($callback[0]), $callback[1]]; + } + } + return $callbacks; + } + + /** + * @param $configs + * @return void + */ + protected static function getEvents($configs) + { + foreach ($configs as $config) { + if (!is_array($config)) { + continue; + } + if (isset($config['event']) && is_array($config['event']) && !isset($config['event']['app']['enable'])) { + foreach ($config['event'] as $event_name => $callbacks) { + $callbacks = static::convertCallable($callbacks); + if (is_callable($callbacks)) { + static::$events[$event_name][] = [$callbacks]; + continue; + } + if (!is_array($callbacks)) { + $msg = "Events: $event_name => " .var_export($callbacks, true) . " is not callable\n"; + echo $msg; + Log::error($msg); + continue; + } + ksort($callbacks, SORT_NATURAL); + foreach ($callbacks as $id => $callback) { + $callback = static::convertCallable($callback); + if (is_callable($callback)) { + static::$events[$event_name][$id][] = $callback; + continue; + } + $msg = "Events: $event_name => " . var_export($callback, true) . " is not callable\n"; + echo $msg; + Log::error($msg); + } + } + unset($config['event']); + } + static::getEvents($config); + } + } + +} diff --git a/vendor/webman/event/src/Event.php b/vendor/webman/event/src/Event.php new file mode 100644 index 0000000..5a0f4fb --- /dev/null +++ b/vendor/webman/event/src/Event.php @@ -0,0 +1,161 @@ +error($e); + } + continue; + } + $responses[] = $response; + if ($halt && !is_null($response)) { + return $response; + } + if ($response === false) { + break; + } + } + return $halt ? null : $responses; + } + + /** + * @param mixed $event_name + * @param mixed $data + * @param bool $halt + * @return array|null|mixed + */ + public static function dispatch($event_name, $data, bool $halt = false) + { + $listeners = static::getListeners($event_name); + $responses = []; + foreach ($listeners as $listener) { + $response = $listener($data, $event_name); + $responses[] = $response; + if ($halt && !is_null($response)) { + return $response; + } + if ($response === false) { + break; + } + } + return $halt ? null : $responses; + } + + /** + * @return array + */ + public static function list(): array + { + $listeners = []; + foreach (static::$eventMap as $event_name => $callback_items) { + foreach ($callback_items as $id => $callback_item) { + $listeners[$id] = [$event_name, $callback_item]; + } + } + foreach (static::$prefixEventMap as $event_name => $callback_items) { + foreach ($callback_items as $id => $callback_item) { + $listeners[$id] = [$event_name . '*', $callback_item]; + } + } + ksort($listeners); + return $listeners; + } + + /** + * @param mixed $event_name + * @return callable[] + */ + public static function getListeners($event_name): array + { + $listeners = static::$eventMap[$event_name] ?? []; + foreach (static::$prefixEventMap as $name => $callback_items) { + if (strpos($event_name, $name) === 0) { + $listeners = array_merge($listeners, $callback_items); + } + } + ksort($listeners); + return $listeners; + } + + /** + * @param mixed $event_name + * @return bool + */ + public static function hasListener($event_name): bool + { + return !empty(static::getListeners($event_name)); + } +} diff --git a/vendor/webman/event/src/EventListCommand.php b/vendor/webman/event/src/EventListCommand.php new file mode 100644 index 0000000..e3d3afd --- /dev/null +++ b/vendor/webman/event/src/EventListCommand.php @@ -0,0 +1,50 @@ + $item) { + $event_name = $item[0]; + $callback = $item[1]; + if (is_array($callback) && is_object($callback[0])) { + $callback[0] = get_class($callback[0]); + } + $cb = $callback instanceof \Closure ? 'Closure' : (is_array($callback) ? json_encode($callback) : var_export($callback, 1)); + $rows[] = [$id, $event_name, $cb]; + } + + $table = new Table($output); + $table->setHeaders($headers); + $table->setRows($rows); + $table->render(); + return self::SUCCESS; + } + +} diff --git a/vendor/webman/event/src/Install.php b/vendor/webman/event/src/Install.php new file mode 100644 index 0000000..5a7712d --- /dev/null +++ b/vendor/webman/event/src/Install.php @@ -0,0 +1,82 @@ + 'config/plugin/webman/event', +); + + /** + * Install + * @return void + */ + public static function install() + { + static::installByRelation(); + $event_config_path = config_path() . '/event.php'; + if (!is_file($event_config_path)) { + file_put_contents($event_config_path, " $dest) { + if ($pos = strrpos($dest, '/')) { + $parent_dir = base_path().'/'.substr($dest, 0, $pos); + if (!is_dir($parent_dir)) { + mkdir($parent_dir, 0777, true); + } + } + //symlink(__DIR__ . "/$source", base_path()."/$dest"); + copy_dir(__DIR__ . "/$source", base_path()."/$dest"); + echo "Create $dest +"; + } + } + + /** + * uninstallByRelation + * @return void + */ + public static function uninstallByRelation() + { + foreach (static::$pathRelation as $source => $dest) { + $path = base_path()."/$dest"; + if (!is_dir($path) && !is_file($path)) { + continue; + } + echo "Remove $dest +"; + if (is_file($path) || is_link($path)) { + unlink($path); + continue; + } + remove_dir($path); + } + } + +} \ No newline at end of file diff --git a/vendor/webman/event/src/config/plugin/webman/event/app.php b/vendor/webman/event/src/config/plugin/webman/event/app.php new file mode 100644 index 0000000..8f9c426 --- /dev/null +++ b/vendor/webman/event/src/config/plugin/webman/event/app.php @@ -0,0 +1,4 @@ + true, +]; \ No newline at end of file diff --git a/vendor/webman/event/src/config/plugin/webman/event/bootstrap.php b/vendor/webman/event/src/config/plugin/webman/event/bootstrap.php new file mode 100644 index 0000000..e5b09ba --- /dev/null +++ b/vendor/webman/event/src/config/plugin/webman/event/bootstrap.php @@ -0,0 +1,17 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return [ + Webman\Event\BootStrap::class, +]; diff --git a/vendor/webman/event/src/config/plugin/webman/event/command.php b/vendor/webman/event/src/config/plugin/webman/event/command.php new file mode 100644 index 0000000..e860cf7 --- /dev/null +++ b/vendor/webman/event/src/config/plugin/webman/event/command.php @@ -0,0 +1,7 @@ + $dest) { + if ($pos = strrpos($dest, '/')) { + $parent_dir = base_path().'/'.substr($dest, 0, $pos); + if (!is_dir($parent_dir)) { + mkdir($parent_dir, 0777, true); + } + } + //symlink(__DIR__ . "/$source", base_path()."/$dest"); + copy_dir(__DIR__ . "/$source", base_path()."/$dest"); + } + } + + /** + * uninstallByRelation + * @return void + */ + public static function uninstallByRelation() + { + foreach (static::$pathRelation as $source => $dest) { + $path = base_path()."/$dest"; + if (!is_dir($path) && !is_file($path)) { + continue; + } + /*if (is_link($path) { + unlink($path); + }*/ + remove_dir($path); + } + } + +} \ No newline at end of file diff --git a/vendor/webman/think-orm/src/ThinkOrm.php b/vendor/webman/think-orm/src/ThinkOrm.php new file mode 100644 index 0000000..a5e2f13 --- /dev/null +++ b/vendor/webman/think-orm/src/ThinkOrm.php @@ -0,0 +1,80 @@ +make(DbManager::class); + } else { + $reflect = new \ReflectionClass(Db::class); + $property = $reflect->getProperty('instance'); + $property->setAccessible(true); + $manager_instance = $property->getValue(); + } + Timer::add(55, function () use ($manager_instance) { + $instances = []; + if (method_exists($manager_instance, 'getInstance')) { + $instances = $manager_instance->getInstance(); + } else { + $reflect = new \ReflectionClass($manager_instance); + $property = $reflect->getProperty('instance'); + $property->setAccessible(true); + $instances = $property->getValue($manager_instance); + } + foreach ($instances as $connection) { + /* @var \think\db\connector\Mysql $connection */ + if (in_array($connection->getConfig('type'), ['mysql', 'oracle', 'sqlsrv'])) { + try { + $connection->query('select 1'); + } catch (Throwable $e) {} + } + } + Db::getDbLog(true); + }); + } + + // 自定义分页组件类 + $bootstrap = $config['connections'][$config['default']]['bootstrap'] ?? false; + if($bootstrap && class_exists($bootstrap)){ + Paginator::maker(function ($items, $listRows, $currentPage, $total, $simple, $options) use ($bootstrap){ + return (new \ReflectionClass($bootstrap))->newInstanceArgs(func_get_args()); + }); + } + + + Paginator::currentPageResolver(function ($pageName = 'page') { + $request = request(); + if (!$request) { + return 1; + } + $page = $request->input($pageName, 1); + if (filter_var($page, FILTER_VALIDATE_INT) !== false && (int)$page >= 1) { + return (int)$page; + } + return 1; + }); + + // 设置分页url中域名与参数之间的path字符串 + Paginator::currentPathResolver(function (){ + $request = request(); + return $request ? $request->path() : '/'; + }); + } +} diff --git a/vendor/webman/think-orm/src/_ide_helper.php b/vendor/webman/think-orm/src/_ide_helper.php new file mode 100644 index 0000000..584a287 --- /dev/null +++ b/vendor/webman/think-orm/src/_ide_helper.php @@ -0,0 +1,450 @@ +name($name); + } + /** + * 指定当前操作的数据表 + * @access public + * @param mixed $table 表名 + * @return \think\Db + */ + public static function table($table) + { + /** @var \think\Db $instance */ + return $instance->table($table); + } + + /** + * 执行数据库事务 + * @access public + * @param callable $callback 数据操作方法回调 + * @return mixed + */ + public static function transaction(callable $callback) + { + /** @var \think\Db $instance */ + return $instance->transaction($callback); + } + + /** + * 启动事务 + * @access public + * @return void + */ + public static function startTrans(): void + { + /** @var \think\Db $instance */ + $instance->startTrans(); + } + + /** + * 用于非自动提交状态下面的查询提交 + * @access public + * @return void + * @throws \PDOException + */ + public static function commit(): void + { + /** @var \think\Db $instance */ + $instance->commit(); + } + + /** + * 事务回滚 + * @access public + * @return void + * @throws \PDOException + */ + public static function rollback(): void + { + /** @var \think\Db $instance */ + $instance->rollback(); + } + /** + * 执行查询 返回数据集 + * @access public + * @param string $sql sql指令 + * @param array $bind 参数绑定 + * @return array + * @throws \think\db\exception\BindParamException + * @throws \PDOException + */ + public static function query(string $sql, array $bind = []): array + { + /** @var \think\Db $instance */ + return $instance->query($sql,$bind); + } + + /** + * 设置从主服务器读取数据 + * @access public + * @param bool $readMaster 是否从主服务器读取 + * @return \think\Db + */ + public function master(bool $readMaster = true) + { + /** @var \think\Db $instance */ + return $instance->master($readMaster); + } + /** + * 执行语句 + * @access public + * @param string $sql sql指令 + * @param array $bind 参数绑定 + * @return int + * @throws \think\db\exception\BindParamException + * @throws \PDOException + */ + public static function execute(string $sql, array $bind = []): int + { + /** @var \think\Db $instance */ + return $instance->execute($sql,$bind); + } + + /** + * 创建/切换数据库连接查询 + * @access public + * @param string|null $name 连接配置标识 + * @param bool $force 强制重新连接 + * @return \think\db\BaseQuery + */ + public function connect(string $name = null, bool $force = false): \think\db\BaseQuery + { + + /** @var \think\Db $instance */ + return $instance->connect($name,$force); + } + + /** + * 设置配置对象 + * @access public + * + * @param Config $config 配置对象 + * + * @return void + */ + public static function setConfig($config): void + { + + /** @var \think\Db $instance */ + $instance->setConfig($config); + } + + /** + * 获取配置参数 + * @access public + * + * @param string $name 配置参数 + * @param mixed $default 默认值 + * + * @return mixed + */ + public static function getConfig(string $name = '', $default = null) + { + + /** @var \think\Db $instance */ + return $instance->getConfig($name, $default); + } + + /** + * 设置Event对象 + * + * @param Event $event + */ + public static function setEvent(Event $event): void + { + + /** @var \think\Db $instance */ + $instance->setEvent($event); + } + + /** + * 注册回调方法 + * @access public + * + * @param string $event 事件名 + * @param callable $callback 回调方法 + * + * @return void + */ + public static function event(string $event, callable $callback): void + { + + /** @var \think\Db $instance */ + $instance->event($event, $callback); + } + + /** + * 触发事件 + * @access public + * + * @param string $event 事件名 + * @param mixed $params 传入参数 + * @param bool $once + * + * @return mixed + */ + public static function trigger(string $event, $params = null, bool $once = false) + { + + /** @var \think\Db $instance */ + return $instance->trigger($event, $params, $once); + } + + } + +} + +namespace think { + + /** + * @method static \think\db\Query where(mixed $field, string $op = null, mixed $condition = null) 查询条件 + * @method static \think\db\Query whereTime(string $field, string $op, mixed $range = null) 查询日期和时间 + * @method static \think\db\Query whereBetweenTime(string $field, mixed $startTime, mixed $endTime) 查询日期或者时间范围 + * @method static \think\db\Query whereBetweenTimeField(string $startField, string $endField) 查询当前时间在两个时间字段范围 + * @method static \think\db\Query whereYear(string $field, string $year = 'this year') 查询某年 + * @method static \think\db\Query whereMonth(string $field, string $month = 'this month') 查询某月 + * @method static \think\db\Query whereDay(string $field, string $day = 'today') 查询某日 + * @method static \think\db\Query whereRaw(string $where, array $bind = []) 表达式查询 + * @method static \think\db\Query whereExp(string $field, string $condition, array $bind = []) 字段表达式查询 + * @method static \think\db\Query when(mixed $condition, mixed $query, mixed $otherwise = null) 条件查询 + * @method static \think\db\Query join(mixed $join, mixed $condition = null, string $type = 'INNER') JOIN查询 + * @method static \think\db\Query view(mixed $join, mixed $field = null, mixed $on = null, string $type = 'INNER') 视图查询 + * @method static \think\db\Query with(mixed $with) 关联预载入 + * @method static \think\db\Query count(string $field) Count统计查询 + * @method static \think\db\Query min(string $field) Min统计查询 + * @method static \think\db\Query max(string $field) Max统计查询 + * @method static \think\db\Query sum(string $field) SUM统计查询 + * @method static \think\db\Query avg(string $field) Avg统计查询 + * @method static \think\db\Query field(mixed $field, boolean $except = false) 指定查询字段 + * @method static \think\db\Query fieldRaw(string $field, array $bind = []) 指定查询字段 + * @method static \think\db\Query union(mixed $union, boolean $all = false) UNION查询 + * @method static \think\db\Query limit(mixed $offset, integer $length = null) 查询LIMIT + * @method static \think\db\Query order(mixed $field, string $order = null) 查询ORDER + * @method static \think\db\Query orderRaw(string $field, array $bind = []) 查询ORDER + * @method static \think\db\Query cache(mixed $key = null, integer $expire = null) 设置查询缓存 + * @method mixed value(string $field) 获取某个字段的值 + * @method array column(string $field, string $key = '') 获取某个列的值 + * @method Model find(mixed $data = null) 查询单个记录 不存在返回Null + * @method Model findOrEmpty(mixed $data = null) 查询单个记录 不存在返回空模型 + * @method \think\model\Collection select(mixed $data = null) 查询多个记录 + * @method Model withAttr(array $name, \Closure $closure) 动态定义获取器 + */ + class Model + { + + /** + * 指定当前数据表名(不含前缀) + * @access public + * @param string $name 不含前缀的数据表名字 + * @return \think\Db + */ + public static function name(string $name) + { + /** @var \think\Db $instance */ + return $instance->name($name); + } + /** + * 指定当前操作的数据表 + * @access public + * @param mixed $table 表名 + * @return \think\Db + */ + public static function table($table) + { + /** @var \think\Db $instance */ + return $instance->table($table); + } + + /** + * 执行数据库事务 + * @access public + * @param callable $callback 数据操作方法回调 + * @return mixed + */ + public static function transaction(callable $callback) + { + /** @var \think\Db $instance */ + return $instance->transaction($callback); + } + + /** + * 启动事务 + * @access public + * @return void + */ + public static function startTrans(): void + { + /** @var \think\Db $instance */ + $instance->startTrans(); + } + + /** + * 用于非自动提交状态下面的查询提交 + * @access public + * @return void + * @throws \PDOException + */ + public static function commit(): void + { + /** @var \think\Db $instance */ + $instance->commit(); + } + + /** + * 事务回滚 + * @access public + * @return void + * @throws \PDOException + */ + public static function rollback(): void + { + /** @var \think\Db $instance */ + $instance->rollback(); + } + /** + * 执行查询 返回数据集 + * @access public + * @param string $sql sql指令 + * @param array $bind 参数绑定 + * @return array + * @throws \think\db\exception\BindParamException + * @throws \PDOException + */ + public function query(string $sql, array $bind = []): array + { + /** @var \think\Db $instance */ + return $instance->query($sql,$bind); + } + + /** + * 设置从主服务器读取数据 + * @access public + * @param bool $readMaster 是否从主服务器读取 + * @return \think\Db + */ + public function master(bool $readMaster = true) + { + /** @var \think\Db $instance */ + return $instance->master($readMaster); + } + /** + * 执行语句 + * @access public + * @param string $sql sql指令 + * @param array $bind 参数绑定 + * @return int + * @throws \think\db\exception\BindParamException + * @throws \PDOException + */ + public function execute(string $sql, array $bind = []): int + { + /** @var \think\Db $instance */ + return $instance->execute($sql,$bind); + } + + /** + * 创建/切换数据库连接查询 + * @access public + * @param string|null $name 连接配置标识 + * @param bool $force 强制重新连接 + * @return \think\db\BaseQuery + */ + public function connect(string $name = null, bool $force = false): \think\db\BaseQuery + { + + /** @var \think\Db $instance */ + return $instance->connect($name,$force); + } + + /** + * 设置配置对象 + * @access public + * + * @param Config $config 配置对象 + * + * @return void + */ + public static function setConfig($config): void + { + + /** @var \think\Db $instance */ + $instance->setConfig($config); + } + + /** + * 获取配置参数 + * @access public + * + * @param string $name 配置参数 + * @param mixed $default 默认值 + * + * @return mixed + */ + public static function getConfig(string $name = '', $default = null) + { + + /** @var \think\Db $instance */ + return $instance->getConfig($name, $default); + } + + /** + * 设置Event对象 + * + * @param Event $event + */ + public static function setEvent(Event $event): void + { + + /** @var \think\Db $instance */ + $instance->setEvent($event); + } + + /** + * 注册回调方法 + * @access public + * + * @param string $event 事件名 + * @param callable $callback 回调方法 + * + * @return void + */ + public static function event(string $event, callable $callback): void + { + + /** @var \think\Db $instance */ + $instance->event($event, $callback); + } + + /** + * 触发事件 + * @access public + * + * @param string $event 事件名 + * @param mixed $params 传入参数 + * @param bool $once + * + * @return mixed + */ + public static function trigger(string $event, $params = null, bool $once = false) + { + + /** @var \think\Db $instance */ + return $instance->trigger($event, $params, $once); + } + + } +} \ No newline at end of file diff --git a/vendor/webman/think-orm/src/config/thinkorm.php b/vendor/webman/think-orm/src/config/thinkorm.php new file mode 100644 index 0000000..37af444 --- /dev/null +++ b/vendor/webman/think-orm/src/config/thinkorm.php @@ -0,0 +1,36 @@ + 'mysql', + 'connections' => [ + 'mysql' => [ + // 数据库类型 + 'type' => 'mysql', + // 服务器地址 + 'hostname' => '127.0.0.1', + // 数据库名 + 'database' => 'test', + // 数据库用户名 + 'username' => 'root', + // 数据库密码 + 'password' => '123456', + // 数据库连接端口 + 'hostport' => '3306', + // 数据库连接参数 + 'params' => [ + // 连接超时3秒 + \PDO::ATTR_TIMEOUT => 3, + ], + // 数据库编码默认采用utf8 + 'charset' => 'utf8', + // 数据库表前缀 + 'prefix' => '', + // 断线重连 + 'break_reconnect' => true, + // 关闭SQL监听日志 + 'trigger_sql' => false, + // 自定义分页类 + 'bootstrap' => '' + ], + ], +]; diff --git a/vendor/workerman/webman-framework/.gitignore b/vendor/workerman/webman-framework/.gitignore new file mode 100644 index 0000000..3f2c43a --- /dev/null +++ b/vendor/workerman/webman-framework/.gitignore @@ -0,0 +1,5 @@ +composer.lock +vendor +vendor/ +.idea +.idea/ \ No newline at end of file diff --git a/vendor/workerman/webman-framework/README.md b/vendor/workerman/webman-framework/README.md new file mode 100644 index 0000000..96a900a --- /dev/null +++ b/vendor/workerman/webman-framework/README.md @@ -0,0 +1,5 @@ +# webman-framework +Note: This repository is the core code of the webman framework. If you want to build an application using webman, visit the main [webman](https://github.com/walkor/webman) repository. + +## LICENSE +MIT diff --git a/vendor/workerman/webman-framework/src/App.php b/vendor/workerman/webman-framework/src/App.php new file mode 100644 index 0000000..22aacb5 --- /dev/null +++ b/vendor/workerman/webman-framework/src/App.php @@ -0,0 +1,929 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace Webman; + +use Closure; +use Exception; +use FastRoute\Dispatcher; +use InvalidArgumentException; +use Monolog\Logger; +use Psr\Container\ContainerExceptionInterface; +use Psr\Container\ContainerInterface; +use Psr\Container\NotFoundExceptionInterface; +use ReflectionClass; +use ReflectionException; +use ReflectionFunction; +use ReflectionFunctionAbstract; +use ReflectionMethod; +use Throwable; +use Webman\Exception\ExceptionHandler; +use Webman\Exception\ExceptionHandlerInterface; +use Webman\Http\Request; +use Webman\Http\Response; +use Webman\Route\Route as RouteObject; +use Workerman\Connection\TcpConnection; +use Workerman\Protocols\Http; +use Workerman\Worker; +use function array_merge; +use function array_pop; +use function array_reduce; +use function array_reverse; +use function array_splice; +use function array_values; +use function class_exists; +use function clearstatcache; +use function count; +use function current; +use function end; +use function explode; +use function file_get_contents; +use function get_class_methods; +use function gettype; +use function implode; +use function in_array; +use function is_a; +use function is_array; +use function is_dir; +use function is_file; +use function is_string; +use function key; +use function method_exists; +use function next; +use function ob_get_clean; +use function ob_start; +use function pathinfo; +use function scandir; +use function str_replace; +use function strpos; +use function strtolower; +use function substr; +use function trim; + +/** + * Class App + * @package Webman + */ +class App +{ + + /** + * @var callable[] + */ + protected static $callbacks = []; + + /** + * @var Worker + */ + protected static $worker = null; + + /** + * @var Logger + */ + protected static $logger = null; + + /** + * @var string + */ + protected static $appPath = ''; + + /** + * @var string + */ + protected static $publicPath = ''; + + /** + * @var string + */ + protected static $requestClass = ''; + + /** + * App constructor. + * @param string $requestClass + * @param Logger $logger + * @param string $appPath + * @param string $publicPath + */ + public function __construct(string $requestClass, Logger $logger, string $appPath, string $publicPath) + { + static::$requestClass = $requestClass; + static::$logger = $logger; + static::$publicPath = $publicPath; + static::$appPath = $appPath; + } + + /** + * OnMessage. + * @param TcpConnection|mixed $connection + * @param Request|mixed $request + * @return null + */ + public function onMessage($connection, $request) + { + try { + Context::set(Request::class, $request); + $path = $request->path(); + $key = $request->method() . $path; + if (isset(static::$callbacks[$key])) { + [$callback, $request->plugin, $request->app, $request->controller, $request->action, $request->route] = static::$callbacks[$key]; + static::send($connection, $callback($request), $request); + return null; + } + + if ( + static::unsafeUri($connection, $path, $request) || + static::findFile($connection, $path, $key, $request) || + static::findRoute($connection, $path, $key, $request) + ) { + return null; + } + + $controllerAndAction = static::parseControllerAction($path); + $plugin = $controllerAndAction['plugin'] ?? static::getPluginByPath($path); + if (!$controllerAndAction || Route::hasDisableDefaultRoute($plugin)) { + $request->plugin = $plugin; + $callback = static::getFallback($plugin); + $request->app = $request->controller = $request->action = ''; + static::send($connection, $callback($request), $request); + return null; + } + $app = $controllerAndAction['app']; + $controller = $controllerAndAction['controller']; + $action = $controllerAndAction['action']; + $callback = static::getCallback($plugin, $app, [$controller, $action]); + static::collectCallbacks($key, [$callback, $plugin, $app, $controller, $action, null]); + [$callback, $request->plugin, $request->app, $request->controller, $request->action, $request->route] = static::$callbacks[$key]; + static::send($connection, $callback($request), $request); + } catch (Throwable $e) { + static::send($connection, static::exceptionResponse($e, $request), $request); + } + return null; + } + + /** + * OnWorkerStart. + * @param $worker + * @return void + */ + public function onWorkerStart($worker) + { + static::$worker = $worker; + Http::requestClass(static::$requestClass); + } + + /** + * CollectCallbacks. + * @param string $key + * @param array $data + * @return void + */ + protected static function collectCallbacks(string $key, array $data) + { + static::$callbacks[$key] = $data; + if (count(static::$callbacks) >= 1024) { + unset(static::$callbacks[key(static::$callbacks)]); + } + } + + /** + * UnsafeUri. + * @param TcpConnection $connection + * @param string $path + * @param $request + * @return bool + */ + protected static function unsafeUri(TcpConnection $connection, string $path, $request): bool + { + if ( + !$path || + strpos($path, '..') !== false || + strpos($path, "\\") !== false || + strpos($path, "\0") !== false + ) { + $callback = static::getFallback(); + $request->plugin = $request->app = $request->controller = $request->action = ''; + static::send($connection, $callback($request), $request); + return true; + } + return false; + } + + /** + * GetFallback. + * @param string $plugin + * @return Closure + */ + protected static function getFallback(string $plugin = ''): Closure + { + // when route, controller and action not found, try to use Route::fallback + return Route::getFallback($plugin) ?: function () { + try { + $notFoundContent = file_get_contents(static::$publicPath . '/404.html'); + } catch (Throwable $e) { + $notFoundContent = '404 Not Found'; + } + return new Response(404, [], $notFoundContent); + }; + } + + /** + * ExceptionResponse. + * @param Throwable $e + * @param $request + * @return Response + */ + protected static function exceptionResponse(Throwable $e, $request): Response + { + try { + $app = $request->app ?: ''; + $plugin = $request->plugin ?: ''; + $exceptionConfig = static::config($plugin, 'exception'); + $defaultException = $exceptionConfig[''] ?? ExceptionHandler::class; + $exceptionHandlerClass = $exceptionConfig[$app] ?? $defaultException; + + /** @var ExceptionHandlerInterface $exceptionHandler */ + $exceptionHandler = static::container($plugin)->make($exceptionHandlerClass, [ + 'logger' => static::$logger, + 'debug' => static::config($plugin, 'app.debug') + ]); + $exceptionHandler->report($e); + $response = $exceptionHandler->render($request, $e); + $response->exception($e); + return $response; + } catch (Throwable $e) { + $response = new Response(500, [], static::config($plugin ?? '', 'app.debug') ? (string)$e : $e->getMessage()); + $response->exception($e); + return $response; + } + } + + /** + * GetCallback. + * @param string $plugin + * @param string $app + * @param $call + * @param array|null $args + * @param bool $withGlobalMiddleware + * @param RouteObject|null $route + * @return callable + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + * @throws ReflectionException + */ + protected static function getCallback(string $plugin, string $app, $call, array $args = null, bool $withGlobalMiddleware = true, RouteObject $route = null) + { + $args = $args === null ? null : array_values($args); + $middlewares = []; + if ($route) { + $routeMiddlewares = array_reverse($route->getMiddleware()); + foreach ($routeMiddlewares as $className) { + $middlewares[] = [$className, 'process']; + } + } + $middlewares = array_merge($middlewares, Middleware::getMiddleware($plugin, $app, $withGlobalMiddleware)); + + foreach ($middlewares as $key => $item) { + $middleware = $item[0]; + if (is_string($middleware)) { + $middleware = static::container($plugin)->get($middleware); + } elseif ($middleware instanceof Closure) { + $middleware = call_user_func($middleware, static::container($plugin)); + } + if (!$middleware instanceof MiddlewareInterface) { + throw new InvalidArgumentException('Not support middleware type'); + } + $middlewares[$key][0] = $middleware; + } + + $needInject = static::isNeedInject($call, $args); + if (is_array($call) && is_string($call[0])) { + $controllerReuse = static::config($plugin, 'app.controller_reuse', true); + if (!$controllerReuse) { + if ($needInject) { + $call = function ($request, ...$args) use ($call, $plugin) { + $call[0] = static::container($plugin)->make($call[0]); + $reflector = static::getReflector($call); + $args = static::resolveMethodDependencies($plugin, $request, $args, $reflector); + return $call(...$args); + }; + $needInject = false; + } else { + $call = function ($request, ...$args) use ($call, $plugin) { + $call[0] = static::container($plugin)->make($call[0]); + return $call($request, ...$args); + }; + } + } else { + $call[0] = static::container($plugin)->get($call[0]); + } + } + + if ($needInject) { + $call = static::resolveInject($plugin, $call); + } + + if ($middlewares) { + $callback = array_reduce($middlewares, function ($carry, $pipe) { + return function ($request) use ($carry, $pipe) { + try { + return $pipe($request, $carry); + } catch (Throwable $e) { + return static::exceptionResponse($e, $request); + } + }; + }, function ($request) use ($call, $args) { + try { + if ($args === null) { + $response = $call($request); + } else { + $response = $call($request, ...$args); + } + } catch (Throwable $e) { + return static::exceptionResponse($e, $request); + } + if (!$response instanceof Response) { + if (!is_string($response)) { + $response = static::stringify($response); + } + $response = new Response(200, [], $response); + } + return $response; + }); + } else { + if ($args === null) { + $callback = $call; + } else { + $callback = function ($request) use ($call, $args) { + return $call($request, ...$args); + }; + } + } + return $callback; + } + + /** + * ResolveInject. + * @param string $plugin + * @param array|Closure $call + * @return Closure + * @see Dependency injection through reflection information + */ + protected static function resolveInject(string $plugin, $call): Closure + { + return function (Request $request, ...$args) use ($plugin, $call) { + $reflector = static::getReflector($call); + $args = static::resolveMethodDependencies($plugin, $request, $args, $reflector); + return $call(...$args); + }; + } + + /** + * Check whether inject is required. + * @param $call + * @param $args + * @return bool + * @throws ReflectionException + */ + protected static function isNeedInject($call, $args): bool + { + if (is_array($call) && !method_exists($call[0], $call[1])) { + return false; + } + $args = $args ?: []; + $reflector = static::getReflector($call); + $reflectionParameters = $reflector->getParameters(); + if (!$reflectionParameters) { + return false; + } + $firstParameter = current($reflectionParameters); + unset($reflectionParameters[key($reflectionParameters)]); + $adaptersList = ['int', 'string', 'bool', 'array', 'object', 'float', 'mixed', 'resource']; + foreach ($reflectionParameters as $parameter) { + if ($parameter->hasType() && !in_array($parameter->getType()->getName(), $adaptersList)) { + return true; + } + } + if (!$firstParameter->hasType()) { + return count($args) > count($reflectionParameters); + } + + if (!is_a(static::$requestClass, $firstParameter->getType()->getName())) { + return true; + } + + return false; + } + + /** + * Get reflector. + * @param $call + * @return ReflectionFunction|ReflectionMethod + * @throws ReflectionException + */ + protected static function getReflector($call) + { + if ($call instanceof Closure || is_string($call)) { + return new ReflectionFunction($call); + } + return new ReflectionMethod($call[0], $call[1]); + } + + /** + * Return dependent parameters + * @param string $plugin + * @param Request $request + * @param array $args + * @param ReflectionFunctionAbstract $reflector + * @return array + */ + protected static function resolveMethodDependencies(string $plugin, Request $request, array $args, ReflectionFunctionAbstract $reflector): array + { + // Specification parameter information + $args = array_values($args); + $parameters = []; + // An array of reflection classes for loop parameters, with each $parameter representing a reflection object of parameters + foreach ($reflector->getParameters() as $parameter) { + // Parameter quota consumption + if ($parameter->hasType()) { + $name = $parameter->getType()->getName(); + switch ($name) { + case 'int': + case 'string': + case 'bool': + case 'array': + case 'object': + case 'float': + case 'mixed': + case 'resource': + goto _else; + default: + if (is_a($request, $name)) { + //Inject Request + $parameters[] = $request; + } else { + $parameters[] = static::container($plugin)->make($name); + } + break; + } + } else { + _else: + // The variable parameter + if (null !== key($args)) { + $parameters[] = current($args); + } else { + // Indicates whether the current parameter has a default value. If yes, return true + $parameters[] = $parameter->isDefaultValueAvailable() ? $parameter->getDefaultValue() : null; + } + // Quota of consumption variables + next($args); + } + } + + // Returns the result of parameters replacement + return $parameters; + } + + /** + * Container. + * @param string $plugin + * @return ContainerInterface + */ + public static function container(string $plugin = '') + { + return static::config($plugin, 'container'); + } + + /** + * Get request. + * @return Request|\support\Request + */ + public static function request() + { + return Context::get(Request::class); + } + + /** + * Get worker. + * @return Worker + */ + public static function worker(): ?Worker + { + return static::$worker; + } + + /** + * Find Route. + * @param TcpConnection $connection + * @param string $path + * @param string $key + * @param Request|mixed $request + * @return bool + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + * @throws ReflectionException + */ + protected static function findRoute(TcpConnection $connection, string $path, string $key, $request): bool + { + $routeInfo = Route::dispatch($request->method(), $path); + if ($routeInfo[0] === Dispatcher::FOUND) { + $routeInfo[0] = 'route'; + $callback = $routeInfo[1]['callback']; + $route = clone $routeInfo[1]['route']; + $app = $controller = $action = ''; + $args = !empty($routeInfo[2]) ? $routeInfo[2] : null; + if ($args) { + $route->setParams($args); + } + if (is_array($callback)) { + $controller = $callback[0]; + $plugin = static::getPluginByClass($controller); + $app = static::getAppByController($controller); + $action = static::getRealMethod($controller, $callback[1]) ?? ''; + } else { + $plugin = static::getPluginByPath($path); + } + $callback = static::getCallback($plugin, $app, $callback, $args, true, $route); + static::collectCallbacks($key, [$callback, $plugin, $app, $controller ?: '', $action, $route]); + [$callback, $request->plugin, $request->app, $request->controller, $request->action, $request->route] = static::$callbacks[$key]; + static::send($connection, $callback($request), $request); + return true; + } + return false; + } + + /** + * Find File. + * @param TcpConnection $connection + * @param string $path + * @param string $key + * @param Request|mixed $request + * @return bool + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + * @throws ReflectionException + */ + protected static function findFile(TcpConnection $connection, string $path, string $key, $request): bool + { + if (preg_match('/%[0-9a-f]{2}/i', $path)) { + $path = urldecode($path); + if (static::unsafeUri($connection, $path, $request)) { + return true; + } + } + + $pathExplodes = explode('/', trim($path, '/')); + $plugin = ''; + if (isset($pathExplodes[1]) && $pathExplodes[0] === 'app') { + $publicDir = BASE_PATH . "/plugin/$pathExplodes[1]/public"; + $plugin = $pathExplodes[1]; + $path = substr($path, strlen("/app/$pathExplodes[1]/")); + } else { + $publicDir = static::$publicPath; + } + $file = "$publicDir/$path"; + if (!is_file($file)) { + return false; + } + + if (pathinfo($file, PATHINFO_EXTENSION) === 'php') { + if (!static::config($plugin, 'app.support_php_files', false)) { + return false; + } + static::collectCallbacks($key, [function () use ($file) { + return static::execPhpFile($file); + }, '', '', '', '', null]); + [, $request->plugin, $request->app, $request->controller, $request->action, $request->route] = static::$callbacks[$key]; + static::send($connection, static::execPhpFile($file), $request); + return true; + } + + if (!static::config($plugin, 'static.enable', false)) { + return false; + } + + static::collectCallbacks($key, [static::getCallback($plugin, '__static__', function ($request) use ($file, $plugin) { + clearstatcache(true, $file); + if (!is_file($file)) { + $callback = static::getFallback($plugin); + return $callback($request); + } + return (new Response())->file($file); + }, null, false), '', '', '', '', null]); + [$callback, $request->plugin, $request->app, $request->controller, $request->action, $request->route] = static::$callbacks[$key]; + static::send($connection, $callback($request), $request); + return true; + } + + /** + * Send. + * @param TcpConnection|mixed $connection + * @param mixed $response + * @param Request|mixed $request + * @return void + */ + protected static function send($connection, $response, $request) + { + $keepAlive = $request->header('connection'); + Context::destroy(); + if (($keepAlive === null && $request->protocolVersion() === '1.1') + || $keepAlive === 'keep-alive' || $keepAlive === 'Keep-Alive' + ) { + $connection->send($response); + return; + } + $connection->close($response); + } + + /** + * ParseControllerAction. + * @param string $path + * @return array|false + * @throws ReflectionException + */ + protected static function parseControllerAction(string $path) + { + $path = str_replace('-', '', $path); + $pathExplode = explode('/', trim($path, '/')); + $isPlugin = isset($pathExplode[1]) && $pathExplode[0] === 'app'; + $configPrefix = $isPlugin ? "plugin.$pathExplode[1]." : ''; + $pathPrefix = $isPlugin ? "/app/$pathExplode[1]" : ''; + $classPrefix = $isPlugin ? "plugin\\$pathExplode[1]" : ''; + $suffix = Config::get("{$configPrefix}app.controller_suffix", ''); + $relativePath = trim(substr($path, strlen($pathPrefix)), '/'); + $pathExplode = $relativePath ? explode('/', $relativePath) : []; + + $action = 'index'; + if ($controllerAction = static::guessControllerAction($pathExplode, $action, $suffix, $classPrefix)) { + return $controllerAction; + } + if (count($pathExplode) <= 1) { + return false; + } + $action = end($pathExplode); + unset($pathExplode[count($pathExplode) - 1]); + return static::guessControllerAction($pathExplode, $action, $suffix, $classPrefix); + } + + /** + * GuessControllerAction. + * @param $pathExplode + * @param $action + * @param $suffix + * @param $classPrefix + * @return array|false + * @throws ReflectionException + */ + protected static function guessControllerAction($pathExplode, $action, $suffix, $classPrefix) + { + $map[] = trim("$classPrefix\\app\\controller\\" . implode('\\', $pathExplode), '\\'); + foreach ($pathExplode as $index => $section) { + $tmp = $pathExplode; + array_splice($tmp, $index, 1, [$section, 'controller']); + $map[] = trim("$classPrefix\\" . implode('\\', array_merge(['app'], $tmp)), '\\'); + } + foreach ($map as $item) { + $map[] = $item . '\\index'; + } + + foreach ($map as $controllerClass) { + // Remove xx\xx\controller + if (substr($controllerClass, -11) === '\\controller') { + continue; + } + $controllerClass .= $suffix; + if ($controllerAction = static::getControllerAction($controllerClass, $action)) { + return $controllerAction; + } + } + return false; + } + + /** + * GetControllerAction. + * @param string $controllerClass + * @param string $action + * @return array|false + * @throws ReflectionException + */ + protected static function getControllerAction(string $controllerClass, string $action) + { + // Disable calling magic methods + if (strpos($action, '__') === 0) { + return false; + } + if (($controllerClass = static::getController($controllerClass)) && ($action = static::getAction($controllerClass, $action))) { + return [ + 'plugin' => static::getPluginByClass($controllerClass), + 'app' => static::getAppByController($controllerClass), + 'controller' => $controllerClass, + 'action' => $action + ]; + } + return false; + } + + /** + * GetController. + * @param string $controllerClass + * @return string|false + * @throws ReflectionException + */ + protected static function getController(string $controllerClass) + { + if (class_exists($controllerClass)) { + return (new ReflectionClass($controllerClass))->name; + } + $explodes = explode('\\', strtolower(ltrim($controllerClass, '\\'))); + $basePath = $explodes[0] === 'plugin' ? BASE_PATH . '/plugin' : static::$appPath; + unset($explodes[0]); + $fileName = array_pop($explodes) . '.php'; + $found = true; + foreach ($explodes as $pathSection) { + if (!$found) { + break; + } + $dirs = Util::scanDir($basePath, false); + $found = false; + foreach ($dirs as $name) { + $path = "$basePath/$name"; + if (is_dir($path) && strtolower($name) === $pathSection) { + $basePath = $path; + $found = true; + break; + } + } + } + if (!$found) { + return false; + } + foreach (scandir($basePath) ?: [] as $name) { + if (strtolower($name) === $fileName) { + require_once "$basePath/$name"; + if (class_exists($controllerClass, false)) { + return (new ReflectionClass($controllerClass))->name; + } + } + } + return false; + } + + /** + * GetAction. + * @param string $controllerClass + * @param string $action + * @return string|false + */ + protected static function getAction(string $controllerClass, string $action) + { + $methods = get_class_methods($controllerClass); + $action = strtolower($action); + $found = false; + foreach ($methods as $candidate) { + if (strtolower($candidate) === $action) { + $action = $candidate; + $found = true; + break; + } + } + if ($found) { + return $action; + } + // Action is not public method + if (method_exists($controllerClass, $action)) { + return false; + } + if (method_exists($controllerClass, '__call')) { + return $action; + } + return false; + } + + /** + * GetPluginByClass. + * @param string $controllerClass + * @return mixed|string + */ + public static function getPluginByClass(string $controllerClass) + { + $controllerClass = trim($controllerClass, '\\'); + $tmp = explode('\\', $controllerClass, 3); + if ($tmp[0] !== 'plugin') { + return ''; + } + return $tmp[1] ?? ''; + } + + /** + * GetPluginByPath. + * @param string $path + * @return mixed|string + */ + public static function getPluginByPath(string $path) + { + $path = trim($path, '/'); + $tmp = explode('/', $path, 3); + if ($tmp[0] !== 'app') { + return ''; + } + return $tmp[1] ?? ''; + } + + /** + * GetAppByController. + * @param string $controllerClass + * @return mixed|string + */ + protected static function getAppByController(string $controllerClass) + { + $controllerClass = trim($controllerClass, '\\'); + $tmp = explode('\\', $controllerClass, 5); + $pos = $tmp[0] === 'plugin' ? 3 : 1; + if (!isset($tmp[$pos])) { + return ''; + } + return strtolower($tmp[$pos]) === 'controller' ? '' : $tmp[$pos]; + } + + /** + * ExecPhpFile. + * @param string $file + * @return false|string + */ + public static function execPhpFile(string $file) + { + ob_start(); + // Try to include php file. + try { + include $file; + } catch (Exception $e) { + echo $e; + } + return ob_get_clean(); + } + + /** + * GetRealMethod. + * @param string $class + * @param string $method + * @return string + */ + protected static function getRealMethod(string $class, string $method): string + { + $method = strtolower($method); + $methods = get_class_methods($class); + foreach ($methods as $candidate) { + if (strtolower($candidate) === $method) { + return $candidate; + } + } + return $method; + } + + /** + * Config. + * @param string $plugin + * @param string $key + * @param $default + * @return array|mixed|null + */ + protected static function config(string $plugin, string $key, $default = null) + { + return Config::get($plugin ? "plugin.$plugin.$key" : $key, $default); + } + + + /** + * @param mixed $data + * @return string + */ + protected static function stringify($data): string + { + $type = gettype($data); + switch ($type) { + case 'boolean': + return $data ? 'true' : 'false'; + case 'NULL': + return 'NULL'; + case 'array': + return 'Array'; + case 'object': + if (!method_exists($data, '__toString')) { + return 'Object'; + } + default: + return (string)$data; + } + } +} diff --git a/vendor/workerman/webman-framework/src/Bootstrap.php b/vendor/workerman/webman-framework/src/Bootstrap.php new file mode 100644 index 0000000..50774a1 --- /dev/null +++ b/vendor/workerman/webman-framework/src/Bootstrap.php @@ -0,0 +1,28 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace Webman; + +use Workerman\Worker; + +interface Bootstrap +{ + /** + * onWorkerStart + * + * @param Worker|null $worker + * @return mixed + */ + public static function start(?Worker $worker); +} diff --git a/vendor/workerman/webman-framework/src/Config.php b/vendor/workerman/webman-framework/src/Config.php new file mode 100644 index 0000000..b0f4918 --- /dev/null +++ b/vendor/workerman/webman-framework/src/Config.php @@ -0,0 +1,296 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace Webman; + +use FilesystemIterator; +use RecursiveDirectoryIterator; +use RecursiveIteratorIterator; +use function array_replace_recursive; +use function array_reverse; +use function count; +use function explode; +use function in_array; +use function is_array; +use function is_dir; +use function is_file; +use function key; +use function str_replace; + +class Config +{ + + /** + * @var array + */ + protected static $config = []; + + /** + * @var string + */ + protected static $configPath = ''; + + /** + * @var bool + */ + protected static $loaded = false; + + /** + * Load. + * @param string $configPath + * @param array $excludeFile + * @param string|null $key + * @return void + */ + public static function load(string $configPath, array $excludeFile = [], string $key = null) + { + static::$configPath = $configPath; + if (!$configPath) { + return; + } + static::$loaded = false; + $config = static::loadFromDir($configPath, $excludeFile); + if (!$config) { + static::$loaded = true; + return; + } + if ($key !== null) { + foreach (array_reverse(explode('.', $key)) as $k) { + $config = [$k => $config]; + } + } + static::$config = array_replace_recursive(static::$config, $config); + static::formatConfig(); + static::$loaded = true; + } + + /** + * This deprecated method will certainly be removed in the future. + * @param string $configPath + * @param array $excludeFile + * @return void + * @deprecated + */ + public static function reload(string $configPath, array $excludeFile = []) + { + static::load($configPath, $excludeFile); + } + + /** + * Clear. + * @return void + */ + public static function clear() + { + static::$config = []; + } + + /** + * FormatConfig. + * @return void + */ + protected static function formatConfig() + { + $config = static::$config; + // Merge log config + foreach ($config['plugin'] ?? [] as $firm => $projects) { + if (isset($projects['app'])) { + foreach ($projects['log'] ?? [] as $key => $item) { + $config['log']["plugin.$firm.$key"] = $item; + } + } + foreach ($projects as $name => $project) { + if (!is_array($project)) { + continue; + } + foreach ($project['log'] ?? [] as $key => $item) { + $config['log']["plugin.$firm.$name.$key"] = $item; + } + } + } + // Merge database config + foreach ($config['plugin'] ?? [] as $firm => $projects) { + if (isset($projects['app'])) { + foreach ($projects['database']['connections'] ?? [] as $key => $connection) { + $config['database']['connections']["plugin.$firm.$key"] = $connection; + } + } + foreach ($projects as $name => $project) { + if (!is_array($project)) { + continue; + } + foreach ($project['database']['connections'] ?? [] as $key => $connection) { + $config['database']['connections']["plugin.$firm.$name.$key"] = $connection; + } + } + } + if (!empty($config['database']['connections'])) { + $config['database']['default'] = $config['database']['default'] ?? key($config['database']['connections']); + } + // Merge thinkorm config + foreach ($config['plugin'] ?? [] as $firm => $projects) { + if (isset($projects['app'])) { + foreach ($projects['thinkorm']['connections'] ?? [] as $key => $connection) { + $config['thinkorm']['connections']["plugin.$firm.$key"] = $connection; + } + } + foreach ($projects as $name => $project) { + if (!is_array($project)) { + continue; + } + foreach ($project['thinkorm']['connections'] ?? [] as $key => $connection) { + $config['thinkorm']['connections']["plugin.$firm.$name.$key"] = $connection; + } + } + } + if (!empty($config['thinkorm']['connections'])) { + $config['thinkorm']['default'] = $config['thinkorm']['default'] ?? key($config['thinkorm']['connections']); + } + // Merge redis config + foreach ($config['plugin'] ?? [] as $firm => $projects) { + if (isset($projects['app'])) { + foreach ($projects['redis'] ?? [] as $key => $connection) { + $config['redis']["plugin.$firm.$key"] = $connection; + } + } + foreach ($projects as $name => $project) { + if (!is_array($project)) { + continue; + } + foreach ($project['redis'] ?? [] as $key => $connection) { + $config['redis']["plugin.$firm.$name.$key"] = $connection; + } + } + } + static::$config = $config; + } + + /** + * LoadFromDir. + * @param string $configPath + * @param array $excludeFile + * @return array + */ + public static function loadFromDir(string $configPath, array $excludeFile = []): array + { + $allConfig = []; + $dirIterator = new RecursiveDirectoryIterator($configPath, FilesystemIterator::FOLLOW_SYMLINKS); + $iterator = new RecursiveIteratorIterator($dirIterator); + foreach ($iterator as $file) { + /** var SplFileInfo $file */ + if (is_dir($file) || $file->getExtension() != 'php' || in_array($file->getBaseName('.php'), $excludeFile)) { + continue; + } + $appConfigFile = $file->getPath() . '/app.php'; + if (!is_file($appConfigFile)) { + continue; + } + $relativePath = str_replace($configPath . DIRECTORY_SEPARATOR, '', substr($file, 0, -4)); + $explode = array_reverse(explode(DIRECTORY_SEPARATOR, $relativePath)); + if (count($explode) >= 2) { + $appConfig = include $appConfigFile; + if (empty($appConfig['enable'])) { + continue; + } + } + $config = include $file; + foreach ($explode as $section) { + $tmp = []; + $tmp[$section] = $config; + $config = $tmp; + } + $allConfig = array_replace_recursive($allConfig, $config); + } + return $allConfig; + } + + /** + * Get. + * @param string|null $key + * @param mixed $default + * @return array|mixed|void|null + */ + public static function get(string $key = null, $default = null) + { + if ($key === null) { + return static::$config; + } + $keyArray = explode('.', $key); + $value = static::$config; + $found = true; + foreach ($keyArray as $index) { + if (!isset($value[$index])) { + if (static::$loaded) { + return $default; + } + $found = false; + break; + } + $value = $value[$index]; + } + if ($found) { + return $value; + } + return static::read($key, $default); + } + + /** + * Read. + * @param string $key + * @param mixed $default + * @return array|mixed|null + */ + protected static function read(string $key, $default = null) + { + $path = static::$configPath; + if ($path === '') { + return $default; + } + $keys = $keyArray = explode('.', $key); + foreach ($keyArray as $index => $section) { + unset($keys[$index]); + if (is_file($file = "$path/$section.php")) { + $config = include $file; + return static::find($keys, $config, $default); + } + if (!is_dir($path = "$path/$section")) { + return $default; + } + } + return $default; + } + + /** + * Find. + * @param array $keyArray + * @param mixed $stack + * @param mixed $default + * @return array|mixed + */ + protected static function find(array $keyArray, $stack, $default) + { + if (!is_array($stack)) { + return $default; + } + $value = $stack; + foreach ($keyArray as $index) { + if (!isset($value[$index])) { + return $default; + } + $value = $value[$index]; + } + return $value; + } + +} diff --git a/vendor/workerman/webman-framework/src/Container.php b/vendor/workerman/webman-framework/src/Container.php new file mode 100644 index 0000000..ef39377 --- /dev/null +++ b/vendor/workerman/webman-framework/src/Container.php @@ -0,0 +1,84 @@ +instances[$name])) { + if (isset($this->definitions[$name])) { + $this->instances[$name] = call_user_func($this->definitions[$name], $this); + } else { + if (!class_exists($name)) { + throw new NotFoundException("Class '$name' not found"); + } + $this->instances[$name] = new $name(); + } + } + return $this->instances[$name]; + } + + /** + * Has. + * @param string $name + * @return bool + */ + public function has(string $name): bool + { + return array_key_exists($name, $this->instances) + || array_key_exists($name, $this->definitions); + } + + /** + * Make. + * @param string $name + * @param array $constructor + * @return mixed + * @throws NotFoundException + */ + public function make(string $name, array $constructor = []) + { + if (!class_exists($name)) { + throw new NotFoundException("Class '$name' not found"); + } + return new $name(... array_values($constructor)); + } + + /** + * AddDefinitions. + * @param array $definitions + * @return $this + */ + public function addDefinitions(array $definitions): Container + { + $this->definitions = array_merge($this->definitions, $definitions); + return $this; + } + +} diff --git a/vendor/workerman/webman-framework/src/Context.php b/vendor/workerman/webman-framework/src/Context.php new file mode 100644 index 0000000..b72abf7 --- /dev/null +++ b/vendor/workerman/webman-framework/src/Context.php @@ -0,0 +1,129 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace Webman; + +use Fiber; +use SplObjectStorage; +use StdClass; +use Swow\Coroutine; +use WeakMap; +use Workerman\Events\Revolt; +use Workerman\Events\Swoole; +use Workerman\Events\Swow; +use Workerman\Worker; +use function property_exists; + +/** + * Class Context + * @package Webman + */ +class Context +{ + + /** + * @var SplObjectStorage|WeakMap + */ + protected static $objectStorage; + + /** + * @var StdClass + */ + protected static $object; + + /** + * @return StdClass + */ + protected static function getObject(): StdClass + { + if (!static::$objectStorage) { + static::$objectStorage = class_exists(WeakMap::class) ? new WeakMap() : new SplObjectStorage(); + static::$object = new StdClass; + } + $key = static::getKey(); + if (!isset(static::$objectStorage[$key])) { + static::$objectStorage[$key] = new StdClass; + } + return static::$objectStorage[$key]; + } + + /** + * @return mixed + */ + protected static function getKey() + { + switch (Worker::$eventLoopClass) { + case Revolt::class: + return Fiber::getCurrent(); + case Swoole::class: + return \Swoole\Coroutine::getContext(); + case Swow::class: + return Coroutine::getCurrent(); + } + return static::$object; + } + + /** + * @param string|null $key + * @return mixed + */ + public static function get(string $key = null) + { + $obj = static::getObject(); + if ($key === null) { + return $obj; + } + return $obj->$key ?? null; + } + + /** + * @param string $key + * @param $value + * @return void + */ + public static function set(string $key, $value): void + { + $obj = static::getObject(); + $obj->$key = $value; + } + + /** + * @param string $key + * @return void + */ + public static function delete(string $key): void + { + $obj = static::getObject(); + unset($obj->$key); + } + + /** + * @param string $key + * @return bool + */ + public static function has(string $key): bool + { + $obj = static::getObject(); + return property_exists($obj, $key); + } + + /** + * @return void + */ + public static function destroy(): void + { + unset(static::$objectStorage[static::getKey()]); + } +} diff --git a/vendor/workerman/webman-framework/src/Exception/ExceptionHandler.php b/vendor/workerman/webman-framework/src/Exception/ExceptionHandler.php new file mode 100644 index 0000000..b8f53e4 --- /dev/null +++ b/vendor/workerman/webman-framework/src/Exception/ExceptionHandler.php @@ -0,0 +1,118 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace Webman\Exception; + +use Psr\Log\LoggerInterface; +use Throwable; +use Webman\Http\Request; +use Webman\Http\Response; +use function json_encode; +use function nl2br; +use function trim; + +/** + * Class Handler + * @package support\exception + */ +class ExceptionHandler implements ExceptionHandlerInterface +{ + /** + * @var LoggerInterface + */ + protected $logger = null; + + /** + * @var bool + */ + protected $debug = false; + + /** + * @var array + */ + public $dontReport = []; + + /** + * ExceptionHandler constructor. + * @param $logger + * @param $debug + */ + public function __construct($logger, $debug) + { + $this->logger = $logger; + $this->debug = $debug; + } + + /** + * @param Throwable $exception + * @return void + */ + public function report(Throwable $exception) + { + if ($this->shouldntReport($exception)) { + return; + } + $logs = ''; + if ($request = \request()) { + $logs = $request->getRealIp() . ' ' . $request->method() . ' ' . trim($request->fullUrl(), '/'); + } + $this->logger->error($logs . PHP_EOL . $exception); + } + + /** + * @param Request $request + * @param Throwable $exception + * @return Response + */ + public function render(Request $request, Throwable $exception): Response + { + $code = $exception->getCode(); + if ($request->expectsJson()) { + $json = ['code' => $code ?: 500, 'msg' => $this->debug ? $exception->getMessage() : 'Server internal error']; + $this->debug && $json['traces'] = (string)$exception; + return new Response(200, ['Content-Type' => 'application/json'], + json_encode($json, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); + } + $error = $this->debug ? nl2br((string)$exception) : 'Server internal error'; + return new Response(500, [], $error); + } + + /** + * @param Throwable $e + * @return bool + */ + protected function shouldntReport(Throwable $e): bool + { + foreach ($this->dontReport as $type) { + if ($e instanceof $type) { + return true; + } + } + return false; + } + + /** + * Compatible $this->_debug + * + * @param string $name + * @return bool|null + */ + public function __get(string $name) + { + if ($name === '_debug') { + return $this->debug; + } + return null; + } +} diff --git a/vendor/workerman/webman-framework/src/Exception/ExceptionHandlerInterface.php b/vendor/workerman/webman-framework/src/Exception/ExceptionHandlerInterface.php new file mode 100644 index 0000000..8868707 --- /dev/null +++ b/vendor/workerman/webman-framework/src/Exception/ExceptionHandlerInterface.php @@ -0,0 +1,35 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace Webman\Exception; + +use Throwable; +use Webman\Http\Request; +use Webman\Http\Response; + +interface ExceptionHandlerInterface +{ + /** + * @param Throwable $exception + * @return mixed + */ + public function report(Throwable $exception); + + /** + * @param Request $request + * @param Throwable $exception + * @return Response + */ + public function render(Request $request, Throwable $exception): Response; +} \ No newline at end of file diff --git a/vendor/workerman/webman-framework/src/Exception/FileException.php b/vendor/workerman/webman-framework/src/Exception/FileException.php new file mode 100644 index 0000000..fa5dbe6 --- /dev/null +++ b/vendor/workerman/webman-framework/src/Exception/FileException.php @@ -0,0 +1,25 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace Webman\Exception; + +use RuntimeException; + +/** + * Class FileException + * @package Webman\Exception + */ +class FileException extends RuntimeException +{ +} \ No newline at end of file diff --git a/vendor/workerman/webman-framework/src/Exception/NotFoundException.php b/vendor/workerman/webman-framework/src/Exception/NotFoundException.php new file mode 100644 index 0000000..735c1d4 --- /dev/null +++ b/vendor/workerman/webman-framework/src/Exception/NotFoundException.php @@ -0,0 +1,25 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace Webman\Exception; + +use Psr\Container\NotFoundExceptionInterface; + +/** + * Class NotFoundException + * @package Webman\Exception + */ +class NotFoundException extends \Exception implements NotFoundExceptionInterface +{ +} diff --git a/vendor/workerman/webman-framework/src/File.php b/vendor/workerman/webman-framework/src/File.php new file mode 100644 index 0000000..a43328c --- /dev/null +++ b/vendor/workerman/webman-framework/src/File.php @@ -0,0 +1,56 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace Webman; + +use SplFileInfo; +use Webman\Exception\FileException; +use function chmod; +use function is_dir; +use function mkdir; +use function pathinfo; +use function restore_error_handler; +use function set_error_handler; +use function sprintf; +use function strip_tags; +use function umask; + +class File extends SplFileInfo +{ + + /** + * Move. + * @param string $destination + * @return File + */ + public function move(string $destination): File + { + set_error_handler(function ($type, $msg) use (&$error) { + $error = $msg; + }); + $path = pathinfo($destination, PATHINFO_DIRNAME); + if (!is_dir($path) && !mkdir($path, 0777, true)) { + restore_error_handler(); + throw new FileException(sprintf('Unable to create the "%s" directory (%s)', $path, strip_tags($error))); + } + if (!rename($this->getPathname(), $destination)) { + restore_error_handler(); + throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $destination, strip_tags($error))); + } + restore_error_handler(); + @chmod($destination, 0666 & ~umask()); + return new self($destination); + } + +} \ No newline at end of file diff --git a/vendor/workerman/webman-framework/src/FileSessionHandler.php b/vendor/workerman/webman-framework/src/FileSessionHandler.php new file mode 100644 index 0000000..ed0b3f2 --- /dev/null +++ b/vendor/workerman/webman-framework/src/FileSessionHandler.php @@ -0,0 +1,25 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Webman; + +/** + * This deprecated class will certainly be removed in the future. + * Please use Webman\Session\FileSessionHandler + * @deprecated + * @package Webman + */ +class FileSessionHandler extends Session\FileSessionHandler +{ + +} diff --git a/vendor/workerman/webman-framework/src/Http/Request.php b/vendor/workerman/webman-framework/src/Http/Request.php new file mode 100644 index 0000000..0690e97 --- /dev/null +++ b/vendor/workerman/webman-framework/src/Http/Request.php @@ -0,0 +1,318 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace Webman\Http; + +use Webman\Route\Route; +use function current; +use function filter_var; +use function ip2long; +use function is_array; +use function strpos; +use const FILTER_FLAG_IPV4; +use const FILTER_FLAG_NO_PRIV_RANGE; +use const FILTER_FLAG_NO_RES_RANGE; +use const FILTER_VALIDATE_IP; + +/** + * Class Request + * @package Webman\Http + */ +class Request extends \Workerman\Protocols\Http\Request +{ + /** + * @var string + */ + public $plugin = null; + + /** + * @var string + */ + public $app = null; + + /** + * @var string + */ + public $controller = null; + + /** + * @var string + */ + public $action = null; + + /** + * @var Route + */ + public $route = null; + + /** + * @return mixed|null + */ + public function all() + { + return $this->post() + $this->get(); + } + + /** + * Input + * @param string $name + * @param mixed $default + * @return mixed|null + */ + public function input(string $name, $default = null) + { + $post = $this->post(); + if (isset($post[$name])) { + return $post[$name]; + } + $get = $this->get(); + return $get[$name] ?? $default; + } + + /** + * Only + * @param array $keys + * @return array + */ + public function only(array $keys): array + { + $all = $this->all(); + $result = []; + foreach ($keys as $key) { + if (isset($all[$key])) { + $result[$key] = $all[$key]; + } + } + return $result; + } + + /** + * Except + * @param array $keys + * @return mixed|null + */ + public function except(array $keys) + { + $all = $this->all(); + foreach ($keys as $key) { + unset($all[$key]); + } + return $all; + } + + /** + * File + * @param string|null $name + * @return null|UploadFile[]|UploadFile + */ + public function file($name = null) + { + $files = parent::file($name); + if (null === $files) { + return $name === null ? [] : null; + } + if ($name !== null) { + // Multi files + if (is_array(current($files))) { + return $this->parseFiles($files); + } + return $this->parseFile($files); + } + $uploadFiles = []; + foreach ($files as $name => $file) { + // Multi files + if (is_array(current($file))) { + $uploadFiles[$name] = $this->parseFiles($file); + } else { + $uploadFiles[$name] = $this->parseFile($file); + } + } + return $uploadFiles; + } + + /** + * ParseFile + * @param array $file + * @return UploadFile + */ + protected function parseFile(array $file): UploadFile + { + return new UploadFile($file['tmp_name'], $file['name'], $file['type'], $file['error']); + } + + /** + * ParseFiles + * @param array $files + * @return array + */ + protected function parseFiles(array $files): array + { + $uploadFiles = []; + foreach ($files as $key => $file) { + if (is_array(current($file))) { + $uploadFiles[$key] = $this->parseFiles($file); + } else { + $uploadFiles[$key] = $this->parseFile($file); + } + } + return $uploadFiles; + } + + /** + * GetRemoteIp + * @return string + */ + public function getRemoteIp(): string + { + return $this->connection->getRemoteIp(); + } + + /** + * GetRemotePort + * @return int + */ + public function getRemotePort(): int + { + return $this->connection->getRemotePort(); + } + + /** + * GetLocalIp + * @return string + */ + public function getLocalIp(): string + { + return $this->connection->getLocalIp(); + } + + /** + * GetLocalPort + * @return int + */ + public function getLocalPort(): int + { + return $this->connection->getLocalPort(); + } + + /** + * GetRealIp + * @param bool $safeMode + * @return string + */ + public function getRealIp(bool $safeMode = true): string + { + $remoteIp = $this->getRemoteIp(); + if ($safeMode && !static::isIntranetIp($remoteIp)) { + return $remoteIp; + } + $ip = $this->header('x-real-ip', $this->header('x-forwarded-for', + $this->header('client-ip', $this->header('x-client-ip', + $this->header('via', $remoteIp))))); + return filter_var($ip, FILTER_VALIDATE_IP) ? $ip : $remoteIp; + } + + /** + * Url + * @return string + */ + public function url(): string + { + return '//' . $this->host() . $this->path(); + } + + /** + * FullUrl + * @return string + */ + public function fullUrl(): string + { + return '//' . $this->host() . $this->uri(); + } + + /** + * IsAjax + * @return bool + */ + public function isAjax(): bool + { + return $this->header('X-Requested-With') === 'XMLHttpRequest'; + } + + /** + * IsPjax + * @return bool + */ + public function isPjax(): bool + { + return (bool)$this->header('X-PJAX'); + } + + /** + * ExpectsJson + * @return bool + */ + public function expectsJson(): bool + { + return ($this->isAjax() && !$this->isPjax()) || $this->acceptJson(); + } + + /** + * AcceptJson + * @return bool + */ + public function acceptJson(): bool + { + return false !== strpos($this->header('accept', ''), 'json'); + } + + /** + * IsIntranetIp + * @param string $ip + * @return bool + */ + public static function isIntranetIp(string $ip): bool + { + // Not validate ip . + if (!filter_var($ip, FILTER_VALIDATE_IP)) { + return false; + } + // Is intranet ip ? For IPv4, the result of false may not be accurate, so we need to check it manually later . + if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) { + return true; + } + // Manual check only for IPv4 . + if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { + return false; + } + // Manual check . + $reservedIps = [ + 1681915904 => 1686110207, // 100.64.0.0 - 100.127.255.255 + 3221225472 => 3221225727, // 192.0.0.0 - 192.0.0.255 + 3221225984 => 3221226239, // 192.0.2.0 - 192.0.2.255 + 3227017984 => 3227018239, // 192.88.99.0 - 192.88.99.255 + 3323068416 => 3323199487, // 198.18.0.0 - 198.19.255.255 + 3325256704 => 3325256959, // 198.51.100.0 - 198.51.100.255 + 3405803776 => 3405804031, // 203.0.113.0 - 203.0.113.255 + 3758096384 => 4026531839, // 224.0.0.0 - 239.255.255.255 + ]; + $ipLong = ip2long($ip); + foreach ($reservedIps as $ipStart => $ipEnd) { + if (($ipLong >= $ipStart) && ($ipLong <= $ipEnd)) { + return true; + } + } + return false; + } + +} diff --git a/vendor/workerman/webman-framework/src/Http/Response.php b/vendor/workerman/webman-framework/src/Http/Response.php new file mode 100644 index 0000000..259146b --- /dev/null +++ b/vendor/workerman/webman-framework/src/Http/Response.php @@ -0,0 +1,87 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace Webman\Http; + +use Throwable; +use Webman\App; +use function filemtime; +use function gmdate; + +/** + * Class Response + * @package Webman\Http + */ +class Response extends \Workerman\Protocols\Http\Response +{ + /** + * @var Throwable + */ + protected $exception = null; + + /** + * File + * @param string $file + * @return $this + */ + public function file(string $file): Response + { + if ($this->notModifiedSince($file)) { + return $this->withStatus(304); + } + return $this->withFile($file); + } + + /** + * Download + * @param string $file + * @param string $downloadName + * @return $this + */ + public function download(string $file, string $downloadName = ''): Response + { + $this->withFile($file); + if ($downloadName) { + $this->header('Content-Disposition', "attachment; filename=\"$downloadName\""); + } + return $this; + } + + /** + * NotModifiedSince + * @param string $file + * @return bool + */ + protected function notModifiedSince(string $file): bool + { + $ifModifiedSince = App::request()->header('if-modified-since'); + if ($ifModifiedSince === null || !($mtime = filemtime($file))) { + return false; + } + return $ifModifiedSince === gmdate('D, d M Y H:i:s', $mtime) . ' GMT'; + } + + /** + * Exception + * @param Throwable|null $exception + * @return Throwable|null + */ + public function exception(Throwable $exception = null): ?Throwable + { + if ($exception) { + $this->exception = $exception; + } + return $this->exception; + } +} diff --git a/vendor/workerman/webman-framework/src/Http/UploadFile.php b/vendor/workerman/webman-framework/src/Http/UploadFile.php new file mode 100644 index 0000000..8a63421 --- /dev/null +++ b/vendor/workerman/webman-framework/src/Http/UploadFile.php @@ -0,0 +1,111 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace Webman\Http; + +use Webman\File; +use function pathinfo; + +/** + * Class UploadFile + * @package Webman\Http + */ +class UploadFile extends File +{ + /** + * @var string + */ + protected $uploadName = null; + + /** + * @var string + */ + protected $uploadMimeType = null; + + /** + * @var int + */ + protected $uploadErrorCode = null; + + /** + * UploadFile constructor. + * + * @param string $fileName + * @param string $uploadName + * @param string $uploadMimeType + * @param int $uploadErrorCode + */ + public function __construct(string $fileName, string $uploadName, string $uploadMimeType, int $uploadErrorCode) + { + $this->uploadName = $uploadName; + $this->uploadMimeType = $uploadMimeType; + $this->uploadErrorCode = $uploadErrorCode; + parent::__construct($fileName); + } + + /** + * GetUploadName + * @return string + */ + public function getUploadName(): ?string + { + return $this->uploadName; + } + + /** + * GetUploadMimeType + * @return string + */ + public function getUploadMimeType(): ?string + { + return $this->uploadMimeType; + } + + /** + * GetUploadExtension + * @return string + */ + public function getUploadExtension(): string + { + return pathinfo($this->uploadName, PATHINFO_EXTENSION); + } + + /** + * GetUploadErrorCode + * @return int + */ + public function getUploadErrorCode(): ?int + { + return $this->uploadErrorCode; + } + + /** + * IsValid + * @return bool + */ + public function isValid(): bool + { + return $this->uploadErrorCode === UPLOAD_ERR_OK; + } + + /** + * GetUploadMineType + * @return string + * @deprecated + */ + public function getUploadMineType(): ?string + { + return $this->uploadMimeType; + } +} diff --git a/vendor/workerman/webman-framework/src/Install.php b/vendor/workerman/webman-framework/src/Install.php new file mode 100644 index 0000000..cc02c4c --- /dev/null +++ b/vendor/workerman/webman-framework/src/Install.php @@ -0,0 +1,59 @@ + 'start.php', + 'windows.php' => 'windows.php', + 'support/bootstrap.php' => 'support/bootstrap.php', + 'support/helpers.php' => 'support/helpers.php', + ]; + + /** + * Install + * @return void + */ + public static function install() + { + static::installByRelation(); + } + + /** + * Uninstall + * @return void + */ + public static function uninstall() + { + + } + + /** + * InstallByRelation + * @return void + */ + public static function installByRelation() + { + foreach (static::$pathRelation as $source => $dest) { + if ($pos = strrpos($dest, '/')) { + $parentDir = base_path() . '/' . substr($dest, 0, $pos); + if (!is_dir($parentDir)) { + mkdir($parentDir, 0777, true); + } + } + $sourceFile = __DIR__ . "/$source"; + copy_dir($sourceFile, base_path() . "/$dest", true); + echo "Create $dest\r\n"; + if (is_file($sourceFile)) { + @unlink($sourceFile); + } + } + } + +} diff --git a/vendor/workerman/webman-framework/src/Middleware.php b/vendor/workerman/webman-framework/src/Middleware.php new file mode 100644 index 0000000..1bdbf06 --- /dev/null +++ b/vendor/workerman/webman-framework/src/Middleware.php @@ -0,0 +1,81 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace Webman; + + +use RuntimeException; +use function array_merge; +use function array_reverse; +use function is_array; +use function method_exists; + +class Middleware +{ + + /** + * @var array + */ + protected static $instances = []; + + /** + * @param mixed $allMiddlewares + * @param string $plugin + * @return void + */ + public static function load($allMiddlewares, string $plugin = '') + { + if (!is_array($allMiddlewares)) { + return; + } + foreach ($allMiddlewares as $appName => $middlewares) { + if (!is_array($middlewares)) { + throw new RuntimeException('Bad middleware config'); + } + foreach ($middlewares as $className) { + if (method_exists($className, 'process')) { + static::$instances[$plugin][$appName][] = [$className, 'process']; + } else { + // @todo Log + echo "middleware $className::process not exsits\n"; + } + } + } + } + + /** + * @param string $plugin + * @param string $appName + * @param bool $withGlobalMiddleware + * @return array|mixed + */ + public static function getMiddleware(string $plugin, string $appName, bool $withGlobalMiddleware = true) + { + $globalMiddleware = $withGlobalMiddleware && isset(static::$instances[$plugin]['']) ? static::$instances[$plugin][''] : []; + if ($appName === '') { + return array_reverse($globalMiddleware); + } + $appMiddleware = static::$instances[$plugin][$appName] ?? []; + return array_reverse(array_merge($globalMiddleware, $appMiddleware)); + } + + /** + * @return void + * @deprecated + */ + public static function container($_) + { + + } +} diff --git a/vendor/workerman/webman-framework/src/MiddlewareInterface.php b/vendor/workerman/webman-framework/src/MiddlewareInterface.php new file mode 100644 index 0000000..47c6d51 --- /dev/null +++ b/vendor/workerman/webman-framework/src/MiddlewareInterface.php @@ -0,0 +1,30 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace Webman; + +use Webman\Http\Request; +use Webman\Http\Response; + +interface MiddlewareInterface +{ + /** + * Process an incoming server request. + * + * Processes an incoming server request in order to produce a response. + * If unable to produce the response itself, it may delegate to the provided + * request handler to do so. + */ + public function process(Request $request, callable $handler): Response; +} diff --git a/vendor/workerman/webman-framework/src/Route.php b/vendor/workerman/webman-framework/src/Route.php new file mode 100644 index 0000000..0854fcf --- /dev/null +++ b/vendor/workerman/webman-framework/src/Route.php @@ -0,0 +1,442 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace Webman; + +use FastRoute\Dispatcher\GroupCountBased; +use FastRoute\RouteCollector; +use FilesystemIterator; +use RecursiveDirectoryIterator; +use RecursiveIteratorIterator; +use Webman\Route\Route as RouteObject; +use function array_diff; +use function array_values; +use function class_exists; +use function explode; +use function FastRoute\simpleDispatcher; +use function in_array; +use function is_array; +use function is_callable; +use function is_file; +use function is_scalar; +use function is_string; +use function json_encode; +use function method_exists; +use function strpos; + +/** + * Class Route + * @package Webman + */ +class Route +{ + /** + * @var Route + */ + protected static $instance = null; + + /** + * @var GroupCountBased + */ + protected static $dispatcher = null; + + /** + * @var RouteCollector + */ + protected static $collector = null; + + /** + * @var null|callable + */ + protected static $fallback = []; + + /** + * @var array + */ + protected static $nameList = []; + + /** + * @var string + */ + protected static $groupPrefix = ''; + + /** + * @var bool + */ + protected static $disableDefaultRoute = []; + + /** + * @var RouteObject[] + */ + protected static $allRoutes = []; + + /** + * @var RouteObject[] + */ + protected $routes = []; + + /** + * @param string $path + * @param callable|mixed $callback + * @return RouteObject + */ + public static function get(string $path, $callback): RouteObject + { + return static::addRoute('GET', $path, $callback); + } + + /** + * @param string $path + * @param callable|mixed $callback + * @return RouteObject + */ + public static function post(string $path, $callback): RouteObject + { + return static::addRoute('POST', $path, $callback); + } + + /** + * @param string $path + * @param callable|mixed $callback + * @return RouteObject + */ + public static function put(string $path, $callback): RouteObject + { + return static::addRoute('PUT', $path, $callback); + } + + /** + * @param string $path + * @param callable|mixed $callback + * @return RouteObject + */ + public static function patch(string $path, $callback): RouteObject + { + return static::addRoute('PATCH', $path, $callback); + } + + /** + * @param string $path + * @param callable|mixed $callback + * @return RouteObject + */ + public static function delete(string $path, $callback): RouteObject + { + return static::addRoute('DELETE', $path, $callback); + } + + /** + * @param string $path + * @param callable|mixed $callback + * @return RouteObject + */ + public static function head(string $path, $callback): RouteObject + { + return static::addRoute('HEAD', $path, $callback); + } + + /** + * @param string $path + * @param callable|mixed $callback + * @return RouteObject + */ + public static function options(string $path, $callback): RouteObject + { + return static::addRoute('OPTIONS', $path, $callback); + } + + /** + * @param string $path + * @param callable|mixed $callback + * @return RouteObject + */ + public static function any(string $path, $callback): RouteObject + { + return static::addRoute(['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'], $path, $callback); + } + + /** + * @param $method + * @param string $path + * @param callable|mixed $callback + * @return RouteObject + */ + public static function add($method, string $path, $callback): RouteObject + { + return static::addRoute($method, $path, $callback); + } + + /** + * @param string|callable $path + * @param callable|null $callback + * @return static + */ + public static function group($path, callable $callback = null): Route + { + if ($callback === null) { + $callback = $path; + $path = ''; + } + $previousGroupPrefix = static::$groupPrefix; + static::$groupPrefix = $previousGroupPrefix . $path; + $instance = static::$instance = new static; + static::$collector->addGroup($path, $callback); + static::$instance = null; + static::$groupPrefix = $previousGroupPrefix; + return $instance; + } + + /** + * @param string $name + * @param string $controller + * @param array $options + * @return void + */ + public static function resource(string $name, string $controller, array $options = []) + { + $name = trim($name, '/'); + if (is_array($options) && !empty($options)) { + $diffOptions = array_diff($options, ['index', 'create', 'store', 'update', 'show', 'edit', 'destroy', 'recovery']); + if (!empty($diffOptions)) { + foreach ($diffOptions as $action) { + static::any("/$name/{$action}[/{id}]", [$controller, $action])->name("$name.{$action}"); + } + } + // 注册路由 由于顺序不同会导致路由无效 因此不适用循环注册 + if (in_array('index', $options)) static::get("/$name", [$controller, 'index'])->name("$name.index"); + if (in_array('create', $options)) static::get("/$name/create", [$controller, 'create'])->name("$name.create"); + if (in_array('store', $options)) static::post("/$name", [$controller, 'store'])->name("$name.store"); + if (in_array('update', $options)) static::put("/$name/{id}", [$controller, 'update'])->name("$name.update"); + if (in_array('show', $options)) static::get("/$name/{id}", [$controller, 'show'])->name("$name.show"); + if (in_array('edit', $options)) static::get("/$name/{id}/edit", [$controller, 'edit'])->name("$name.edit"); + if (in_array('destroy', $options)) static::delete("/$name/{id}", [$controller, 'destroy'])->name("$name.destroy"); + if (in_array('recovery', $options)) static::put("/$name/{id}/recovery", [$controller, 'recovery'])->name("$name.recovery"); + } else { + //为空时自动注册所有常用路由 + if (method_exists($controller, 'index')) static::get("/$name", [$controller, 'index'])->name("$name.index"); + if (method_exists($controller, 'create')) static::get("/$name/create", [$controller, 'create'])->name("$name.create"); + if (method_exists($controller, 'store')) static::post("/$name", [$controller, 'store'])->name("$name.store"); + if (method_exists($controller, 'update')) static::put("/$name/{id}", [$controller, 'update'])->name("$name.update"); + if (method_exists($controller, 'show')) static::get("/$name/{id}", [$controller, 'show'])->name("$name.show"); + if (method_exists($controller, 'edit')) static::get("/$name/{id}/edit", [$controller, 'edit'])->name("$name.edit"); + if (method_exists($controller, 'destroy')) static::delete("/$name/{id}", [$controller, 'destroy'])->name("$name.destroy"); + if (method_exists($controller, 'recovery')) static::put("/$name/{id}/recovery", [$controller, 'recovery'])->name("$name.recovery"); + } + } + + /** + * @return RouteObject[] + */ + public static function getRoutes(): array + { + return static::$allRoutes; + } + + /** + * disableDefaultRoute. + * + * @return void + */ + public static function disableDefaultRoute($plugin = '') + { + static::$disableDefaultRoute[$plugin] = true; + } + + /** + * @param string $plugin + * @return bool + */ + public static function hasDisableDefaultRoute(string $plugin = ''): bool + { + return static::$disableDefaultRoute[$plugin] ?? false; + } + + /** + * @param $middleware + * @return $this + */ + public function middleware($middleware): Route + { + foreach ($this->routes as $route) { + $route->middleware($middleware); + } + return $this; + } + + /** + * @param RouteObject $route + */ + public function collect(RouteObject $route) + { + $this->routes[] = $route; + } + + /** + * @param string $name + * @param RouteObject $instance + */ + public static function setByName(string $name, RouteObject $instance) + { + static::$nameList[$name] = $instance; + } + + /** + * @param string $name + * @return null|RouteObject + */ + public static function getByName(string $name): ?RouteObject + { + return static::$nameList[$name] ?? null; + } + + + /** + * @param string $method + * @param string $path + * @return array + */ + public static function dispatch(string $method, string $path): array + { + return static::$dispatcher->dispatch($method, $path); + } + + /** + * @param string $path + * @param callable|mixed $callback + * @return callable|false|string[] + */ + public static function convertToCallable(string $path, $callback) + { + if (is_string($callback) && strpos($callback, '@')) { + $callback = explode('@', $callback, 2); + } + + if (!is_array($callback)) { + if (!is_callable($callback)) { + $callStr = is_scalar($callback) ? $callback : 'Closure'; + echo "Route $path $callStr is not callable\n"; + return false; + } + } else { + $callback = array_values($callback); + if (!isset($callback[1]) || !class_exists($callback[0]) || !method_exists($callback[0], $callback[1])) { + echo "Route $path " . json_encode($callback) . " is not callable\n"; + return false; + } + } + + return $callback; + } + + /** + * @param array|string $methods + * @param string $path + * @param callable|mixed $callback + * @return RouteObject + */ + protected static function addRoute($methods, string $path, $callback): RouteObject + { + $route = new RouteObject($methods, static::$groupPrefix . $path, $callback); + static::$allRoutes[] = $route; + + if ($callback = static::convertToCallable($path, $callback)) { + static::$collector->addRoute($methods, $path, ['callback' => $callback, 'route' => $route]); + } + if (static::$instance) { + static::$instance->collect($route); + } + return $route; + } + + /** + * Load. + * @param mixed $paths + * @return void + */ + public static function load($paths) + { + if (!is_array($paths)) { + return; + } + static::$dispatcher = simpleDispatcher(function (RouteCollector $route) use ($paths) { + Route::setCollector($route); + foreach ($paths as $configPath) { + $routeConfigFile = $configPath . '/route.php'; + if (is_file($routeConfigFile)) { + require_once $routeConfigFile; + } + if (!is_dir($pluginConfigPath = $configPath . '/plugin')) { + continue; + } + $dirIterator = new RecursiveDirectoryIterator($pluginConfigPath, FilesystemIterator::FOLLOW_SYMLINKS); + $iterator = new RecursiveIteratorIterator($dirIterator); + foreach ($iterator as $file) { + if ($file->getBaseName('.php') !== 'route') { + continue; + } + $appConfigFile = pathinfo($file, PATHINFO_DIRNAME) . '/app.php'; + if (!is_file($appConfigFile)) { + continue; + } + $appConfig = include $appConfigFile; + if (empty($appConfig['enable'])) { + continue; + } + require_once $file; + } + } + }); + } + + /** + * SetCollector. + * @param RouteCollector $route + * @return void + */ + public static function setCollector(RouteCollector $route) + { + static::$collector = $route; + } + + /** + * Fallback. + * @param callable|mixed $callback + * @param string $plugin + * @return void + */ + public static function fallback(callable $callback, string $plugin = '') + { + static::$fallback[$plugin] = $callback; + } + + /** + * GetFallBack. + * @param string $plugin + * @return callable|null + */ + public static function getFallback(string $plugin = ''): ?callable + { + return static::$fallback[$plugin] ?? null; + } + + /** + * @return void + * @deprecated + */ + public static function container() + { + + } + +} diff --git a/vendor/workerman/webman-framework/src/Route/Route.php b/vendor/workerman/webman-framework/src/Route/Route.php new file mode 100644 index 0000000..96a7222 --- /dev/null +++ b/vendor/workerman/webman-framework/src/Route/Route.php @@ -0,0 +1,199 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace Webman\Route; + +use Webman\Route as Router; +use function array_merge; +use function count; +use function preg_replace_callback; +use function str_replace; + +/** + * Class Route + * @package Webman + */ +class Route +{ + /** + * @var string|null + */ + protected $name = null; + + /** + * @var array + */ + protected $methods = []; + + /** + * @var string + */ + protected $path = ''; + + /** + * @var callable + */ + protected $callback = null; + + /** + * @var array + */ + protected $middlewares = []; + + /** + * @var array + */ + protected $params = []; + + /** + * Route constructor. + * @param array $methods + * @param string $path + * @param callable $callback + */ + public function __construct($methods, string $path, $callback) + { + $this->methods = (array)$methods; + $this->path = $path; + $this->callback = $callback; + } + + /** + * Get name. + * @return string|null + */ + public function getName(): ?string + { + return $this->name ?? null; + } + + /** + * Name. + * @param string $name + * @return $this + */ + public function name(string $name): Route + { + $this->name = $name; + Router::setByName($name, $this); + return $this; + } + + /** + * Middleware. + * @param mixed $middleware + * @return $this|array + */ + public function middleware($middleware = null) + { + if ($middleware === null) { + return $this->middlewares; + } + $this->middlewares = array_merge($this->middlewares, is_array($middleware) ? $middleware : [$middleware]); + return $this; + } + + /** + * GetPath. + * @return string + */ + public function getPath(): string + { + return $this->path; + } + + /** + * GetMethods. + * @return array + */ + public function getMethods(): array + { + return $this->methods; + } + + /** + * GetCallback. + * @return callable|null + */ + public function getCallback() + { + return $this->callback; + } + + /** + * GetMiddleware. + * @return array + */ + public function getMiddleware(): array + { + return $this->middlewares; + } + + /** + * Param. + * @param string|null $name + * @param $default + * @return array|mixed|null + */ + public function param(string $name = null, $default = null) + { + if ($name === null) { + return $this->params; + } + return $this->params[$name] ?? $default; + } + + /** + * SetParams. + * @param array $params + * @return $this + */ + public function setParams(array $params): Route + { + $this->params = array_merge($this->params, $params); + return $this; + } + + /** + * Url. + * @param array $parameters + * @return string + */ + public function url(array $parameters = []): string + { + if (empty($parameters)) { + return $this->path; + } + $path = str_replace(['[', ']'], '', $this->path); + $path = preg_replace_callback('/\{(.*?)(?:\:[^\}]*?)*?\}/', function ($matches) use (&$parameters) { + if (!$parameters) { + return $matches[0]; + } + if (isset($parameters[$matches[1]])) { + $value = $parameters[$matches[1]]; + unset($parameters[$matches[1]]); + return $value; + } + $key = key($parameters); + if (is_int($key)) { + $value = $parameters[$key]; + unset($parameters[$key]); + return $value; + } + return $matches[0]; + }, $path); + return count($parameters) > 0 ? $path . '?' . http_build_query($parameters) : $path; + } + +} diff --git a/vendor/workerman/webman-framework/src/Session/FileSessionHandler.php b/vendor/workerman/webman-framework/src/Session/FileSessionHandler.php new file mode 100644 index 0000000..10fda70 --- /dev/null +++ b/vendor/workerman/webman-framework/src/Session/FileSessionHandler.php @@ -0,0 +1,26 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace Webman\Session; + +use Workerman\Protocols\Http\Session\FileSessionHandler as FileHandler; + +/** + * Class FileSessionHandler + * @package Webman + */ +class FileSessionHandler extends FileHandler +{ + +} diff --git a/vendor/workerman/webman-framework/src/Session/RedisClusterSessionHandler.php b/vendor/workerman/webman-framework/src/Session/RedisClusterSessionHandler.php new file mode 100644 index 0000000..c00efae --- /dev/null +++ b/vendor/workerman/webman-framework/src/Session/RedisClusterSessionHandler.php @@ -0,0 +1,22 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace Webman\Session; + +use Workerman\Protocols\Http\Session\RedisClusterSessionHandler as RedisClusterHandler; + +class RedisClusterSessionHandler extends RedisClusterHandler +{ + +} diff --git a/vendor/workerman/webman-framework/src/Session/RedisSessionHandler.php b/vendor/workerman/webman-framework/src/Session/RedisSessionHandler.php new file mode 100644 index 0000000..c124cbd --- /dev/null +++ b/vendor/workerman/webman-framework/src/Session/RedisSessionHandler.php @@ -0,0 +1,26 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace Webman\Session; + +use Workerman\Protocols\Http\Session\RedisSessionHandler as RedisHandler; + +/** + * Class FileSessionHandler + * @package Webman + */ +class RedisSessionHandler extends RedisHandler +{ + +} diff --git a/vendor/workerman/webman-framework/src/Util.php b/vendor/workerman/webman-framework/src/Util.php new file mode 100644 index 0000000..0a19632 --- /dev/null +++ b/vendor/workerman/webman-framework/src/Util.php @@ -0,0 +1,44 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace Webman; + +use function array_diff; +use function array_map; +use function scandir; + +/** + * Class Util + * @package Webman + */ +class Util +{ + /** + * ScanDir. + * @param string $basePath + * @param bool $withBasePath + * @return array + */ + public static function scanDir(string $basePath, bool $withBasePath = true): array + { + if (!is_dir($basePath)) { + return []; + } + $paths = array_diff(scandir($basePath), array('.', '..')) ?: []; + return $withBasePath ? array_map(static function ($path) use ($basePath) { + return $basePath . DIRECTORY_SEPARATOR . $path; + }, $paths) : $paths; + } + +} diff --git a/vendor/workerman/webman-framework/src/View.php b/vendor/workerman/webman-framework/src/View.php new file mode 100644 index 0000000..b50cee0 --- /dev/null +++ b/vendor/workerman/webman-framework/src/View.php @@ -0,0 +1,27 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace Webman; + +interface View +{ + /** + * Render. + * @param string $template + * @param array $vars + * @param string|null $app + * @return string + */ + public static function render(string $template, array $vars, string $app = null): string; +} diff --git a/vendor/workerman/webman-framework/src/support/App.php b/vendor/workerman/webman-framework/src/support/App.php new file mode 100644 index 0000000..53619dd --- /dev/null +++ b/vendor/workerman/webman-framework/src/support/App.php @@ -0,0 +1,151 @@ +load(); + } else { + Dotenv::createMutable(run_path())->load(); + } + } + + static::loadAllConfig(['route', 'container']); + + $errorReporting = config('app.error_reporting'); + if (isset($errorReporting)) { + error_reporting($errorReporting); + } + if ($timezone = config('app.default_timezone')) { + date_default_timezone_set($timezone); + } + + $runtimeLogsPath = runtime_path() . DIRECTORY_SEPARATOR . 'logs'; + if (!file_exists($runtimeLogsPath) || !is_dir($runtimeLogsPath)) { + if (!mkdir($runtimeLogsPath, 0777, true)) { + throw new RuntimeException("Failed to create runtime logs directory. Please check the permission."); + } + } + + $runtimeViewsPath = runtime_path() . DIRECTORY_SEPARATOR . 'views'; + if (!file_exists($runtimeViewsPath) || !is_dir($runtimeViewsPath)) { + if (!mkdir($runtimeViewsPath, 0777, true)) { + throw new RuntimeException("Failed to create runtime views directory. Please check the permission."); + } + } + + Worker::$onMasterReload = function () { + if (function_exists('opcache_get_status')) { + if ($status = opcache_get_status()) { + if (isset($status['scripts']) && $scripts = $status['scripts']) { + foreach (array_keys($scripts) as $file) { + opcache_invalidate($file, true); + } + } + } + } + }; + + $config = config('server'); + Worker::$pidFile = $config['pid_file']; + Worker::$stdoutFile = $config['stdout_file']; + Worker::$logFile = $config['log_file']; + Worker::$eventLoopClass = $config['event_loop'] ?? ''; + TcpConnection::$defaultMaxPackageSize = $config['max_package_size'] ?? 10 * 1024 * 1024; + if (property_exists(Worker::class, 'statusFile')) { + Worker::$statusFile = $config['status_file'] ?? ''; + } + if (property_exists(Worker::class, 'stopTimeout')) { + Worker::$stopTimeout = $config['stop_timeout'] ?? 2; + } + + if ($config['listen']) { + $worker = new Worker($config['listen'], $config['context']); + $propertyMap = [ + 'name', + 'count', + 'user', + 'group', + 'reusePort', + 'transport', + 'protocol' + ]; + foreach ($propertyMap as $property) { + if (isset($config[$property])) { + $worker->$property = $config[$property]; + } + } + + $worker->onWorkerStart = function ($worker) { + require_once base_path() . '/support/bootstrap.php'; + $app = new \Webman\App(config('app.request_class', Request::class), Log::channel('default'), app_path(), public_path()); + $worker->onMessage = [$app, 'onMessage']; + call_user_func([$app, 'onWorkerStart'], $worker); + }; + } + + // Windows does not support custom processes. + if (DIRECTORY_SEPARATOR === '/') { + foreach (config('process', []) as $processName => $config) { + worker_start($processName, $config); + } + foreach (config('plugin', []) as $firm => $projects) { + foreach ($projects as $name => $project) { + if (!is_array($project)) { + continue; + } + foreach ($project['process'] ?? [] as $processName => $config) { + worker_start("plugin.$firm.$name.$processName", $config); + } + } + foreach ($projects['process'] ?? [] as $processName => $config) { + worker_start("plugin.$firm.$processName", $config); + } + } + } + + Worker::runAll(); + } + + /** + * LoadAllConfig. + * @param array $excludes + * @return void + */ + public static function loadAllConfig(array $excludes = []) + { + Config::load(config_path(), $excludes); + $directory = base_path() . '/plugin'; + foreach (Util::scanDir($directory, false) as $name) { + $dir = "$directory/$name/config"; + if (is_dir($dir)) { + Config::load($dir, $excludes, "plugin.$name"); + } + } + } + +} diff --git a/vendor/workerman/webman-framework/src/support/Cache.php b/vendor/workerman/webman-framework/src/support/Cache.php new file mode 100644 index 0000000..8cf5a44 --- /dev/null +++ b/vendor/workerman/webman-framework/src/support/Cache.php @@ -0,0 +1,50 @@ +client()); + self::$instance = new Psr16Cache($adapter); + } + return static::$instance; + } + + /** + * @param $name + * @param $arguments + * @return mixed + */ + public static function __callStatic($name, $arguments) + { + return static::instance()->{$name}(... $arguments); + } +} diff --git a/vendor/workerman/webman-framework/src/support/Container.php b/vendor/workerman/webman-framework/src/support/Container.php new file mode 100644 index 0000000..c3734c7 --- /dev/null +++ b/vendor/workerman/webman-framework/src/support/Container.php @@ -0,0 +1,48 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace support; + +use Webman\Config; + +/** + * Class Container + * @package support + * @method static mixed get($name) + * @method static mixed make($name, array $parameters) + * @method static bool has($name) + */ +class Container +{ + /** + * Instance + * @param string $plugin + * @return array|mixed|void|null + */ + public static function instance(string $plugin = '') + { + return Config::get($plugin ? "plugin.$plugin.container" : 'container'); + } + + /** + * @param string $name + * @param array $arguments + * @return mixed + */ + public static function __callStatic(string $name, array $arguments) + { + $plugin = \Webman\App::getPluginByClass($name); + return static::instance($plugin)->{$name}(... $arguments); + } +} diff --git a/vendor/workerman/webman-framework/src/support/Context.php b/vendor/workerman/webman-framework/src/support/Context.php new file mode 100644 index 0000000..7a8348b --- /dev/null +++ b/vendor/workerman/webman-framework/src/support/Context.php @@ -0,0 +1,25 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace support; + +/** + * Class Context + * @package Webman + */ +class Context extends \Webman\Context +{ + +} diff --git a/vendor/workerman/webman-framework/src/support/Db.php b/vendor/workerman/webman-framework/src/support/Db.php new file mode 100644 index 0000000..2bce62f --- /dev/null +++ b/vendor/workerman/webman-framework/src/support/Db.php @@ -0,0 +1,36 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace support; + +use Closure; +use Illuminate\Database\Capsule\Manager; + +/** + * Class Db + * @package support + * @method static array select(string $query, $bindings = [], $useReadPdo = true) + * @method static int insert(string $query, $bindings = []) + * @method static int update(string $query, $bindings = []) + * @method static int delete(string $query, $bindings = []) + * @method static bool statement(string $query, $bindings = []) + * @method static mixed transaction(Closure $callback, $attempts = 1) + * @method static void beginTransaction() + * @method static void rollBack($toLevel = null) + * @method static void commit() + */ +class Db extends Manager +{ + +} \ No newline at end of file diff --git a/vendor/workerman/webman-framework/src/support/Log.php b/vendor/workerman/webman-framework/src/support/Log.php new file mode 100644 index 0000000..11512de --- /dev/null +++ b/vendor/workerman/webman-framework/src/support/Log.php @@ -0,0 +1,139 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace support; + +use Monolog\Formatter\FormatterInterface; +use Monolog\Handler\FormattableHandlerInterface; +use Monolog\Handler\HandlerInterface; +use Monolog\Logger; +use function array_values; +use function config; +use function is_array; + +/** + * Class Log + * @package support + * + * @method static void log($level, $message, array $context = []) + * @method static void debug($message, array $context = []) + * @method static void info($message, array $context = []) + * @method static void notice($message, array $context = []) + * @method static void warning($message, array $context = []) + * @method static void error($message, array $context = []) + * @method static void critical($message, array $context = []) + * @method static void alert($message, array $context = []) + * @method static void emergency($message, array $context = []) + */ +class Log +{ + /** + * @var array + */ + protected static $instance = []; + + /** + * Channel. + * @param string $name + * @return Logger + */ + public static function channel(string $name = 'default'): Logger + { + if (!isset(static::$instance[$name])) { + $config = config('log', [])[$name]; + $handlers = self::handlers($config); + $processors = self::processors($config); + static::$instance[$name] = new Logger($name, $handlers, $processors); + } + return static::$instance[$name]; + } + + /** + * Handlers. + * @param array $config + * @return array + */ + protected static function handlers(array $config): array + { + $handlerConfigs = $config['handlers'] ?? [[]]; + $handlers = []; + foreach ($handlerConfigs as $value) { + $class = $value['class'] ?? []; + $constructor = $value['constructor'] ?? []; + + $formatterConfig = $value['formatter'] ?? []; + + $class && $handlers[] = self::handler($class, $constructor, $formatterConfig); + } + + return $handlers; + } + + /** + * Handler. + * @param string $class + * @param array $constructor + * @param array $formatterConfig + * @return HandlerInterface + */ + protected static function handler(string $class, array $constructor, array $formatterConfig): HandlerInterface + { + /** @var HandlerInterface $handler */ + $handler = new $class(... array_values($constructor)); + + if ($handler instanceof FormattableHandlerInterface && $formatterConfig) { + $formatterClass = $formatterConfig['class']; + $formatterConstructor = $formatterConfig['constructor']; + + /** @var FormatterInterface $formatter */ + $formatter = new $formatterClass(... array_values($formatterConstructor)); + + $handler->setFormatter($formatter); + } + + return $handler; + } + + /** + * Processors. + * @param array $config + * @return array + */ + protected static function processors(array $config): array + { + $result = []; + if (!isset($config['processors']) && isset($config['processor'])) { + $config['processors'] = [$config['processor']]; + } + + foreach ($config['processors'] ?? [] as $value) { + if (is_array($value) && isset($value['class'])) { + $value = new $value['class'](... array_values($value['constructor'] ?? [])); + } + $result[] = $value; + } + + return $result; + } + + /** + * @param string $name + * @param array $arguments + * @return mixed + */ + public static function __callStatic(string $name, array $arguments) + { + return static::channel()->{$name}(... $arguments); + } +} diff --git a/vendor/workerman/webman-framework/src/support/Model.php b/vendor/workerman/webman-framework/src/support/Model.php new file mode 100644 index 0000000..dbbedf4 --- /dev/null +++ b/vendor/workerman/webman-framework/src/support/Model.php @@ -0,0 +1,262 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace support; + +use Closure; +use Illuminate\Contracts\Pagination\CursorPaginator; +use Illuminate\Contracts\Pagination\LengthAwarePaginator; +use Illuminate\Contracts\Pagination\Paginator; +use Illuminate\Database\Eloquent\Model as BaseModel; +use Illuminate\Database\Query\Builder; +use Illuminate\Database\Query\Expression; +use Illuminate\Database\Query\Grammars\Grammar; +use Illuminate\Database\Query\Processors\Processor; +use Illuminate\Support\Collection; +use Illuminate\Support\LazyCollection; + +/** + * @method static BaseModel make($attributes = []) + * @method static \Illuminate\Database\Eloquent\Builder withGlobalScope($identifier, $scope) + * @method static \Illuminate\Database\Eloquent\Builder withoutGlobalScope($scope) + * @method static \Illuminate\Database\Eloquent\Builder withoutGlobalScopes($scopes = null) + * @method static array removedScopes() + * @method static \Illuminate\Database\Eloquent\Builder whereKey($id) + * @method static \Illuminate\Database\Eloquent\Builder whereKeyNot($id) + * @method static \Illuminate\Database\Eloquent\Builder where($column, $operator = null, $value = null, $boolean = 'and') + * @method static BaseModel|null firstWhere($column, $operator = null, $value = null, $boolean = 'and') + * @method static \Illuminate\Database\Eloquent\Builder orWhere($column, $operator = null, $value = null) + * @method static \Illuminate\Database\Eloquent\Builder latest($column = null) + * @method static \Illuminate\Database\Eloquent\Builder oldest($column = null) + * @method static \Illuminate\Database\Eloquent\Collection hydrate($items) + * @method static \Illuminate\Database\Eloquent\Collection fromQuery($query, $bindings = []) + * @method static BaseModel|\Illuminate\Database\Eloquent\Collection|static[]|static|null find($id, $columns = []) + * @method static \Illuminate\Database\Eloquent\Collection findMany($ids, $columns = []) + * @method static BaseModel|\Illuminate\Database\Eloquent\Collection|static|static[] findOrFail($id, $columns = []) + * @method static BaseModel|static findOrNew($id, $columns = []) + * @method static BaseModel|static firstOrNew($attributes = [], $values = []) + * @method static BaseModel|static firstOrCreate($attributes = [], $values = []) + * @method static BaseModel|static updateOrCreate($attributes, $values = []) + * @method static BaseModel|static firstOrFail($columns = []) + * @method static BaseModel|static|mixed firstOr($columns = [], $callback = null) + * @method static BaseModel sole($columns = []) + * @method static mixed value($column) + * @method static \Illuminate\Database\Eloquent\Collection[]|static[] get($columns = []) + * @method static BaseModel[]|static[] getModels($columns = []) + * @method static array eagerLoadRelations($models) + * @method static LazyCollection cursor() + * @method static Collection pluck($column, $key = null) + * @method static LengthAwarePaginator paginate($perPage = null, $columns = [], $pageName = 'page', $page = null) + * @method static Paginator simplePaginate($perPage = null, $columns = [], $pageName = 'page', $page = null) + * @method static CursorPaginator cursorPaginate($perPage = null, $columns = [], $cursorName = 'cursor', $cursor = null) + * @method static BaseModel|$this create($attributes = []) + * @method static BaseModel|$this forceCreate($attributes) + * @method static int upsert($values, $uniqueBy, $update = null) + * @method static void onDelete($callback) + * @method static static|mixed scopes($scopes) + * @method static static applyScopes() + * @method static \Illuminate\Database\Eloquent\Builder without($relations) + * @method static \Illuminate\Database\Eloquent\Builder withOnly($relations) + * @method static BaseModel newModelInstance($attributes = []) + * @method static \Illuminate\Database\Eloquent\Builder withCasts($casts) + * @method static Builder getQuery() + * @method static \Illuminate\Database\Eloquent\Builder setQuery($query) + * @method static Builder toBase() + * @method static array getEagerLoads() + * @method static \Illuminate\Database\Eloquent\Builder setEagerLoads($eagerLoad) + * @method static BaseModel getModel() + * @method static \Illuminate\Database\Eloquent\Builder setModel($model) + * @method static Closure getMacro($name) + * @method static bool hasMacro($name) + * @method static Closure getGlobalMacro($name) + * @method static bool hasGlobalMacro($name) + * @method static static clone () + * @method static \Illuminate\Database\Eloquent\Builder has($relation, $operator = '>=', $count = 1, $boolean = 'and', $callback = null) + * @method static \Illuminate\Database\Eloquent\Builder orHas($relation, $operator = '>=', $count = 1) + * @method static \Illuminate\Database\Eloquent\Builder doesntHave($relation, $boolean = 'and', $callback = null) + * @method static \Illuminate\Database\Eloquent\Builder orDoesntHave($relation) + * @method static \Illuminate\Database\Eloquent\Builder whereHas($relation, $callback = null, $operator = '>=', $count = 1) + * @method static \Illuminate\Database\Eloquent\Builder orWhereHas($relation, $callback = null, $operator = '>=', $count = 1) + * @method static \Illuminate\Database\Eloquent\Builder whereDoesntHave($relation, $callback = null) + * @method static \Illuminate\Database\Eloquent\Builder orWhereDoesntHave($relation, $callback = null) + * @method static \Illuminate\Database\Eloquent\Builder hasMorph($relation, $types, $operator = '>=', $count = 1, $boolean = 'and', $callback = null) + * @method static \Illuminate\Database\Eloquent\Builder orHasMorph($relation, $types, $operator = '>=', $count = 1) + * @method static \Illuminate\Database\Eloquent\Builder doesntHaveMorph($relation, $types, $boolean = 'and', $callback = null) + * @method static \Illuminate\Database\Eloquent\Builder orDoesntHaveMorph($relation, $types) + * @method static \Illuminate\Database\Eloquent\Builder whereHasMorph($relation, $types, $callback = null, $operator = '>=', $count = 1) + * @method static \Illuminate\Database\Eloquent\Builder orWhereHasMorph($relation, $types, $callback = null, $operator = '>=', $count = 1) + * @method static \Illuminate\Database\Eloquent\Builder whereDoesntHaveMorph($relation, $types, $callback = null) + * @method static \Illuminate\Database\Eloquent\Builder orWhereDoesntHaveMorph($relation, $types, $callback = null) + * @method static \Illuminate\Database\Eloquent\Builder withAggregate($relations, $column, $function = null) + * @method static \Illuminate\Database\Eloquent\Builder withCount($relations) + * @method static \Illuminate\Database\Eloquent\Builder withMax($relation, $column) + * @method static \Illuminate\Database\Eloquent\Builder withMin($relation, $column) + * @method static \Illuminate\Database\Eloquent\Builder withSum($relation, $column) + * @method static \Illuminate\Database\Eloquent\Builder withAvg($relation, $column) + * @method static \Illuminate\Database\Eloquent\Builder withExists($relation) + * @method static \Illuminate\Database\Eloquent\Builder mergeConstraintsFrom($from) + * @method static Collection explain() + * @method static bool chunk($count, $callback) + * @method static Collection chunkMap($callback, $count = 1000) + * @method static bool each($callback, $count = 1000) + * @method static bool chunkById($count, $callback, $column = null, $alias = null) + * @method static bool eachById($callback, $count = 1000, $column = null, $alias = null) + * @method static LazyCollection lazy($chunkSize = 1000) + * @method static LazyCollection lazyById($chunkSize = 1000, $column = null, $alias = null) + * @method static BaseModel|object|static|null first($columns = []) + * @method static BaseModel|object|null baseSole($columns = []) + * @method static \Illuminate\Database\Eloquent\Builder tap($callback) + * @method static mixed when($value, $callback, $default = null) + * @method static mixed unless($value, $callback, $default = null) + * @method static Builder select($columns = []) + * @method static Builder selectSub($query, $as) + * @method static Builder selectRaw($expression, $bindings = []) + * @method static Builder fromSub($query, $as) + * @method static Builder fromRaw($expression, $bindings = []) + * @method static Builder addSelect($column) + * @method static Builder distinct() + * @method static Builder from($table, $as = null) + * @method static Builder join($table, $first, $operator = null, $second = null, $type = 'inner', $where = false) + * @method static Builder joinWhere($table, $first, $operator, $second, $type = 'inner') + * @method static Builder joinSub($query, $as, $first, $operator = null, $second = null, $type = 'inner', $where = false) + * @method static Builder leftJoin($table, $first, $operator = null, $second = null) + * @method static Builder leftJoinWhere($table, $first, $operator, $second) + * @method static Builder leftJoinSub($query, $as, $first, $operator = null, $second = null) + * @method static Builder rightJoin($table, $first, $operator = null, $second = null) + * @method static Builder rightJoinWhere($table, $first, $operator, $second) + * @method static Builder rightJoinSub($query, $as, $first, $operator = null, $second = null) + * @method static Builder crossJoin($table, $first = null, $operator = null, $second = null) + * @method static Builder crossJoinSub($query, $as) + * @method static void mergeWheres($wheres, $bindings) + * @method static array prepareValueAndOperator($value, $operator, $useDefault = false) + * @method static Builder whereColumn($first, $operator = null, $second = null, $boolean = 'and') + * @method static Builder orWhereColumn($first, $operator = null, $second = null) + * @method static Builder whereRaw($sql, $bindings = [], $boolean = 'and') + * @method static Builder orWhereRaw($sql, $bindings = []) + * @method static Builder whereIn($column, $values, $boolean = 'and', $not = false) + * @method static Builder orWhereIn($column, $values) + * @method static Builder whereNotIn($column, $values, $boolean = 'and') + * @method static Builder orWhereNotIn($column, $values) + * @method static Builder whereIntegerInRaw($column, $values, $boolean = 'and', $not = false) + * @method static Builder orWhereIntegerInRaw($column, $values) + * @method static Builder whereIntegerNotInRaw($column, $values, $boolean = 'and') + * @method static Builder orWhereIntegerNotInRaw($column, $values) + * @method static Builder whereNull($columns, $boolean = 'and', $not = false) + * @method static Builder orWhereNull($column) + * @method static Builder whereNotNull($columns, $boolean = 'and') + * @method static Builder whereBetween($column, $values, $boolean = 'and', $not = false) + * @method static Builder whereBetweenColumns($column, $values, $boolean = 'and', $not = false) + * @method static Builder orWhereBetween($column, $values) + * @method static Builder orWhereBetweenColumns($column, $values) + * @method static Builder whereNotBetween($column, $values, $boolean = 'and') + * @method static Builder whereNotBetweenColumns($column, $values, $boolean = 'and') + * @method static Builder orWhereNotBetween($column, $values) + * @method static Builder orWhereNotBetweenColumns($column, $values) + * @method static Builder orWhereNotNull($column) + * @method static Builder whereDate($column, $operator, $value = null, $boolean = 'and') + * @method static Builder orWhereDate($column, $operator, $value = null) + * @method static Builder whereTime($column, $operator, $value = null, $boolean = 'and') + * @method static Builder orWhereTime($column, $operator, $value = null) + * @method static Builder whereDay($column, $operator, $value = null, $boolean = 'and') + * @method static Builder orWhereDay($column, $operator, $value = null) + * @method static Builder whereMonth($column, $operator, $value = null, $boolean = 'and') + * @method static Builder orWhereMonth($column, $operator, $value = null) + * @method static Builder whereYear($column, $operator, $value = null, $boolean = 'and') + * @method static Builder orWhereYear($column, $operator, $value = null) + * @method static Builder whereNested($callback, $boolean = 'and') + * @method static Builder forNestedWhere() + * @method static Builder addNestedWhereQuery($query, $boolean = 'and') + * @method static Builder whereExists($callback, $boolean = 'and', $not = false) + * @method static Builder orWhereExists($callback, $not = false) + * @method static Builder whereNotExists($callback, $boolean = 'and') + * @method static Builder orWhereNotExists($callback) + * @method static Builder addWhereExistsQuery($query, $boolean = 'and', $not = false) + * @method static Builder whereRowValues($columns, $operator, $values, $boolean = 'and') + * @method static Builder orWhereRowValues($columns, $operator, $values) + * @method static Builder whereJsonContains($column, $value, $boolean = 'and', $not = false) + * @method static Builder orWhereJsonContains($column, $value) + * @method static Builder whereJsonDoesntContain($column, $value, $boolean = 'and') + * @method static Builder orWhereJsonDoesntContain($column, $value) + * @method static Builder whereJsonLength($column, $operator, $value = null, $boolean = 'and') + * @method static Builder orWhereJsonLength($column, $operator, $value = null) + * @method static Builder dynamicWhere($method, $parameters) + * @method static Builder groupBy(...$groups) + * @method static Builder groupByRaw($sql, $bindings = []) + * @method static Builder having($column, $operator = null, $value = null, $boolean = 'and') + * @method static Builder orHaving($column, $operator = null, $value = null) + * @method static Builder havingBetween($column, $values, $boolean = 'and', $not = false) + * @method static Builder havingRaw($sql, $bindings = [], $boolean = 'and') + * @method static Builder orHavingRaw($sql, $bindings = []) + * @method static Builder orderBy($column, $direction = 'asc') + * @method static Builder orderByDesc($column) + * @method static Builder inRandomOrder($seed = '') + * @method static Builder orderByRaw($sql, $bindings = []) + * @method static Builder skip($value) + * @method static Builder offset($value) + * @method static Builder take($value) + * @method static Builder limit($value) + * @method static Builder forPage($page, $perPage = 15) + * @method static Builder forPageBeforeId($perPage = 15, $lastId = 0, $column = 'id') + * @method static Builder forPageAfterId($perPage = 15, $lastId = 0, $column = 'id') + * @method static Builder reorder($column = null, $direction = 'asc') + * @method static Builder union($query, $all = false) + * @method static Builder unionAll($query) + * @method static Builder lock($value = true) + * @method static Builder lockForUpdate() + * @method static Builder sharedLock() + * @method static Builder beforeQuery($callback) + * @method static void applyBeforeQueryCallbacks() + * @method static string toSql() + * @method static int getCountForPagination($columns = []) + * @method static string implode($column, $glue = '') + * @method static bool exists() + * @method static bool doesntExist() + * @method static mixed existsOr($callback) + * @method static mixed doesntExistOr($callback) + * @method static int count($columns = '*') + * @method static mixed min($column) + * @method static mixed max($column) + * @method static mixed sum($column) + * @method static mixed avg($column) + * @method static mixed average($column) + * @method static mixed aggregate($function, $columns = []) + * @method static float|int numericAggregate($function, $columns = []) + * @method static bool insert($values) + * @method static int insertOrIgnore($values) + * @method static int insertGetId($values, $sequence = null) + * @method static int insertUsing($columns, $query) + * @method static bool updateOrInsert($attributes, $values = []) + * @method static void truncate() + * @method static Expression raw($value) + * @method static array getBindings() + * @method static array getRawBindings() + * @method static Builder setBindings($bindings, $type = 'where') + * @method static Builder addBinding($value, $type = 'where') + * @method static Builder mergeBindings($query) + * @method static array cleanBindings($bindings) + * @method static Processor getProcessor() + * @method static Grammar getGrammar() + * @method static Builder useWritePdo() + * @method static static cloneWithout($properties) + * @method static static cloneWithoutBindings($except) + * @method static Builder dump() + * @method static void dd() + * @method static void macro($name, $macro) + * @method static void mixin($mixin, $replace = true) + * @method static mixed macroCall($method, $parameters) + */ +class Model extends BaseModel +{ + +} diff --git a/vendor/workerman/webman-framework/src/support/Plugin.php b/vendor/workerman/webman-framework/src/support/Plugin.php new file mode 100644 index 0000000..7b296be --- /dev/null +++ b/vendor/workerman/webman-framework/src/support/Plugin.php @@ -0,0 +1,108 @@ + $path) { + $pluginConst = "\\{$namespace}Install::WEBMAN_PLUGIN"; + if (!defined($pluginConst)) { + continue; + } + $installFunction = "\\{$namespace}Install::install"; + if (is_callable($installFunction)) { + $installFunction(true); + } + } + } + + /** + * Update. + * @param mixed $event + * @return void + */ + public static function update($event) + { + static::findHelper(); + $psr4 = static::getPsr4($event); + foreach ($psr4 as $namespace => $path) { + $pluginConst = "\\{$namespace}Install::WEBMAN_PLUGIN"; + if (!defined($pluginConst)) { + continue; + } + $updateFunction = "\\{$namespace}Install::update"; + if (is_callable($updateFunction)) { + $updateFunction(); + continue; + } + $installFunction = "\\{$namespace}Install::install"; + if (is_callable($installFunction)) { + $installFunction(false); + } + } + } + + /** + * Uninstall. + * @param mixed $event + * @return void + */ + public static function uninstall($event) + { + static::findHelper(); + $psr4 = static::getPsr4($event); + foreach ($psr4 as $namespace => $path) { + $pluginConst = "\\{$namespace}Install::WEBMAN_PLUGIN"; + if (!defined($pluginConst)) { + continue; + } + $uninstallFunction = "\\{$namespace}Install::uninstall"; + if (is_callable($uninstallFunction)) { + $uninstallFunction(); + } + } + } + + /** + * Get psr-4 info + * + * @param mixed $event + * @return array + */ + protected static function getPsr4($event) + { + $operation = $event->getOperation(); + $autoload = method_exists($operation, 'getPackage') ? $operation->getPackage()->getAutoload() : $operation->getTargetPackage()->getAutoload(); + return $autoload['psr-4'] ?? []; + } + + /** + * FindHelper. + * @return void + */ + protected static function findHelper() + { + // Plugin.php in vendor + $file = __DIR__ . '/../../../../../support/helpers.php'; + if (is_file($file)) { + require_once $file; + return; + } + // Plugin.php in webman + require_once __DIR__ . '/helpers.php'; + } +} diff --git a/vendor/workerman/webman-framework/src/support/Redis.php b/vendor/workerman/webman-framework/src/support/Redis.php new file mode 100644 index 0000000..de566c5 --- /dev/null +++ b/vendor/workerman/webman-framework/src/support/Redis.php @@ -0,0 +1,283 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace support; + +use Illuminate\Events\Dispatcher; +use Illuminate\Redis\Connections\Connection; +use Illuminate\Redis\RedisManager; +use Workerman\Timer; +use Workerman\Worker; +use function class_exists; +use function config; +use function in_array; + + +/** + * Class Redis + * @package support + * + * Strings methods + * @method static int append($key, $value) + * @method static int bitCount($key) + * @method static int decr($key, $value = 1) + * @method static int decrBy($key, $value) + * @method static string|bool get($key) + * @method static int getBit($key, $offset) + * @method static string getRange($key, $start, $end) + * @method static string getSet($key, $value) + * @method static int incr($key, $value = 1) + * @method static int incrBy($key, $value) + * @method static float incrByFloat($key, $value) + * @method static array mGet(array $keys) + * @method static array getMultiple(array $keys) + * @method static bool mSet($pairs) + * @method static bool mSetNx($pairs) + * @method static bool set($key, $value, $expireResolution = null, $expireTTL = null, $flag = null) + * @method static bool setBit($key, $offset, $value) + * @method static bool setEx($key, $ttl, $value) + * @method static bool pSetEx($key, $ttl, $value) + * @method static bool setNx($key, $value) + * @method static string setRange($key, $offset, $value) + * @method static int strLen($key) + * Keys methods + * @method static int del(...$keys) + * @method static int unlink(...$keys) + * @method static false|string dump($key) + * @method static int exists(...$keys) + * @method static bool expire($key, $ttl) + * @method static bool pexpire($key, $ttl) + * @method static bool expireAt($key, $timestamp) + * @method static bool pexpireAt($key, $timestamp) + * @method static array keys($pattern) + * @method static bool|array scan($it) + * @method static void migrate($host, $port, $keys, $dbIndex, $timeout, $copy = false, $replace = false) + * @method static bool select($dbIndex) + * @method static bool move($key, $dbIndex) + * @method static string|int|bool object($information, $key) + * @method static bool persist($key) + * @method static string randomKey() + * @method static bool rename($srcKey, $dstKey) + * @method static bool renameNx($srcKey, $dstKey) + * @method static string type($key) + * @method static int|array sort($key, $options = []) + * @method static int ttl($key) + * @method static int pttl($key) + * @method static void restore($key, $ttl, $value) + * Hashes methods + * @method static false|int hSet($key, $hashKey, $value) + * @method static bool hSetNx($key, $hashKey, $value) + * @method static false|string hGet($key, $hashKey) + * @method static false|int hLen($key) + * @method static false|int hDel($key, ...$hashKeys) + * @method static array hKeys($key) + * @method static array hVals($key) + * @method static array hGetAll($key) + * @method static bool hExists($key, $hashKey) + * @method static int hIncrBy($key, $hashKey, $value) + * @method static float hIncrByFloat($key, $hashKey, $value) + * @method static bool hMSet($key, $members) + * @method static array hMGet($key, $memberKeys) + * @method static array hScan($key, $iterator, $pattern = '', $count = 0) + * @method static int hStrLen($key, $hashKey) + * Lists methods + * @method static array blPop($keys, $timeout) + * @method static array brPop($keys, $timeout) + * @method static false|string bRPopLPush($srcKey, $dstKey, $timeout) + * @method static false|string lIndex($key, $index) + * @method static int lInsert($key, $position, $pivot, $value) + * @method static false|string lPop($key) + * @method static false|int lPush($key, ...$entries) + * @method static false|int lPushx($key, $value) + * @method static array lRange($key, $start, $end) + * @method static false|int lRem($key, $count, $value) + * @method static bool lSet($key, $index, $value) + * @method static false|array lTrim($key, $start, $end) + * @method static false|string rPop($key) + * @method static false|string rPopLPush($srcKey, $dstKey) + * @method static false|int rPush($key, ...$entries) + * @method static false|int rPushX($key, $value) + * @method static false|int lLen($key) + * Sets methods + * @method static int sAdd($key, $value) + * @method static int sCard($key) + * @method static array sDiff($keys) + * @method static false|int sDiffStore($dst, $keys) + * @method static false|array sInter($keys) + * @method static false|int sInterStore($dst, $keys) + * @method static bool sIsMember($key, $member) + * @method static array sMembers($key) + * @method static bool sMove($src, $dst, $member) + * @method static false|string|array sPop($key, $count = 0) + * @method static false|string|array sRandMember($key, $count = 0) + * @method static int sRem($key, ...$members) + * @method static array sUnion(...$keys) + * @method static false|int sUnionStore($dst, ...$keys) + * @method static false|array sScan($key, $iterator, $pattern = '', $count = 0) + * Sorted sets methods + * @method static array bzPopMin($keys, $timeout) + * @method static array bzPopMax($keys, $timeout) + * @method static int zAdd($key, $score, $value) + * @method static int zCard($key) + * @method static int zCount($key, $start, $end) + * @method static double zIncrBy($key, $value, $member) + * @method static int zinterstore($keyOutput, $arrayZSetKeys, $arrayWeights = [], $aggregateFunction = '') + * @method static array zPopMin($key, $count) + * @method static array zPopMax($key, $count) + * @method static array zRange($key, $start, $end, $withScores = false) + * @method static array zRangeByScore($key, $start, $end, $options = []) + * @method static array zRevRangeByScore($key, $start, $end, $options = []) + * @method static array zRangeByLex($key, $min, $max, $offset = 0, $limit = 0) + * @method static int zRank($key, $member) + * @method static int zRevRank($key, $member) + * @method static int zRem($key, ...$members) + * @method static int zRemRangeByRank($key, $start, $end) + * @method static int zRemRangeByScore($key, $start, $end) + * @method static array zRevRange($key, $start, $end, $withScores = false) + * @method static double zScore($key, $member) + * @method static int zunionstore($keyOutput, $arrayZSetKeys, $arrayWeights = [], $aggregateFunction = '') + * @method static false|array zScan($key, $iterator, $pattern = '', $count = 0) + * HyperLogLogs methods + * @method static int pfAdd($key, $values) + * @method static int pfCount($keys) + * @method static bool pfMerge($dstKey, $srcKeys) + * Geocoding methods + * @method static int geoAdd($key, $longitude, $latitude, $member, ...$items) + * @method static array geoHash($key, ...$members) + * @method static array geoPos($key, ...$members) + * @method static double geoDist($key, $members, $unit = '') + * @method static int|array geoRadius($key, $longitude, $latitude, $radius, $unit, $options = []) + * @method static array geoRadiusByMember($key, $member, $radius, $units, $options = []) + * Streams methods + * @method static int xAck($stream, $group, $arrMessages) + * @method static string xAdd($strKey, $strId, $arrMessage, $iMaxLen = 0, $booApproximate = false) + * @method static array xClaim($strKey, $strGroup, $strConsumer, $minIdleTime, $arrIds, $arrOptions = []) + * @method static int xDel($strKey, $arrIds) + * @method static mixed xGroup($command, $strKey, $strGroup, $strMsgId, $booMKStream = null) + * @method static mixed xInfo($command, $strStream, $strGroup = null) + * @method static int xLen($stream) + * @method static array xPending($strStream, $strGroup, $strStart = 0, $strEnd = 0, $iCount = 0, $strConsumer = null) + * @method static array xRange($strStream, $strStart, $strEnd, $iCount = 0) + * @method static array xRead($arrStreams, $iCount = 0, $iBlock = null) + * @method static array xReadGroup($strGroup, $strConsumer, $arrStreams, $iCount = 0, $iBlock = null) + * @method static array xRevRange($strStream, $strEnd, $strStart, $iCount = 0) + * @method static int xTrim($strStream, $iMaxLen, $booApproximate = null) + * Pub/sub methods + * @method static mixed pSubscribe($patterns, $callback) + * @method static mixed publish($channel, $message) + * @method static mixed subscribe($channels, $callback) + * @method static mixed pubSub($keyword, $argument = null) + * Generic methods + * @method static mixed rawCommand(...$commandAndArgs) + * Transactions methods + * @method static \Redis multi() + * @method static mixed exec() + * @method static mixed discard() + * @method static mixed watch($keys) + * @method static mixed unwatch($keys) + * Scripting methods + * @method static mixed eval($script, $numkeys, $keyOrArg1 = null, $keyOrArgN = null) + * @method static mixed evalSha($scriptSha, $numkeys, ...$arguments) + * @method static mixed script($command, ...$scripts) + * @method static mixed client(...$args) + * @method static null|string getLastError() + * @method static bool clearLastError() + * @method static mixed _prefix($value) + * @method static mixed _serialize($value) + * @method static mixed _unserialize($value) + * Introspection methods + * @method static bool isConnected() + * @method static mixed getHost() + * @method static mixed getPort() + * @method static false|int getDbNum() + * @method static false|double getTimeout() + * @method static mixed getReadTimeout() + * @method static mixed getPersistentID() + * @method static mixed getAuth() + */ +class Redis +{ + + /** + * @var RedisManager + */ + protected static $instance = null; + + /** + * need to install phpredis extension + */ + const PHPREDIS_CLIENT = 'phpredis'; + + /** + * need to install the 'predis/predis' packgage. + * cmd: composer install predis/predis + */ + const PREDIS_CLIENT = 'predis'; + + /** + * Support client collection + */ + static $allowClient = [ + self::PHPREDIS_CLIENT, + self::PREDIS_CLIENT + ]; + + /** + * @return RedisManager + */ + public static function instance(): ?RedisManager + { + if (!static::$instance) { + $config = config('redis'); + $client = $config['client'] ?? self::PHPREDIS_CLIENT; + + if (!in_array($client, static::$allowClient)) { + $client = self::PHPREDIS_CLIENT; + } + + static::$instance = new RedisManager('', $client, $config); + } + return static::$instance; + } + + /** + * Connection. + * @param string $name + * @return Connection + */ + public static function connection(string $name = 'default'): Connection + { + static $timers = []; + $connection = static::instance()->connection($name); + if (!isset($timers[$name])) { + $timers[$name] = Worker::getAllWorkers() ? Timer::add(55, function () use ($connection) { + $connection->get('ping'); + }) : 1; + if (class_exists(Dispatcher::class)) { + $connection->setEventDispatcher(new Dispatcher()); + } + } + return $connection; + } + + /** + * @param string $name + * @param array $arguments + * @return mixed + */ + public static function __callStatic(string $name, array $arguments) + { + return static::connection()->{$name}(... $arguments); + } +} diff --git a/vendor/workerman/webman-framework/src/support/Request.php b/vendor/workerman/webman-framework/src/support/Request.php new file mode 100644 index 0000000..e3f6ac3 --- /dev/null +++ b/vendor/workerman/webman-framework/src/support/Request.php @@ -0,0 +1,24 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace support; + +/** + * Class Request + * @package support + */ +class Request extends \Webman\Http\Request +{ + +} \ No newline at end of file diff --git a/vendor/workerman/webman-framework/src/support/Response.php b/vendor/workerman/webman-framework/src/support/Response.php new file mode 100644 index 0000000..9bc4e1e --- /dev/null +++ b/vendor/workerman/webman-framework/src/support/Response.php @@ -0,0 +1,24 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace support; + +/** + * Class Response + * @package support + */ +class Response extends \Webman\Http\Response +{ + +} \ No newline at end of file diff --git a/vendor/workerman/webman-framework/src/support/Translation.php b/vendor/workerman/webman-framework/src/support/Translation.php new file mode 100644 index 0000000..84ab031 --- /dev/null +++ b/vendor/workerman/webman-framework/src/support/Translation.php @@ -0,0 +1,98 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace support; + +use Symfony\Component\Translation\Translator; +use Webman\Exception\NotFoundException; +use function basename; +use function config; +use function get_realpath; +use function glob; +use function pathinfo; +use function request; +use function substr; + +/** + * Class Translation + * @package support + * @method static string trans(?string $id, array $parameters = [], string $domain = null, string $locale = null) + * @method static void setLocale(string $locale) + * @method static string getLocale() + */ +class Translation +{ + + /** + * @var Translator[] + */ + protected static $instance = []; + + /** + * Instance. + * @param string $plugin + * @return Translator + * @throws NotFoundException + */ + public static function instance(string $plugin = ''): Translator + { + if (!isset(static::$instance[$plugin])) { + $config = config($plugin ? "plugin.$plugin.translation" : 'translation', []); + // Phar support. Compatible with the 'realpath' function in the phar file. + if (!$translationsPath = get_realpath($config['path'])) { + throw new NotFoundException("File {$config['path']} not found"); + } + + static::$instance[$plugin] = $translator = new Translator($config['locale']); + $translator->setFallbackLocales($config['fallback_locale']); + + $classes = [ + 'Symfony\Component\Translation\Loader\PhpFileLoader' => [ + 'extension' => '.php', + 'format' => 'phpfile' + ], + 'Symfony\Component\Translation\Loader\PoFileLoader' => [ + 'extension' => '.po', + 'format' => 'pofile' + ] + ]; + + foreach ($classes as $class => $opts) { + $translator->addLoader($opts['format'], new $class); + foreach (glob($translationsPath . DIRECTORY_SEPARATOR . '*' . DIRECTORY_SEPARATOR . '*' . $opts['extension']) as $file) { + $domain = basename($file, $opts['extension']); + $dirName = pathinfo($file, PATHINFO_DIRNAME); + $locale = substr(strrchr($dirName, DIRECTORY_SEPARATOR), 1); + if ($domain && $locale) { + $translator->addResource($opts['format'], $file, $locale, $domain); + } + } + } + } + return static::$instance[$plugin]; + } + + /** + * @param string $name + * @param array $arguments + * @return mixed + * @throws NotFoundException + */ + public static function __callStatic(string $name, array $arguments) + { + $request = request(); + $plugin = $request->plugin ?? ''; + return static::instance($plugin)->{$name}(... $arguments); + } +} diff --git a/vendor/workerman/webman-framework/src/support/View.php b/vendor/workerman/webman-framework/src/support/View.php new file mode 100644 index 0000000..f98afe0 --- /dev/null +++ b/vendor/workerman/webman-framework/src/support/View.php @@ -0,0 +1,35 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace support; + +use function config; +use function request; + +class View +{ + /** + * Assign. + * @param mixed $name + * @param mixed $value + * @return void + */ + public static function assign($name, $value = null) + { + $request = request(); + $plugin = $request->plugin ?? ''; + $handler = config($plugin ? "plugin.$plugin.view.handler" : 'view.handler'); + $handler::assign($name, $value); + } +} \ No newline at end of file diff --git a/vendor/workerman/webman-framework/src/support/bootstrap/LaravelDb.php b/vendor/workerman/webman-framework/src/support/bootstrap/LaravelDb.php new file mode 100644 index 0000000..6bc7c72 --- /dev/null +++ b/vendor/workerman/webman-framework/src/support/bootstrap/LaravelDb.php @@ -0,0 +1,115 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace support\bootstrap; + +use Illuminate\Container\Container as IlluminateContainer; +use Illuminate\Database\Capsule\Manager as Capsule; +use Illuminate\Database\MySqlConnection; +use Illuminate\Events\Dispatcher; +use Illuminate\Pagination\Paginator; +use Jenssegers\Mongodb\Connection as MongodbConnection; +use support\Container; +use Throwable; +use Webman\Bootstrap; +use Workerman\Timer; +use Workerman\Worker; +use function class_exists; +use function config; + +/** + * Class Laravel + * @package support\Bootstrap + */ +class LaravelDb implements Bootstrap +{ + /** + * @param Worker|null $worker + * + * @return void + */ + public static function start(?Worker $worker) + { + if (!class_exists(Capsule::class)) { + return; + } + + $config = config('database', []); + $connections = $config['connections'] ?? []; + if (!$connections) { + return; + } + + $capsule = new Capsule(IlluminateContainer::getInstance()); + + $capsule->getDatabaseManager()->extend('mongodb', function ($config, $name) { + $config['name'] = $name; + return new MongodbConnection($config); + }); + + $default = $config['default'] ?? false; + if ($default) { + $defaultConfig = $connections[$config['default']]; + $capsule->addConnection($defaultConfig); + } + + foreach ($connections as $name => $config) { + $capsule->addConnection($config, $name); + } + + if (class_exists(Dispatcher::class) && !$capsule->getEventDispatcher()) { + $capsule->setEventDispatcher(Container::make(Dispatcher::class, [IlluminateContainer::getInstance()])); + } + + $capsule->setAsGlobal(); + + $capsule->bootEloquent(); + + // Heartbeat + if ($worker) { + Timer::add(55, function () use ($default, $connections, $capsule) { + foreach ($capsule->getDatabaseManager()->getConnections() as $connection) { + /* @var MySqlConnection $connection **/ + if ($connection->getConfig('driver') == 'mysql') { + try { + $connection->select('select 1'); + } catch (Throwable $e) {} + } + } + }); + } + + // Paginator + if (class_exists(Paginator::class)) { + if (method_exists(Paginator::class, 'queryStringResolver')) { + Paginator::queryStringResolver(function () { + $request = request(); + return $request ? $request->queryString() : null; + }); + } + Paginator::currentPathResolver(function () { + $request = request(); + return $request ? $request->path(): '/'; + }); + Paginator::currentPageResolver(function ($pageName = 'page') { + $request = request(); + if (!$request) { + return 1; + } + $page = (int)($request->input($pageName, 1)); + return $page > 0 ? $page : 1; + }); + } + } +} diff --git a/vendor/workerman/webman-framework/src/support/bootstrap/Session.php b/vendor/workerman/webman-framework/src/support/bootstrap/Session.php new file mode 100644 index 0000000..7360d0e --- /dev/null +++ b/vendor/workerman/webman-framework/src/support/bootstrap/Session.php @@ -0,0 +1,61 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace support\bootstrap; + +use Webman\Bootstrap; +use Workerman\Protocols\Http; +use Workerman\Protocols\Http\Session as SessionBase; +use Workerman\Worker; +use function config; +use function property_exists; + +/** + * Class Session + * @package support + */ +class Session implements Bootstrap +{ + + /** + * @param Worker|null $worker + * @return void + */ + public static function start(?Worker $worker) + { + $config = config('session'); + if (property_exists(SessionBase::class, 'name')) { + SessionBase::$name = $config['session_name']; + } else { + Http::sessionName($config['session_name']); + } + SessionBase::handlerClass($config['handler'], $config['config'][$config['type']]); + $map = [ + 'auto_update_timestamp' => 'autoUpdateTimestamp', + 'cookie_lifetime' => 'cookieLifetime', + 'gc_probability' => 'gcProbability', + 'cookie_path' => 'cookiePath', + 'http_only' => 'httpOnly', + 'same_site' => 'sameSite', + 'lifetime' => 'lifetime', + 'domain' => 'domain', + 'secure' => 'secure', + ]; + foreach ($map as $key => $name) { + if (isset($config[$key]) && property_exists(SessionBase::class, $name)) { + SessionBase::${$name} = $config[$key]; + } + } + } +} diff --git a/vendor/workerman/webman-framework/src/support/exception/BusinessException.php b/vendor/workerman/webman-framework/src/support/exception/BusinessException.php new file mode 100644 index 0000000..aee7877 --- /dev/null +++ b/vendor/workerman/webman-framework/src/support/exception/BusinessException.php @@ -0,0 +1,38 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace support\exception; + +use Exception; +use Webman\Http\Request; +use Webman\Http\Response; +use function json_encode; + +/** + * Class BusinessException + * @package support\exception + */ +class BusinessException extends Exception +{ + public function render(Request $request): ?Response + { + if ($request->expectsJson()) { + $code = $this->getCode(); + $json = ['code' => $code ?: 500, 'msg' => $this->getMessage()]; + return new Response(200, ['Content-Type' => 'application/json'], + json_encode($json, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); + } + return new Response(200, [], $this->getMessage()); + } +} diff --git a/vendor/workerman/webman-framework/src/support/exception/Handler.php b/vendor/workerman/webman-framework/src/support/exception/Handler.php new file mode 100644 index 0000000..98dc618 --- /dev/null +++ b/vendor/workerman/webman-framework/src/support/exception/Handler.php @@ -0,0 +1,47 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace support\exception; + +use Throwable; +use Webman\Exception\ExceptionHandler; +use Webman\Http\Request; +use Webman\Http\Response; + +/** + * Class Handler + * @package support\exception + */ +class Handler extends ExceptionHandler +{ + public $dontReport = [ + BusinessException::class, + ]; + + public function report(Throwable $exception) + { + parent::report($exception); + } + + public function render(Request $request, Throwable $exception): Response + { + if(($exception instanceof BusinessException) && ($response = $exception->render($request))) + { + return $response; + } + + return parent::render($request, $exception); + } + +} \ No newline at end of file diff --git a/vendor/workerman/webman-framework/src/support/view/Blade.php b/vendor/workerman/webman-framework/src/support/view/Blade.php new file mode 100644 index 0000000..ca9634f --- /dev/null +++ b/vendor/workerman/webman-framework/src/support/view/Blade.php @@ -0,0 +1,79 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace support\view; + +use Jenssegers\Blade\Blade as BladeView; +use Webman\View; +use function app_path; +use function array_merge; +use function base_path; +use function config; +use function is_array; +use function request; +use function runtime_path; + +/** + * Class Blade + * composer require jenssegers/blade + * @package support\view + */ +class Blade implements View +{ + /** + * @var array + */ + protected static $vars = []; + + /** + * Assign. + * @param string|array $name + * @param mixed $value + */ + public static function assign($name, $value = null) + { + static::$vars = array_merge(static::$vars, is_array($name) ? $name : [$name => $value]); + } + + /** + * Render. + * @param string $template + * @param array $vars + * @param string|null $app + * @param string|null $plugin + * @return string + */ + public static function render(string $template, array $vars, string $app = null, string $plugin = null): string + { + static $views = []; + $request = request(); + $plugin = $plugin === null ? ($request->plugin ?? '') : $plugin; + $app = $app === null ? $request->app : $app; + $configPrefix = $plugin ? "plugin.$plugin." : ''; + $baseViewPath = $plugin ? base_path() . "/plugin/$plugin/app" : app_path(); + $key = "$plugin-$app"; + if (!isset($views[$key])) { + $viewPath = $app === '' ? "$baseViewPath/view" : "$baseViewPath/$app/view"; + $views[$key] = new BladeView($viewPath, runtime_path() . '/views'); + $extension = config("{$configPrefix}view.extension"); + if ($extension) { + $extension($views[$key]); + } + } + $vars = array_merge(static::$vars, $vars); + $content = $views[$key]->render($template, $vars); + static::$vars = []; + return $content; + } +} diff --git a/vendor/workerman/webman-framework/src/support/view/Raw.php b/vendor/workerman/webman-framework/src/support/view/Raw.php new file mode 100644 index 0000000..5a37ffa --- /dev/null +++ b/vendor/workerman/webman-framework/src/support/view/Raw.php @@ -0,0 +1,84 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace support\view; + +use Throwable; +use Webman\View; +use function app_path; +use function array_merge; +use function base_path; +use function config; +use function extract; +use function is_array; +use function ob_end_clean; +use function ob_get_clean; +use function ob_start; +use function request; + +/** + * Class Raw + * @package support\view + */ +class Raw implements View +{ + /** + * @var array + */ + protected static $vars = []; + + /** + * Assign. + * @param string|array $name + * @param mixed $value + */ + public static function assign($name, $value = null) + { + static::$vars = array_merge(static::$vars, is_array($name) ? $name : [$name => $value]); + } + + /** + * Render. + * @param string $template + * @param array $vars + * @param string|null $app + * @param string|null $plugin + * @return false|string + */ + public static function render(string $template, array $vars, string $app = null, string $plugin = null): string + { + $request = request(); + $plugin = $plugin === null ? ($request->plugin ?? '') : $plugin; + $configPrefix = $plugin ? "plugin.$plugin." : ''; + $viewSuffix = config("{$configPrefix}view.options.view_suffix", 'html'); + $app = $app === null ? $request->app : $app; + $baseViewPath = $plugin ? base_path() . "/plugin/$plugin/app" : app_path(); + $__template_path__ = $app === '' ? "$baseViewPath/view/$template.$viewSuffix" : "$baseViewPath/$app/view/$template.$viewSuffix"; + + extract(static::$vars); + extract($vars); + ob_start(); + // Try to include php file. + try { + include $__template_path__; + } catch (Throwable $e) { + static::$vars = []; + ob_end_clean(); + throw $e; + } + static::$vars = []; + return ob_get_clean(); + } + +} diff --git a/vendor/workerman/webman-framework/src/support/view/ThinkPHP.php b/vendor/workerman/webman-framework/src/support/view/ThinkPHP.php new file mode 100644 index 0000000..b8d6590 --- /dev/null +++ b/vendor/workerman/webman-framework/src/support/view/ThinkPHP.php @@ -0,0 +1,81 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace support\view; + +use think\Template; +use Webman\View; +use function app_path; +use function array_merge; +use function base_path; +use function config; +use function is_array; +use function ob_get_clean; +use function ob_start; +use function request; +use function runtime_path; + +/** + * Class Blade + * @package support\view + */ +class ThinkPHP implements View +{ + /** + * @var array + */ + protected static $vars = []; + + /** + * Assign. + * @param string|array $name + * @param mixed $value + */ + public static function assign($name, $value = null) + { + static::$vars = array_merge(static::$vars, is_array($name) ? $name : [$name => $value]); + } + + /** + * Render. + * @param string $template + * @param array $vars + * @param string|null $app + * @param string|null $plugin + * @return false|string + */ + public static function render(string $template, array $vars, string $app = null, string $plugin = null): string + { + $request = request(); + $plugin = $plugin === null ? ($request->plugin ?? '') : $plugin; + $app = $app === null ? $request->app : $app; + $configPrefix = $plugin ? "plugin.$plugin." : ''; + $viewSuffix = config("{$configPrefix}view.options.view_suffix", 'html'); + $baseViewPath = $plugin ? base_path() . "/plugin/$plugin/app" : app_path(); + $viewPath = $app === '' ? "$baseViewPath/view/" : "$baseViewPath/$app/view/"; + $defaultOptions = [ + 'view_path' => $viewPath, + 'cache_path' => runtime_path() . '/views/', + 'view_suffix' => $viewSuffix + ]; + $options = $defaultOptions + config("{$configPrefix}view.options", []); + $views = new Template($options); + ob_start(); + $vars = array_merge(static::$vars, $vars); + $views->fetch($template, $vars); + $content = ob_get_clean(); + static::$vars = []; + return $content; + } +} diff --git a/vendor/workerman/webman-framework/src/support/view/Twig.php b/vendor/workerman/webman-framework/src/support/view/Twig.php new file mode 100644 index 0000000..83fc922 --- /dev/null +++ b/vendor/workerman/webman-framework/src/support/view/Twig.php @@ -0,0 +1,82 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace support\view; + +use Twig\Environment; +use Twig\Error\LoaderError; +use Twig\Error\RuntimeError; +use Twig\Error\SyntaxError; +use Twig\Loader\FilesystemLoader; +use Webman\View; +use function app_path; +use function array_merge; +use function base_path; +use function config; +use function is_array; +use function request; + +/** + * Class Blade + * @package support\view + */ +class Twig implements View +{ + /** + * @var array + */ + protected static $vars = []; + + /** + * Assign. + * @param string|array $name + * @param mixed $value + */ + public static function assign($name, $value = null) + { + static::$vars = array_merge(static::$vars, is_array($name) ? $name : [$name => $value]); + } + + /** + * Render. + * @param string $template + * @param array $vars + * @param string|null $app + * @param string|null $plugin + * @return string + */ + public static function render(string $template, array $vars, string $app = null, string $plugin = null): string + { + static $views = []; + $request = request(); + $plugin = $plugin === null ? ($request->plugin ?? '') : $plugin; + $app = $app === null ? $request->app : $app; + $configPrefix = $plugin ? "plugin.$plugin." : ''; + $viewSuffix = config("{$configPrefix}view.options.view_suffix", 'html'); + $key = "$plugin-$app"; + if (!isset($views[$key])) { + $baseViewPath = $plugin ? base_path() . "/plugin/$plugin/app" : app_path(); + $viewPath = $app === '' ? "$baseViewPath/view/" : "$baseViewPath/$app/view/"; + $views[$key] = new Environment(new FilesystemLoader($viewPath), config("{$configPrefix}view.options", [])); + $extension = config("{$configPrefix}view.extension"); + if ($extension) { + $extension($views[$key]); + } + } + $vars = array_merge(static::$vars, $vars); + $content = $views[$key]->render("$template.$viewSuffix", $vars); + static::$vars = []; + return $content; + } +} diff --git a/vendor/workerman/workerman/.github/FUNDING.yml b/vendor/workerman/workerman/.github/FUNDING.yml new file mode 100644 index 0000000..beae44f --- /dev/null +++ b/vendor/workerman/workerman/.github/FUNDING.yml @@ -0,0 +1,4 @@ +# These are supported funding model platforms + +open_collective: workerman +patreon: walkor diff --git a/vendor/workerman/workerman/.gitignore b/vendor/workerman/workerman/.gitignore new file mode 100644 index 0000000..f3f9e18 --- /dev/null +++ b/vendor/workerman/workerman/.gitignore @@ -0,0 +1,6 @@ +logs +.buildpath +.project +.settings +.idea +.DS_Store diff --git a/vendor/workerman/workerman/Autoloader.php b/vendor/workerman/workerman/Autoloader.php new file mode 100644 index 0000000..7d760e9 --- /dev/null +++ b/vendor/workerman/workerman/Autoloader.php @@ -0,0 +1,69 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman; + +/** + * Autoload. + */ +class Autoloader +{ + /** + * Autoload root path. + * + * @var string + */ + protected static $_autoloadRootPath = ''; + + /** + * Set autoload root path. + * + * @param string $root_path + * @return void + */ + public static function setRootPath($root_path) + { + self::$_autoloadRootPath = $root_path; + } + + /** + * Load files by namespace. + * + * @param string $name + * @return boolean + */ + public static function loadByNamespace($name) + { + $class_path = \str_replace('\\', \DIRECTORY_SEPARATOR, $name); + if (\strpos($name, 'Workerman\\') === 0) { + $class_file = __DIR__ . \substr($class_path, \strlen('Workerman')) . '.php'; + } else { + if (self::$_autoloadRootPath) { + $class_file = self::$_autoloadRootPath . \DIRECTORY_SEPARATOR . $class_path . '.php'; + } + if (empty($class_file) || !\is_file($class_file)) { + $class_file = __DIR__ . \DIRECTORY_SEPARATOR . '..' . \DIRECTORY_SEPARATOR . "$class_path.php"; + } + } + + if (\is_file($class_file)) { + require_once($class_file); + if (\class_exists($name, false)) { + return true; + } + } + return false; + } +} + +\spl_autoload_register('\Workerman\Autoloader::loadByNamespace'); \ No newline at end of file diff --git a/vendor/workerman/workerman/Connection/AsyncTcpConnection.php b/vendor/workerman/workerman/Connection/AsyncTcpConnection.php new file mode 100644 index 0000000..5bc8676 --- /dev/null +++ b/vendor/workerman/workerman/Connection/AsyncTcpConnection.php @@ -0,0 +1,378 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Connection; + +use StdClass; +use Workerman\Events\EventInterface; +use Workerman\Lib\Timer; +use Workerman\Worker; +use Exception; + +/** + * AsyncTcpConnection. + */ +class AsyncTcpConnection extends TcpConnection +{ + /** + * Emitted when socket connection is successfully established. + * + * @var callable|null + */ + public $onConnect = null; + + /** + * Transport layer protocol. + * + * @var string + */ + public $transport = 'tcp'; + + /** + * Status. + * + * @var int + */ + protected $_status = self::STATUS_INITIAL; + + /** + * Remote host. + * + * @var string + */ + protected $_remoteHost = ''; + + /** + * Remote port. + * + * @var int + */ + protected $_remotePort = 80; + + /** + * Connect start time. + * + * @var float + */ + protected $_connectStartTime = 0; + + /** + * Remote URI. + * + * @var string + */ + protected $_remoteURI = ''; + + /** + * Context option. + * + * @var array + */ + protected $_contextOption = null; + + /** + * Reconnect timer. + * + * @var int + */ + protected $_reconnectTimer = null; + + + /** + * PHP built-in protocols. + * + * @var array + */ + protected static $_builtinTransports = array( + 'tcp' => 'tcp', + 'udp' => 'udp', + 'unix' => 'unix', + 'ssl' => 'ssl', + 'sslv2' => 'sslv2', + 'sslv3' => 'sslv3', + 'tls' => 'tls' + ); + + /** + * Construct. + * + * @param string $remote_address + * @param array $context_option + * @throws Exception + */ + public function __construct($remote_address, array $context_option = array()) + { + $address_info = \parse_url($remote_address); + if (!$address_info) { + list($scheme, $this->_remoteAddress) = \explode(':', $remote_address, 2); + if('unix' === strtolower($scheme)) { + $this->_remoteAddress = substr($remote_address, strpos($remote_address, '/') + 2); + } + if (!$this->_remoteAddress) { + Worker::safeEcho(new \Exception('bad remote_address')); + } + } else { + if (!isset($address_info['port'])) { + $address_info['port'] = 0; + } + if (!isset($address_info['path'])) { + $address_info['path'] = '/'; + } + if (!isset($address_info['query'])) { + $address_info['query'] = ''; + } else { + $address_info['query'] = '?' . $address_info['query']; + } + $this->_remoteHost = $address_info['host']; + $this->_remotePort = $address_info['port']; + $this->_remoteURI = "{$address_info['path']}{$address_info['query']}"; + $scheme = isset($address_info['scheme']) ? $address_info['scheme'] : 'tcp'; + $this->_remoteAddress = 'unix' === strtolower($scheme) + ? substr($remote_address, strpos($remote_address, '/') + 2) + : $this->_remoteHost . ':' . $this->_remotePort; + } + + $this->id = $this->_id = self::$_idRecorder++; + if(\PHP_INT_MAX === self::$_idRecorder){ + self::$_idRecorder = 0; + } + // Check application layer protocol class. + if (!isset(self::$_builtinTransports[$scheme])) { + $scheme = \ucfirst($scheme); + $this->protocol = '\\Protocols\\' . $scheme; + if (!\class_exists($this->protocol)) { + $this->protocol = "\\Workerman\\Protocols\\$scheme"; + if (!\class_exists($this->protocol)) { + throw new Exception("class \\Protocols\\$scheme not exist"); + } + } + } else { + $this->transport = self::$_builtinTransports[$scheme]; + } + + // For statistics. + ++self::$statistics['connection_count']; + $this->maxSendBufferSize = self::$defaultMaxSendBufferSize; + $this->maxPackageSize = self::$defaultMaxPackageSize; + $this->_contextOption = $context_option; + $this->context = new StdClass; + static::$connections[$this->_id] = $this; + } + + /** + * Do connect. + * + * @return void + */ + public function connect() + { + if ($this->_status !== self::STATUS_INITIAL && $this->_status !== self::STATUS_CLOSING && + $this->_status !== self::STATUS_CLOSED) { + return; + } + $this->_status = self::STATUS_CONNECTING; + $this->_connectStartTime = \microtime(true); + if ($this->transport !== 'unix') { + if (!$this->_remotePort) { + $this->_remotePort = $this->transport === 'ssl' ? 443 : 80; + $this->_remoteAddress = $this->_remoteHost.':'.$this->_remotePort; + } + // Open socket connection asynchronously. + if ($this->_contextOption) { + $context = \stream_context_create($this->_contextOption); + $this->_socket = \stream_socket_client("tcp://{$this->_remoteHost}:{$this->_remotePort}", + $errno, $errstr, 0, \STREAM_CLIENT_ASYNC_CONNECT, $context); + } else { + $this->_socket = \stream_socket_client("tcp://{$this->_remoteHost}:{$this->_remotePort}", + $errno, $errstr, 0, \STREAM_CLIENT_ASYNC_CONNECT); + } + } else { + $this->_socket = \stream_socket_client("{$this->transport}://{$this->_remoteAddress}", $errno, $errstr, 0, + \STREAM_CLIENT_ASYNC_CONNECT); + } + // If failed attempt to emit onError callback. + if (!$this->_socket || !\is_resource($this->_socket)) { + $this->emitError(\WORKERMAN_CONNECT_FAIL, $errstr); + if ($this->_status === self::STATUS_CLOSING) { + $this->destroy(); + } + if ($this->_status === self::STATUS_CLOSED) { + $this->onConnect = null; + } + return; + } + // Add socket to global event loop waiting connection is successfully established or faild. + Worker::$globalEvent->add($this->_socket, EventInterface::EV_WRITE, array($this, 'checkConnection')); + // For windows. + if(\DIRECTORY_SEPARATOR === '\\') { + Worker::$globalEvent->add($this->_socket, EventInterface::EV_EXCEPT, array($this, 'checkConnection')); + } + } + + /** + * Reconnect. + * + * @param int $after + * @return void + */ + public function reconnect($after = 0) + { + $this->_status = self::STATUS_INITIAL; + static::$connections[$this->_id] = $this; + if ($this->_reconnectTimer) { + Timer::del($this->_reconnectTimer); + } + if ($after > 0) { + $this->_reconnectTimer = Timer::add($after, array($this, 'connect'), null, false); + return; + } + $this->connect(); + } + + /** + * CancelReconnect. + */ + public function cancelReconnect() + { + if ($this->_reconnectTimer) { + Timer::del($this->_reconnectTimer); + } + } + + /** + * Get remote address. + * + * @return string + */ + public function getRemoteHost() + { + return $this->_remoteHost; + } + + /** + * Get remote URI. + * + * @return string + */ + public function getRemoteURI() + { + return $this->_remoteURI; + } + + /** + * Try to emit onError callback. + * + * @param int $code + * @param string $msg + * @return void + */ + protected function emitError($code, $msg) + { + $this->_status = self::STATUS_CLOSING; + if ($this->onError) { + try { + \call_user_func($this->onError, $this, $code, $msg); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + } + } + + /** + * Check connection is successfully established or faild. + * + * @param resource $socket + * @return void + */ + public function checkConnection() + { + // Remove EV_EXPECT for windows. + if(\DIRECTORY_SEPARATOR === '\\') { + Worker::$globalEvent->del($this->_socket, EventInterface::EV_EXCEPT); + } + + // Remove write listener. + Worker::$globalEvent->del($this->_socket, EventInterface::EV_WRITE); + + if ($this->_status !== self::STATUS_CONNECTING) { + return; + } + + // Check socket state. + if ($address = \stream_socket_get_name($this->_socket, true)) { + // Nonblocking. + \stream_set_blocking($this->_socket, false); + // Compatible with hhvm + if (\function_exists('stream_set_read_buffer')) { + \stream_set_read_buffer($this->_socket, 0); + } + // Try to open keepalive for tcp and disable Nagle algorithm. + if (\function_exists('socket_import_stream') && $this->transport === 'tcp') { + $raw_socket = \socket_import_stream($this->_socket); + \socket_set_option($raw_socket, \SOL_SOCKET, \SO_KEEPALIVE, 1); + \socket_set_option($raw_socket, \SOL_TCP, \TCP_NODELAY, 1); + } + + // SSL handshake. + if ($this->transport === 'ssl') { + $this->_sslHandshakeCompleted = $this->doSslHandshake($this->_socket); + if ($this->_sslHandshakeCompleted === false) { + return; + } + } else { + // There are some data waiting to send. + if ($this->_sendBuffer) { + Worker::$globalEvent->add($this->_socket, EventInterface::EV_WRITE, array($this, 'baseWrite')); + } + } + + // Register a listener waiting read event. + Worker::$globalEvent->add($this->_socket, EventInterface::EV_READ, array($this, 'baseRead')); + + $this->_status = self::STATUS_ESTABLISHED; + $this->_remoteAddress = $address; + + // Try to emit onConnect callback. + if ($this->onConnect) { + try { + \call_user_func($this->onConnect, $this); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + } + // Try to emit protocol::onConnect + if ($this->protocol && \method_exists($this->protocol, 'onConnect')) { + try { + \call_user_func(array($this->protocol, 'onConnect'), $this); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + } + } else { + // Connection failed. + $this->emitError(\WORKERMAN_CONNECT_FAIL, 'connect ' . $this->_remoteAddress . ' fail after ' . round(\microtime(true) - $this->_connectStartTime, 4) . ' seconds'); + if ($this->_status === self::STATUS_CLOSING) { + $this->destroy(); + } + if ($this->_status === self::STATUS_CLOSED) { + $this->onConnect = null; + } + } + } +} diff --git a/vendor/workerman/workerman/Connection/AsyncUdpConnection.php b/vendor/workerman/workerman/Connection/AsyncUdpConnection.php new file mode 100644 index 0000000..745f060 --- /dev/null +++ b/vendor/workerman/workerman/Connection/AsyncUdpConnection.php @@ -0,0 +1,203 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Connection; + +use Workerman\Events\EventInterface; +use Workerman\Worker; +use \Exception; + +/** + * AsyncUdpConnection. + */ +class AsyncUdpConnection extends UdpConnection +{ + /** + * Emitted when socket connection is successfully established. + * + * @var callable + */ + public $onConnect = null; + + /** + * Emitted when socket connection closed. + * + * @var callable + */ + public $onClose = null; + + /** + * Connected or not. + * + * @var bool + */ + protected $connected = false; + + /** + * Context option. + * + * @var array + */ + protected $_contextOption = null; + + /** + * Construct. + * + * @param string $remote_address + * @throws Exception + */ + public function __construct($remote_address, $context_option = null) + { + // Get the application layer communication protocol and listening address. + list($scheme, $address) = \explode(':', $remote_address, 2); + // Check application layer protocol class. + if ($scheme !== 'udp') { + $scheme = \ucfirst($scheme); + $this->protocol = '\\Protocols\\' . $scheme; + if (!\class_exists($this->protocol)) { + $this->protocol = "\\Workerman\\Protocols\\$scheme"; + if (!\class_exists($this->protocol)) { + throw new Exception("class \\Protocols\\$scheme not exist"); + } + } + } + + $this->_remoteAddress = \substr($address, 2); + $this->_contextOption = $context_option; + } + + /** + * For udp package. + * + * @param resource $socket + * @return bool + */ + public function baseRead($socket) + { + $recv_buffer = \stream_socket_recvfrom($socket, Worker::MAX_UDP_PACKAGE_SIZE, 0, $remote_address); + if (false === $recv_buffer || empty($remote_address)) { + return false; + } + + if ($this->onMessage) { + if ($this->protocol) { + $parser = $this->protocol; + $recv_buffer = $parser::decode($recv_buffer, $this); + } + ++ConnectionInterface::$statistics['total_request']; + try { + \call_user_func($this->onMessage, $this, $recv_buffer); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + } + return true; + } + + /** + * Sends data on the connection. + * + * @param string $send_buffer + * @param bool $raw + * @return void|boolean + */ + public function send($send_buffer, $raw = false) + { + if (false === $raw && $this->protocol) { + $parser = $this->protocol; + $send_buffer = $parser::encode($send_buffer, $this); + if ($send_buffer === '') { + return; + } + } + if ($this->connected === false) { + $this->connect(); + } + return \strlen($send_buffer) === \stream_socket_sendto($this->_socket, $send_buffer, 0); + } + + + /** + * Close connection. + * + * @param mixed $data + * @param bool $raw + * + * @return bool + */ + public function close($data = null, $raw = false) + { + if ($data !== null) { + $this->send($data, $raw); + } + Worker::$globalEvent->del($this->_socket, EventInterface::EV_READ); + \fclose($this->_socket); + $this->connected = false; + // Try to emit onClose callback. + if ($this->onClose) { + try { + \call_user_func($this->onClose, $this); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + } + $this->onConnect = $this->onMessage = $this->onClose = null; + return true; + } + + /** + * Connect. + * + * @return void + */ + public function connect() + { + if ($this->connected === true) { + return; + } + if ($this->_contextOption) { + $context = \stream_context_create($this->_contextOption); + $this->_socket = \stream_socket_client("udp://{$this->_remoteAddress}", $errno, $errmsg, + 30, \STREAM_CLIENT_CONNECT, $context); + } else { + $this->_socket = \stream_socket_client("udp://{$this->_remoteAddress}", $errno, $errmsg); + } + + if (!$this->_socket) { + Worker::safeEcho(new \Exception($errmsg)); + return; + } + + \stream_set_blocking($this->_socket, false); + + if ($this->onMessage) { + Worker::$globalEvent->add($this->_socket, EventInterface::EV_READ, array($this, 'baseRead')); + } + $this->connected = true; + // Try to emit onConnect callback. + if ($this->onConnect) { + try { + \call_user_func($this->onConnect, $this); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + } + } + +} diff --git a/vendor/workerman/workerman/Connection/ConnectionInterface.php b/vendor/workerman/workerman/Connection/ConnectionInterface.php new file mode 100644 index 0000000..5d815d8 --- /dev/null +++ b/vendor/workerman/workerman/Connection/ConnectionInterface.php @@ -0,0 +1,126 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Connection; + +/** + * ConnectionInterface. + */ +#[\AllowDynamicProperties] +abstract class ConnectionInterface +{ + /** + * Statistics for status command. + * + * @var array + */ + public static $statistics = array( + 'connection_count' => 0, + 'total_request' => 0, + 'throw_exception' => 0, + 'send_fail' => 0, + ); + + /** + * Emitted when data is received. + * + * @var callable + */ + public $onMessage = null; + + /** + * Emitted when the other end of the socket sends a FIN packet. + * + * @var callable + */ + public $onClose = null; + + /** + * Emitted when an error occurs with connection. + * + * @var callable + */ + public $onError = null; + + /** + * Sends data on the connection. + * + * @param mixed $send_buffer + * @return void|boolean + */ + abstract public function send($send_buffer); + + /** + * Get remote IP. + * + * @return string + */ + abstract public function getRemoteIp(); + + /** + * Get remote port. + * + * @return int + */ + abstract public function getRemotePort(); + + /** + * Get remote address. + * + * @return string + */ + abstract public function getRemoteAddress(); + + /** + * Get local IP. + * + * @return string + */ + abstract public function getLocalIp(); + + /** + * Get local port. + * + * @return int + */ + abstract public function getLocalPort(); + + /** + * Get local address. + * + * @return string + */ + abstract public function getLocalAddress(); + + /** + * Is ipv4. + * + * @return bool + */ + abstract public function isIPv4(); + + /** + * Is ipv6. + * + * @return bool + */ + abstract public function isIPv6(); + + /** + * Close connection. + * + * @param string|null $data + * @return void + */ + abstract public function close($data = null); +} diff --git a/vendor/workerman/workerman/Connection/TcpConnection.php b/vendor/workerman/workerman/Connection/TcpConnection.php new file mode 100644 index 0000000..740f01d --- /dev/null +++ b/vendor/workerman/workerman/Connection/TcpConnection.php @@ -0,0 +1,982 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Connection; + +use Workerman\Events\EventInterface; +use Workerman\Worker; +use \Exception; + +/** + * TcpConnection. + */ +class TcpConnection extends ConnectionInterface +{ + /** + * Read buffer size. + * + * @var int + */ + const READ_BUFFER_SIZE = 65535; + + /** + * Status initial. + * + * @var int + */ + const STATUS_INITIAL = 0; + + /** + * Status connecting. + * + * @var int + */ + const STATUS_CONNECTING = 1; + + /** + * Status connection established. + * + * @var int + */ + const STATUS_ESTABLISHED = 2; + + /** + * Status closing. + * + * @var int + */ + const STATUS_CLOSING = 4; + + /** + * Status closed. + * + * @var int + */ + const STATUS_CLOSED = 8; + + /** + * Emitted when data is received. + * + * @var callable + */ + public $onMessage = null; + + /** + * Emitted when the other end of the socket sends a FIN packet. + * + * @var callable + */ + public $onClose = null; + + /** + * Emitted when an error occurs with connection. + * + * @var callable + */ + public $onError = null; + + /** + * Emitted when the send buffer becomes full. + * + * @var callable + */ + public $onBufferFull = null; + + /** + * Emitted when the send buffer becomes empty. + * + * @var callable + */ + public $onBufferDrain = null; + + /** + * Application layer protocol. + * The format is like this Workerman\\Protocols\\Http. + * + * @var \Workerman\Protocols\ProtocolInterface + */ + public $protocol = null; + + /** + * Transport (tcp/udp/unix/ssl). + * + * @var string + */ + public $transport = 'tcp'; + + /** + * Which worker belong to. + * + * @var Worker + */ + public $worker = null; + + /** + * Bytes read. + * + * @var int + */ + public $bytesRead = 0; + + /** + * Bytes written. + * + * @var int + */ + public $bytesWritten = 0; + + /** + * Connection->id. + * + * @var int + */ + public $id = 0; + + /** + * A copy of $worker->id which used to clean up the connection in worker->connections + * + * @var int + */ + protected $_id = 0; + + /** + * Sets the maximum send buffer size for the current connection. + * OnBufferFull callback will be emited When the send buffer is full. + * + * @var int + */ + public $maxSendBufferSize = 1048576; + + /** + * Context. + * + * @var object|null + */ + public $context = null; + + /** + * Default send buffer size. + * + * @var int + */ + public static $defaultMaxSendBufferSize = 1048576; + + /** + * Sets the maximum acceptable packet size for the current connection. + * + * @var int + */ + public $maxPackageSize = 1048576; + + /** + * Default maximum acceptable packet size. + * + * @var int + */ + public static $defaultMaxPackageSize = 10485760; + + /** + * Id recorder. + * + * @var int + */ + protected static $_idRecorder = 1; + + /** + * Socket + * + * @var resource + */ + protected $_socket = null; + + /** + * Send buffer. + * + * @var string + */ + protected $_sendBuffer = ''; + + /** + * Receive buffer. + * + * @var string + */ + protected $_recvBuffer = ''; + + /** + * Current package length. + * + * @var int + */ + protected $_currentPackageLength = 0; + + /** + * Connection status. + * + * @var int + */ + protected $_status = self::STATUS_ESTABLISHED; + + /** + * Remote address. + * + * @var string + */ + protected $_remoteAddress = ''; + + /** + * Is paused. + * + * @var bool + */ + protected $_isPaused = false; + + /** + * SSL handshake completed or not. + * + * @var bool + */ + protected $_sslHandshakeCompleted = false; + + /** + * All connection instances. + * + * @var array + */ + public static $connections = array(); + + /** + * Status to string. + * + * @var array + */ + public static $_statusToString = array( + self::STATUS_INITIAL => 'INITIAL', + self::STATUS_CONNECTING => 'CONNECTING', + self::STATUS_ESTABLISHED => 'ESTABLISHED', + self::STATUS_CLOSING => 'CLOSING', + self::STATUS_CLOSED => 'CLOSED', + ); + + /** + * Construct. + * + * @param resource $socket + * @param string $remote_address + */ + public function __construct($socket, $remote_address = '') + { + ++self::$statistics['connection_count']; + $this->id = $this->_id = self::$_idRecorder++; + if(self::$_idRecorder === \PHP_INT_MAX){ + self::$_idRecorder = 0; + } + $this->_socket = $socket; + \stream_set_blocking($this->_socket, 0); + // Compatible with hhvm + if (\function_exists('stream_set_read_buffer')) { + \stream_set_read_buffer($this->_socket, 0); + } + Worker::$globalEvent->add($this->_socket, EventInterface::EV_READ, array($this, 'baseRead')); + $this->maxSendBufferSize = self::$defaultMaxSendBufferSize; + $this->maxPackageSize = self::$defaultMaxPackageSize; + $this->_remoteAddress = $remote_address; + static::$connections[$this->id] = $this; + $this->context = new \stdClass; + } + + /** + * Get status. + * + * @param bool $raw_output + * + * @return int|string + */ + public function getStatus($raw_output = true) + { + if ($raw_output) { + return $this->_status; + } + return self::$_statusToString[$this->_status]; + } + + /** + * Sends data on the connection. + * + * @param mixed $send_buffer + * @param bool $raw + * @return bool|null + */ + public function send($send_buffer, $raw = false) + { + if ($this->_status === self::STATUS_CLOSING || $this->_status === self::STATUS_CLOSED) { + return false; + } + + // Try to call protocol::encode($send_buffer) before sending. + if (false === $raw && $this->protocol !== null) { + $parser = $this->protocol; + $send_buffer = $parser::encode($send_buffer, $this); + if ($send_buffer === '') { + return; + } + } + + if ($this->_status !== self::STATUS_ESTABLISHED || + ($this->transport === 'ssl' && $this->_sslHandshakeCompleted !== true) + ) { + if ($this->_sendBuffer && $this->bufferIsFull()) { + ++self::$statistics['send_fail']; + return false; + } + $this->_sendBuffer .= $send_buffer; + $this->checkBufferWillFull(); + return; + } + + // Attempt to send data directly. + if ($this->_sendBuffer === '') { + if ($this->transport === 'ssl') { + Worker::$globalEvent->add($this->_socket, EventInterface::EV_WRITE, array($this, 'baseWrite')); + $this->_sendBuffer = $send_buffer; + $this->checkBufferWillFull(); + return; + } + $len = 0; + try { + $len = @\fwrite($this->_socket, $send_buffer); + } catch (\Exception $e) { + Worker::log($e); + } catch (\Error $e) { + Worker::log($e); + } + // send successful. + if ($len === \strlen($send_buffer)) { + $this->bytesWritten += $len; + return true; + } + // Send only part of the data. + if ($len > 0) { + $this->_sendBuffer = \substr($send_buffer, $len); + $this->bytesWritten += $len; + } else { + // Connection closed? + if (!\is_resource($this->_socket) || \feof($this->_socket)) { + ++self::$statistics['send_fail']; + if ($this->onError) { + try { + \call_user_func($this->onError, $this, \WORKERMAN_SEND_FAIL, 'client closed'); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + } + $this->destroy(); + return false; + } + $this->_sendBuffer = $send_buffer; + } + Worker::$globalEvent->add($this->_socket, EventInterface::EV_WRITE, array($this, 'baseWrite')); + // Check if the send buffer will be full. + $this->checkBufferWillFull(); + return; + } + + if ($this->bufferIsFull()) { + ++self::$statistics['send_fail']; + return false; + } + + $this->_sendBuffer .= $send_buffer; + // Check if the send buffer is full. + $this->checkBufferWillFull(); + } + + /** + * Get remote IP. + * + * @return string + */ + public function getRemoteIp() + { + $pos = \strrpos($this->_remoteAddress, ':'); + if ($pos) { + return (string) \substr($this->_remoteAddress, 0, $pos); + } + return ''; + } + + /** + * Get remote port. + * + * @return int + */ + public function getRemotePort() + { + if ($this->_remoteAddress) { + return (int) \substr(\strrchr($this->_remoteAddress, ':'), 1); + } + return 0; + } + + /** + * Get remote address. + * + * @return string + */ + public function getRemoteAddress() + { + return $this->_remoteAddress; + } + + /** + * Get local IP. + * + * @return string + */ + public function getLocalIp() + { + $address = $this->getLocalAddress(); + $pos = \strrpos($address, ':'); + if (!$pos) { + return ''; + } + return \substr($address, 0, $pos); + } + + /** + * Get local port. + * + * @return int + */ + public function getLocalPort() + { + $address = $this->getLocalAddress(); + $pos = \strrpos($address, ':'); + if (!$pos) { + return 0; + } + return (int)\substr(\strrchr($address, ':'), 1); + } + + /** + * Get local address. + * + * @return string + */ + public function getLocalAddress() + { + if (!\is_resource($this->_socket)) { + return ''; + } + return (string)@\stream_socket_get_name($this->_socket, false); + } + + /** + * Get send buffer queue size. + * + * @return integer + */ + public function getSendBufferQueueSize() + { + return \strlen($this->_sendBuffer); + } + + /** + * Get recv buffer queue size. + * + * @return integer + */ + public function getRecvBufferQueueSize() + { + return \strlen($this->_recvBuffer); + } + + /** + * Is ipv4. + * + * return bool. + */ + public function isIpV4() + { + if ($this->transport === 'unix') { + return false; + } + return \strpos($this->getRemoteIp(), ':') === false; + } + + /** + * Is ipv6. + * + * return bool. + */ + public function isIpV6() + { + if ($this->transport === 'unix') { + return false; + } + return \strpos($this->getRemoteIp(), ':') !== false; + } + + /** + * Pauses the reading of data. That is onMessage will not be emitted. Useful to throttle back an upload. + * + * @return void + */ + public function pauseRecv() + { + Worker::$globalEvent->del($this->_socket, EventInterface::EV_READ); + $this->_isPaused = true; + } + + /** + * Resumes reading after a call to pauseRecv. + * + * @return void + */ + public function resumeRecv() + { + if ($this->_isPaused === true) { + Worker::$globalEvent->add($this->_socket, EventInterface::EV_READ, array($this, 'baseRead')); + $this->_isPaused = false; + $this->baseRead($this->_socket, false); + } + } + + + + /** + * Base read handler. + * + * @param resource $socket + * @param bool $check_eof + * @return void + */ + public function baseRead($socket, $check_eof = true) + { + // SSL handshake. + if ($this->transport === 'ssl' && $this->_sslHandshakeCompleted !== true) { + if ($this->doSslHandshake($socket)) { + $this->_sslHandshakeCompleted = true; + if ($this->_sendBuffer) { + Worker::$globalEvent->add($socket, EventInterface::EV_WRITE, array($this, 'baseWrite')); + } + } else { + return; + } + } + + $buffer = ''; + try { + $buffer = @\fread($socket, self::READ_BUFFER_SIZE); + } catch (\Exception $e) {} catch (\Error $e) {} + + // Check connection closed. + if ($buffer === '' || $buffer === false) { + if ($check_eof && (\feof($socket) || !\is_resource($socket) || $buffer === false)) { + $this->destroy(); + return; + } + } else { + $this->bytesRead += \strlen($buffer); + $this->_recvBuffer .= $buffer; + } + + // If the application layer protocol has been set up. + if ($this->protocol !== null) { + $parser = $this->protocol; + while ($this->_recvBuffer !== '' && !$this->_isPaused) { + // The current packet length is known. + if ($this->_currentPackageLength) { + // Data is not enough for a package. + if ($this->_currentPackageLength > \strlen($this->_recvBuffer)) { + break; + } + } else { + // Get current package length. + try { + $this->_currentPackageLength = $parser::input($this->_recvBuffer, $this); + } catch (\Exception $e) {} catch (\Error $e) {} + // The packet length is unknown. + if ($this->_currentPackageLength === 0) { + break; + } elseif ($this->_currentPackageLength > 0 && $this->_currentPackageLength <= $this->maxPackageSize) { + // Data is not enough for a package. + if ($this->_currentPackageLength > \strlen($this->_recvBuffer)) { + break; + } + } // Wrong package. + else { + Worker::safeEcho('Error package. package_length=' . \var_export($this->_currentPackageLength, true)); + $this->destroy(); + return; + } + } + + // The data is enough for a packet. + ++self::$statistics['total_request']; + // The current packet length is equal to the length of the buffer. + if (\strlen($this->_recvBuffer) === $this->_currentPackageLength) { + $one_request_buffer = $this->_recvBuffer; + $this->_recvBuffer = ''; + } else { + // Get a full package from the buffer. + $one_request_buffer = \substr($this->_recvBuffer, 0, $this->_currentPackageLength); + // Remove the current package from the receive buffer. + $this->_recvBuffer = \substr($this->_recvBuffer, $this->_currentPackageLength); + } + // Reset the current packet length to 0. + $this->_currentPackageLength = 0; + if (!$this->onMessage) { + continue; + } + try { + // Decode request buffer before Emitting onMessage callback. + \call_user_func($this->onMessage, $this, $parser::decode($one_request_buffer, $this)); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + } + return; + } + + if ($this->_recvBuffer === '' || $this->_isPaused) { + return; + } + + // Applications protocol is not set. + ++self::$statistics['total_request']; + if (!$this->onMessage) { + $this->_recvBuffer = ''; + return; + } + try { + \call_user_func($this->onMessage, $this, $this->_recvBuffer); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + // Clean receive buffer. + $this->_recvBuffer = ''; + } + + /** + * Base write handler. + * + * @return void|bool + */ + public function baseWrite() + { + \set_error_handler(function(){}); + if ($this->transport === 'ssl') { + $len = @\fwrite($this->_socket, $this->_sendBuffer, 8192); + } else { + $len = @\fwrite($this->_socket, $this->_sendBuffer); + } + \restore_error_handler(); + if ($len === \strlen($this->_sendBuffer)) { + $this->bytesWritten += $len; + Worker::$globalEvent->del($this->_socket, EventInterface::EV_WRITE); + $this->_sendBuffer = ''; + // Try to emit onBufferDrain callback when the send buffer becomes empty. + if ($this->onBufferDrain) { + try { + \call_user_func($this->onBufferDrain, $this); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + } + if ($this->_status === self::STATUS_CLOSING) { + $this->destroy(); + } + return true; + } + if ($len > 0) { + $this->bytesWritten += $len; + $this->_sendBuffer = \substr($this->_sendBuffer, $len); + } else { + ++self::$statistics['send_fail']; + $this->destroy(); + } + } + + /** + * SSL handshake. + * + * @param resource $socket + * @return bool + */ + public function doSslHandshake($socket){ + if (\feof($socket)) { + $this->destroy(); + return false; + } + $async = $this instanceof AsyncTcpConnection; + + /** + * We disabled ssl3 because https://blog.qualys.com/ssllabs/2014/10/15/ssl-3-is-dead-killed-by-the-poodle-attack. + * You can enable ssl3 by the codes below. + */ + /*if($async){ + $type = STREAM_CRYPTO_METHOD_SSLv2_CLIENT | STREAM_CRYPTO_METHOD_SSLv23_CLIENT | STREAM_CRYPTO_METHOD_SSLv3_CLIENT; + }else{ + $type = STREAM_CRYPTO_METHOD_SSLv2_SERVER | STREAM_CRYPTO_METHOD_SSLv23_SERVER | STREAM_CRYPTO_METHOD_SSLv3_SERVER; + }*/ + + if($async){ + $type = \STREAM_CRYPTO_METHOD_SSLv2_CLIENT | \STREAM_CRYPTO_METHOD_SSLv23_CLIENT; + }else{ + $type = \STREAM_CRYPTO_METHOD_SSLv2_SERVER | \STREAM_CRYPTO_METHOD_SSLv23_SERVER; + } + + // Hidden error. + \set_error_handler(function($errno, $errstr, $file){ + if (!Worker::$daemonize) { + Worker::safeEcho("SSL handshake error: $errstr \n"); + } + }); + $ret = \stream_socket_enable_crypto($socket, true, $type); + \restore_error_handler(); + // Negotiation has failed. + if (false === $ret) { + $this->destroy(); + return false; + } elseif (0 === $ret) { + // There isn't enough data and should try again. + return 0; + } + if (isset($this->onSslHandshake)) { + try { + \call_user_func($this->onSslHandshake, $this); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + } + return true; + } + + /** + * This method pulls all the data out of a readable stream, and writes it to the supplied destination. + * + * @param self $dest + * @return void + */ + public function pipe(self $dest) + { + $source = $this; + $this->onMessage = function ($source, $data) use ($dest) { + $dest->send($data); + }; + $this->onClose = function ($source) use ($dest) { + $dest->close(); + }; + $dest->onBufferFull = function ($dest) use ($source) { + $source->pauseRecv(); + }; + $dest->onBufferDrain = function ($dest) use ($source) { + $source->resumeRecv(); + }; + } + + /** + * Remove $length of data from receive buffer. + * + * @param int $length + * @return void + */ + public function consumeRecvBuffer($length) + { + $this->_recvBuffer = \substr($this->_recvBuffer, $length); + } + + /** + * Close connection. + * + * @param mixed $data + * @param bool $raw + * @return void + */ + public function close($data = null, $raw = false) + { + if($this->_status === self::STATUS_CONNECTING){ + $this->destroy(); + return; + } + + if ($this->_status === self::STATUS_CLOSING || $this->_status === self::STATUS_CLOSED) { + return; + } + + if ($data !== null) { + $this->send($data, $raw); + } + + $this->_status = self::STATUS_CLOSING; + + if ($this->_sendBuffer === '') { + $this->destroy(); + } else { + $this->pauseRecv(); + } + } + + /** + * Get the real socket. + * + * @return resource + */ + public function getSocket() + { + return $this->_socket; + } + + /** + * Check whether the send buffer will be full. + * + * @return void + */ + protected function checkBufferWillFull() + { + if ($this->maxSendBufferSize <= \strlen($this->_sendBuffer)) { + if ($this->onBufferFull) { + try { + \call_user_func($this->onBufferFull, $this); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + } + } + } + + /** + * Whether send buffer is full. + * + * @return bool + */ + protected function bufferIsFull() + { + // Buffer has been marked as full but still has data to send then the packet is discarded. + if ($this->maxSendBufferSize <= \strlen($this->_sendBuffer)) { + if ($this->onError) { + try { + \call_user_func($this->onError, $this, \WORKERMAN_SEND_FAIL, 'send buffer full and drop package'); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + } + return true; + } + return false; + } + + /** + * Whether send buffer is Empty. + * + * @return bool + */ + public function bufferIsEmpty() + { + return empty($this->_sendBuffer); + } + + /** + * Destroy connection. + * + * @return void + */ + public function destroy() + { + // Avoid repeated calls. + if ($this->_status === self::STATUS_CLOSED) { + return; + } + // Remove event listener. + Worker::$globalEvent->del($this->_socket, EventInterface::EV_READ); + Worker::$globalEvent->del($this->_socket, EventInterface::EV_WRITE); + + // Close socket. + try { + @\fclose($this->_socket); + } catch (\Exception $e) {} catch (\Error $e) {} + + $this->_status = self::STATUS_CLOSED; + // Try to emit onClose callback. + if ($this->onClose) { + try { + \call_user_func($this->onClose, $this); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + } + // Try to emit protocol::onClose + if ($this->protocol && \method_exists($this->protocol, 'onClose')) { + try { + \call_user_func(array($this->protocol, 'onClose'), $this); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + } + $this->_sendBuffer = $this->_recvBuffer = ''; + $this->_currentPackageLength = 0; + $this->_isPaused = $this->_sslHandshakeCompleted = false; + if ($this->_status === self::STATUS_CLOSED) { + // Cleaning up the callback to avoid memory leaks. + $this->onMessage = $this->onClose = $this->onError = $this->onBufferFull = $this->onBufferDrain = null; + // Remove from worker->connections. + if ($this->worker) { + unset($this->worker->connections[$this->_id]); + } + unset(static::$connections[$this->_id]); + } + } + + /** + * Destruct. + * + * @return void + */ + public function __destruct() + { + static $mod; + self::$statistics['connection_count']--; + if (Worker::getGracefulStop()) { + if (!isset($mod)) { + $mod = \ceil((self::$statistics['connection_count'] + 1) / 3); + } + + if (0 === self::$statistics['connection_count'] % $mod) { + Worker::log('worker[' . \posix_getpid() . '] remains ' . self::$statistics['connection_count'] . ' connection(s)'); + } + + if(0 === self::$statistics['connection_count']) { + Worker::stopAll(); + } + } + } +} diff --git a/vendor/workerman/workerman/Connection/UdpConnection.php b/vendor/workerman/workerman/Connection/UdpConnection.php new file mode 100644 index 0000000..9cd95ba --- /dev/null +++ b/vendor/workerman/workerman/Connection/UdpConnection.php @@ -0,0 +1,208 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Connection; + +/** + * UdpConnection. + */ +class UdpConnection extends ConnectionInterface +{ + /** + * Application layer protocol. + * The format is like this Workerman\\Protocols\\Http. + * + * @var \Workerman\Protocols\ProtocolInterface + */ + public $protocol = null; + + /** + * Transport layer protocol. + * + * @var string + */ + public $transport = 'udp'; + + /** + * Udp socket. + * + * @var resource + */ + protected $_socket = null; + + /** + * Remote address. + * + * @var string + */ + protected $_remoteAddress = ''; + + /** + * Construct. + * + * @param resource $socket + * @param string $remote_address + */ + public function __construct($socket, $remote_address) + { + $this->_socket = $socket; + $this->_remoteAddress = $remote_address; + } + + /** + * Sends data on the connection. + * + * @param string $send_buffer + * @param bool $raw + * @return void|boolean + */ + public function send($send_buffer, $raw = false) + { + if (false === $raw && $this->protocol) { + $parser = $this->protocol; + $send_buffer = $parser::encode($send_buffer, $this); + if ($send_buffer === '') { + return; + } + } + return \strlen($send_buffer) === \stream_socket_sendto($this->_socket, $send_buffer, 0, $this->isIpV6() ? '[' . $this->getRemoteIp() . ']:' . $this->getRemotePort() : $this->_remoteAddress); + } + + /** + * Get remote IP. + * + * @return string + */ + public function getRemoteIp() + { + $pos = \strrpos($this->_remoteAddress, ':'); + if ($pos) { + return \trim(\substr($this->_remoteAddress, 0, $pos), '[]'); + } + return ''; + } + + /** + * Get remote port. + * + * @return int + */ + public function getRemotePort() + { + if ($this->_remoteAddress) { + return (int)\substr(\strrchr($this->_remoteAddress, ':'), 1); + } + return 0; + } + + /** + * Get remote address. + * + * @return string + */ + public function getRemoteAddress() + { + return $this->_remoteAddress; + } + + /** + * Get local IP. + * + * @return string + */ + public function getLocalIp() + { + $address = $this->getLocalAddress(); + $pos = \strrpos($address, ':'); + if (!$pos) { + return ''; + } + return \substr($address, 0, $pos); + } + + /** + * Get local port. + * + * @return int + */ + public function getLocalPort() + { + $address = $this->getLocalAddress(); + $pos = \strrpos($address, ':'); + if (!$pos) { + return 0; + } + return (int)\substr(\strrchr($address, ':'), 1); + } + + /** + * Get local address. + * + * @return string + */ + public function getLocalAddress() + { + return (string)@\stream_socket_get_name($this->_socket, false); + } + + /** + * Is ipv4. + * + * @return bool. + */ + public function isIpV4() + { + if ($this->transport === 'unix') { + return false; + } + return \strpos($this->getRemoteIp(), ':') === false; + } + + /** + * Is ipv6. + * + * @return bool. + */ + public function isIpV6() + { + if ($this->transport === 'unix') { + return false; + } + return \strpos($this->getRemoteIp(), ':') !== false; + } + + /** + * Close connection. + * + * @param mixed $data + * @param bool $raw + * @return bool + */ + public function close($data = null, $raw = false) + { + if ($data !== null) { + $this->send($data, $raw); + } + return true; + } + + /** + * Get the real socket. + * + * @return resource + */ + public function getSocket() + { + return $this->_socket; + } +} diff --git a/vendor/workerman/workerman/Events/Ev.php b/vendor/workerman/workerman/Events/Ev.php new file mode 100644 index 0000000..82c9bdf --- /dev/null +++ b/vendor/workerman/workerman/Events/Ev.php @@ -0,0 +1,191 @@ + + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events; + +use Workerman\Worker; +use \EvWatcher; + +/** + * ev eventloop + */ +class Ev implements EventInterface +{ + /** + * All listeners for read/write event. + * + * @var array + */ + protected $_allEvents = array(); + + /** + * Event listeners of signal. + * + * @var array + */ + protected $_eventSignal = array(); + + /** + * All timer event listeners. + * [func, args, event, flag, time_interval] + * + * @var array + */ + protected $_eventTimer = array(); + + /** + * Timer id. + * + * @var int + */ + protected static $_timerId = 1; + + /** + * Add a timer. + * {@inheritdoc} + */ + public function add($fd, $flag, $func, $args = null) + { + $callback = function ($event, $socket) use ($fd, $func) { + try { + \call_user_func($func, $fd); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + }; + switch ($flag) { + case self::EV_SIGNAL: + $event = new \EvSignal($fd, $callback); + $this->_eventSignal[$fd] = $event; + return true; + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + $repeat = $flag === self::EV_TIMER_ONCE ? 0 : $fd; + $param = array($func, (array)$args, $flag, $fd, self::$_timerId); + $event = new \EvTimer($fd, $repeat, array($this, 'timerCallback'), $param); + $this->_eventTimer[self::$_timerId] = $event; + return self::$_timerId++; + default : + $fd_key = (int)$fd; + $real_flag = $flag === self::EV_READ ? \Ev::READ : \Ev::WRITE; + $event = new \EvIo($fd, $real_flag, $callback); + $this->_allEvents[$fd_key][$flag] = $event; + return true; + } + + } + + /** + * Remove a timer. + * {@inheritdoc} + */ + public function del($fd, $flag) + { + switch ($flag) { + case self::EV_READ: + case self::EV_WRITE: + $fd_key = (int)$fd; + if (isset($this->_allEvents[$fd_key][$flag])) { + $this->_allEvents[$fd_key][$flag]->stop(); + unset($this->_allEvents[$fd_key][$flag]); + } + if (empty($this->_allEvents[$fd_key])) { + unset($this->_allEvents[$fd_key]); + } + break; + case self::EV_SIGNAL: + $fd_key = (int)$fd; + if (isset($this->_eventSignal[$fd_key])) { + $this->_eventSignal[$fd_key]->stop(); + unset($this->_eventSignal[$fd_key]); + } + break; + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + if (isset($this->_eventTimer[$fd])) { + $this->_eventTimer[$fd]->stop(); + unset($this->_eventTimer[$fd]); + } + break; + } + return true; + } + + /** + * Timer callback. + * + * @param EvWatcher $event + */ + public function timerCallback(EvWatcher $event) + { + $param = $event->data; + $timer_id = $param[4]; + if ($param[2] === self::EV_TIMER_ONCE) { + $this->_eventTimer[$timer_id]->stop(); + unset($this->_eventTimer[$timer_id]); + } + try { + \call_user_func_array($param[0], $param[1]); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + } + + /** + * Remove all timers. + * + * @return void + */ + public function clearAllTimer() + { + foreach ($this->_eventTimer as $event) { + $event->stop(); + } + $this->_eventTimer = array(); + } + + /** + * Main loop. + * + * @see EventInterface::loop() + */ + public function loop() + { + \Ev::run(); + } + + /** + * Destroy loop. + * + * @return void + */ + public function destroy() + { + foreach ($this->_allEvents as $event) { + $event->stop(); + } + } + + /** + * Get timer count. + * + * @return integer + */ + public function getTimerCount() + { + return \count($this->_eventTimer); + } +} diff --git a/vendor/workerman/workerman/Events/Event.php b/vendor/workerman/workerman/Events/Event.php new file mode 100644 index 0000000..9e25521 --- /dev/null +++ b/vendor/workerman/workerman/Events/Event.php @@ -0,0 +1,215 @@ + + * @copyright 有个鬼<42765633@qq.com> + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events; + +use Workerman\Worker; + +/** + * libevent eventloop + */ +class Event implements EventInterface +{ + /** + * Event base. + * @var object + */ + protected $_eventBase = null; + + /** + * All listeners for read/write event. + * @var array + */ + protected $_allEvents = array(); + + /** + * Event listeners of signal. + * @var array + */ + protected $_eventSignal = array(); + + /** + * All timer event listeners. + * [func, args, event, flag, time_interval] + * @var array + */ + protected $_eventTimer = array(); + + /** + * Timer id. + * @var int + */ + protected static $_timerId = 1; + + /** + * construct + * @return void + */ + public function __construct() + { + if (\class_exists('\\\\EventBase', false)) { + $class_name = '\\\\EventBase'; + } else { + $class_name = '\EventBase'; + } + $this->_eventBase = new $class_name(); + } + + /** + * @see EventInterface::add() + */ + public function add($fd, $flag, $func, $args=array()) + { + if (\class_exists('\\\\Event', false)) { + $class_name = '\\\\Event'; + } else { + $class_name = '\Event'; + } + switch ($flag) { + case self::EV_SIGNAL: + + $fd_key = (int)$fd; + $event = $class_name::signal($this->_eventBase, $fd, $func); + if (!$event||!$event->add()) { + return false; + } + $this->_eventSignal[$fd_key] = $event; + return true; + + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + + $param = array($func, (array)$args, $flag, $fd, self::$_timerId); + $event = new $class_name($this->_eventBase, -1, $class_name::TIMEOUT|$class_name::PERSIST, array($this, "timerCallback"), $param); + if (!$event||!$event->addTimer($fd)) { + return false; + } + $this->_eventTimer[self::$_timerId] = $event; + return self::$_timerId++; + + default : + $fd_key = (int)$fd; + $real_flag = $flag === self::EV_READ ? $class_name::READ | $class_name::PERSIST : $class_name::WRITE | $class_name::PERSIST; + $event = new $class_name($this->_eventBase, $fd, $real_flag, $func, $fd); + if (!$event||!$event->add()) { + return false; + } + $this->_allEvents[$fd_key][$flag] = $event; + return true; + } + } + + /** + * @see Events\EventInterface::del() + */ + public function del($fd, $flag) + { + switch ($flag) { + + case self::EV_READ: + case self::EV_WRITE: + + $fd_key = (int)$fd; + if (isset($this->_allEvents[$fd_key][$flag])) { + $this->_allEvents[$fd_key][$flag]->del(); + unset($this->_allEvents[$fd_key][$flag]); + } + if (empty($this->_allEvents[$fd_key])) { + unset($this->_allEvents[$fd_key]); + } + break; + + case self::EV_SIGNAL: + $fd_key = (int)$fd; + if (isset($this->_eventSignal[$fd_key])) { + $this->_eventSignal[$fd_key]->del(); + unset($this->_eventSignal[$fd_key]); + } + break; + + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + if (isset($this->_eventTimer[$fd])) { + $this->_eventTimer[$fd]->del(); + unset($this->_eventTimer[$fd]); + } + break; + } + return true; + } + + /** + * Timer callback. + * @param int|null $fd + * @param int $what + * @param int $timer_id + */ + public function timerCallback($fd, $what, $param) + { + $timer_id = $param[4]; + + if ($param[2] === self::EV_TIMER_ONCE) { + $this->_eventTimer[$timer_id]->del(); + unset($this->_eventTimer[$timer_id]); + } + + try { + \call_user_func_array($param[0], $param[1]); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + } + + /** + * @see Events\EventInterface::clearAllTimer() + * @return void + */ + public function clearAllTimer() + { + foreach ($this->_eventTimer as $event) { + $event->del(); + } + $this->_eventTimer = array(); + } + + + /** + * @see EventInterface::loop() + */ + public function loop() + { + $this->_eventBase->loop(); + } + + /** + * Destroy loop. + * + * @return void + */ + public function destroy() + { + $this->_eventBase->exit(); + } + + /** + * Get timer count. + * + * @return integer + */ + public function getTimerCount() + { + return \count($this->_eventTimer); + } +} diff --git a/vendor/workerman/workerman/Events/EventInterface.php b/vendor/workerman/workerman/Events/EventInterface.php new file mode 100644 index 0000000..e6f59c6 --- /dev/null +++ b/vendor/workerman/workerman/Events/EventInterface.php @@ -0,0 +1,107 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events; + +interface EventInterface +{ + /** + * Read event. + * + * @var int + */ + const EV_READ = 1; + + /** + * Write event. + * + * @var int + */ + const EV_WRITE = 2; + + /** + * Except event + * + * @var int + */ + const EV_EXCEPT = 3; + + /** + * Signal event. + * + * @var int + */ + const EV_SIGNAL = 4; + + /** + * Timer event. + * + * @var int + */ + const EV_TIMER = 8; + + /** + * Timer once event. + * + * @var int + */ + const EV_TIMER_ONCE = 16; + + /** + * Add event listener to event loop. + * + * @param mixed $fd + * @param int $flag + * @param callable $func + * @param array $args + * @return bool + */ + public function add($fd, $flag, $func, $args = array()); + + /** + * Remove event listener from event loop. + * + * @param mixed $fd + * @param int $flag + * @return bool + */ + public function del($fd, $flag); + + /** + * Remove all timers. + * + * @return void + */ + public function clearAllTimer(); + + /** + * Main loop. + * + * @return void + */ + public function loop(); + + /** + * Destroy loop. + * + * @return mixed + */ + public function destroy(); + + /** + * Get Timer count. + * + * @return mixed + */ + public function getTimerCount(); +} diff --git a/vendor/workerman/workerman/Events/Libevent.php b/vendor/workerman/workerman/Events/Libevent.php new file mode 100644 index 0000000..5f61e9c --- /dev/null +++ b/vendor/workerman/workerman/Events/Libevent.php @@ -0,0 +1,225 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events; + +use Workerman\Worker; + +/** + * libevent eventloop + */ +class Libevent implements EventInterface +{ + /** + * Event base. + * + * @var resource + */ + protected $_eventBase = null; + + /** + * All listeners for read/write event. + * + * @var array + */ + protected $_allEvents = array(); + + /** + * Event listeners of signal. + * + * @var array + */ + protected $_eventSignal = array(); + + /** + * All timer event listeners. + * [func, args, event, flag, time_interval] + * + * @var array + */ + protected $_eventTimer = array(); + + /** + * construct + */ + public function __construct() + { + $this->_eventBase = \event_base_new(); + } + + /** + * {@inheritdoc} + */ + public function add($fd, $flag, $func, $args = array()) + { + switch ($flag) { + case self::EV_SIGNAL: + $fd_key = (int)$fd; + $real_flag = \EV_SIGNAL | \EV_PERSIST; + $this->_eventSignal[$fd_key] = \event_new(); + if (!\event_set($this->_eventSignal[$fd_key], $fd, $real_flag, $func, null)) { + return false; + } + if (!\event_base_set($this->_eventSignal[$fd_key], $this->_eventBase)) { + return false; + } + if (!\event_add($this->_eventSignal[$fd_key])) { + return false; + } + return true; + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + $event = \event_new(); + $timer_id = (int)$event; + if (!\event_set($event, 0, \EV_TIMEOUT, array($this, 'timerCallback'), $timer_id)) { + return false; + } + + if (!\event_base_set($event, $this->_eventBase)) { + return false; + } + + $time_interval = $fd * 1000000; + if (!\event_add($event, $time_interval)) { + return false; + } + $this->_eventTimer[$timer_id] = array($func, (array)$args, $event, $flag, $time_interval); + return $timer_id; + + default : + $fd_key = (int)$fd; + $real_flag = $flag === self::EV_READ ? \EV_READ | \EV_PERSIST : \EV_WRITE | \EV_PERSIST; + + $event = \event_new(); + + if (!\event_set($event, $fd, $real_flag, $func, null)) { + return false; + } + + if (!\event_base_set($event, $this->_eventBase)) { + return false; + } + + if (!\event_add($event)) { + return false; + } + + $this->_allEvents[$fd_key][$flag] = $event; + + return true; + } + + } + + /** + * {@inheritdoc} + */ + public function del($fd, $flag) + { + switch ($flag) { + case self::EV_READ: + case self::EV_WRITE: + $fd_key = (int)$fd; + if (isset($this->_allEvents[$fd_key][$flag])) { + \event_del($this->_allEvents[$fd_key][$flag]); + unset($this->_allEvents[$fd_key][$flag]); + } + if (empty($this->_allEvents[$fd_key])) { + unset($this->_allEvents[$fd_key]); + } + break; + case self::EV_SIGNAL: + $fd_key = (int)$fd; + if (isset($this->_eventSignal[$fd_key])) { + \event_del($this->_eventSignal[$fd_key]); + unset($this->_eventSignal[$fd_key]); + } + break; + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + // 这里 fd 为timerid + if (isset($this->_eventTimer[$fd])) { + \event_del($this->_eventTimer[$fd][2]); + unset($this->_eventTimer[$fd]); + } + break; + } + return true; + } + + /** + * Timer callback. + * + * @param mixed $_null1 + * @param int $_null2 + * @param mixed $timer_id + */ + protected function timerCallback($_null1, $_null2, $timer_id) + { + if ($this->_eventTimer[$timer_id][3] === self::EV_TIMER) { + \event_add($this->_eventTimer[$timer_id][2], $this->_eventTimer[$timer_id][4]); + } + try { + \call_user_func_array($this->_eventTimer[$timer_id][0], $this->_eventTimer[$timer_id][1]); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + if (isset($this->_eventTimer[$timer_id]) && $this->_eventTimer[$timer_id][3] === self::EV_TIMER_ONCE) { + $this->del($timer_id, self::EV_TIMER_ONCE); + } + } + + /** + * {@inheritdoc} + */ + public function clearAllTimer() + { + foreach ($this->_eventTimer as $task_data) { + \event_del($task_data[2]); + } + $this->_eventTimer = array(); + } + + /** + * {@inheritdoc} + */ + public function loop() + { + \event_base_loop($this->_eventBase); + } + + /** + * Destroy loop. + * + * @return void + */ + public function destroy() + { + foreach ($this->_eventSignal as $event) { + \event_del($event); + } + } + + /** + * Get timer count. + * + * @return integer + */ + public function getTimerCount() + { + return \count($this->_eventTimer); + } +} + diff --git a/vendor/workerman/workerman/Events/React/Base.php b/vendor/workerman/workerman/Events/React/Base.php new file mode 100644 index 0000000..bce4f73 --- /dev/null +++ b/vendor/workerman/workerman/Events/React/Base.php @@ -0,0 +1,264 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events\React; + +use Workerman\Events\EventInterface; +use React\EventLoop\TimerInterface; +use React\EventLoop\LoopInterface; + +/** + * Class StreamSelectLoop + * @package Workerman\Events\React + */ +class Base implements LoopInterface +{ + /** + * @var array + */ + protected $_timerIdMap = array(); + + /** + * @var int + */ + protected $_timerIdIndex = 0; + + /** + * @var array + */ + protected $_signalHandlerMap = array(); + + /** + * @var LoopInterface + */ + protected $_eventLoop = null; + + /** + * Base constructor. + */ + public function __construct() + { + $this->_eventLoop = new \React\EventLoop\StreamSelectLoop(); + } + + /** + * Add event listener to event loop. + * + * @param int $fd + * @param int $flag + * @param callable $func + * @param array $args + * @return bool + */ + public function add($fd, $flag, $func, array $args = array()) + { + $args = (array)$args; + switch ($flag) { + case EventInterface::EV_READ: + return $this->addReadStream($fd, $func); + case EventInterface::EV_WRITE: + return $this->addWriteStream($fd, $func); + case EventInterface::EV_SIGNAL: + if (isset($this->_signalHandlerMap[$fd])) { + $this->removeSignal($fd, $this->_signalHandlerMap[$fd]); + } + $this->_signalHandlerMap[$fd] = $func; + return $this->addSignal($fd, $func); + case EventInterface::EV_TIMER: + $timer_obj = $this->addPeriodicTimer($fd, function() use ($func, $args) { + \call_user_func_array($func, $args); + }); + $this->_timerIdMap[++$this->_timerIdIndex] = $timer_obj; + return $this->_timerIdIndex; + case EventInterface::EV_TIMER_ONCE: + $index = ++$this->_timerIdIndex; + $timer_obj = $this->addTimer($fd, function() use ($func, $args, $index) { + $this->del($index,EventInterface::EV_TIMER_ONCE); + \call_user_func_array($func, $args); + }); + $this->_timerIdMap[$index] = $timer_obj; + return $this->_timerIdIndex; + } + return false; + } + + /** + * Remove event listener from event loop. + * + * @param mixed $fd + * @param int $flag + * @return bool + */ + public function del($fd, $flag) + { + switch ($flag) { + case EventInterface::EV_READ: + return $this->removeReadStream($fd); + case EventInterface::EV_WRITE: + return $this->removeWriteStream($fd); + case EventInterface::EV_SIGNAL: + if (!isset($this->_eventLoop[$fd])) { + return false; + } + $func = $this->_eventLoop[$fd]; + unset($this->_eventLoop[$fd]); + return $this->removeSignal($fd, $func); + + case EventInterface::EV_TIMER: + case EventInterface::EV_TIMER_ONCE: + if (isset($this->_timerIdMap[$fd])){ + $timer_obj = $this->_timerIdMap[$fd]; + unset($this->_timerIdMap[$fd]); + $this->cancelTimer($timer_obj); + return true; + } + } + return false; + } + + + /** + * Main loop. + * + * @return void + */ + public function loop() + { + $this->run(); + } + + + /** + * Destroy loop. + * + * @return void + */ + public function destroy() + { + + } + + /** + * Get timer count. + * + * @return integer + */ + public function getTimerCount() + { + return \count($this->_timerIdMap); + } + + /** + * @param resource $stream + * @param callable $listener + */ + public function addReadStream($stream, $listener) + { + return $this->_eventLoop->addReadStream($stream, $listener); + } + + /** + * @param resource $stream + * @param callable $listener + */ + public function addWriteStream($stream, $listener) + { + return $this->_eventLoop->addWriteStream($stream, $listener); + } + + /** + * @param resource $stream + */ + public function removeReadStream($stream) + { + return $this->_eventLoop->removeReadStream($stream); + } + + /** + * @param resource $stream + */ + public function removeWriteStream($stream) + { + return $this->_eventLoop->removeWriteStream($stream); + } + + /** + * @param float|int $interval + * @param callable $callback + * @return \React\EventLoop\Timer\Timer|TimerInterface + */ + public function addTimer($interval, $callback) + { + return $this->_eventLoop->addTimer($interval, $callback); + } + + /** + * @param float|int $interval + * @param callable $callback + * @return \React\EventLoop\Timer\Timer|TimerInterface + */ + public function addPeriodicTimer($interval, $callback) + { + return $this->_eventLoop->addPeriodicTimer($interval, $callback); + } + + /** + * @param TimerInterface $timer + */ + public function cancelTimer(TimerInterface $timer) + { + return $this->_eventLoop->cancelTimer($timer); + } + + /** + * @param callable $listener + */ + public function futureTick($listener) + { + return $this->_eventLoop->futureTick($listener); + } + + /** + * @param int $signal + * @param callable $listener + */ + public function addSignal($signal, $listener) + { + return $this->_eventLoop->addSignal($signal, $listener); + } + + /** + * @param int $signal + * @param callable $listener + */ + public function removeSignal($signal, $listener) + { + return $this->_eventLoop->removeSignal($signal, $listener); + } + + /** + * Run. + */ + public function run() + { + return $this->_eventLoop->run(); + } + + /** + * Stop. + */ + public function stop() + { + return $this->_eventLoop->stop(); + } +} diff --git a/vendor/workerman/workerman/Events/React/ExtEventLoop.php b/vendor/workerman/workerman/Events/React/ExtEventLoop.php new file mode 100644 index 0000000..3dab25b --- /dev/null +++ b/vendor/workerman/workerman/Events/React/ExtEventLoop.php @@ -0,0 +1,27 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events\React; + +/** + * Class ExtEventLoop + * @package Workerman\Events\React + */ +class ExtEventLoop extends Base +{ + + public function __construct() + { + $this->_eventLoop = new \React\EventLoop\ExtEventLoop(); + } +} diff --git a/vendor/workerman/workerman/Events/React/ExtLibEventLoop.php b/vendor/workerman/workerman/Events/React/ExtLibEventLoop.php new file mode 100644 index 0000000..eb02b35 --- /dev/null +++ b/vendor/workerman/workerman/Events/React/ExtLibEventLoop.php @@ -0,0 +1,27 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events\React; +use Workerman\Events\EventInterface; + +/** + * Class ExtLibEventLoop + * @package Workerman\Events\React + */ +class ExtLibEventLoop extends Base +{ + public function __construct() + { + $this->_eventLoop = new \React\EventLoop\ExtLibeventLoop(); + } +} diff --git a/vendor/workerman/workerman/Events/React/StreamSelectLoop.php b/vendor/workerman/workerman/Events/React/StreamSelectLoop.php new file mode 100644 index 0000000..7f5f94b --- /dev/null +++ b/vendor/workerman/workerman/Events/React/StreamSelectLoop.php @@ -0,0 +1,26 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events\React; + +/** + * Class StreamSelectLoop + * @package Workerman\Events\React + */ +class StreamSelectLoop extends Base +{ + public function __construct() + { + $this->_eventLoop = new \React\EventLoop\StreamSelectLoop(); + } +} diff --git a/vendor/workerman/workerman/Events/Select.php b/vendor/workerman/workerman/Events/Select.php new file mode 100644 index 0000000..ebc263e --- /dev/null +++ b/vendor/workerman/workerman/Events/Select.php @@ -0,0 +1,357 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events; + +use Throwable; +use Workerman\Worker; + +/** + * select eventloop + */ +class Select implements EventInterface +{ + /** + * All listeners for read/write event. + * + * @var array + */ + public $_allEvents = array(); + + /** + * Event listeners of signal. + * + * @var array + */ + public $_signalEvents = array(); + + /** + * Fds waiting for read event. + * + * @var array + */ + protected $_readFds = array(); + + /** + * Fds waiting for write event. + * + * @var array + */ + protected $_writeFds = array(); + + /** + * Fds waiting for except event. + * + * @var array + */ + protected $_exceptFds = array(); + + /** + * Timer scheduler. + * {['data':timer_id, 'priority':run_timestamp], ..} + * + * @var \SplPriorityQueue + */ + protected $_scheduler = null; + + /** + * All timer event listeners. + * [[func, args, flag, timer_interval], ..] + * + * @var array + */ + protected $_eventTimer = array(); + + /** + * Timer id. + * + * @var int + */ + protected $_timerId = 1; + + /** + * Select timeout. + * + * @var int + */ + protected $_selectTimeout = 100000000; + + /** + * Paired socket channels + * + * @var array + */ + protected $channel = array(); + + /** + * Construct. + */ + public function __construct() + { + // Init SplPriorityQueue. + $this->_scheduler = new \SplPriorityQueue(); + $this->_scheduler->setExtractFlags(\SplPriorityQueue::EXTR_BOTH); + } + + /** + * {@inheritdoc} + */ + public function add($fd, $flag, $func, $args = array()) + { + switch ($flag) { + case self::EV_READ: + case self::EV_WRITE: + $count = $flag === self::EV_READ ? \count($this->_readFds) : \count($this->_writeFds); + if ($count >= 1024) { + echo "Warning: system call select exceeded the maximum number of connections 1024, please install event/libevent extension for more connections.\n"; + } else if (\DIRECTORY_SEPARATOR !== '/' && $count >= 256) { + echo "Warning: system call select exceeded the maximum number of connections 256.\n"; + } + $fd_key = (int)$fd; + $this->_allEvents[$fd_key][$flag] = array($func, $fd); + if ($flag === self::EV_READ) { + $this->_readFds[$fd_key] = $fd; + } else { + $this->_writeFds[$fd_key] = $fd; + } + break; + case self::EV_EXCEPT: + $fd_key = (int)$fd; + $this->_allEvents[$fd_key][$flag] = array($func, $fd); + $this->_exceptFds[$fd_key] = $fd; + break; + case self::EV_SIGNAL: + // Windows not support signal. + if(\DIRECTORY_SEPARATOR !== '/') { + return false; + } + $fd_key = (int)$fd; + $this->_signalEvents[$fd_key][$flag] = array($func, $fd); + \pcntl_signal($fd, array($this, 'signalHandler')); + break; + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + $timer_id = $this->_timerId++; + $run_time = \microtime(true) + $fd; + $this->_scheduler->insert($timer_id, -$run_time); + $this->_eventTimer[$timer_id] = array($func, (array)$args, $flag, $fd); + $select_timeout = ($run_time - \microtime(true)) * 1000000; + $select_timeout = $select_timeout <= 0 ? 1 : $select_timeout; + if( $this->_selectTimeout > $select_timeout ){ + $this->_selectTimeout = (int) $select_timeout; + } + return $timer_id; + } + + return true; + } + + /** + * Signal handler. + * + * @param int $signal + */ + public function signalHandler($signal) + { + \call_user_func_array($this->_signalEvents[$signal][self::EV_SIGNAL][0], array($signal)); + } + + /** + * {@inheritdoc} + */ + public function del($fd, $flag) + { + $fd_key = (int)$fd; + switch ($flag) { + case self::EV_READ: + unset($this->_allEvents[$fd_key][$flag], $this->_readFds[$fd_key]); + if (empty($this->_allEvents[$fd_key])) { + unset($this->_allEvents[$fd_key]); + } + return true; + case self::EV_WRITE: + unset($this->_allEvents[$fd_key][$flag], $this->_writeFds[$fd_key]); + if (empty($this->_allEvents[$fd_key])) { + unset($this->_allEvents[$fd_key]); + } + return true; + case self::EV_EXCEPT: + unset($this->_allEvents[$fd_key][$flag], $this->_exceptFds[$fd_key]); + if(empty($this->_allEvents[$fd_key])) + { + unset($this->_allEvents[$fd_key]); + } + return true; + case self::EV_SIGNAL: + if(\DIRECTORY_SEPARATOR !== '/') { + return false; + } + unset($this->_signalEvents[$fd_key]); + \pcntl_signal($fd, SIG_IGN); + break; + case self::EV_TIMER: + case self::EV_TIMER_ONCE; + unset($this->_eventTimer[$fd_key]); + return true; + } + return false; + } + + /** + * Tick for timer. + * + * @return void + */ + protected function tick() + { + $tasks_to_insert = []; + while (!$this->_scheduler->isEmpty()) { + $scheduler_data = $this->_scheduler->top(); + $timer_id = $scheduler_data['data']; + $next_run_time = -$scheduler_data['priority']; + $time_now = \microtime(true); + $this->_selectTimeout = (int) (($next_run_time - $time_now) * 1000000); + if ($this->_selectTimeout <= 0) { + $this->_scheduler->extract(); + + if (!isset($this->_eventTimer[$timer_id])) { + continue; + } + + // [func, args, flag, timer_interval] + $task_data = $this->_eventTimer[$timer_id]; + if ($task_data[2] === self::EV_TIMER) { + $next_run_time = $time_now + $task_data[3]; + $tasks_to_insert[] = [$timer_id, -$next_run_time]; + } + try { + \call_user_func_array($task_data[0], $task_data[1]); + } catch (Throwable $e) { + Worker::stopAll(250, $e); + } + if (isset($this->_eventTimer[$timer_id]) && $task_data[2] === self::EV_TIMER_ONCE) { + $this->del($timer_id, self::EV_TIMER_ONCE); + } + } else { + break; + } + } + foreach ($tasks_to_insert as $item) { + $this->_scheduler->insert($item[0], $item[1]); + } + if (!$this->_scheduler->isEmpty()) { + $scheduler_data = $this->_scheduler->top(); + $next_run_time = -$scheduler_data['priority']; + $time_now = \microtime(true); + $this->_selectTimeout = \max((int) (($next_run_time - $time_now) * 1000000), 0); + return; + } + $this->_selectTimeout = 100000000; + } + + /** + * {@inheritdoc} + */ + public function clearAllTimer() + { + $this->_scheduler = new \SplPriorityQueue(); + $this->_scheduler->setExtractFlags(\SplPriorityQueue::EXTR_BOTH); + $this->_eventTimer = array(); + } + + /** + * {@inheritdoc} + */ + public function loop() + { + while (1) { + if(\DIRECTORY_SEPARATOR === '/') { + // Calls signal handlers for pending signals + \pcntl_signal_dispatch(); + } + + $read = $this->_readFds; + $write = $this->_writeFds; + $except = $this->_exceptFds; + $ret = false; + + if ($read || $write || $except) { + // Waiting read/write/signal/timeout events. + try { + $ret = @stream_select($read, $write, $except, 0, $this->_selectTimeout); + } catch (\Exception $e) {} catch (\Error $e) {} + + } else { + $this->_selectTimeout >= 1 && usleep($this->_selectTimeout); + } + + if (!$this->_scheduler->isEmpty()) { + $this->tick(); + } + + if (!$ret) { + continue; + } + + if ($read) { + foreach ($read as $fd) { + $fd_key = (int)$fd; + if (isset($this->_allEvents[$fd_key][self::EV_READ])) { + \call_user_func_array($this->_allEvents[$fd_key][self::EV_READ][0], + array($this->_allEvents[$fd_key][self::EV_READ][1])); + } + } + } + + if ($write) { + foreach ($write as $fd) { + $fd_key = (int)$fd; + if (isset($this->_allEvents[$fd_key][self::EV_WRITE])) { + \call_user_func_array($this->_allEvents[$fd_key][self::EV_WRITE][0], + array($this->_allEvents[$fd_key][self::EV_WRITE][1])); + } + } + } + + if($except) { + foreach($except as $fd) { + $fd_key = (int) $fd; + if(isset($this->_allEvents[$fd_key][self::EV_EXCEPT])) { + \call_user_func_array($this->_allEvents[$fd_key][self::EV_EXCEPT][0], + array($this->_allEvents[$fd_key][self::EV_EXCEPT][1])); + } + } + } + } + } + + /** + * Destroy loop. + * + * @return void + */ + public function destroy() + { + + } + + /** + * Get timer count. + * + * @return integer + */ + public function getTimerCount() + { + return \count($this->_eventTimer); + } +} diff --git a/vendor/workerman/workerman/Events/Swoole.php b/vendor/workerman/workerman/Events/Swoole.php new file mode 100644 index 0000000..fcd7472 --- /dev/null +++ b/vendor/workerman/workerman/Events/Swoole.php @@ -0,0 +1,230 @@ + + * @link http://www.workerman.net/ + * @link https://github.com/ares333/Workerman + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events; + +use Workerman\Worker; +use Swoole\Event; +use Swoole\Timer; + +class Swoole implements EventInterface +{ + + protected $_timer = array(); + + protected $_timerOnceMap = array(); + + protected $mapId = 0; + + protected $_fd = array(); + + // milisecond + public static $signalDispatchInterval = 500; + + protected $_hasSignal = false; + + /** + * + * {@inheritdoc} + * + * @see \Workerman\Events\EventInterface::add() + */ + public function add($fd, $flag, $func, $args = array()) + { + switch ($flag) { + case self::EV_SIGNAL: + $res = \pcntl_signal($fd, $func, false); + if (! $this->_hasSignal && $res) { + Timer::tick(static::$signalDispatchInterval, + function () { + \pcntl_signal_dispatch(); + }); + $this->_hasSignal = true; + } + return $res; + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + $method = self::EV_TIMER === $flag ? 'tick' : 'after'; + if ($this->mapId > \PHP_INT_MAX) { + $this->mapId = 0; + } + $mapId = $this->mapId++; + $t = (int)($fd * 1000); + if ($t < 1) { + $t = 1; + } + $timer_id = Timer::$method($t, + function ($timer_id = null) use ($func, $args, $mapId) { + try { + \call_user_func_array($func, (array)$args); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + // EV_TIMER_ONCE + if (! isset($timer_id)) { + // may be deleted in $func + if (\array_key_exists($mapId, $this->_timerOnceMap)) { + $timer_id = $this->_timerOnceMap[$mapId]; + unset($this->_timer[$timer_id], + $this->_timerOnceMap[$mapId]); + } + } + }); + if ($flag === self::EV_TIMER_ONCE) { + $this->_timerOnceMap[$mapId] = $timer_id; + $this->_timer[$timer_id] = $mapId; + } else { + $this->_timer[$timer_id] = null; + } + return $timer_id; + case self::EV_READ: + case self::EV_WRITE: + $fd_key = (int) $fd; + if (! isset($this->_fd[$fd_key])) { + if ($flag === self::EV_READ) { + $res = Event::add($fd, $func, null, SWOOLE_EVENT_READ); + $fd_type = SWOOLE_EVENT_READ; + } else { + $res = Event::add($fd, null, $func, SWOOLE_EVENT_WRITE); + $fd_type = SWOOLE_EVENT_WRITE; + } + if ($res) { + $this->_fd[$fd_key] = $fd_type; + } + } else { + $fd_val = $this->_fd[$fd_key]; + $res = true; + if ($flag === self::EV_READ) { + if (($fd_val & SWOOLE_EVENT_READ) !== SWOOLE_EVENT_READ) { + $res = Event::set($fd, $func, null, + SWOOLE_EVENT_READ | SWOOLE_EVENT_WRITE); + $this->_fd[$fd_key] |= SWOOLE_EVENT_READ; + } + } else { + if (($fd_val & SWOOLE_EVENT_WRITE) !== SWOOLE_EVENT_WRITE) { + $res = Event::set($fd, null, $func, + SWOOLE_EVENT_READ | SWOOLE_EVENT_WRITE); + $this->_fd[$fd_key] |= SWOOLE_EVENT_WRITE; + } + } + } + return $res; + } + } + + /** + * + * {@inheritdoc} + * + * @see \Workerman\Events\EventInterface::del() + */ + public function del($fd, $flag) + { + switch ($flag) { + case self::EV_SIGNAL: + return \pcntl_signal($fd, SIG_IGN, false); + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + // already remove in EV_TIMER_ONCE callback. + if (! \array_key_exists($fd, $this->_timer)) { + return true; + } + $res = Timer::clear($fd); + if ($res) { + $mapId = $this->_timer[$fd]; + if (isset($mapId)) { + unset($this->_timerOnceMap[$mapId]); + } + unset($this->_timer[$fd]); + } + return $res; + case self::EV_READ: + case self::EV_WRITE: + $fd_key = (int) $fd; + if (isset($this->_fd[$fd_key])) { + $fd_val = $this->_fd[$fd_key]; + if ($flag === self::EV_READ) { + $flag_remove = ~ SWOOLE_EVENT_READ; + } else { + $flag_remove = ~ SWOOLE_EVENT_WRITE; + } + $fd_val &= $flag_remove; + if (0 === $fd_val) { + $res = Event::del($fd); + if ($res) { + unset($this->_fd[$fd_key]); + } + } else { + $res = Event::set($fd, null, null, $fd_val); + if ($res) { + $this->_fd[$fd_key] = $fd_val; + } + } + } else { + $res = true; + } + return $res; + } + } + + /** + * + * {@inheritdoc} + * + * @see \Workerman\Events\EventInterface::clearAllTimer() + */ + public function clearAllTimer() + { + foreach (array_keys($this->_timer) as $v) { + Timer::clear($v); + } + $this->_timer = array(); + $this->_timerOnceMap = array(); + } + + /** + * + * {@inheritdoc} + * + * @see \Workerman\Events\EventInterface::loop() + */ + public function loop() + { + Event::wait(); + } + + /** + * + * {@inheritdoc} + * + * @see \Workerman\Events\EventInterface::destroy() + */ + public function destroy() + { + Event::exit(); + posix_kill(posix_getpid(), SIGINT); + } + + /** + * + * {@inheritdoc} + * + * @see \Workerman\Events\EventInterface::getTimerCount() + */ + public function getTimerCount() + { + return \count($this->_timer); + } +} diff --git a/vendor/workerman/workerman/Events/Uv.php b/vendor/workerman/workerman/Events/Uv.php new file mode 100644 index 0000000..6efe16b --- /dev/null +++ b/vendor/workerman/workerman/Events/Uv.php @@ -0,0 +1,260 @@ + + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events; + +use Workerman\Worker; + +/** + * libuv eventloop + */ +class Uv implements EventInterface +{ + /** + * Event Loop. + * @var object + */ + protected $_eventLoop = null; + + /** + * All listeners for read/write event. + * + * @var array + */ + protected $_allEvents = array(); + + /** + * Event listeners of signal. + * + * @var array + */ + protected $_eventSignal = array(); + + /** + * All timer event listeners. + * + * @var array + */ + protected $_eventTimer = array(); + + /** + * Timer id. + * + * @var int + */ + protected static $_timerId = 1; + + /** + * @brief Constructor + * + * @param object $loop + * + * @return void + */ + public function __construct(\UVLoop $loop = null) + { + if(!extension_loaded('uv')) + { + throw new \Exception(__CLASS__ . ' requires the UV extension, but detected it has NOT been installed yet.'); + } + + if(empty($loop) || !$loop instanceof \UVLoop) + { + $this->_eventLoop = \uv_default_loop(); + return; + } + + $this->_eventLoop = $loop; + } + + /** + * @brief Add a timer + * + * @param resource $fd + * @param int $flag + * @param callback $func + * @param mixed $args + * + * @return mixed + */ + public function add($fd, $flag, $func, $args = null) + { + switch ($flag) + { + case self::EV_SIGNAL: + $signalCallback = function($watcher, $socket)use($func, $fd){ + try { + \call_user_func($func, $fd); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + }; + $signalWatcher = \uv_signal_init(); + \uv_signal_start($signalWatcher, $signalCallback, $fd); + $this->_eventSignal[$fd] = $signalWatcher; + return true; + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + $repeat = $flag === self::EV_TIMER_ONCE ? 0 : (int)($fd * 1000); + $param = array($func, (array)$args, $flag, $fd, self::$_timerId); + $timerWatcher = \uv_timer_init(); + \uv_timer_start($timerWatcher, 1, $repeat, function($watcher)use($param){ + call_user_func_array([$this, 'timerCallback'], [$param]); + }); + $this->_eventTimer[self::$_timerId] = $timerWatcher; + return self::$_timerId++; + case self::EV_READ: + case self::EV_WRITE: + $fd_key = (int)$fd; + $ioCallback = function($watcher, $status, $events, $fd)use($func){ + try { + \call_user_func($func, $fd); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + }; + $ioWatcher = \uv_poll_init($this->_eventLoop, $fd); + $real_flag = $flag === self::EV_READ ? \Uv::READABLE : \Uv::WRITABLE; + \uv_poll_start($ioWatcher, $real_flag, $ioCallback); + $this->_allEvents[$fd_key][$flag] = $ioWatcher; + return true; + default: + break; + } + } + + /** + * @brief Remove a timer + * + * @param resource $fd + * @param int $flag + * + * @return boolean + */ + public function del($fd, $flag) + { + switch ($flag) + { + case self::EV_READ: + case self::EV_WRITE: + $fd_key = (int)$fd; + if (isset($this->_allEvents[$fd_key][$flag])) { + $watcher = $this->_allEvents[$fd_key][$flag]; + \uv_is_active($watcher) && \uv_poll_stop($watcher); + unset($this->_allEvents[$fd_key][$flag]); + } + if (empty($this->_allEvents[$fd_key])) { + unset($this->_allEvents[$fd_key]); + } + break; + case self::EV_SIGNAL: + $fd_key = (int)$fd; + if (isset($this->_eventSignal[$fd_key])) { + $watcher = $this->_eventSignal[$fd_key]; + \uv_is_active($watcher) && \uv_signal_stop($watcher); + unset($this->_eventSignal[$fd_key]); + } + break; + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + if (isset($this->_eventTimer[$fd])) { + $watcher = $this->_eventTimer[$fd]; + \uv_is_active($watcher) && \uv_timer_stop($watcher); + unset($this->_eventTimer[$fd]); + } + break; + } + + return true; + } + + /** + * @brief Timer callback + * + * @param array $input + * + * @return void + */ + public function timerCallback($input) + { + if(!is_array($input)) return; + + $timer_id = $input[4]; + + if ($input[2] === self::EV_TIMER_ONCE) + { + $watcher = $this->_eventTimer[$timer_id]; + \uv_is_active($watcher) && \uv_timer_stop($watcher); + unset($this->_eventTimer[$timer_id]); + } + + try { + \call_user_func_array($input[0], $input[1]); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + } + + /** + * @brief Remove all timers + * + * @return void + */ + public function clearAllTimer() + { + if(!is_array($this->_eventTimer)) return; + + foreach($this->_eventTimer as $watcher) + { + \uv_is_active($watcher) && \uv_timer_stop($watcher); + } + + $this->_eventTimer = array(); + } + + /** + * @brief Start loop + * + * @return void + */ + public function loop() + { + \Uv_run(); + } + + /** + * @brief Destroy loop + * + * @return void + */ + public function destroy() + { + !empty($this->_eventLoop) && \uv_loop_delete($this->_eventLoop); + $this->_allEvents = []; + } + + /** + * @brief Get timer count + * + * @return integer + */ + public function getTimerCount() + { + return \count($this->_eventTimer); + } +} diff --git a/vendor/workerman/workerman/Lib/Constants.php b/vendor/workerman/workerman/Lib/Constants.php new file mode 100644 index 0000000..f5e2424 --- /dev/null +++ b/vendor/workerman/workerman/Lib/Constants.php @@ -0,0 +1,44 @@ + + * @copyright walkor + * @license http://www.opensource.org/licenses/mit-license.php MIT License + * + * @link http://www.workerman.net/ + */ + +// Pcre.jit is not stable, temporarily disabled. +ini_set('pcre.jit', 0); + +// For onError callback. +const WORKERMAN_CONNECT_FAIL = 1; +// For onError callback. +const WORKERMAN_SEND_FAIL = 2; + +// Define OS Type +const OS_TYPE_LINUX = 'linux'; +const OS_TYPE_WINDOWS = 'windows'; + +// Compatible with php7 +if (!class_exists('Error')) { + class Error extends Exception + { + } +} + +if (!interface_exists('SessionHandlerInterface')) { + interface SessionHandlerInterface { + public function close(); + public function destroy($session_id); + public function gc($maxlifetime); + public function open($save_path ,$session_name); + public function read($session_id); + public function write($session_id , $session_data); + } +} diff --git a/vendor/workerman/workerman/Lib/Timer.php b/vendor/workerman/workerman/Lib/Timer.php new file mode 100644 index 0000000..b110051 --- /dev/null +++ b/vendor/workerman/workerman/Lib/Timer.php @@ -0,0 +1,22 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Lib; + +/** + * Do not use Workerman\Lib\Timer. + * Please use Workerman\Timer. + * This class is only used for compatibility with workerman 3.* + * @package Workerman\Lib + */ +class Timer extends \Workerman\Timer {} \ No newline at end of file diff --git a/vendor/workerman/workerman/MIT-LICENSE.txt b/vendor/workerman/workerman/MIT-LICENSE.txt new file mode 100644 index 0000000..fd6b1c8 --- /dev/null +++ b/vendor/workerman/workerman/MIT-LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2009-2015 walkor and contributors (see https://github.com/walkor/workerman/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/vendor/workerman/workerman/Protocols/Frame.php b/vendor/workerman/workerman/Protocols/Frame.php new file mode 100644 index 0000000..26b04de --- /dev/null +++ b/vendor/workerman/workerman/Protocols/Frame.php @@ -0,0 +1,61 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols; + +use Workerman\Connection\TcpConnection; + +/** + * Frame Protocol. + */ +class Frame +{ + /** + * Check the integrity of the package. + * + * @param string $buffer + * @param TcpConnection $connection + * @return int + */ + public static function input($buffer, TcpConnection $connection) + { + if (\strlen($buffer) < 4) { + return 0; + } + $unpack_data = \unpack('Ntotal_length', $buffer); + return $unpack_data['total_length']; + } + + /** + * Decode. + * + * @param string $buffer + * @return string + */ + public static function decode($buffer) + { + return \substr($buffer, 4); + } + + /** + * Encode. + * + * @param string $buffer + * @return string + */ + public static function encode($buffer) + { + $total_length = 4 + \strlen($buffer); + return \pack('N', $total_length) . $buffer; + } +} diff --git a/vendor/workerman/workerman/Protocols/Http.php b/vendor/workerman/workerman/Protocols/Http.php new file mode 100644 index 0000000..9e5d928 --- /dev/null +++ b/vendor/workerman/workerman/Protocols/Http.php @@ -0,0 +1,323 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols; + +use Workerman\Connection\TcpConnection; +use Workerman\Protocols\Http\Request; +use Workerman\Protocols\Http\Response; +use Workerman\Protocols\Http\Session; +use Workerman\Protocols\Websocket; +use Workerman\Worker; + +/** + * Class Http. + * @package Workerman\Protocols + */ +class Http +{ + /** + * Request class name. + * + * @var string + */ + protected static $_requestClass = 'Workerman\Protocols\Http\Request'; + + /** + * Upload tmp dir. + * + * @var string + */ + protected static $_uploadTmpDir = ''; + + /** + * Open cache. + * + * @var bool. + */ + protected static $_enableCache = true; + + /** + * Get or set session name. + * + * @param string|null $name + * @return string + */ + public static function sessionName($name = null) + { + if ($name !== null && $name !== '') { + Session::$name = (string)$name; + } + return Session::$name; + } + + /** + * Get or set the request class name. + * + * @param string|null $class_name + * @return string + */ + public static function requestClass($class_name = null) + { + if ($class_name) { + static::$_requestClass = $class_name; + } + return static::$_requestClass; + } + + /** + * Enable or disable Cache. + * + * @param mixed $value + */ + public static function enableCache($value) + { + static::$_enableCache = (bool)$value; + } + + /** + * Check the integrity of the package. + * + * @param string $recv_buffer + * @param TcpConnection $connection + * @return int + */ + public static function input($recv_buffer, TcpConnection $connection) + { + static $input = []; + if (!isset($recv_buffer[512]) && isset($input[$recv_buffer])) { + return $input[$recv_buffer]; + } + $crlf_pos = \strpos($recv_buffer, "\r\n\r\n"); + if (false === $crlf_pos) { + // Judge whether the package length exceeds the limit. + if (\strlen($recv_buffer) >= 16384) { + $connection->close("HTTP/1.1 413 Request Entity Too Large\r\n\r\n", true); + return 0; + } + return 0; + } + + $length = $crlf_pos + 4; + $method = \strstr($recv_buffer, ' ', true); + + if (!\in_array($method, ['GET', 'POST', 'OPTIONS', 'HEAD', 'DELETE', 'PUT', 'PATCH'])) { + $connection->close("HTTP/1.1 400 Bad Request\r\n\r\n", true); + return 0; + } + + $header = \substr($recv_buffer, 0, $crlf_pos); + if ($pos = \strpos($header, "\r\nContent-Length: ")) { + $length = $length + (int)\substr($header, $pos + 18, 10); + $has_content_length = true; + } else if (\preg_match("/\r\ncontent-length: ?(\d+)/i", $header, $match)) { + $length = $length + $match[1]; + $has_content_length = true; + } else { + $has_content_length = false; + if (false !== stripos($header, "\r\nTransfer-Encoding:")) { + $connection->close("HTTP/1.1 400 Bad Request\r\n\r\n", true); + return 0; + } + } + + if ($has_content_length) { + if ($length > $connection->maxPackageSize) { + $connection->close("HTTP/1.1 413 Request Entity Too Large\r\n\r\n", true); + return 0; + } + } + + if (!isset($recv_buffer[512])) { + $input[$recv_buffer] = $length; + if (\count($input) > 512) { + unset($input[key($input)]); + } + } + + return $length; + } + + /** + * Http decode. + * + * @param string $recv_buffer + * @param TcpConnection $connection + * @return \Workerman\Protocols\Http\Request + */ + public static function decode($recv_buffer, TcpConnection $connection) + { + static $requests = array(); + $cacheable = static::$_enableCache && !isset($recv_buffer[512]); + if (true === $cacheable && isset($requests[$recv_buffer])) { + $request = $requests[$recv_buffer]; + $request->connection = $connection; + $connection->__request = $request; + $request->properties = array(); + return $request; + } + $request = new static::$_requestClass($recv_buffer); + $request->connection = $connection; + $connection->__request = $request; + if (true === $cacheable) { + $requests[$recv_buffer] = $request; + if (\count($requests) > 512) { + unset($requests[key($requests)]); + } + } + return $request; + } + + /** + * Http encode. + * + * @param string|Response $response + * @param TcpConnection $connection + * @return string + */ + public static function encode($response, TcpConnection $connection) + { + if (isset($connection->__request)) { + $connection->__request->session = null; + $connection->__request->connection = null; + $connection->__request = null; + } + if (!\is_object($response)) { + $ext_header = ''; + if (isset($connection->__header)) { + foreach ($connection->__header as $name => $value) { + if (\is_array($value)) { + foreach ($value as $item) { + $ext_header = "$name: $item\r\n"; + } + } else { + $ext_header = "$name: $value\r\n"; + } + } + unset($connection->__header); + } + $body_len = \strlen((string)$response); + return "HTTP/1.1 200 OK\r\nServer: workerman\r\n{$ext_header}Connection: keep-alive\r\nContent-Type: text/html;charset=utf-8\r\nContent-Length: $body_len\r\n\r\n$response"; + } + + if (isset($connection->__header)) { + $response->withHeaders($connection->__header); + unset($connection->__header); + } + + if (isset($response->file)) { + $file = $response->file['file']; + $offset = $response->file['offset']; + $length = $response->file['length']; + clearstatcache(); + $file_size = (int)\filesize($file); + $body_len = $length > 0 ? $length : $file_size - $offset; + $response->withHeaders(array( + 'Content-Length' => $body_len, + 'Accept-Ranges' => 'bytes', + )); + if ($offset || $length) { + $offset_end = $offset + $body_len - 1; + $response->header('Content-Range', "bytes $offset-$offset_end/$file_size"); + } + if ($body_len < 2 * 1024 * 1024) { + $connection->send((string)$response . file_get_contents($file, false, null, $offset, $body_len), true); + return ''; + } + $handler = \fopen($file, 'r'); + if (false === $handler) { + $connection->close(new Response(403, null, '403 Forbidden')); + return ''; + } + $connection->send((string)$response, true); + static::sendStream($connection, $handler, $offset, $length); + return ''; + } + + return (string)$response; + } + + /** + * Send remainder of a stream to client. + * + * @param TcpConnection $connection + * @param resource $handler + * @param int $offset + * @param int $length + */ + protected static function sendStream(TcpConnection $connection, $handler, $offset = 0, $length = 0) + { + $connection->bufferFull = false; + if ($offset !== 0) { + \fseek($handler, $offset); + } + $offset_end = $offset + $length; + // Read file content from disk piece by piece and send to client. + $do_write = function () use ($connection, $handler, $length, $offset_end) { + // Send buffer not full. + while ($connection->bufferFull === false) { + // Read from disk. + $size = 1024 * 1024; + if ($length !== 0) { + $tell = \ftell($handler); + $remain_size = $offset_end - $tell; + if ($remain_size <= 0) { + fclose($handler); + $connection->onBufferDrain = null; + return; + } + $size = $remain_size > $size ? $size : $remain_size; + } + + $buffer = \fread($handler, $size); + // Read eof. + if ($buffer === '' || $buffer === false) { + fclose($handler); + $connection->onBufferDrain = null; + return; + } + $connection->send($buffer, true); + } + }; + // Send buffer full. + $connection->onBufferFull = function ($connection) { + $connection->bufferFull = true; + }; + // Send buffer drain. + $connection->onBufferDrain = function ($connection) use ($do_write) { + $connection->bufferFull = false; + $do_write(); + }; + $do_write(); + } + + /** + * Set or get uploadTmpDir. + * + * @return bool|string + */ + public static function uploadTmpDir($dir = null) + { + if (null !== $dir) { + static::$_uploadTmpDir = $dir; + } + if (static::$_uploadTmpDir === '') { + if ($upload_tmp_dir = \ini_get('upload_tmp_dir')) { + static::$_uploadTmpDir = $upload_tmp_dir; + } else if ($upload_tmp_dir = \sys_get_temp_dir()) { + static::$_uploadTmpDir = $upload_tmp_dir; + } + } + return static::$_uploadTmpDir; + } +} diff --git a/vendor/workerman/workerman/Protocols/Http/Chunk.php b/vendor/workerman/workerman/Protocols/Http/Chunk.php new file mode 100644 index 0000000..ab06a9c --- /dev/null +++ b/vendor/workerman/workerman/Protocols/Http/Chunk.php @@ -0,0 +1,48 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols\Http; + + +/** + * Class Chunk + * @package Workerman\Protocols\Http + */ +class Chunk +{ + /** + * Chunk buffer. + * + * @var string + */ + protected $_buffer = null; + + /** + * Chunk constructor. + * @param string $buffer + */ + public function __construct($buffer) + { + $this->_buffer = $buffer; + } + + /** + * __toString + * + * @return string + */ + public function __toString() + { + return \dechex(\strlen($this->_buffer))."\r\n$this->_buffer\r\n"; + } +} \ No newline at end of file diff --git a/vendor/workerman/workerman/Protocols/Http/Request.php b/vendor/workerman/workerman/Protocols/Http/Request.php new file mode 100644 index 0000000..4744d9e --- /dev/null +++ b/vendor/workerman/workerman/Protocols/Http/Request.php @@ -0,0 +1,677 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols\Http; + +use Workerman\Connection\TcpConnection; +use Workerman\Protocols\Http\Session; +use Workerman\Protocols\Http; +use Workerman\Worker; + +/** + * Class Request + * @package Workerman\Protocols\Http + */ +class Request +{ + /** + * Connection. + * + * @var TcpConnection + */ + public $connection = null; + + /** + * Session instance. + * + * @var Session + */ + public $session = null; + + /** + * Properties. + * + * @var array + */ + public $properties = array(); + + /** + * @var int + */ + public static $maxFileUploads = 1024; + + /** + * Http buffer. + * + * @var string + */ + protected $_buffer = null; + + /** + * Request data. + * + * @var array + */ + protected $_data = null; + + /** + * Enable cache. + * + * @var bool + */ + protected static $_enableCache = true; + + + /** + * Request constructor. + * + * @param string $buffer + */ + public function __construct($buffer) + { + $this->_buffer = $buffer; + } + + /** + * $_GET. + * + * @param string|null $name + * @param mixed|null $default + * @return mixed|null + */ + public function get($name = null, $default = null) + { + if (!isset($this->_data['get'])) { + $this->parseGet(); + } + if (null === $name) { + return $this->_data['get']; + } + return isset($this->_data['get'][$name]) ? $this->_data['get'][$name] : $default; + } + + /** + * $_POST. + * + * @param string|null $name + * @param mixed|null $default + * @return mixed|null + */ + public function post($name = null, $default = null) + { + if (!isset($this->_data['post'])) { + $this->parsePost(); + } + if (null === $name) { + return $this->_data['post']; + } + return isset($this->_data['post'][$name]) ? $this->_data['post'][$name] : $default; + } + + /** + * Get header item by name. + * + * @param string|null $name + * @param mixed|null $default + * @return array|string|null + */ + public function header($name = null, $default = null) + { + if (!isset($this->_data['headers'])) { + $this->parseHeaders(); + } + if (null === $name) { + return $this->_data['headers']; + } + $name = \strtolower($name); + return isset($this->_data['headers'][$name]) ? $this->_data['headers'][$name] : $default; + } + + /** + * Get cookie item by name. + * + * @param string|null $name + * @param mixed|null $default + * @return array|string|null + */ + public function cookie($name = null, $default = null) + { + if (!isset($this->_data['cookie'])) { + $this->_data['cookie'] = array(); + \parse_str(\preg_replace('/; ?/', '&', $this->header('cookie', '')), $this->_data['cookie']); + } + if ($name === null) { + return $this->_data['cookie']; + } + return isset($this->_data['cookie'][$name]) ? $this->_data['cookie'][$name] : $default; + } + + /** + * Get upload files. + * + * @param string|null $name + * @return array|null + */ + public function file($name = null) + { + if (!isset($this->_data['files'])) { + $this->parsePost(); + } + if (null === $name) { + return $this->_data['files']; + } + return isset($this->_data['files'][$name]) ? $this->_data['files'][$name] : null; + } + + /** + * Get method. + * + * @return string + */ + public function method() + { + if (!isset($this->_data['method'])) { + $this->parseHeadFirstLine(); + } + return $this->_data['method']; + } + + /** + * Get http protocol version. + * + * @return string + */ + public function protocolVersion() + { + if (!isset($this->_data['protocolVersion'])) { + $this->parseProtocolVersion(); + } + return $this->_data['protocolVersion']; + } + + /** + * Get host. + * + * @param bool $without_port + * @return string + */ + public function host($without_port = false) + { + $host = $this->header('host'); + if ($host && $without_port && $pos = \strpos($host, ':')) { + return \substr($host, 0, $pos); + } + return $host; + } + + /** + * Get uri. + * + * @return mixed + */ + public function uri() + { + if (!isset($this->_data['uri'])) { + $this->parseHeadFirstLine(); + } + return $this->_data['uri']; + } + + /** + * Get path. + * + * @return mixed + */ + public function path() + { + if (!isset($this->_data['path'])) { + $this->_data['path'] = (string)\parse_url($this->uri(), PHP_URL_PATH); + } + return $this->_data['path']; + } + + /** + * Get query string. + * + * @return mixed + */ + public function queryString() + { + if (!isset($this->_data['query_string'])) { + $this->_data['query_string'] = (string)\parse_url($this->uri(), PHP_URL_QUERY); + } + return $this->_data['query_string']; + } + + /** + * Get session. + * + * @return bool|\Workerman\Protocols\Http\Session + */ + public function session() + { + if ($this->session === null) { + $session_id = $this->sessionId(); + if ($session_id === false) { + return false; + } + $this->session = new Session($session_id); + } + return $this->session; + } + + /** + * Get/Set session id. + * + * @param $session_id + * @return string + */ + public function sessionId($session_id = null) + { + if ($session_id) { + unset($this->sid); + } + if (!isset($this->sid)) { + $session_name = Session::$name; + $sid = $session_id ? '' : $this->cookie($session_name); + if ($sid === '' || $sid === null) { + if ($this->connection === null) { + Worker::safeEcho('Request->session() fail, header already send'); + return false; + } + $sid = $session_id ? $session_id : static::createSessionId(); + $cookie_params = Session::getCookieParams(); + $this->connection->__header['Set-Cookie'] = array($session_name . '=' . $sid + . (empty($cookie_params['domain']) ? '' : '; Domain=' . $cookie_params['domain']) + . (empty($cookie_params['lifetime']) ? '' : '; Max-Age=' . $cookie_params['lifetime']) + . (empty($cookie_params['path']) ? '' : '; Path=' . $cookie_params['path']) + . (empty($cookie_params['samesite']) ? '' : '; SameSite=' . $cookie_params['samesite']) + . (!$cookie_params['secure'] ? '' : '; Secure') + . (!$cookie_params['httponly'] ? '' : '; HttpOnly')); + } + $this->sid = $sid; + } + return $this->sid; + } + + /** + * Get http raw head. + * + * @return string + */ + public function rawHead() + { + if (!isset($this->_data['head'])) { + $this->_data['head'] = \strstr($this->_buffer, "\r\n\r\n", true); + } + return $this->_data['head']; + } + + /** + * Get http raw body. + * + * @return string + */ + public function rawBody() + { + return \substr($this->_buffer, \strpos($this->_buffer, "\r\n\r\n") + 4); + } + + /** + * Get raw buffer. + * + * @return string + */ + public function rawBuffer() + { + return $this->_buffer; + } + + /** + * Enable or disable cache. + * + * @param mixed $value + */ + public static function enableCache($value) + { + static::$_enableCache = (bool)$value; + } + + /** + * Parse first line of http header buffer. + * + * @return void + */ + protected function parseHeadFirstLine() + { + $first_line = \strstr($this->_buffer, "\r\n", true); + $tmp = \explode(' ', $first_line, 3); + $this->_data['method'] = $tmp[0]; + $this->_data['uri'] = isset($tmp[1]) ? $tmp[1] : '/'; + } + + /** + * Parse protocol version. + * + * @return void + */ + protected function parseProtocolVersion() + { + $first_line = \strstr($this->_buffer, "\r\n", true); + $protoco_version = substr(\strstr($first_line, 'HTTP/'), 5); + $this->_data['protocolVersion'] = $protoco_version ? $protoco_version : '1.0'; + } + + /** + * Parse headers. + * + * @return void + */ + protected function parseHeaders() + { + static $cache = []; + $this->_data['headers'] = array(); + $raw_head = $this->rawHead(); + $end_line_position = \strpos($raw_head, "\r\n"); + if ($end_line_position === false) { + return; + } + $head_buffer = \substr($raw_head, $end_line_position + 2); + $cacheable = static::$_enableCache && !isset($head_buffer[2048]); + if ($cacheable && isset($cache[$head_buffer])) { + $this->_data['headers'] = $cache[$head_buffer]; + return; + } + $head_data = \explode("\r\n", $head_buffer); + foreach ($head_data as $content) { + if (false !== \strpos($content, ':')) { + list($key, $value) = \explode(':', $content, 2); + $key = \strtolower($key); + $value = \ltrim($value); + } else { + $key = \strtolower($content); + $value = ''; + } + if (isset($this->_data['headers'][$key])) { + $this->_data['headers'][$key] = "{$this->_data['headers'][$key]},$value"; + } else { + $this->_data['headers'][$key] = $value; + } + } + if ($cacheable) { + $cache[$head_buffer] = $this->_data['headers']; + if (\count($cache) > 128) { + unset($cache[key($cache)]); + } + } + } + + /** + * Parse head. + * + * @return void + */ + protected function parseGet() + { + static $cache = []; + $query_string = $this->queryString(); + $this->_data['get'] = array(); + if ($query_string === '') { + return; + } + $cacheable = static::$_enableCache && !isset($query_string[1024]); + if ($cacheable && isset($cache[$query_string])) { + $this->_data['get'] = $cache[$query_string]; + return; + } + \parse_str($query_string, $this->_data['get']); + if ($cacheable) { + $cache[$query_string] = $this->_data['get']; + if (\count($cache) > 256) { + unset($cache[key($cache)]); + } + } + } + + /** + * Parse post. + * + * @return void + */ + protected function parsePost() + { + static $cache = []; + $this->_data['post'] = $this->_data['files'] = array(); + $content_type = $this->header('content-type', ''); + if (\preg_match('/boundary="?(\S+)"?/', $content_type, $match)) { + $http_post_boundary = '--' . $match[1]; + $this->parseUploadFiles($http_post_boundary); + return; + } + $body_buffer = $this->rawBody(); + if ($body_buffer === '') { + return; + } + $cacheable = static::$_enableCache && !isset($body_buffer[1024]); + if ($cacheable && isset($cache[$body_buffer])) { + $this->_data['post'] = $cache[$body_buffer]; + return; + } + if (\preg_match('/\bjson\b/i', $content_type)) { + $this->_data['post'] = (array) json_decode($body_buffer, true); + } else { + \parse_str($body_buffer, $this->_data['post']); + } + if ($cacheable) { + $cache[$body_buffer] = $this->_data['post']; + if (\count($cache) > 256) { + unset($cache[key($cache)]); + } + } + } + + /** + * Parse upload files. + * + * @param string $http_post_boundary + * @return void + */ + protected function parseUploadFiles($http_post_boundary) + { + $http_post_boundary = \trim($http_post_boundary, '"'); + $buffer = $this->_buffer; + $post_encode_string = ''; + $files_encode_string = ''; + $files = []; + $boday_position = strpos($buffer, "\r\n\r\n") + 4; + $offset = $boday_position + strlen($http_post_boundary) + 2; + $max_count = static::$maxFileUploads; + while ($max_count-- > 0 && $offset) { + $offset = $this->parseUploadFile($http_post_boundary, $offset, $post_encode_string, $files_encode_string, $files); + } + if ($post_encode_string) { + parse_str($post_encode_string, $this->_data['post']); + } + + if ($files_encode_string) { + parse_str($files_encode_string, $this->_data['files']); + \array_walk_recursive($this->_data['files'], function (&$value) use ($files) { + $value = $files[$value]; + }); + } + } + + /** + * @param $boundary + * @param $section_start_offset + * @return int + */ + protected function parseUploadFile($boundary, $section_start_offset, &$post_encode_string, &$files_encode_str, &$files) + { + $file = []; + $boundary = "\r\n$boundary"; + if (\strlen($this->_buffer) < $section_start_offset) { + return 0; + } + $section_end_offset = \strpos($this->_buffer, $boundary, $section_start_offset); + if (!$section_end_offset) { + return 0; + } + $content_lines_end_offset = \strpos($this->_buffer, "\r\n\r\n", $section_start_offset); + if (!$content_lines_end_offset || $content_lines_end_offset + 4 > $section_end_offset) { + return 0; + } + $content_lines_str = \substr($this->_buffer, $section_start_offset, $content_lines_end_offset - $section_start_offset); + $content_lines = \explode("\r\n", trim($content_lines_str . "\r\n")); + $boundary_value = \substr($this->_buffer, $content_lines_end_offset + 4, $section_end_offset - $content_lines_end_offset - 4); + $upload_key = false; + foreach ($content_lines as $content_line) { + if (!\strpos($content_line, ': ')) { + return 0; + } + list($key, $value) = \explode(': ', $content_line); + switch (strtolower($key)) { + case "content-disposition": + // Is file data. + if (\preg_match('/name="(.*?)"; filename="(.*?)"/i', $value, $match)) { + $error = 0; + $tmp_file = ''; + $file_name = $match[2]; + $size = \strlen($boundary_value); + $tmp_upload_dir = HTTP::uploadTmpDir(); + if (!$tmp_upload_dir) { + $error = UPLOAD_ERR_NO_TMP_DIR; + } else if ($boundary_value === '' && $file_name === '') { + $error = UPLOAD_ERR_NO_FILE; + } else { + $tmp_file = \tempnam($tmp_upload_dir, 'workerman.upload.'); + if ($tmp_file === false || false === \file_put_contents($tmp_file, $boundary_value)) { + $error = UPLOAD_ERR_CANT_WRITE; + } + } + $upload_key = $match[1]; + // Parse upload files. + $file = [ + 'name' => $file_name, + 'tmp_name' => $tmp_file, + 'size' => $size, + 'error' => $error, + 'type' => '', + ]; + break; + } // Is post field. + else { + // Parse $_POST. + if (\preg_match('/name="(.*?)"$/', $value, $match)) { + $k = $match[1]; + $post_encode_string .= \urlencode($k) . "=" . \urlencode($boundary_value) . '&'; + } + return $section_end_offset + \strlen($boundary) + 2; + } + break; + case "content-type": + $file['type'] = \trim($value); + break; + } + } + if ($upload_key === false) { + return 0; + } + $files_encode_str .= \urlencode($upload_key) . '=' . \count($files) . '&'; + $files[] = $file; + + return $section_end_offset + \strlen($boundary) + 2; + } + + /** + * Create session id. + * + * @return string + */ + protected static function createSessionId() + { + return \bin2hex(\pack('d', \microtime(true)) . random_bytes(8)); + } + + /** + * Setter. + * + * @param string $name + * @param mixed $value + * @return void + */ + public function __set($name, $value) + { + $this->properties[$name] = $value; + } + + /** + * Getter. + * + * @param string $name + * @return mixed|null + */ + public function __get($name) + { + return isset($this->properties[$name]) ? $this->properties[$name] : null; + } + + /** + * Isset. + * + * @param string $name + * @return bool + */ + public function __isset($name) + { + return isset($this->properties[$name]); + } + + /** + * Unset. + * + * @param string $name + * @return void + */ + public function __unset($name) + { + unset($this->properties[$name]); + } + + /** + * __toString. + */ + public function __toString() + { + return $this->_buffer; + } + + /** + * __destruct. + * + * @return void + */ + public function __destruct() + { + if (isset($this->_data['files'])) { + \clearstatcache(); + \array_walk_recursive($this->_data['files'], function($value, $key){ + if ($key === 'tmp_name') { + if (\is_file($value)) { + \unlink($value); + } + } + }); + } + } +} diff --git a/vendor/workerman/workerman/Protocols/Http/Response.php b/vendor/workerman/workerman/Protocols/Http/Response.php new file mode 100644 index 0000000..a71cefa --- /dev/null +++ b/vendor/workerman/workerman/Protocols/Http/Response.php @@ -0,0 +1,458 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols\Http; + +/** + * Class Response + * @package Workerman\Protocols\Http + */ +class Response +{ + /** + * Header data. + * + * @var array + */ + protected $_header = null; + + /** + * Http status. + * + * @var int + */ + protected $_status = null; + + /** + * Http reason. + * + * @var string + */ + protected $_reason = null; + + /** + * Http version. + * + * @var string + */ + protected $_version = '1.1'; + + /** + * Http body. + * + * @var string + */ + protected $_body = null; + + /** + * Send file info + * + * @var array + */ + public $file = null; + + /** + * Mine type map. + * @var array + */ + protected static $_mimeTypeMap = null; + + /** + * Phrases. + * + * @var array + */ + protected static $_phrases = array( + 100 => 'Continue', + 101 => 'Switching Protocols', + 102 => 'Processing', + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 207 => 'Multi-status', + 208 => 'Already Reported', + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 306 => 'Switch Proxy', + 307 => 'Temporary Redirect', + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Time-out', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Large', + 415 => 'Unsupported Media Type', + 416 => 'Requested range not satisfiable', + 417 => 'Expectation Failed', + 418 => 'I\'m a teapot', + 422 => 'Unprocessable Entity', + 423 => 'Locked', + 424 => 'Failed Dependency', + 425 => 'Unordered Collection', + 426 => 'Upgrade Required', + 428 => 'Precondition Required', + 429 => 'Too Many Requests', + 431 => 'Request Header Fields Too Large', + 451 => 'Unavailable For Legal Reasons', + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Time-out', + 505 => 'HTTP Version not supported', + 506 => 'Variant Also Negotiates', + 507 => 'Insufficient Storage', + 508 => 'Loop Detected', + 511 => 'Network Authentication Required', + ); + + /** + * Init. + * + * @return void + */ + public static function init() { + static::initMimeTypeMap(); + } + + /** + * Response constructor. + * + * @param int $status + * @param array $headers + * @param string $body + */ + public function __construct( + $status = 200, + $headers = array(), + $body = '' + ) { + $this->_status = $status; + $this->_header = $headers; + $this->_body = (string)$body; + } + + /** + * Set header. + * + * @param string $name + * @param string $value + * @return $this + */ + public function header($name, $value) { + $this->_header[$name] = $value; + return $this; + } + + /** + * Set header. + * + * @param string $name + * @param string $value + * @return Response + */ + public function withHeader($name, $value) { + return $this->header($name, $value); + } + + /** + * Set headers. + * + * @param array $headers + * @return $this + */ + public function withHeaders($headers) { + $this->_header = \array_merge_recursive($this->_header, $headers); + return $this; + } + + /** + * Remove header. + * + * @param string $name + * @return $this + */ + public function withoutHeader($name) { + unset($this->_header[$name]); + return $this; + } + + /** + * Get header. + * + * @param string $name + * @return null|array|string + */ + public function getHeader($name) { + if (!isset($this->_header[$name])) { + return null; + } + return $this->_header[$name]; + } + + /** + * Get headers. + * + * @return array + */ + public function getHeaders() { + return $this->_header; + } + + /** + * Set status. + * + * @param int $code + * @param string|null $reason_phrase + * @return $this + */ + public function withStatus($code, $reason_phrase = null) { + $this->_status = $code; + $this->_reason = $reason_phrase; + return $this; + } + + /** + * Get status code. + * + * @return int + */ + public function getStatusCode() { + return $this->_status; + } + + /** + * Get reason phrase. + * + * @return string + */ + public function getReasonPhrase() { + return $this->_reason; + } + + /** + * Set protocol version. + * + * @param int $version + * @return $this + */ + public function withProtocolVersion($version) { + $this->_version = $version; + return $this; + } + + /** + * Set http body. + * + * @param string $body + * @return $this + */ + public function withBody($body) { + $this->_body = $body; + return $this; + } + + /** + * Get http raw body. + * + * @return string + */ + public function rawBody() { + return $this->_body; + } + + /** + * Send file. + * + * @param string $file + * @param int $offset + * @param int $length + * @return $this + */ + public function withFile($file, $offset = 0, $length = 0) { + if (!\is_file($file)) { + return $this->withStatus(404)->withBody('

    404 Not Found

    '); + } + $this->file = array('file' => $file, 'offset' => $offset, 'length' => $length); + return $this; + } + + /** + * Set cookie. + * + * @param $name + * @param string $value + * @param int $max_age + * @param string $path + * @param string $domain + * @param bool $secure + * @param bool $http_only + * @param bool $same_site + * @return $this + */ + public function cookie($name, $value = '', $max_age = null, $path = '', $domain = '', $secure = false, $http_only = false, $same_site = false) + { + $this->_header['Set-Cookie'][] = $name . '=' . \rawurlencode($value) + . (empty($domain) ? '' : '; Domain=' . $domain) + . ($max_age === null ? '' : '; Max-Age=' . $max_age) + . (empty($path) ? '' : '; Path=' . $path) + . (!$secure ? '' : '; Secure') + . (!$http_only ? '' : '; HttpOnly') + . (empty($same_site) ? '' : '; SameSite=' . $same_site); + return $this; + } + + /** + * Create header for file. + * + * @param array $file_info + * @return string + */ + protected function createHeadForFile($file_info) + { + $file = $file_info['file']; + $reason = $this->_reason ? $this->_reason : static::$_phrases[$this->_status]; + $head = "HTTP/{$this->_version} {$this->_status} $reason\r\n"; + $headers = $this->_header; + if (!isset($headers['Server'])) { + $head .= "Server: workerman\r\n"; + } + foreach ($headers as $name => $value) { + if (\is_array($value)) { + foreach ($value as $item) { + $head .= "$name: $item\r\n"; + } + continue; + } + $head .= "$name: $value\r\n"; + } + + if (!isset($headers['Connection'])) { + $head .= "Connection: keep-alive\r\n"; + } + + $file_info = \pathinfo($file); + $extension = isset($file_info['extension']) ? $file_info['extension'] : ''; + $base_name = isset($file_info['basename']) ? $file_info['basename'] : 'unknown'; + if (!isset($headers['Content-Type'])) { + if (isset(self::$_mimeTypeMap[$extension])) { + $head .= "Content-Type: " . self::$_mimeTypeMap[$extension] . "\r\n"; + } else { + $head .= "Content-Type: application/octet-stream\r\n"; + } + } + + if (!isset($headers['Content-Disposition']) && !isset(self::$_mimeTypeMap[$extension])) { + $head .= "Content-Disposition: attachment; filename=\"$base_name\"\r\n"; + } + + if (!isset($headers['Last-Modified'])) { + if ($mtime = \filemtime($file)) { + $head .= 'Last-Modified: '. \gmdate('D, d M Y H:i:s', $mtime) . ' GMT' . "\r\n"; + } + } + + return "{$head}\r\n"; + } + + /** + * __toString. + * + * @return string + */ + public function __toString() + { + if (isset($this->file)) { + return $this->createHeadForFile($this->file); + } + + $reason = $this->_reason ? $this->_reason : static::$_phrases[$this->_status]; + $body_len = \strlen($this->_body); + if (empty($this->_header)) { + return "HTTP/{$this->_version} {$this->_status} $reason\r\nServer: workerman\r\nContent-Type: text/html;charset=utf-8\r\nContent-Length: $body_len\r\nConnection: keep-alive\r\n\r\n{$this->_body}"; + } + + $head = "HTTP/{$this->_version} {$this->_status} $reason\r\n"; + $headers = $this->_header; + if (!isset($headers['Server'])) { + $head .= "Server: workerman\r\n"; + } + foreach ($headers as $name => $value) { + if (\is_array($value)) { + foreach ($value as $item) { + $head .= "$name: $item\r\n"; + } + continue; + } + $head .= "$name: $value\r\n"; + } + + if (!isset($headers['Connection'])) { + $head .= "Connection: keep-alive\r\n"; + } + + if (!isset($headers['Content-Type'])) { + $head .= "Content-Type: text/html;charset=utf-8\r\n"; + } else if ($headers['Content-Type'] === 'text/event-stream') { + return $head . $this->_body; + } + + if (!isset($headers['Transfer-Encoding'])) { + $head .= "Content-Length: $body_len\r\n\r\n"; + } else { + return "$head\r\n".dechex($body_len)."\r\n{$this->_body}\r\n"; + } + + // The whole http package + return $head . $this->_body; + } + + /** + * Init mime map. + * + * @return void + */ + public static function initMimeTypeMap() + { + $mime_file = __DIR__ . '/mime.types'; + $items = \file($mime_file, \FILE_IGNORE_NEW_LINES | \FILE_SKIP_EMPTY_LINES); + foreach ($items as $content) { + if (\preg_match("/\s*(\S+)\s+(\S.+)/", $content, $match)) { + $mime_type = $match[1]; + $extension_var = $match[2]; + $extension_array = \explode(' ', \substr($extension_var, 0, -1)); + foreach ($extension_array as $file_extension) { + static::$_mimeTypeMap[$file_extension] = $mime_type; + } + } + } + } +} +Response::init(); diff --git a/vendor/workerman/workerman/Protocols/Http/ServerSentEvents.php b/vendor/workerman/workerman/Protocols/Http/ServerSentEvents.php new file mode 100644 index 0000000..a6e9e0d --- /dev/null +++ b/vendor/workerman/workerman/Protocols/Http/ServerSentEvents.php @@ -0,0 +1,64 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols\Http; + +/** + * Class ServerSentEvents + * @package Workerman\Protocols\Http + */ +class ServerSentEvents +{ + /** + * Data. + * @var array + */ + protected $_data = null; + + /** + * ServerSentEvents constructor. + * $data for example ['event'=>'ping', 'data' => 'some thing', 'id' => 1000, 'retry' => 5000] + * @param array $data + */ + public function __construct(array $data) + { + $this->_data = $data; + } + + /** + * __toString. + * + * @return string + */ + public function __toString() + { + $buffer = ''; + $data = $this->_data; + if (isset($data[''])) { + $buffer = ": {$data['']}\n"; + } + if (isset($data['event'])) { + $buffer .= "event: {$data['event']}\n"; + } + if (isset($data['id'])) { + $buffer .= "id: {$data['id']}\n"; + } + if (isset($data['retry'])) { + $buffer .= "retry: {$data['retry']}\n"; + } + if (isset($data['data'])) { + $buffer .= 'data: ' . str_replace("\n", "\ndata: ", $data['data']) . "\n"; + } + return $buffer . "\n"; + } +} \ No newline at end of file diff --git a/vendor/workerman/workerman/Protocols/Http/Session.php b/vendor/workerman/workerman/Protocols/Http/Session.php new file mode 100644 index 0000000..45735ce --- /dev/null +++ b/vendor/workerman/workerman/Protocols/Http/Session.php @@ -0,0 +1,441 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace Workerman\Protocols\Http; + +use Workerman\Protocols\Http\Session\SessionHandlerInterface; + +/** + * Class Session + * @package Workerman\Protocols\Http + */ +class Session +{ + /** + * Session andler class which implements SessionHandlerInterface. + * + * @var string + */ + protected static $_handlerClass = 'Workerman\Protocols\Http\Session\FileSessionHandler'; + + /** + * Parameters of __constructor for session handler class. + * + * @var null + */ + protected static $_handlerConfig = null; + + /** + * Session name. + * + * @var string + */ + public static $name = 'PHPSID'; + + /** + * Auto update timestamp. + * + * @var bool + */ + public static $autoUpdateTimestamp = false; + + /** + * Session lifetime. + * + * @var int + */ + public static $lifetime = 1440; + + /** + * Cookie lifetime. + * + * @var int + */ + public static $cookieLifetime = 1440; + + /** + * Session cookie path. + * + * @var string + */ + public static $cookiePath = '/'; + + /** + * Session cookie domain. + * + * @var string + */ + public static $domain = ''; + + /** + * HTTPS only cookies. + * + * @var bool + */ + public static $secure = false; + + /** + * HTTP access only. + * + * @var bool + */ + public static $httpOnly = true; + + /** + * Same-site cookies. + * + * @var string + */ + public static $sameSite = ''; + + /** + * Gc probability. + * + * @var int[] + */ + public static $gcProbability = [1, 1000]; + + /** + * Session handler instance. + * + * @var SessionHandlerInterface + */ + protected static $_handler = null; + + /** + * Session data. + * + * @var array + */ + protected $_data = []; + + /** + * Session changed and need to save. + * + * @var bool + */ + protected $_needSave = false; + + /** + * Session id. + * + * @var null + */ + protected $_sessionId = null; + + /** + * Session constructor. + * + * @param string $session_id + */ + public function __construct($session_id) + { + static::checkSessionId($session_id); + if (static::$_handler === null) { + static::initHandler(); + } + $this->_sessionId = $session_id; + if ($data = static::$_handler->read($session_id)) { + $this->_data = \unserialize($data); + } + } + + /** + * Get session id. + * + * @return string + */ + public function getId() + { + return $this->_sessionId; + } + + /** + * Get session. + * + * @param string $name + * @param mixed|null $default + * @return mixed|null + */ + public function get($name, $default = null) + { + return isset($this->_data[$name]) ? $this->_data[$name] : $default; + } + + /** + * Store data in the session. + * + * @param string $name + * @param mixed $value + */ + public function set($name, $value) + { + $this->_data[$name] = $value; + $this->_needSave = true; + } + + /** + * Delete an item from the session. + * + * @param string $name + */ + public function delete($name) + { + unset($this->_data[$name]); + $this->_needSave = true; + } + + /** + * Retrieve and delete an item from the session. + * + * @param string $name + * @param mixed|null $default + * @return mixed|null + */ + public function pull($name, $default = null) + { + $value = $this->get($name, $default); + $this->delete($name); + return $value; + } + + /** + * Store data in the session. + * + * @param string|array $key + * @param mixed|null $value + */ + public function put($key, $value = null) + { + if (!\is_array($key)) { + $this->set($key, $value); + return; + } + + foreach ($key as $k => $v) { + $this->_data[$k] = $v; + } + $this->_needSave = true; + } + + /** + * Remove a piece of data from the session. + * + * @param string $name + */ + public function forget($name) + { + if (\is_scalar($name)) { + $this->delete($name); + return; + } + if (\is_array($name)) { + foreach ($name as $key) { + unset($this->_data[$key]); + } + } + $this->_needSave = true; + } + + /** + * Retrieve all the data in the session. + * + * @return array + */ + public function all() + { + return $this->_data; + } + + /** + * Remove all data from the session. + * + * @return void + */ + public function flush() + { + $this->_needSave = true; + $this->_data = []; + } + + /** + * Determining If An Item Exists In The Session. + * + * @param string $name + * @return bool + */ + public function has($name) + { + return isset($this->_data[$name]); + } + + /** + * To determine if an item is present in the session, even if its value is null. + * + * @param string $name + * @return bool + */ + public function exists($name) + { + return \array_key_exists($name, $this->_data); + } + + /** + * Save session to store. + * + * @return void + */ + public function save() + { + if ($this->_needSave) { + if (empty($this->_data)) { + static::$_handler->destroy($this->_sessionId); + } else { + static::$_handler->write($this->_sessionId, \serialize($this->_data)); + } + } elseif (static::$autoUpdateTimestamp) { + static::refresh(); + } + $this->_needSave = false; + } + + /** + * Refresh session expire time. + * + * @return bool + */ + public function refresh() + { + static::$_handler->updateTimestamp($this->getId()); + } + + /** + * Init. + * + * @return void + */ + public static function init() + { + if (($gc_probability = (int)\ini_get('session.gc_probability')) && ($gc_divisor = (int)\ini_get('session.gc_divisor'))) { + static::$gcProbability = [$gc_probability, $gc_divisor]; + } + + if ($gc_max_life_time = \ini_get('session.gc_maxlifetime')) { + self::$lifetime = (int)$gc_max_life_time; + } + + $session_cookie_params = \session_get_cookie_params(); + static::$cookieLifetime = $session_cookie_params['lifetime']; + static::$cookiePath = $session_cookie_params['path']; + static::$domain = $session_cookie_params['domain']; + static::$secure = $session_cookie_params['secure']; + static::$httpOnly = $session_cookie_params['httponly']; + } + + /** + * Set session handler class. + * + * @param mixed|null $class_name + * @param mixed|null $config + * @return string + */ + public static function handlerClass($class_name = null, $config = null) + { + if ($class_name) { + static::$_handlerClass = $class_name; + } + if ($config) { + static::$_handlerConfig = $config; + } + return static::$_handlerClass; + } + + /** + * Get cookie params. + * + * @return array + */ + public static function getCookieParams() + { + return [ + 'lifetime' => static::$cookieLifetime, + 'path' => static::$cookiePath, + 'domain' => static::$domain, + 'secure' => static::$secure, + 'httponly' => static::$httpOnly, + 'samesite' => static::$sameSite, + ]; + } + + /** + * Init handler. + * + * @return void + */ + protected static function initHandler() + { + if (static::$_handlerConfig === null) { + static::$_handler = new static::$_handlerClass(); + } else { + static::$_handler = new static::$_handlerClass(static::$_handlerConfig); + } + } + + /** + * GC sessions. + * + * @return void + */ + public function gc() + { + static::$_handler->gc(static::$lifetime); + } + + /** + * __destruct. + * + * @return void + */ + public function __destruct() + { + $this->save(); + if (\random_int(1, static::$gcProbability[1]) <= static::$gcProbability[0]) { + $this->gc(); + } + } + + /** + * Check session id. + * + * @param string $session_id + */ + protected static function checkSessionId($session_id) + { + if (!\preg_match('/^[a-zA-Z0-9]+$/', $session_id)) { + throw new SessionException("session_id $session_id is invalid"); + } + } +} + +/** + * Class SessionException + * @package Workerman\Protocols\Http + */ +class SessionException extends \RuntimeException +{ + +} + +// Init session. +Session::init(); diff --git a/vendor/workerman/workerman/Protocols/Http/Session/FileSessionHandler.php b/vendor/workerman/workerman/Protocols/Http/Session/FileSessionHandler.php new file mode 100644 index 0000000..a7cefbd --- /dev/null +++ b/vendor/workerman/workerman/Protocols/Http/Session/FileSessionHandler.php @@ -0,0 +1,183 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols\Http\Session; + +use Workerman\Protocols\Http\Session; + +/** + * Class FileSessionHandler + * @package Workerman\Protocols\Http\Session + */ +class FileSessionHandler implements SessionHandlerInterface +{ + /** + * Session save path. + * + * @var string + */ + protected static $_sessionSavePath = null; + + /** + * Session file prefix. + * + * @var string + */ + protected static $_sessionFilePrefix = 'session_'; + + /** + * Init. + */ + public static function init() { + $save_path = @\session_save_path(); + if (!$save_path || \strpos($save_path, 'tcp://') === 0) { + $save_path = \sys_get_temp_dir(); + } + static::sessionSavePath($save_path); + } + + /** + * FileSessionHandler constructor. + * @param array $config + */ + public function __construct($config = array()) { + if (isset($config['save_path'])) { + static::sessionSavePath($config['save_path']); + } + } + + /** + * {@inheritdoc} + */ + public function open($save_path, $name) + { + return true; + } + + /** + * {@inheritdoc} + */ + public function read($session_id) + { + $session_file = static::sessionFile($session_id); + \clearstatcache(); + if (\is_file($session_file)) { + if (\time() - \filemtime($session_file) > Session::$lifetime) { + \unlink($session_file); + return ''; + } + $data = \file_get_contents($session_file); + return $data ? $data : ''; + } + return ''; + } + + /** + * {@inheritdoc} + */ + public function write($session_id, $session_data) + { + $temp_file = static::$_sessionSavePath . uniqid(bin2hex(random_bytes(8)), true); + if (!\file_put_contents($temp_file, $session_data)) { + return false; + } + return \rename($temp_file, static::sessionFile($session_id)); + } + + /** + * Update sesstion modify time. + * + * @see https://www.php.net/manual/en/class.sessionupdatetimestamphandlerinterface.php + * @see https://www.php.net/manual/zh/function.touch.php + * + * @param string $id Session id. + * @param string $data Session Data. + * + * @return bool + */ + public function updateTimestamp($id, $data = "") + { + $session_file = static::sessionFile($id); + if (!file_exists($session_file)) { + return false; + } + // set file modify time to current time + $set_modify_time = \touch($session_file); + // clear file stat cache + \clearstatcache(); + return $set_modify_time; + } + + /** + * {@inheritdoc} + */ + public function close() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function destroy($session_id) + { + $session_file = static::sessionFile($session_id); + if (\is_file($session_file)) { + \unlink($session_file); + } + return true; + } + + /** + * {@inheritdoc} + */ + public function gc($maxlifetime) { + $time_now = \time(); + foreach (\glob(static::$_sessionSavePath . static::$_sessionFilePrefix . '*') as $file) { + if(\is_file($file) && $time_now - \filemtime($file) > $maxlifetime) { + \unlink($file); + } + } + } + + /** + * Get session file path. + * + * @param string $session_id + * @return string + */ + protected static function sessionFile($session_id) { + return static::$_sessionSavePath.static::$_sessionFilePrefix.$session_id; + } + + /** + * Get or set session file path. + * + * @param string $path + * @return string + */ + public static function sessionSavePath($path) { + if ($path) { + if ($path[\strlen($path)-1] !== DIRECTORY_SEPARATOR) { + $path .= DIRECTORY_SEPARATOR; + } + static::$_sessionSavePath = $path; + if (!\is_dir($path)) { + \mkdir($path, 0777, true); + } + } + return $path; + } +} + +FileSessionHandler::init(); \ No newline at end of file diff --git a/vendor/workerman/workerman/Protocols/Http/Session/RedisClusterSessionHandler.php b/vendor/workerman/workerman/Protocols/Http/Session/RedisClusterSessionHandler.php new file mode 100644 index 0000000..281759a --- /dev/null +++ b/vendor/workerman/workerman/Protocols/Http/Session/RedisClusterSessionHandler.php @@ -0,0 +1,46 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace Workerman\Protocols\Http\Session; + +use Workerman\Protocols\Http\Session; + +class RedisClusterSessionHandler extends RedisSessionHandler +{ + public function __construct($config) + { + $timeout = isset($config['timeout']) ? $config['timeout'] : 2; + $read_timeout = isset($config['read_timeout']) ? $config['read_timeout'] : $timeout; + $persistent = isset($config['persistent']) ? $config['persistent'] : false; + $auth = isset($config['auth']) ? $config['auth'] : ''; + if ($auth) { + $this->_redis = new \RedisCluster(null, $config['host'], $timeout, $read_timeout, $persistent, $auth); + } else { + $this->_redis = new \RedisCluster(null, $config['host'], $timeout, $read_timeout, $persistent); + } + if (empty($config['prefix'])) { + $config['prefix'] = 'redis_session_'; + } + $this->_redis->setOption(\Redis::OPT_PREFIX, $config['prefix']); + } + + /** + * {@inheritdoc} + */ + public function read($session_id) + { + return $this->_redis->get($session_id); + } + +} diff --git a/vendor/workerman/workerman/Protocols/Http/Session/RedisSessionHandler.php b/vendor/workerman/workerman/Protocols/Http/Session/RedisSessionHandler.php new file mode 100644 index 0000000..e1b5bd5 --- /dev/null +++ b/vendor/workerman/workerman/Protocols/Http/Session/RedisSessionHandler.php @@ -0,0 +1,154 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols\Http\Session; + +use Workerman\Protocols\Http\Session; +use Workerman\Timer; +use RedisException; + +/** + * Class RedisSessionHandler + * @package Workerman\Protocols\Http\Session + */ +class RedisSessionHandler implements SessionHandlerInterface +{ + + /** + * @var \Redis + */ + protected $_redis; + + /** + * @var array + */ + protected $_config; + + /** + * RedisSessionHandler constructor. + * @param array $config = [ + * 'host' => '127.0.0.1', + * 'port' => 6379, + * 'timeout' => 2, + * 'auth' => '******', + * 'database' => 2, + * 'prefix' => 'redis_session_', + * 'ping' => 55, + * ] + */ + public function __construct($config) + { + if (false === extension_loaded('redis')) { + throw new \RuntimeException('Please install redis extension.'); + } + + if (!isset($config['timeout'])) { + $config['timeout'] = 2; + } + + $this->_config = $config; + + $this->connect(); + + Timer::add(!empty($config['ping']) ? $config['ping'] : 55, function () { + $this->_redis->get('ping'); + }); + } + + public function connect() + { + $config = $this->_config; + + $this->_redis = new \Redis(); + if (false === $this->_redis->connect($config['host'], $config['port'], $config['timeout'])) { + throw new \RuntimeException("Redis connect {$config['host']}:{$config['port']} fail."); + } + if (!empty($config['auth'])) { + $this->_redis->auth($config['auth']); + } + if (!empty($config['database'])) { + $this->_redis->select($config['database']); + } + if (empty($config['prefix'])) { + $config['prefix'] = 'redis_session_'; + } + $this->_redis->setOption(\Redis::OPT_PREFIX, $config['prefix']); + } + + /** + * {@inheritdoc} + */ + public function open($save_path, $name) + { + return true; + } + + /** + * {@inheritdoc} + */ + public function read($session_id) + { + try { + return $this->_redis->get($session_id); + } catch (RedisException $e) { + $msg = strtolower($e->getMessage()); + if ($msg === 'connection lost' || strpos($msg, 'went away')) { + $this->connect(); + return $this->_redis->get($session_id); + } + throw $e; + } + + } + + /** + * {@inheritdoc} + */ + public function write($session_id, $session_data) + { + return true === $this->_redis->setex($session_id, Session::$lifetime, $session_data); + } + + /** + * {@inheritdoc} + */ + public function updateTimestamp($id, $data = "") + { + return true === $this->_redis->expire($id, Session::$lifetime); + } + + /** + * {@inheritdoc} + */ + public function destroy($session_id) + { + $this->_redis->del($session_id); + return true; + } + + /** + * {@inheritdoc} + */ + public function close() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function gc($maxlifetime) + { + return true; + } +} diff --git a/vendor/workerman/workerman/Protocols/Http/Session/SessionHandlerInterface.php b/vendor/workerman/workerman/Protocols/Http/Session/SessionHandlerInterface.php new file mode 100644 index 0000000..23a47f2 --- /dev/null +++ b/vendor/workerman/workerman/Protocols/Http/Session/SessionHandlerInterface.php @@ -0,0 +1,114 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols\Http\Session; + +interface SessionHandlerInterface +{ + /** + * Close the session + * @link http://php.net/manual/en/sessionhandlerinterface.close.php + * @return bool

    + * The return value (usually TRUE on success, FALSE on failure). + * Note this value is returned internally to PHP for processing. + *

    + * @since 5.4.0 + */ + public function close(); + + /** + * Destroy a session + * @link http://php.net/manual/en/sessionhandlerinterface.destroy.php + * @param string $session_id The session ID being destroyed. + * @return bool

    + * The return value (usually TRUE on success, FALSE on failure). + * Note this value is returned internally to PHP for processing. + *

    + * @since 5.4.0 + */ + public function destroy($session_id); + + /** + * Cleanup old sessions + * @link http://php.net/manual/en/sessionhandlerinterface.gc.php + * @param int $maxlifetime

    + * Sessions that have not updated for + * the last maxlifetime seconds will be removed. + *

    + * @return bool

    + * The return value (usually TRUE on success, FALSE on failure). + * Note this value is returned internally to PHP for processing. + *

    + * @since 5.4.0 + */ + public function gc($maxlifetime); + + /** + * Initialize session + * @link http://php.net/manual/en/sessionhandlerinterface.open.php + * @param string $save_path The path where to store/retrieve the session. + * @param string $name The session name. + * @return bool

    + * The return value (usually TRUE on success, FALSE on failure). + * Note this value is returned internally to PHP for processing. + *

    + * @since 5.4.0 + */ + public function open($save_path, $name); + + + /** + * Read session data + * @link http://php.net/manual/en/sessionhandlerinterface.read.php + * @param string $session_id The session id to read data for. + * @return string

    + * Returns an encoded string of the read data. + * If nothing was read, it must return an empty string. + * Note this value is returned internally to PHP for processing. + *

    + * @since 5.4.0 + */ + public function read($session_id); + + /** + * Write session data + * @link http://php.net/manual/en/sessionhandlerinterface.write.php + * @param string $session_id The session id. + * @param string $session_data

    + * The encoded session data. This data is the + * result of the PHP internally encoding + * the $_SESSION superglobal to a serialized + * string and passing it as this parameter. + * Please note sessions use an alternative serialization method. + *

    + * @return bool

    + * The return value (usually TRUE on success, FALSE on failure). + * Note this value is returned internally to PHP for processing. + *

    + * @since 5.4.0 + */ + public function write($session_id, $session_data); + + /** + * Update sesstion modify time. + * + * @see https://www.php.net/manual/en/class.sessionupdatetimestamphandlerinterface.php + * + * @param string $id Session id. + * @param string $data Session Data. + * + * @return bool + */ + public function updateTimestamp($id, $data = ""); + +} diff --git a/vendor/workerman/workerman/Protocols/Http/mime.types b/vendor/workerman/workerman/Protocols/Http/mime.types new file mode 100644 index 0000000..e6ccf0a --- /dev/null +++ b/vendor/workerman/workerman/Protocols/Http/mime.types @@ -0,0 +1,90 @@ + +types { + text/html html htm shtml; + text/css css; + text/xml xml; + image/gif gif; + image/jpeg jpeg jpg; + application/javascript js; + application/atom+xml atom; + application/rss+xml rss; + + text/mathml mml; + text/plain txt; + text/vnd.sun.j2me.app-descriptor jad; + text/vnd.wap.wml wml; + text/x-component htc; + + image/png png; + image/tiff tif tiff; + image/vnd.wap.wbmp wbmp; + image/x-icon ico; + image/x-jng jng; + image/x-ms-bmp bmp; + image/svg+xml svg svgz; + image/webp webp; + + application/font-woff woff; + application/java-archive jar war ear; + application/json json; + application/mac-binhex40 hqx; + application/msword doc; + application/pdf pdf; + application/postscript ps eps ai; + application/rtf rtf; + application/vnd.apple.mpegurl m3u8; + application/vnd.ms-excel xls; + application/vnd.ms-fontobject eot; + application/vnd.ms-powerpoint ppt; + application/vnd.wap.wmlc wmlc; + application/vnd.google-earth.kml+xml kml; + application/vnd.google-earth.kmz kmz; + application/x-7z-compressed 7z; + application/x-cocoa cco; + application/x-java-archive-diff jardiff; + application/x-java-jnlp-file jnlp; + application/x-makeself run; + application/x-perl pl pm; + application/x-pilot prc pdb; + application/x-rar-compressed rar; + application/x-redhat-package-manager rpm; + application/x-sea sea; + application/x-shockwave-flash swf; + application/x-stuffit sit; + application/x-tcl tcl tk; + application/x-x509-ca-cert der pem crt; + application/x-xpinstall xpi; + application/xhtml+xml xhtml; + application/xspf+xml xspf; + application/zip zip; + + application/octet-stream bin exe dll; + application/octet-stream deb; + application/octet-stream dmg; + application/octet-stream iso img; + application/octet-stream msi msp msm; + + application/vnd.openxmlformats-officedocument.wordprocessingml.document docx; + application/vnd.openxmlformats-officedocument.spreadsheetml.sheet xlsx; + application/vnd.openxmlformats-officedocument.presentationml.presentation pptx; + + audio/midi mid midi kar; + audio/mpeg mp3; + audio/ogg ogg; + audio/x-m4a m4a; + audio/x-realaudio ra; + + video/3gpp 3gpp 3gp; + video/mp2t ts; + video/mp4 mp4; + video/mpeg mpeg mpg; + video/quicktime mov; + video/webm webm; + video/x-flv flv; + video/x-m4v m4v; + video/x-mng mng; + video/x-ms-asf asx asf; + video/x-ms-wmv wmv; + video/x-msvideo avi; + font/ttf ttf; +} diff --git a/vendor/workerman/workerman/Protocols/ProtocolInterface.php b/vendor/workerman/workerman/Protocols/ProtocolInterface.php new file mode 100644 index 0000000..4fea87d --- /dev/null +++ b/vendor/workerman/workerman/Protocols/ProtocolInterface.php @@ -0,0 +1,52 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols; + +use Workerman\Connection\ConnectionInterface; + +/** + * Protocol interface + */ +interface ProtocolInterface +{ + /** + * Check the integrity of the package. + * Please return the length of package. + * If length is unknow please return 0 that mean wating more data. + * If the package has something wrong please return false the connection will be closed. + * + * @param string $recv_buffer + * @param ConnectionInterface $connection + * @return int|false + */ + public static function input($recv_buffer, ConnectionInterface $connection); + + /** + * Decode package and emit onMessage($message) callback, $message is the result that decode returned. + * + * @param string $recv_buffer + * @param ConnectionInterface $connection + * @return mixed + */ + public static function decode($recv_buffer, ConnectionInterface $connection); + + /** + * Encode package brefore sending to client. + * + * @param mixed $data + * @param ConnectionInterface $connection + * @return string + */ + public static function encode($data, ConnectionInterface $connection); +} diff --git a/vendor/workerman/workerman/Protocols/Text.php b/vendor/workerman/workerman/Protocols/Text.php new file mode 100644 index 0000000..407ea2d --- /dev/null +++ b/vendor/workerman/workerman/Protocols/Text.php @@ -0,0 +1,70 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols; + +use Workerman\Connection\ConnectionInterface; + +/** + * Text Protocol. + */ +class Text +{ + /** + * Check the integrity of the package. + * + * @param string $buffer + * @param ConnectionInterface $connection + * @return int + */ + public static function input($buffer, ConnectionInterface $connection) + { + // Judge whether the package length exceeds the limit. + if (isset($connection->maxPackageSize) && \strlen($buffer) >= $connection->maxPackageSize) { + $connection->close(); + return 0; + } + // Find the position of "\n". + $pos = \strpos($buffer, "\n"); + // No "\n", packet length is unknown, continue to wait for the data so return 0. + if ($pos === false) { + return 0; + } + // Return the current package length. + return $pos + 1; + } + + /** + * Encode. + * + * @param string $buffer + * @return string + */ + public static function encode($buffer) + { + // Add "\n" + return $buffer . "\n"; + } + + /** + * Decode. + * + * @param string $buffer + * @return string + */ + public static function decode($buffer) + { + // Remove "\n" + return \rtrim($buffer, "\r\n"); + } +} \ No newline at end of file diff --git a/vendor/workerman/workerman/Protocols/Websocket.php b/vendor/workerman/workerman/Protocols/Websocket.php new file mode 100644 index 0000000..0f94de6 --- /dev/null +++ b/vendor/workerman/workerman/Protocols/Websocket.php @@ -0,0 +1,564 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace Workerman\Protocols; + +use Workerman\Connection\ConnectionInterface; +use Workerman\Connection\TcpConnection; +use Workerman\Protocols\Http\Request; +use Workerman\Worker; + +/** + * WebSocket protocol. + */ +class Websocket implements \Workerman\Protocols\ProtocolInterface +{ + /** + * Websocket blob type. + * + * @var string + */ + const BINARY_TYPE_BLOB = "\x81"; + + /** + * Websocket blob type. + * + * @var string + */ + const BINARY_TYPE_BLOB_DEFLATE = "\xc1"; + + /** + * Websocket arraybuffer type. + * + * @var string + */ + const BINARY_TYPE_ARRAYBUFFER = "\x82"; + + /** + * Websocket arraybuffer type. + * + * @var string + */ + const BINARY_TYPE_ARRAYBUFFER_DEFLATE = "\xc2"; + + /** + * Check the integrity of the package. + * + * @param string $buffer + * @param ConnectionInterface $connection + * @return int + */ + public static function input($buffer, ConnectionInterface $connection) + { + // Receive length. + $recv_len = \strlen($buffer); + // We need more data. + if ($recv_len < 6) { + return 0; + } + + // Has not yet completed the handshake. + if (empty($connection->context->websocketHandshake)) { + return static::dealHandshake($buffer, $connection); + } + + // Buffer websocket frame data. + if ($connection->context->websocketCurrentFrameLength) { + // We need more frame data. + if ($connection->context->websocketCurrentFrameLength > $recv_len) { + // Return 0, because it is not clear the full packet length, waiting for the frame of fin=1. + return 0; + } + } else { + $first_byte = \ord($buffer[0]); + $second_byte = \ord($buffer[1]); + $data_len = $second_byte & 127; + $is_fin_frame = $first_byte >> 7; + $masked = $second_byte >> 7; + + if (!$masked) { + Worker::safeEcho("frame not masked so close the connection\n"); + $connection->close(); + return 0; + } + + $opcode = $first_byte & 0xf; + switch ($opcode) { + case 0x0: + break; + // Blob type. + case 0x1: + break; + // Arraybuffer type. + case 0x2: + break; + // Close package. + case 0x8: + // Try to emit onWebSocketClose callback. + $close_cb = $connection->onWebSocketClose ?? $connection->worker->onWebSocketClose ?? false; + if ($close_cb) { + try { + $close_cb($connection); + } catch (\Throwable $e) { + Worker::stopAll(250, $e); + } + } // Close connection. + else { + $connection->close("\x88\x02\x03\xe8", true); + } + return 0; + // Ping package. + case 0x9: + break; + // Pong package. + case 0xa: + break; + // Wrong opcode. + default : + Worker::safeEcho("error opcode $opcode and close websocket connection. Buffer:" . bin2hex($buffer) . "\n"); + $connection->close(); + return 0; + } + + // Calculate packet length. + $head_len = 6; + if ($data_len === 126) { + $head_len = 8; + if ($head_len > $recv_len) { + return 0; + } + $pack = \unpack('nn/ntotal_len', $buffer); + $data_len = $pack['total_len']; + } else { + if ($data_len === 127) { + $head_len = 14; + if ($head_len > $recv_len) { + return 0; + } + $arr = \unpack('n/N2c', $buffer); + $data_len = $arr['c1'] * 4294967296 + $arr['c2']; + } + } + $current_frame_length = $head_len + $data_len; + + $total_package_size = \strlen($connection->context->websocketDataBuffer) + $current_frame_length; + if ($total_package_size > $connection->maxPackageSize) { + Worker::safeEcho("error package. package_length=$total_package_size\n"); + $connection->close(); + return 0; + } + + if ($is_fin_frame) { + if ($opcode === 0x9) { + if ($recv_len >= $current_frame_length) { + $ping_data = static::decode(\substr($buffer, 0, $current_frame_length), $connection); + $connection->consumeRecvBuffer($current_frame_length); + $tmp_connection_type = isset($connection->websocketType) ? $connection->websocketType : static::BINARY_TYPE_BLOB; + $connection->websocketType = "\x8a"; + $ping_cb = $connection->onWebSocketPing ?? $connection->worker->onWebSocketPing ?? false; + if ($ping_cb) { + try { + $ping_cb($connection, $ping_data); + } catch (\Throwable $e) { + Worker::stopAll(250, $e); + } + } else { + $connection->send($ping_data); + } + $connection->websocketType = $tmp_connection_type; + if ($recv_len > $current_frame_length) { + return static::input(\substr($buffer, $current_frame_length), $connection); + } + } + return 0; + } else if ($opcode === 0xa) { + if ($recv_len >= $current_frame_length) { + $pong_data = static::decode(\substr($buffer, 0, $current_frame_length), $connection); + $connection->consumeRecvBuffer($current_frame_length); + $tmp_connection_type = isset($connection->websocketType) ? $connection->websocketType : static::BINARY_TYPE_BLOB; + $connection->websocketType = "\x8a"; + // Try to emit onWebSocketPong callback. + $pong_cb = $connection->onWebSocketPong ?? $connection->worker->onWebSocketPong ?? false; + if ($pong_cb) { + try { + $pong_cb($connection, $pong_data); + } catch (\Throwable $e) { + Worker::stopAll(250, $e); + } + } + $connection->websocketType = $tmp_connection_type; + if ($recv_len > $current_frame_length) { + return static::input(\substr($buffer, $current_frame_length), $connection); + } + } + return 0; + } + return $current_frame_length; + } else { + $connection->context->websocketCurrentFrameLength = $current_frame_length; + } + } + + // Received just a frame length data. + if ($connection->context->websocketCurrentFrameLength === $recv_len) { + static::decode($buffer, $connection); + $connection->consumeRecvBuffer($connection->context->websocketCurrentFrameLength); + $connection->context->websocketCurrentFrameLength = 0; + return 0; + } // The length of the received data is greater than the length of a frame. + elseif ($connection->context->websocketCurrentFrameLength < $recv_len) { + static::decode(\substr($buffer, 0, $connection->context->websocketCurrentFrameLength), $connection); + $connection->consumeRecvBuffer($connection->context->websocketCurrentFrameLength); + $current_frame_length = $connection->context->websocketCurrentFrameLength; + $connection->context->websocketCurrentFrameLength = 0; + // Continue to read next frame. + return static::input(\substr($buffer, $current_frame_length), $connection); + } // The length of the received data is less than the length of a frame. + else { + return 0; + } + } + + /** + * Websocket encode. + * + * @param string $buffer + * @param ConnectionInterface $connection + * @return string + */ + public static function encode($buffer, ConnectionInterface $connection) + { + if (!is_scalar($buffer)) { + throw new \Exception("You can't send(" . \gettype($buffer) . ") to client, you need to convert it to a string. "); + } + + if (empty($connection->websocketType)) { + $connection->websocketType = static::BINARY_TYPE_BLOB; + } + + // permessage-deflate + if (\ord($connection->websocketType) & 64) { + $buffer = static::deflate($connection, $buffer); + } + + $first_byte = $connection->websocketType; + $len = \strlen($buffer); + + if ($len <= 125) { + $encode_buffer = $first_byte . \chr($len) . $buffer; + } else { + if ($len <= 65535) { + $encode_buffer = $first_byte . \chr(126) . \pack("n", $len) . $buffer; + } else { + $encode_buffer = $first_byte . \chr(127) . \pack("xxxxN", $len) . $buffer; + } + } + + // Handshake not completed so temporary buffer websocket data waiting for send. + if (empty($connection->context->websocketHandshake)) { + if (empty($connection->context->tmpWebsocketData)) { + $connection->context->tmpWebsocketData = ''; + } + // If buffer has already full then discard the current package. + if (\strlen($connection->context->tmpWebsocketData) > $connection->maxSendBufferSize) { + if ($connection->onError) { + try { + ($connection->onError)($connection, WORKERMAN_SEND_FAIL, 'send buffer full and drop package'); + } catch (\Throwable $e) { + Worker::stopAll(250, $e); + } + } + return ''; + } + $connection->context->tmpWebsocketData .= $encode_buffer; + // Check buffer is full. + if ($connection->maxSendBufferSize <= \strlen($connection->context->tmpWebsocketData)) { + if ($connection->onBufferFull) { + try { + ($connection->onBufferFull)($connection); + } catch (\Throwable $e) { + Worker::stopAll(250, $e); + } + } + } + // Return empty string. + return ''; + } + + return $encode_buffer; + } + + /** + * Websocket decode. + * + * @param string $buffer + * @param ConnectionInterface $connection + * @return string + */ + public static function decode($buffer, ConnectionInterface $connection) + { + $first_byte = \ord($buffer[0]); + $second_byte = \ord($buffer[1]); + $len = $second_byte & 127; + $is_fin_frame = $first_byte >> 7; + $rsv1 = 64 === ($first_byte & 64); + + if ($len === 126) { + $masks = \substr($buffer, 4, 4); + $data = \substr($buffer, 8); + } else { + if ($len === 127) { + $masks = \substr($buffer, 10, 4); + $data = \substr($buffer, 14); + } else { + $masks = \substr($buffer, 2, 4); + $data = \substr($buffer, 6); + } + } + $dataLength = \strlen($data); + $masks = \str_repeat($masks, \floor($dataLength / 4)) . \substr($masks, 0, $dataLength % 4); + $decoded = $data ^ $masks; + if ($connection->context->websocketCurrentFrameLength) { + $connection->context->websocketDataBuffer .= $decoded; + if ($rsv1) { + return static::inflate($connection, $connection->context->websocketDataBuffer, $is_fin_frame); + } + return $connection->context->websocketDataBuffer; + } else { + if ($connection->context->websocketDataBuffer !== '') { + $decoded = $connection->context->websocketDataBuffer . $decoded; + $connection->context->websocketDataBuffer = ''; + } + if ($rsv1) { + return static::inflate($connection, $decoded, $is_fin_frame); + } + return $decoded; + } + } + + /** + * Inflate. + * + * @param $connection + * @param $buffer + * @param $is_fin_frame + * @return false|string + */ + protected static function inflate($connection, $buffer, $is_fin_frame) + { + if (!isset($connection->context->inflator)) { + $connection->context->inflator = \inflate_init( + \ZLIB_ENCODING_RAW, + [ + 'level' => -1, + 'memory' => 8, + 'window' => 9, + 'strategy' => \ZLIB_DEFAULT_STRATEGY + ] + ); + } + if ($is_fin_frame) { + $buffer .= "\x00\x00\xff\xff"; + } + return \inflate_add($connection->context->inflator, $buffer); + } + + /** + * Deflate. + * + * @param $connection + * @param $buffer + * @return false|string + */ + protected static function deflate($connection, $buffer) + { + if (!isset($connection->context->deflator)) { + $connection->context->deflator = \deflate_init( + \ZLIB_ENCODING_RAW, + [ + 'level' => -1, + 'memory' => 8, + 'window' => 9, + 'strategy' => \ZLIB_DEFAULT_STRATEGY + ] + ); + } + return \substr(\deflate_add($connection->context->deflator, $buffer), 0, -4); + } + + /** + * Websocket handshake. + * + * @param string $buffer + * @param TcpConnection $connection + * @return int + */ + public static function dealHandshake($buffer, $connection) + { + // HTTP protocol. + if (0 === \strpos($buffer, 'GET')) { + // Find \r\n\r\n. + $header_end_pos = \strpos($buffer, "\r\n\r\n"); + if (!$header_end_pos) { + return 0; + } + $header_length = $header_end_pos + 4; + + // Get Sec-WebSocket-Key. + $Sec_WebSocket_Key = ''; + if (\preg_match("/Sec-WebSocket-Key: *(.*?)\r\n/i", $buffer, $match)) { + $Sec_WebSocket_Key = $match[1]; + } else { + $connection->close("HTTP/1.1 200 WebSocket\r\nServer: workerman/" . Worker::VERSION . "\r\n\r\n

    WebSocket


    workerman/" . Worker::VERSION . "
    ", + true); + return 0; + } + // Calculation websocket key. + $new_key = \base64_encode(\sha1($Sec_WebSocket_Key . "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", true)); + // Handshake response data. + $handshake_message = "HTTP/1.1 101 Switching Protocols\r\n" + . "Upgrade: websocket\r\n" + . "Sec-WebSocket-Version: 13\r\n" + . "Connection: Upgrade\r\n" + . "Sec-WebSocket-Accept: " . $new_key . "\r\n"; + + // Websocket data buffer. + $connection->context->websocketDataBuffer = ''; + // Current websocket frame length. + $connection->context->websocketCurrentFrameLength = 0; + // Current websocket frame data. + $connection->context->websocketCurrentFrameBuffer = ''; + // Consume handshake data. + $connection->consumeRecvBuffer($header_length); + + // Try to emit onWebSocketConnect callback. + $on_websocket_connect = $connection->onWebSocketConnect ?? $connection->worker->onWebSocketConnect ?? false; + if ($on_websocket_connect) { + static::parseHttpHeader($buffer); + try { + \call_user_func($on_websocket_connect, $connection, $buffer); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + if (!empty($_SESSION) && \class_exists('\GatewayWorker\Lib\Context')) { + $connection->session = \GatewayWorker\Lib\Context::sessionEncode($_SESSION); + } + $_GET = $_SERVER = $_SESSION = $_COOKIE = array(); + } + + // blob or arraybuffer + if (empty($connection->websocketType)) { + $connection->websocketType = static::BINARY_TYPE_BLOB; + } + + $has_server_header = false; + + if (isset($connection->headers)) { + if (\is_array($connection->headers)) { + foreach ($connection->headers as $header) { + if (\stripos($header, 'Server:') === 0) { + $has_server_header = true; + } + $handshake_message .= "$header\r\n"; + } + } else { + if (\stripos($connection->headers, 'Server:') !== false) { + $has_server_header = true; + } + $handshake_message .= "$connection->headers\r\n"; + } + } + if (!$has_server_header) { + $handshake_message .= "Server: workerman/" . Worker::VERSION . "\r\n"; + } + $handshake_message .= "\r\n"; + // Send handshake response. + $connection->send($handshake_message, true); + // Mark handshake complete.. + $connection->context->websocketHandshake = true; + + // There are data waiting to be sent. + if (!empty($connection->context->tmpWebsocketData)) { + $connection->send($connection->context->tmpWebsocketData, true); + $connection->context->tmpWebsocketData = ''; + } + if (\strlen($buffer) > $header_length) { + return static::input(\substr($buffer, $header_length), $connection); + } + return 0; + } + // Bad websocket handshake request. + $connection->close("HTTP/1.1 200 WebSocket\r\nServer: workerman/" . Worker::VERSION . "\r\n\r\n

    WebSocket


    workerman/" . Worker::VERSION . "
    ", + true); + return 0; + } + + /** + * Parse http header. + * + * @param string $buffer + * @return void + */ + protected static function parseHttpHeader($buffer) + { + // Parse headers. + list($http_header, ) = \explode("\r\n\r\n", $buffer, 2); + $header_data = \explode("\r\n", $http_header); + + if ($_SERVER) { + $_SERVER = array(); + } + + list($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI'], $_SERVER['SERVER_PROTOCOL']) = \explode(' ', + $header_data[0]); + + unset($header_data[0]); + foreach ($header_data as $content) { + // \r\n\r\n + if (empty($content)) { + continue; + } + list($key, $value) = \explode(':', $content, 2); + $key = \str_replace('-', '_', \strtoupper($key)); + $value = \trim($value); + $_SERVER['HTTP_' . $key] = $value; + switch ($key) { + // HTTP_HOST + case 'HOST': + $tmp = \explode(':', $value); + $_SERVER['SERVER_NAME'] = $tmp[0]; + if (isset($tmp[1])) { + $_SERVER['SERVER_PORT'] = $tmp[1]; + } + break; + // cookie + case 'COOKIE': + \parse_str(\str_replace('; ', '&', $_SERVER['HTTP_COOKIE']), $_COOKIE); + break; + } + } + + // QUERY_STRING + $_SERVER['QUERY_STRING'] = \parse_url($_SERVER['REQUEST_URI'], \PHP_URL_QUERY); + if ($_SERVER['QUERY_STRING']) { + // $GET + \parse_str($_SERVER['QUERY_STRING'], $_GET); + } else { + $_SERVER['QUERY_STRING'] = ''; + } + } + +} diff --git a/vendor/workerman/workerman/Protocols/Ws.php b/vendor/workerman/workerman/Protocols/Ws.php new file mode 100644 index 0000000..3db887e --- /dev/null +++ b/vendor/workerman/workerman/Protocols/Ws.php @@ -0,0 +1,432 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace Workerman\Protocols; + +use Workerman\Worker; +use Workerman\Timer; +use Workerman\Connection\TcpConnection; +use Workerman\Connection\ConnectionInterface; + +/** + * Websocket protocol for client. + */ +class Ws +{ + /** + * Websocket blob type. + * + * @var string + */ + const BINARY_TYPE_BLOB = "\x81"; + + /** + * Websocket arraybuffer type. + * + * @var string + */ + const BINARY_TYPE_ARRAYBUFFER = "\x82"; + + /** + * Check the integrity of the package. + * + * @param string $buffer + * @param ConnectionInterface $connection + * @return int + */ + public static function input($buffer, ConnectionInterface $connection) + { + if (empty($connection->context->handshakeStep)) { + Worker::safeEcho("recv data before handshake. Buffer:" . \bin2hex($buffer) . "\n"); + return false; + } + // Recv handshake response + if ($connection->context->handshakeStep === 1) { + return self::dealHandshake($buffer, $connection); + } + $recvLen = \strlen($buffer); + if ($recvLen < 2) { + return 0; + } + // Buffer websocket frame data. + if ($connection->context->websocketCurrentFrameLength) { + // We need more frame data. + if ($connection->context->websocketCurrentFrameLength > $recvLen) { + // Return 0, because it is not clear the full packet length, waiting for the frame of fin=1. + return 0; + } + } else { + + $firstbyte = \ord($buffer[0]); + $secondbyte = \ord($buffer[1]); + $dataLen = $secondbyte & 127; + $isFinFrame = $firstbyte >> 7; + $masked = $secondbyte >> 7; + + if ($masked) { + Worker::safeEcho("frame masked so close the connection\n"); + $connection->close(); + return 0; + } + + $opcode = $firstbyte & 0xf; + + switch ($opcode) { + case 0x0: + // Blob type. + case 0x1: + // Arraybuffer type. + case 0x2: + // Ping package. + case 0x9: + // Pong package. + case 0xa: + break; + // Close package. + case 0x8: + // Try to emit onWebSocketClose callback. + if (isset($connection->onWebSocketClose)) { + try { + ($connection->onWebSocketClose)($connection); + } catch (\Throwable $e) { + Worker::stopAll(250, $e); + } + } // Close connection. + else { + $connection->close(); + } + return 0; + // Wrong opcode. + default : + Worker::safeEcho("error opcode $opcode and close websocket connection. Buffer:" . $buffer . "\n"); + $connection->close(); + return 0; + } + // Calculate packet length. + if ($dataLen === 126) { + if (\strlen($buffer) < 4) { + return 0; + } + $pack = \unpack('nn/ntotal_len', $buffer); + $currentFrameLength = $pack['total_len'] + 4; + } else if ($dataLen === 127) { + if (\strlen($buffer) < 10) { + return 0; + } + $arr = \unpack('n/N2c', $buffer); + $currentFrameLength = $arr['c1'] * 4294967296 + $arr['c2'] + 10; + } else { + $currentFrameLength = $dataLen + 2; + } + + $totalPackageSize = \strlen($connection->context->websocketDataBuffer) + $currentFrameLength; + if ($totalPackageSize > $connection->maxPackageSize) { + Worker::safeEcho("error package. package_length=$totalPackageSize\n"); + $connection->close(); + return 0; + } + + if ($isFinFrame) { + if ($opcode === 0x9) { + if ($recvLen >= $currentFrameLength) { + $pingData = static::decode(\substr($buffer, 0, $currentFrameLength), $connection); + $connection->consumeRecvBuffer($currentFrameLength); + $tmpConnectionType = isset($connection->websocketType) ? $connection->websocketType : static::BINARY_TYPE_BLOB; + $connection->websocketType = "\x8a"; + if (isset($connection->onWebSocketPing)) { + try { + ($connection->onWebSocketPing)($connection, $pingData); + } catch (\Throwable $e) { + Worker::stopAll(250, $e); + } + } else { + $connection->send($pingData); + } + $connection->websocketType = $tmpConnectionType; + if ($recvLen > $currentFrameLength) { + return static::input(\substr($buffer, $currentFrameLength), $connection); + } + } + return 0; + + } else if ($opcode === 0xa) { + if ($recvLen >= $currentFrameLength) { + $pongData = static::decode(\substr($buffer, 0, $currentFrameLength), $connection); + $connection->consumeRecvBuffer($currentFrameLength); + $tmpConnectionType = isset($connection->websocketType) ? $connection->websocketType : static::BINARY_TYPE_BLOB; + $connection->websocketType = "\x8a"; + // Try to emit onWebSocketPong callback. + if (isset($connection->onWebSocketPong)) { + try { + ($connection->onWebSocketPong)($connection, $pongData); + } catch (\Throwable $e) { + Worker::stopAll(250, $e); + } + } + $connection->websocketType = $tmpConnectionType; + if ($recvLen > $currentFrameLength) { + return static::input(\substr($buffer, $currentFrameLength), $connection); + } + } + return 0; + } + return $currentFrameLength; + } else { + $connection->context->websocketCurrentFrameLength = $currentFrameLength; + } + } + // Received just a frame length data. + if ($connection->context->websocketCurrentFrameLength === $recvLen) { + self::decode($buffer, $connection); + $connection->consumeRecvBuffer($connection->context->websocketCurrentFrameLength); + $connection->context->websocketCurrentFrameLength = 0; + return 0; + } // The length of the received data is greater than the length of a frame. + elseif ($connection->context->websocketCurrentFrameLength < $recvLen) { + self::decode(\substr($buffer, 0, $connection->context->websocketCurrentFrameLength), $connection); + $connection->consumeRecvBuffer($connection->context->websocketCurrentFrameLength); + $currentFrameLength = $connection->context->websocketCurrentFrameLength; + $connection->context->websocketCurrentFrameLength = 0; + // Continue to read next frame. + return self::input(\substr($buffer, $currentFrameLength), $connection); + } // The length of the received data is less than the length of a frame. + else { + return 0; + } + } + + /** + * Websocket encode. + * + * @param string $buffer + * @param ConnectionInterface $connection + * @return string + */ + public static function encode($payload, ConnectionInterface $connection) + { + if (empty($connection->websocketType)) { + $connection->websocketType = self::BINARY_TYPE_BLOB; + } + $payload = (string)$payload; + if (empty($connection->context->handshakeStep)) { + static::sendHandshake($connection); + } + + $maskKey = "\x00\x00\x00\x00"; + $length = \strlen($payload); + + if (strlen($payload) < 126) { + $head = chr(0x80 | $length); + } elseif ($length < 0xFFFF) { + $head = chr(0x80 | 126) . pack("n", $length); + } else { + $head = chr(0x80 | 127) . pack("N", 0) . pack("N", $length); + } + + $frame = $connection->websocketType . $head . $maskKey; + // append payload to frame: + $maskKey = \str_repeat($maskKey, \floor($length / 4)) . \substr($maskKey, 0, $length % 4); + $frame .= $payload ^ $maskKey; + if ($connection->context->handshakeStep === 1) { + // If buffer has already full then discard the current package. + if (\strlen($connection->context->tmpWebsocketData) > $connection->maxSendBufferSize) { + if ($connection->onError) { + try { + ($connection->onError)($connection, WORKERMAN_SEND_FAIL, 'send buffer full and drop package'); + } catch (\Throwable $e) { + Worker::stopAll(250, $e); + } + } + return ''; + } + $connection->context->tmpWebsocketData = $connection->context->tmpWebsocketData . $frame; + // Check buffer is full. + if ($connection->maxSendBufferSize <= \strlen($connection->context->tmpWebsocketData)) { + if ($connection->onBufferFull) { + try { + ($connection->onBufferFull)($connection); + } catch (\Throwable $e) { + Worker::stopAll(250, $e); + } + } + } + return ''; + } + return $frame; + } + + /** + * Websocket decode. + * + * @param string $buffer + * @param ConnectionInterface $connection + * @return string + */ + public static function decode($bytes, ConnectionInterface $connection) + { + $dataLength = \ord($bytes[1]); + + if ($dataLength === 126) { + $decodedData = \substr($bytes, 4); + } else if ($dataLength === 127) { + $decodedData = \substr($bytes, 10); + } else { + $decodedData = \substr($bytes, 2); + } + if ($connection->context->websocketCurrentFrameLength) { + $connection->context->websocketDataBuffer .= $decodedData; + return $connection->context->websocketDataBuffer; + } else { + if ($connection->context->websocketDataBuffer !== '') { + $decodedData = $connection->context->websocketDataBuffer . $decodedData; + $connection->context->websocketDataBuffer = ''; + } + return $decodedData; + } + } + + /** + * Send websocket handshake data. + * + * @return void + */ + public static function onConnect($connection) + { + static::sendHandshake($connection); + } + + /** + * Clean + * + * @param TcpConnection $connection + */ + public static function onClose($connection) + { + $connection->context->handshakeStep = null; + $connection->context->websocketCurrentFrameLength = 0; + $connection->context->tmpWebsocketData = ''; + $connection->context->websocketDataBuffer = ''; + if (!empty($connection->context->websocketPingTimer)) { + Timer::del($connection->context->websocketPingTimer); + $connection->context->websocketPingTimer = null; + } + } + + /** + * Send websocket handshake. + * + * @param TcpConnection $connection + * @return void + */ + public static function sendHandshake(ConnectionInterface $connection) + { + if (!empty($connection->context->handshakeStep)) { + return; + } + // Get Host. + $port = $connection->getRemotePort(); + $host = $port === 80 ? $connection->getRemoteHost() : $connection->getRemoteHost() . ':' . $port; + // Handshake header. + $connection->context->websocketSecKey = \base64_encode(random_bytes(16)); + $userHeader = $connection->headers ?? null; + $userHeaderStr = ''; + if (!empty($userHeader)) { + if (\is_array($userHeader)) { + foreach ($userHeader as $k => $v) { + $userHeaderStr .= "$k: $v\r\n"; + } + } else { + $userHeaderStr .= $userHeader; + } + $userHeaderStr = "\r\n" . \trim($userHeaderStr); + } + $header = 'GET ' . $connection->getRemoteURI() . " HTTP/1.1\r\n" . + (!\preg_match("/\nHost:/i", $userHeaderStr) ? "Host: $host\r\n" : '') . + "Connection: Upgrade\r\n" . + "Upgrade: websocket\r\n" . + (isset($connection->websocketOrigin) ? "Origin: " . $connection->websocketOrigin . "\r\n" : '') . + (isset($connection->websocketClientProtocol) ? "Sec-WebSocket-Protocol: " . $connection->websocketClientProtocol . "\r\n" : '') . + "Sec-WebSocket-Version: 13\r\n" . + "Sec-WebSocket-Key: " . $connection->context->websocketSecKey . $userHeaderStr . "\r\n\r\n"; + $connection->send($header, true); + $connection->context->handshakeStep = 1; + $connection->context->websocketCurrentFrameLength = 0; + $connection->context->websocketDataBuffer = ''; + $connection->context->tmpWebsocketData = ''; + } + + /** + * Websocket handshake. + * + * @param string $buffer + * @param TcpConnection $connection + * @return int + */ + public static function dealHandshake($buffer, ConnectionInterface $connection) + { + $pos = \strpos($buffer, "\r\n\r\n"); + if ($pos) { + //checking Sec-WebSocket-Accept + if (\preg_match("/Sec-WebSocket-Accept: *(.*?)\r\n/i", $buffer, $match)) { + if ($match[1] !== \base64_encode(\sha1($connection->context->websocketSecKey . "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", true))) { + Worker::safeEcho("Sec-WebSocket-Accept not match. Header:\n" . \substr($buffer, 0, $pos) . "\n"); + $connection->close(); + return 0; + } + } else { + Worker::safeEcho("Sec-WebSocket-Accept not found. Header:\n" . \substr($buffer, 0, $pos) . "\n"); + $connection->close(); + return 0; + } + + // handshake complete + + // Get WebSocket subprotocol (if specified by server) + if (\preg_match("/Sec-WebSocket-Protocol: *(.*?)\r\n/i", $buffer, $match)) { + $connection->websocketServerProtocol = \trim($match[1]); + } + + $connection->context->handshakeStep = 2; + $handshakeResponseLength = $pos + 4; + // Try to emit onWebSocketConnect callback. + if (isset($connection->onWebSocketConnect)) { + try { + ($connection->onWebSocketConnect)($connection, \substr($buffer, 0, $handshakeResponseLength)); + } catch (\Throwable $e) { + Worker::stopAll(250, $e); + } + } + // Headbeat. + if (!empty($connection->websocketPingInterval)) { + $connection->context->websocketPingTimer = Timer::add($connection->websocketPingInterval, function () use ($connection) { + if (false === $connection->send(\pack('H*', '898000000000'), true)) { + Timer::del($connection->context->websocketPingTimer); + $connection->context->websocketPingTimer = null; + } + }); + } + + $connection->consumeRecvBuffer($handshakeResponseLength); + if (!empty($connection->context->tmpWebsocketData)) { + $connection->send($connection->context->tmpWebsocketData, true); + $connection->context->tmpWebsocketData = ''; + } + if (\strlen($buffer) > $handshakeResponseLength) { + return self::input(\substr($buffer, $handshakeResponseLength), $connection); + } + } + return 0; + } + +} diff --git a/vendor/workerman/workerman/README.md b/vendor/workerman/workerman/README.md new file mode 100644 index 0000000..6038c02 --- /dev/null +++ b/vendor/workerman/workerman/README.md @@ -0,0 +1,342 @@ +# Workerman +[![Gitter](https://badges.gitter.im/walkor/Workerman.svg)](https://gitter.im/walkor/Workerman?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=body_badge) +[![Latest Stable Version](https://poser.pugx.org/workerman/workerman/v/stable)](https://packagist.org/packages/workerman/workerman) +[![Total Downloads](https://poser.pugx.org/workerman/workerman/downloads)](https://packagist.org/packages/workerman/workerman) +[![Monthly Downloads](https://poser.pugx.org/workerman/workerman/d/monthly)](https://packagist.org/packages/workerman/workerman) +[![Daily Downloads](https://poser.pugx.org/workerman/workerman/d/daily)](https://packagist.org/packages/workerman/workerman) +[![License](https://poser.pugx.org/workerman/workerman/license)](https://packagist.org/packages/workerman/workerman) + +## What is it +Workerman is an asynchronous event-driven PHP framework with high performance to build fast and scalable network applications. +Workerman supports HTTP, Websocket, SSL and other custom protocols. +Workerman supports event extension. + +## Requires +PHP 7.0 or Higher +A POSIX compatible operating system (Linux, OSX, BSD) +POSIX and PCNTL extensions required +Event extension recommended for better performance + +## Installation + +``` +composer require workerman/workerman +``` + +## Basic Usage + +### A websocket server +```php +onConnect = function ($connection) { + echo "New connection\n"; +}; + +// Emitted when data received +$ws_worker->onMessage = function ($connection, $data) { + // Send hello $data + $connection->send('Hello ' . $data); +}; + +// Emitted when connection closed +$ws_worker->onClose = function ($connection) { + echo "Connection closed\n"; +}; + +// Run worker +Worker::runAll(); +``` + +### An http server +```php +count = 4; + +// Emitted when data received +$http_worker->onMessage = function ($connection, $request) { + //$request->get(); + //$request->post(); + //$request->header(); + //$request->cookie(); + //$request->session(); + //$request->uri(); + //$request->path(); + //$request->method(); + + // Send data to client + $connection->send("Hello World"); +}; + +// Run all workers +Worker::runAll(); +``` + +### A tcp server +```php +count = 4; + +// Emitted when new connection come +$tcp_worker->onConnect = function ($connection) { + echo "New Connection\n"; +}; + +// Emitted when data received +$tcp_worker->onMessage = function ($connection, $data) { + // Send data to client + $connection->send("Hello $data \n"); +}; + +// Emitted when connection is closed +$tcp_worker->onClose = function ($connection) { + echo "Connection closed\n"; +}; + +Worker::runAll(); +``` + +### A udp server + +```php +count = 4; + +// Emitted when data received +$worker->onMessage = function($connection, $data) +{ + $connection->send($data); +}; + +Worker::runAll(); +``` + +### Enable SSL +```php + array( + 'local_cert' => '/your/path/of/server.pem', + 'local_pk' => '/your/path/of/server.key', + 'verify_peer' => false, + ) +); + +// Create a Websocket server with ssl context. +$ws_worker = new Worker('websocket://0.0.0.0:2346', $context); + +// Enable SSL. WebSocket+SSL means that Secure WebSocket (wss://). +// The similar approaches for Https etc. +$ws_worker->transport = 'ssl'; + +$ws_worker->onMessage = function ($connection, $data) { + // Send hello $data + $connection->send('Hello ' . $data); +}; + +Worker::runAll(); +``` + +### Custom protocol +Protocols/MyTextProtocol.php +```php +onConnect = function ($connection) { + echo "New connection\n"; +}; + +$text_worker->onMessage = function ($connection, $data) { + // Send data to client + $connection->send("Hello world\n"); +}; + +$text_worker->onClose = function ($connection) { + echo "Connection closed\n"; +}; + +// Run all workers +Worker::runAll(); +``` + +### Timer +```php +onWorkerStart = function ($task) { + // 2.5 seconds + $time_interval = 2.5; + $timer_id = Timer::add($time_interval, function () { + echo "Timer run\n"; + }); +}; + +// Run all workers +Worker::runAll(); +``` + +### AsyncTcpConnection (tcp/ws/text/frame etc...) +```php +onWorkerStart = function () { + // Websocket protocol for client. + $ws_connection = new AsyncTcpConnection('ws://echo.websocket.org:80'); + $ws_connection->onConnect = function ($connection) { + $connection->send('Hello'); + }; + $ws_connection->onMessage = function ($connection, $data) { + echo "Recv: $data\n"; + }; + $ws_connection->onError = function ($connection, $code, $msg) { + echo "Error: $msg\n"; + }; + $ws_connection->onClose = function ($connection) { + echo "Connection closed\n"; + }; + $ws_connection->connect(); +}; + +Worker::runAll(); +``` + + + +## Available commands +```php start.php start ``` +```php start.php start -d ``` +![workerman start](http://www.workerman.net/img/workerman-start.png) +```php start.php status ``` +![workerman satus](http://www.workerman.net/img/workerman-status.png?a=123) +```php start.php connections``` +```php start.php stop ``` +```php start.php restart ``` +```php start.php reload ``` + +## Documentation + +中文主页:[http://www.workerman.net](https://www.workerman.net) + +中文文档: [https://www.workerman.net/doc/workerman](https://www.workerman.net/doc/workerman) + +Documentation:[https://github.com/walkor/workerman-manual](https://github.com/walkor/workerman-manual/blob/master/english/SUMMARY.md) + +# Benchmarks +https://www.techempower.com/benchmarks/#section=data-r20&hw=ph&test=db&l=yyku7z-e7&a=2 +![image](https://user-images.githubusercontent.com/6073368/146704320-1559fe97-aa67-4ee3-95d6-61e341b3c93b.png) + +## Sponsors +[opencollective.com/walkor](https://opencollective.com/walkor) + +[patreon.com/walkor](https://patreon.com/walkor) + +## Donate + + + +## Other links with workerman + +[webman](https://github.com/walkor/webman) +[PHPSocket.IO](https://github.com/walkor/phpsocket.io) +[php-socks5](https://github.com/walkor/php-socks5) +[php-http-proxy](https://github.com/walkor/php-http-proxy) + +## LICENSE + +Workerman is released under the [MIT license](https://github.com/walkor/workerman/blob/master/MIT-LICENSE.txt). diff --git a/vendor/workerman/workerman/Timer.php b/vendor/workerman/workerman/Timer.php new file mode 100644 index 0000000..9f152f3 --- /dev/null +++ b/vendor/workerman/workerman/Timer.php @@ -0,0 +1,220 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman; + +use Workerman\Events\EventInterface; +use Workerman\Worker; +use \Exception; + +/** + * Timer. + * + * example: + * Workerman\Timer::add($time_interval, callback, array($arg1, $arg2..)); + */ +class Timer +{ + /** + * Tasks that based on ALARM signal. + * [ + * run_time => [[$func, $args, $persistent, time_interval],[$func, $args, $persistent, time_interval],..]], + * run_time => [[$func, $args, $persistent, time_interval],[$func, $args, $persistent, time_interval],..]], + * .. + * ] + * + * @var array + */ + protected static $_tasks = array(); + + /** + * event + * + * @var EventInterface + */ + protected static $_event = null; + + /** + * timer id + * + * @var int + */ + protected static $_timerId = 0; + + /** + * timer status + * [ + * timer_id1 => bool, + * timer_id2 => bool, + * ...................., + * ] + * + * @var array + */ + protected static $_status = array(); + + /** + * Init. + * + * @param EventInterface $event + * @return void + */ + public static function init($event = null) + { + if ($event) { + self::$_event = $event; + return; + } + if (\function_exists('pcntl_signal')) { + \pcntl_signal(\SIGALRM, array('\Workerman\Lib\Timer', 'signalHandle'), false); + } + } + + /** + * ALARM signal handler. + * + * @return void + */ + public static function signalHandle() + { + if (!self::$_event) { + \pcntl_alarm(1); + self::tick(); + } + } + + /** + * Add a timer. + * + * @param float $time_interval + * @param callable $func + * @param mixed $args + * @param bool $persistent + * @return int|bool + */ + public static function add($time_interval, $func, $args = array(), $persistent = true) + { + if ($time_interval <= 0) { + Worker::safeEcho(new Exception("bad time_interval")); + return false; + } + + if ($args === null) { + $args = array(); + } + + if (self::$_event) { + return self::$_event->add($time_interval, + $persistent ? EventInterface::EV_TIMER : EventInterface::EV_TIMER_ONCE, $func, $args); + } + + // If not workerman runtime just return. + if (!Worker::getAllWorkers()) { + return; + } + + if (!\is_callable($func)) { + Worker::safeEcho(new Exception("not callable")); + return false; + } + + if (empty(self::$_tasks)) { + \pcntl_alarm(1); + } + + $run_time = \time() + $time_interval; + if (!isset(self::$_tasks[$run_time])) { + self::$_tasks[$run_time] = array(); + } + + self::$_timerId = self::$_timerId == \PHP_INT_MAX ? 1 : ++self::$_timerId; + self::$_status[self::$_timerId] = true; + self::$_tasks[$run_time][self::$_timerId] = array($func, (array)$args, $persistent, $time_interval); + + return self::$_timerId; + } + + + /** + * Tick. + * + * @return void + */ + public static function tick() + { + if (empty(self::$_tasks)) { + \pcntl_alarm(0); + return; + } + $time_now = \time(); + foreach (self::$_tasks as $run_time => $task_data) { + if ($time_now >= $run_time) { + foreach ($task_data as $index => $one_task) { + $task_func = $one_task[0]; + $task_args = $one_task[1]; + $persistent = $one_task[2]; + $time_interval = $one_task[3]; + try { + \call_user_func_array($task_func, $task_args); + } catch (\Exception $e) { + Worker::safeEcho($e); + } + if($persistent && !empty(self::$_status[$index])) { + $new_run_time = \time() + $time_interval; + if(!isset(self::$_tasks[$new_run_time])) self::$_tasks[$new_run_time] = array(); + self::$_tasks[$new_run_time][$index] = array($task_func, (array)$task_args, $persistent, $time_interval); + } + } + unset(self::$_tasks[$run_time]); + } + } + } + + /** + * Remove a timer. + * + * @param mixed $timer_id + * @return bool + */ + public static function del($timer_id) + { + if (self::$_event) { + return self::$_event->del($timer_id, EventInterface::EV_TIMER); + } + + foreach(self::$_tasks as $run_time => $task_data) + { + if(array_key_exists($timer_id, $task_data)) unset(self::$_tasks[$run_time][$timer_id]); + } + + if(array_key_exists($timer_id, self::$_status)) unset(self::$_status[$timer_id]); + + return true; + } + + /** + * Remove all timers. + * + * @return void + */ + public static function delAll() + { + self::$_tasks = self::$_status = array(); + if (\function_exists('pcntl_alarm')) { + \pcntl_alarm(0); + } + if (self::$_event) { + self::$_event->clearAllTimer(); + } + } +} diff --git a/vendor/workerman/workerman/Worker.php b/vendor/workerman/workerman/Worker.php new file mode 100644 index 0000000..a0f926d --- /dev/null +++ b/vendor/workerman/workerman/Worker.php @@ -0,0 +1,2663 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman; +require_once __DIR__ . '/Lib/Constants.php'; + +use Workerman\Events\EventInterface; +use Workerman\Connection\ConnectionInterface; +use Workerman\Connection\TcpConnection; +use Workerman\Connection\UdpConnection; +use Workerman\Lib\Timer; +use Workerman\Events\Select; +use \Exception; + +/** + * Worker class + * A container for listening ports + */ +#[\AllowDynamicProperties] +class Worker +{ + /** + * Version. + * + * @var string + */ + const VERSION = '4.1.10'; + + /** + * Status starting. + * + * @var int + */ + const STATUS_STARTING = 1; + + /** + * Status running. + * + * @var int + */ + const STATUS_RUNNING = 2; + + /** + * Status shutdown. + * + * @var int + */ + const STATUS_SHUTDOWN = 4; + + /** + * Status reloading. + * + * @var int + */ + const STATUS_RELOADING = 8; + + /** + * Default backlog. Backlog is the maximum length of the queue of pending connections. + * + * @var int + */ + const DEFAULT_BACKLOG = 102400; + + /** + * Max udp package size. + * + * @var int + */ + const MAX_UDP_PACKAGE_SIZE = 65535; + + /** + * The safe distance for columns adjacent + * + * @var int + */ + const UI_SAFE_LENGTH = 4; + + /** + * Worker id. + * + * @var int + */ + public $id = 0; + + /** + * Name of the worker processes. + * + * @var string + */ + public $name = 'none'; + + /** + * Number of worker processes. + * + * @var int + */ + public $count = 1; + + /** + * Unix user of processes, needs appropriate privileges (usually root). + * + * @var string + */ + public $user = ''; + + /** + * Unix group of processes, needs appropriate privileges (usually root). + * + * @var string + */ + public $group = ''; + + /** + * reloadable. + * + * @var bool + */ + public $reloadable = true; + + /** + * reuse port. + * + * @var bool + */ + public $reusePort = false; + + /** + * Emitted when worker processes start. + * + * @var callable + */ + public $onWorkerStart = null; + + /** + * Emitted when a socket connection is successfully established. + * + * @var callable + */ + public $onConnect = null; + + /** + * Emitted when data is received. + * + * @var callable + */ + public $onMessage = null; + + /** + * Emitted when the other end of the socket sends a FIN packet. + * + * @var callable + */ + public $onClose = null; + + /** + * Emitted when an error occurs with connection. + * + * @var callable + */ + public $onError = null; + + /** + * Emitted when the send buffer becomes full. + * + * @var callable + */ + public $onBufferFull = null; + + /** + * Emitted when the send buffer becomes empty. + * + * @var callable + */ + public $onBufferDrain = null; + + /** + * Emitted when worker processes stopped. + * + * @var callable + */ + public $onWorkerStop = null; + + /** + * Emitted when worker processes get reload signal. + * + * @var callable + */ + public $onWorkerReload = null; + + /** + * Emitted when worker processes exited. + * + * @var callable + */ + public $onWorkerExit = null; + + /** + * Transport layer protocol. + * + * @var string + */ + public $transport = 'tcp'; + + /** + * Store all connections of clients. + * + * @var array + */ + public $connections = array(); + + /** + * Application layer protocol. + * + * @var string + */ + public $protocol = null; + + /** + * Root path for autoload. + * + * @var string + */ + protected $_autoloadRootPath = ''; + + /** + * Pause accept new connections or not. + * + * @var bool + */ + protected $_pauseAccept = true; + + /** + * Is worker stopping ? + * @var bool + */ + public $stopping = false; + + /** + * Daemonize. + * + * @var bool + */ + public static $daemonize = false; + + /** + * Stdout file. + * + * @var string + */ + public static $stdoutFile = '/dev/null'; + + /** + * The file to store master process PID. + * + * @var string + */ + public static $pidFile = ''; + + /** + * The file used to store the master process status file. + * + * @var string + */ + public static $statusFile = ''; + + /** + * Log file. + * + * @var mixed + */ + public static $logFile = ''; + + /** + * Global event loop. + * + * @var EventInterface + */ + public static $globalEvent = null; + + /** + * Emitted when the master process get reload signal. + * + * @var callable + */ + public static $onMasterReload = null; + + /** + * Emitted when the master process terminated. + * + * @var callable + */ + public static $onMasterStop = null; + + /** + * EventLoopClass + * + * @var string + */ + public static $eventLoopClass = ''; + + /** + * Process title + * + * @var string + */ + public static $processTitle = 'WorkerMan'; + + /** + * After sending the stop command to the child process stopTimeout seconds, + * if the process is still living then forced to kill. + * + * @var int + */ + public static $stopTimeout = 2; + + /** + * The PID of master process. + * + * @var int + */ + protected static $_masterPid = 0; + + /** + * Listening socket. + * + * @var resource + */ + protected $_mainSocket = null; + + /** + * Socket name. The format is like this http://0.0.0.0:80 . + * + * @var string + */ + protected $_socketName = ''; + + /** parse from _socketName avoid parse again in master or worker + * LocalSocket The format is like tcp://0.0.0.0:8080 + * @var string + */ + + protected $_localSocket=null; + + /** + * Context of socket. + * + * @var resource + */ + protected $_context = null; + + /** + * All worker instances. + * + * @var Worker[] + */ + protected static $_workers = array(); + + /** + * All worker processes pid. + * The format is like this [worker_id=>[pid=>pid, pid=>pid, ..], ..] + * + * @var array + */ + protected static $_pidMap = array(); + + /** + * All worker processes waiting for restart. + * The format is like this [pid=>pid, pid=>pid]. + * + * @var array + */ + protected static $_pidsToRestart = array(); + + /** + * Mapping from PID to worker process ID. + * The format is like this [worker_id=>[0=>$pid, 1=>$pid, ..], ..]. + * + * @var array + */ + protected static $_idMap = array(); + + /** + * Current status. + * + * @var int + */ + protected static $_status = self::STATUS_STARTING; + + /** + * Maximum length of the worker names. + * + * @var int + */ + protected static $_maxWorkerNameLength = 12; + + /** + * Maximum length of the socket names. + * + * @var int + */ + protected static $_maxSocketNameLength = 12; + + /** + * Maximum length of the process user names. + * + * @var int + */ + protected static $_maxUserNameLength = 12; + + /** + * Maximum length of the Proto names. + * + * @var int + */ + protected static $_maxProtoNameLength = 4; + + /** + * Maximum length of the Processes names. + * + * @var int + */ + protected static $_maxProcessesNameLength = 9; + + /** + * Maximum length of the Status names. + * + * @var int + */ + protected static $_maxStatusNameLength = 1; + + /** + * The file to store status info of current worker process. + * + * @var string + */ + protected static $_statisticsFile = ''; + + /** + * Start file. + * + * @var string + */ + protected static $_startFile = ''; + + /** + * OS. + * + * @var string + */ + protected static $_OS = \OS_TYPE_LINUX; + + /** + * Processes for windows. + * + * @var array + */ + protected static $_processForWindows = array(); + + /** + * Status info of current worker process. + * + * @var array + */ + protected static $_globalStatistics = array( + 'start_timestamp' => 0, + 'worker_exit_info' => array() + ); + + /** + * Available event loops. + * + * @var array + */ + protected static $_availableEventLoops = array( + 'event' => '\Workerman\Events\Event', + 'libevent' => '\Workerman\Events\Libevent' + ); + + /** + * PHP built-in protocols. + * + * @var array + */ + protected static $_builtinTransports = array( + 'tcp' => 'tcp', + 'udp' => 'udp', + 'unix' => 'unix', + 'ssl' => 'tcp' + ); + + /** + * PHP built-in error types. + * + * @var array + */ + protected static $_errorType = array( + \E_ERROR => 'E_ERROR', // 1 + \E_WARNING => 'E_WARNING', // 2 + \E_PARSE => 'E_PARSE', // 4 + \E_NOTICE => 'E_NOTICE', // 8 + \E_CORE_ERROR => 'E_CORE_ERROR', // 16 + \E_CORE_WARNING => 'E_CORE_WARNING', // 32 + \E_COMPILE_ERROR => 'E_COMPILE_ERROR', // 64 + \E_COMPILE_WARNING => 'E_COMPILE_WARNING', // 128 + \E_USER_ERROR => 'E_USER_ERROR', // 256 + \E_USER_WARNING => 'E_USER_WARNING', // 512 + \E_USER_NOTICE => 'E_USER_NOTICE', // 1024 + \E_STRICT => 'E_STRICT', // 2048 + \E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR', // 4096 + \E_DEPRECATED => 'E_DEPRECATED', // 8192 + \E_USER_DEPRECATED => 'E_USER_DEPRECATED' // 16384 + ); + + /** + * Graceful stop or not. + * + * @var bool + */ + protected static $_gracefulStop = false; + + /** + * Standard output stream + * @var resource + */ + protected static $_outputStream = null; + + /** + * If $outputStream support decorated + * @var bool + */ + protected static $_outputDecorated = null; + + /** + * Run all worker instances. + * + * @return void + */ + public static function runAll() + { + static::checkSapiEnv(); + static::init(); + static::parseCommand(); + static::lock(); + static::daemonize(); + static::initWorkers(); + static::installSignal(); + static::saveMasterPid(); + static::lock(\LOCK_UN); + static::displayUI(); + static::forkWorkers(); + static::resetStd(); + static::monitorWorkers(); + } + + /** + * Check sapi. + * + * @return void + */ + protected static function checkSapiEnv() + { + // Only for cli. + if (\PHP_SAPI !== 'cli') { + exit("Only run in command line mode \n"); + } + if (\DIRECTORY_SEPARATOR === '\\') { + self::$_OS = \OS_TYPE_WINDOWS; + } + } + + /** + * Init. + * + * @return void + */ + protected static function init() + { + \set_error_handler(function($code, $msg, $file, $line){ + Worker::safeEcho("$msg in file $file on line $line\n"); + }); + + // Start file. + $backtrace = \debug_backtrace(); + static::$_startFile = $backtrace[\count($backtrace) - 1]['file']; + + + $unique_prefix = \str_replace('/', '_', static::$_startFile); + + // Pid file. + if (empty(static::$pidFile)) { + static::$pidFile = __DIR__ . "/../$unique_prefix.pid"; + } + + // Log file. + if (empty(static::$logFile)) { + static::$logFile = __DIR__ . '/../workerman.log'; + } + $log_file = (string)static::$logFile; + if (!\is_file($log_file)) { + \touch($log_file); + \chmod($log_file, 0622); + } + + // State. + static::$_status = static::STATUS_STARTING; + + // For statistics. + static::$_globalStatistics['start_timestamp'] = \time(); + + // Process title. + static::setProcessTitle(static::$processTitle . ': master process start_file=' . static::$_startFile); + + // Init data for worker id. + static::initId(); + + // Timer init. + Timer::init(); + } + + /** + * Lock. + * + * @return void + */ + protected static function lock($flag = \LOCK_EX) + { + static $fd; + if (\DIRECTORY_SEPARATOR !== '/') { + return; + } + $lock_file = static::$pidFile . '.lock'; + $fd = $fd ?: \fopen($lock_file, 'a+'); + if ($fd) { + flock($fd, $flag); + if ($flag === \LOCK_UN) { + fclose($fd); + $fd = null; + clearstatcache(); + if (\is_file($lock_file)) { + unlink($lock_file); + } + } + } + } + + /** + * Init All worker instances. + * + * @return void + */ + protected static function initWorkers() + { + if (static::$_OS !== \OS_TYPE_LINUX) { + return; + } + + static::$_statisticsFile = static::$statusFile ? static::$statusFile : __DIR__ . '/../workerman-' .posix_getpid().'.status'; + + foreach (static::$_workers as $worker) { + // Worker name. + if (empty($worker->name)) { + $worker->name = 'none'; + } + + // Get unix user of the worker process. + if (empty($worker->user)) { + $worker->user = static::getCurrentUser(); + } else { + if (\posix_getuid() !== 0 && $worker->user !== static::getCurrentUser()) { + static::log('Warning: You must have the root privileges to change uid and gid.'); + } + } + + // Socket name. + $worker->socket = $worker->getSocketName(); + + // Status name. + $worker->status = ' [OK] '; + + // Get column mapping for UI + foreach(static::getUiColumns() as $column_name => $prop){ + !isset($worker->{$prop}) && $worker->{$prop} = 'NNNN'; + $prop_length = \strlen((string) $worker->{$prop}); + $key = '_max' . \ucfirst(\strtolower($column_name)) . 'NameLength'; + static::$$key = \max(static::$$key, $prop_length); + } + + // Listen. + if (!$worker->reusePort) { + $worker->listen(); + } + } + } + + /** + * Reload all worker instances. + * + * @return void + */ + public static function reloadAllWorkers() + { + static::init(); + static::initWorkers(); + static::displayUI(); + static::$_status = static::STATUS_RELOADING; + } + + /** + * Get all worker instances. + * + * @return array + */ + public static function getAllWorkers() + { + return static::$_workers; + } + + /** + * Get global event-loop instance. + * + * @return EventInterface + */ + public static function getEventLoop() + { + return static::$globalEvent; + } + + /** + * Get main socket resource + * @return resource + */ + public function getMainSocket(){ + return $this->_mainSocket; + } + + /** + * Init idMap. + * return void + */ + protected static function initId() + { + foreach (static::$_workers as $worker_id => $worker) { + $new_id_map = array(); + $worker->count = $worker->count < 1 ? 1 : $worker->count; + for($key = 0; $key < $worker->count; $key++) { + $new_id_map[$key] = isset(static::$_idMap[$worker_id][$key]) ? static::$_idMap[$worker_id][$key] : 0; + } + static::$_idMap[$worker_id] = $new_id_map; + } + } + + /** + * Get unix user of current porcess. + * + * @return string + */ + protected static function getCurrentUser() + { + $user_info = \posix_getpwuid(\posix_getuid()); + return $user_info['name'] ?? 'unknown'; + } + + /** + * Display staring UI. + * + * @return void + */ + protected static function displayUI() + { + global $argv; + if (\in_array('-q', $argv)) { + return; + } + if (static::$_OS !== \OS_TYPE_LINUX) { + static::safeEcho("----------------------- WORKERMAN -----------------------------\r\n"); + static::safeEcho('Workerman version:'. static::VERSION. ' PHP version:'. \PHP_VERSION. "\r\n"); + static::safeEcho("------------------------ WORKERS -------------------------------\r\n"); + static::safeEcho("worker listen processes status\r\n"); + return; + } + + //show version + $line_version = 'Workerman version:' . static::VERSION . \str_pad('PHP version:', 22, ' ', \STR_PAD_LEFT) . \PHP_VERSION; + $line_version .= \str_pad('Event-Loop:', 22, ' ', \STR_PAD_LEFT) . static::getEventLoopName() . \PHP_EOL; + !\defined('LINE_VERSIOIN_LENGTH') && \define('LINE_VERSIOIN_LENGTH', \strlen($line_version)); + $total_length = static::getSingleLineTotalLength(); + $line_one = '' . \str_pad(' WORKERMAN ', $total_length + \strlen(''), '-', \STR_PAD_BOTH) . ''. \PHP_EOL; + $line_two = \str_pad(' WORKERS ' , $total_length + \strlen(''), '-', \STR_PAD_BOTH) . \PHP_EOL; + static::safeEcho($line_one . $line_version . $line_two); + + //Show title + $title = ''; + foreach(static::getUiColumns() as $column_name => $prop){ + $key = '_max' . \ucfirst(\strtolower($column_name)) . 'NameLength'; + //just keep compatible with listen name + $column_name === 'socket' && $column_name = 'listen'; + $title.= "{$column_name}" . \str_pad('', static::$$key + static::UI_SAFE_LENGTH - \strlen($column_name)); + } + $title && static::safeEcho($title . \PHP_EOL); + + //Show content + foreach (static::$_workers as $worker) { + $content = ''; + foreach(static::getUiColumns() as $column_name => $prop){ + $key = '_max' . \ucfirst(\strtolower($column_name)) . 'NameLength'; + \preg_match_all("/(|<\/n>||<\/w>||<\/g>)/is", (string) $worker->{$prop}, $matches); + $place_holder_length = !empty($matches) ? \strlen(\implode('', $matches[0])) : 0; + $content .= \str_pad((string) $worker->{$prop}, static::$$key + static::UI_SAFE_LENGTH + $place_holder_length); + } + $content && static::safeEcho($content . \PHP_EOL); + } + + //Show last line + $line_last = \str_pad('', static::getSingleLineTotalLength(), '-') . \PHP_EOL; + !empty($content) && static::safeEcho($line_last); + + if (static::$daemonize) { + $tmpArgv = $argv; + foreach ($tmpArgv as $index => $value) { + if ($value == '-d') { + unset($tmpArgv[$index]); + } elseif ($value == 'start' || $value == 'restart') { + $tmpArgv[$index] = 'stop'; + } + } + static::safeEcho("Input \"php ".implode(' ', $tmpArgv)."\" to stop. Start success.\n\n"); + } else { + static::safeEcho("Press Ctrl+C to stop. Start success.\n"); + } + } + + /** + * Get UI columns to be shown in terminal + * + * 1. $column_map: array('ui_column_name' => 'clas_property_name') + * 2. Consider move into configuration in future + * + * @return array + */ + public static function getUiColumns() + { + return array( + 'proto' => 'transport', + 'user' => 'user', + 'worker' => 'name', + 'socket' => 'socket', + 'processes' => 'count', + 'status' => 'status', + ); + } + + /** + * Get single line total length for ui + * + * @return int + */ + public static function getSingleLineTotalLength() + { + $total_length = 0; + + foreach(static::getUiColumns() as $column_name => $prop){ + $key = '_max' . \ucfirst(\strtolower($column_name)) . 'NameLength'; + $total_length += static::$$key + static::UI_SAFE_LENGTH; + } + + //keep beauty when show less colums + !\defined('LINE_VERSIOIN_LENGTH') && \define('LINE_VERSIOIN_LENGTH', 0); + $total_length <= LINE_VERSIOIN_LENGTH && $total_length = LINE_VERSIOIN_LENGTH; + + return $total_length; + } + + /** + * Parse command. + * + * @return void + */ + protected static function parseCommand() + { + if (static::$_OS !== \OS_TYPE_LINUX) { + return; + } + global $argv; + // Check argv; + $start_file = $argv[0]; + $usage = "Usage: php yourfile [mode]\nCommands: \nstart\t\tStart worker in DEBUG mode.\n\t\tUse mode -d to start in DAEMON mode.\nstop\t\tStop worker.\n\t\tUse mode -g to stop gracefully.\nrestart\t\tRestart workers.\n\t\tUse mode -d to start in DAEMON mode.\n\t\tUse mode -g to stop gracefully.\nreload\t\tReload codes.\n\t\tUse mode -g to reload gracefully.\nstatus\t\tGet worker status.\n\t\tUse mode -d to show live status.\nconnections\tGet worker connections.\n"; + $available_commands = array( + 'start', + 'stop', + 'restart', + 'reload', + 'status', + 'connections', + ); + $available_mode = array( + '-d', + '-g' + ); + $command = $mode = ''; + foreach ($argv as $value) { + if (\in_array($value, $available_commands)) { + $command = $value; + } elseif (\in_array($value, $available_mode)) { + $mode = $value; + } + } + + if (!$command) { + exit($usage); + } + + // Start command. + $mode_str = ''; + if ($command === 'start') { + if ($mode === '-d' || static::$daemonize) { + $mode_str = 'in DAEMON mode'; + } else { + $mode_str = 'in DEBUG mode'; + } + } + static::log("Workerman[$start_file] $command $mode_str"); + + // Get master process PID. + $master_pid = \is_file(static::$pidFile) ? (int)\file_get_contents(static::$pidFile) : 0; + // Master is still alive? + if (static::checkMasterIsAlive($master_pid)) { + if ($command === 'start') { + static::log("Workerman[$start_file] already running"); + exit; + } + } elseif ($command !== 'start' && $command !== 'restart') { + static::log("Workerman[$start_file] not run"); + exit; + } + + $statistics_file = static::$statusFile ? static::$statusFile : __DIR__ . "/../workerman-$master_pid.$command"; + + // execute command. + switch ($command) { + case 'start': + if ($mode === '-d') { + static::$daemonize = true; + } + break; + case 'status': + while (1) { + if (\is_file($statistics_file)) { + @\unlink($statistics_file); + } + // Master process will send SIGIOT signal to all child processes. + \posix_kill($master_pid, SIGIOT); + // Sleep 1 second. + \sleep(1); + // Clear terminal. + if ($mode === '-d') { + static::safeEcho("\33[H\33[2J\33(B\33[m", true); + } + // Echo status data. + static::safeEcho(static::formatStatusData($statistics_file)); + if ($mode !== '-d') { + exit(0); + } + static::safeEcho("\nPress Ctrl+C to quit.\n\n"); + } + exit(0); + case 'connections': + if (\is_file($statistics_file) && \is_writable($statistics_file)) { + \unlink($statistics_file); + } + // Master process will send SIGIO signal to all child processes. + \posix_kill($master_pid, SIGIO); + // Waiting amoment. + \usleep(500000); + // Display statisitcs data from a disk file. + if(\is_readable($statistics_file)) { + \readfile($statistics_file); + } + exit(0); + case 'restart': + case 'stop': + if ($mode === '-g') { + static::$_gracefulStop = true; + $sig = \SIGQUIT; + static::log("Workerman[$start_file] is gracefully stopping ..."); + } else { + static::$_gracefulStop = false; + $sig = \SIGINT; + static::log("Workerman[$start_file] is stopping ..."); + } + // Send stop signal to master process. + $master_pid && \posix_kill($master_pid, $sig); + // Timeout. + $timeout = static::$stopTimeout + 3; + $start_time = \time(); + // Check master process is still alive? + while (1) { + $master_is_alive = $master_pid && \posix_kill((int) $master_pid, 0); + if ($master_is_alive) { + // Timeout? + if (!static::$_gracefulStop && \time() - $start_time >= $timeout) { + static::log("Workerman[$start_file] stop fail"); + exit; + } + // Waiting amoment. + \usleep(10000); + continue; + } + // Stop success. + static::log("Workerman[$start_file] stop success"); + if ($command === 'stop') { + exit(0); + } + if ($mode === '-d') { + static::$daemonize = true; + } + break; + } + break; + case 'reload': + if($mode === '-g'){ + $sig = \SIGUSR2; + }else{ + $sig = \SIGUSR1; + } + \posix_kill($master_pid, $sig); + exit; + default : + if (isset($command)) { + static::safeEcho('Unknown command: ' . $command . "\n"); + } + exit($usage); + } + } + + /** + * Format status data. + * + * @param $statistics_file + * @return string + */ + protected static function formatStatusData($statistics_file) + { + static $total_request_cache = array(); + if (!\is_readable($statistics_file)) { + return ''; + } + $info = \file($statistics_file, \FILE_IGNORE_NEW_LINES); + if (!$info) { + return ''; + } + $status_str = ''; + $current_total_request = array(); + $worker_info = \unserialize($info[0]); + \ksort($worker_info, SORT_NUMERIC); + unset($info[0]); + $data_waiting_sort = array(); + $read_process_status = false; + $total_requests = 0; + $total_qps = 0; + $total_connections = 0; + $total_fails = 0; + $total_memory = 0; + $total_timers = 0; + $maxLen1 = static::$_maxSocketNameLength; + $maxLen2 = static::$_maxWorkerNameLength; + foreach($info as $key => $value) { + if (!$read_process_status) { + $status_str .= $value . "\n"; + if (\preg_match('/^pid.*?memory.*?listening/', $value)) { + $read_process_status = true; + } + continue; + } + if(\preg_match('/^[0-9]+/', $value, $pid_math)) { + $pid = $pid_math[0]; + $data_waiting_sort[$pid] = $value; + if(\preg_match('/^\S+?\s+?(\S+?)\s+?(\S+?)\s+?(\S+?)\s+?(\S+?)\s+?(\S+?)\s+?(\S+?)\s+?(\S+?)\s+?/', $value, $match)) { + $total_memory += \intval(\str_ireplace('M','',$match[1])); + $maxLen1 = \max($maxLen1,\strlen($match[2])); + $maxLen2 = \max($maxLen2,\strlen($match[3])); + $total_connections += \intval($match[4]); + $total_fails += \intval($match[5]); + $total_timers += \intval($match[6]); + $current_total_request[$pid] = $match[7]; + $total_requests += \intval($match[7]); + } + } + } + foreach($worker_info as $pid => $info) { + if (!isset($data_waiting_sort[$pid])) { + $status_str .= "$pid\t" . \str_pad('N/A', 7) . " " + . \str_pad($info['listen'], static::$_maxSocketNameLength) . " " + . \str_pad($info['name'], static::$_maxWorkerNameLength) . " " + . \str_pad('N/A', 11) . " " . \str_pad('N/A', 9) . " " + . \str_pad('N/A', 7) . " " . \str_pad('N/A', 13) . " N/A [busy] \n"; + continue; + } + //$qps = isset($total_request_cache[$pid]) ? $current_total_request[$pid] + if (!isset($total_request_cache[$pid]) || !isset($current_total_request[$pid])) { + $qps = 0; + } else { + $qps = $current_total_request[$pid] - $total_request_cache[$pid]; + $total_qps += $qps; + } + $status_str .= $data_waiting_sort[$pid]. " " . \str_pad($qps, 6) ." [idle]\n"; + } + $total_request_cache = $current_total_request; + $status_str .= "----------------------------------------------PROCESS STATUS---------------------------------------------------\n"; + $status_str .= "Summary\t" . \str_pad($total_memory.'M', 7) . " " + . \str_pad('-', $maxLen1) . " " + . \str_pad('-', $maxLen2) . " " + . \str_pad($total_connections, 11) . " " . \str_pad($total_fails, 9) . " " + . \str_pad($total_timers, 7) . " " . \str_pad($total_requests, 13) . " " + . \str_pad($total_qps,6)." [Summary] \n"; + return $status_str; + } + + + /** + * Install signal handler. + * + * @return void + */ + protected static function installSignal() + { + if (static::$_OS !== \OS_TYPE_LINUX) { + return; + } + $signalHandler = '\Workerman\Worker::signalHandler'; + // stop + \pcntl_signal(\SIGINT, $signalHandler, false); + // stop + \pcntl_signal(\SIGTERM, $signalHandler, false); + // stop + \pcntl_signal(\SIGHUP, $signalHandler, false); + // stop + \pcntl_signal(\SIGTSTP, $signalHandler, false); + // graceful stop + \pcntl_signal(\SIGQUIT, $signalHandler, false); + // reload + \pcntl_signal(\SIGUSR1, $signalHandler, false); + // graceful reload + \pcntl_signal(\SIGUSR2, $signalHandler, false); + // status + \pcntl_signal(\SIGIOT, $signalHandler, false); + // connection status + \pcntl_signal(\SIGIO, $signalHandler, false); + // ignore + \pcntl_signal(\SIGPIPE, \SIG_IGN, false); + } + + /** + * Reinstall signal handler. + * + * @return void + */ + protected static function reinstallSignal() + { + if (static::$_OS !== \OS_TYPE_LINUX) { + return; + } + $signalHandler = '\Workerman\Worker::signalHandler'; + // uninstall stop signal handler + \pcntl_signal(\SIGINT, \SIG_IGN, false); + // uninstall stop signal handler + \pcntl_signal(\SIGTERM, \SIG_IGN, false); + // uninstall stop signal handler + \pcntl_signal(\SIGHUP, \SIG_IGN, false); + // uninstall stop signal handler + \pcntl_signal(\SIGTSTP, \SIG_IGN, false); + // uninstall graceful stop signal handler + \pcntl_signal(\SIGQUIT, \SIG_IGN, false); + // uninstall reload signal handler + \pcntl_signal(\SIGUSR1, \SIG_IGN, false); + // uninstall graceful reload signal handler + \pcntl_signal(\SIGUSR2, \SIG_IGN, false); + // uninstall status signal handler + \pcntl_signal(\SIGIOT, \SIG_IGN, false); + // uninstall connections status signal handler + \pcntl_signal(\SIGIO, \SIG_IGN, false); + // reinstall stop signal handler + static::$globalEvent->add(\SIGINT, EventInterface::EV_SIGNAL, $signalHandler); + // reinstall graceful stop signal handler + static::$globalEvent->add(\SIGQUIT, EventInterface::EV_SIGNAL, $signalHandler); + // reinstall graceful stop signal handler + static::$globalEvent->add(\SIGHUP, EventInterface::EV_SIGNAL, $signalHandler); + // reinstall graceful stop signal handler + static::$globalEvent->add(\SIGTSTP, EventInterface::EV_SIGNAL, $signalHandler); + // reinstall reload signal handler + static::$globalEvent->add(\SIGUSR1, EventInterface::EV_SIGNAL, $signalHandler); + // reinstall graceful reload signal handler + static::$globalEvent->add(\SIGUSR2, EventInterface::EV_SIGNAL, $signalHandler); + // reinstall status signal handler + static::$globalEvent->add(\SIGIOT, EventInterface::EV_SIGNAL, $signalHandler); + // reinstall connection status signal handler + static::$globalEvent->add(\SIGIO, EventInterface::EV_SIGNAL, $signalHandler); + } + + /** + * Signal handler. + * + * @param int $signal + */ + public static function signalHandler($signal) + { + switch ($signal) { + // Stop. + case \SIGINT: + case \SIGTERM: + case \SIGHUP: + case \SIGTSTP: + static::$_gracefulStop = false; + static::stopAll(); + break; + // Graceful stop. + case \SIGQUIT: + static::$_gracefulStop = true; + static::stopAll(); + break; + // Reload. + case \SIGUSR2: + case \SIGUSR1: + if (static::$_status === static::STATUS_SHUTDOWN || static::$_status === static::STATUS_RELOADING) { + return; + } + static::$_gracefulStop = $signal === \SIGUSR2; + static::$_pidsToRestart = static::getAllWorkerPids(); + static::reload(); + break; + // Show status. + case \SIGIOT: + static::writeStatisticsToStatusFile(); + break; + // Show connection status. + case \SIGIO: + static::writeConnectionsStatisticsToStatusFile(); + break; + } + } + + /** + * Run as daemon mode. + * + * @throws Exception + */ + protected static function daemonize() + { + if (!static::$daemonize || static::$_OS !== \OS_TYPE_LINUX) { + return; + } + \umask(0); + $pid = \pcntl_fork(); + if (-1 === $pid) { + throw new Exception('Fork fail'); + } elseif ($pid > 0) { + exit(0); + } + if (-1 === \posix_setsid()) { + throw new Exception("Setsid fail"); + } + // Fork again avoid SVR4 system regain the control of terminal. + $pid = \pcntl_fork(); + if (-1 === $pid) { + throw new Exception("Fork fail"); + } elseif (0 !== $pid) { + exit(0); + } + } + + /** + * Redirect standard input and output. + * + * @throws Exception + */ + public static function resetStd() + { + if (!static::$daemonize || \DIRECTORY_SEPARATOR !== '/') { + return; + } + global $STDOUT, $STDERR; + $handle = \fopen(static::$stdoutFile, "a"); + if ($handle) { + unset($handle); + \set_error_handler(function(){}); + if ($STDOUT) { + \fclose($STDOUT); + } + if ($STDERR) { + \fclose($STDERR); + } + if (\is_resource(\STDOUT)) { + \fclose(\STDOUT); + } + if (\is_resource(\STDERR)) { + \fclose(\STDERR); + } + $STDOUT = \fopen(static::$stdoutFile, "a"); + $STDERR = \fopen(static::$stdoutFile, "a"); + // Fix standard output cannot redirect of PHP 8.1.8's bug + if (\function_exists('posix_isatty') && \posix_isatty(2)) { + \ob_start(function ($string) { + \file_put_contents(static::$stdoutFile, $string, FILE_APPEND); + }, 1); + } + // change output stream + static::$_outputStream = null; + static::outputStream($STDOUT); + \restore_error_handler(); + return; + } + + throw new Exception('Can not open stdoutFile ' . static::$stdoutFile); + } + + /** + * Save pid. + * + * @throws Exception + */ + protected static function saveMasterPid() + { + if (static::$_OS !== \OS_TYPE_LINUX) { + return; + } + + static::$_masterPid = \posix_getpid(); + if (false === \file_put_contents(static::$pidFile, static::$_masterPid)) { + throw new Exception('can not save pid to ' . static::$pidFile); + } + } + + /** + * Get event loop name. + * + * @return string + */ + protected static function getEventLoopName() + { + if (static::$eventLoopClass) { + return static::$eventLoopClass; + } + + if (!\class_exists('\Swoole\Event', false)) { + unset(static::$_availableEventLoops['swoole']); + } + + $loop_name = ''; + foreach (static::$_availableEventLoops as $name=>$class) { + if (\extension_loaded($name)) { + $loop_name = $name; + break; + } + } + + if ($loop_name) { + static::$eventLoopClass = static::$_availableEventLoops[$loop_name]; + } else { + static::$eventLoopClass = '\Workerman\Events\Select'; + } + return static::$eventLoopClass; + } + + /** + * Get all pids of worker processes. + * + * @return array + */ + protected static function getAllWorkerPids() + { + $pid_array = array(); + foreach (static::$_pidMap as $worker_pid_array) { + foreach ($worker_pid_array as $worker_pid) { + $pid_array[$worker_pid] = $worker_pid; + } + } + return $pid_array; + } + + /** + * Fork some worker processes. + * + * @return void + */ + protected static function forkWorkers() + { + if (static::$_OS === \OS_TYPE_LINUX) { + static::forkWorkersForLinux(); + } else { + static::forkWorkersForWindows(); + } + } + + /** + * Fork some worker processes. + * + * @return void + */ + protected static function forkWorkersForLinux() + { + + foreach (static::$_workers as $worker) { + if (static::$_status === static::STATUS_STARTING) { + if (empty($worker->name)) { + $worker->name = $worker->getSocketName(); + } + $worker_name_length = \strlen($worker->name); + if (static::$_maxWorkerNameLength < $worker_name_length) { + static::$_maxWorkerNameLength = $worker_name_length; + } + } + + while (\count(static::$_pidMap[$worker->workerId]) < $worker->count) { + static::forkOneWorkerForLinux($worker); + } + } + } + + /** + * Fork some worker processes. + * + * @return void + */ + protected static function forkWorkersForWindows() + { + $files = static::getStartFilesForWindows(); + global $argv; + if(\in_array('-q', $argv) || \count($files) === 1) + { + if(\count(static::$_workers) > 1) + { + static::safeEcho("@@@ Error: multi workers init in one php file are not support @@@\r\n"); + static::safeEcho("@@@ See http://doc.workerman.net/faq/multi-woker-for-windows.html @@@\r\n"); + } + elseif(\count(static::$_workers) <= 0) + { + exit("@@@no worker inited@@@\r\n\r\n"); + } + + \reset(static::$_workers); + /** @var Worker $worker */ + $worker = current(static::$_workers); + + \Workerman\Timer::delAll(); + + //Update process state. + static::$_status = static::STATUS_RUNNING; + + // Register shutdown function for checking errors. + \register_shutdown_function([__CLASS__, 'checkErrors']); + + // Create a global event loop. + if (!static::$globalEvent) { + $eventLoopClass = static::getEventLoopName(); + static::$globalEvent = new $eventLoopClass; + } + + // Reinstall signal. + static::reinstallSignal(); + + // Init Timer. + Timer::init(static::$globalEvent); + + \restore_error_handler(); + + // Display UI. + static::safeEcho(\str_pad($worker->name, 21) . \str_pad($worker->getSocketName(), 36) . \str_pad('1', 10) . "[ok]\n"); + $worker->listen(); + $worker->run(); + static::$globalEvent->loop(); + if (static::$_status !== self::STATUS_SHUTDOWN) { + $err = new Exception('event-loop exited'); + static::log($err); + exit(250); + } + exit(0); + } + else + { + static::$globalEvent = new \Workerman\Events\Select(); + Timer::init(static::$globalEvent); + foreach($files as $start_file) + { + static::forkOneWorkerForWindows($start_file); + } + } + } + + /** + * Get start files for windows. + * + * @return array + */ + public static function getStartFilesForWindows() { + global $argv; + $files = array(); + foreach($argv as $file) + { + if(\is_file($file)) + { + $files[$file] = $file; + } + } + return $files; + } + + /** + * Fork one worker process. + * + * @param string $start_file + */ + public static function forkOneWorkerForWindows($start_file) + { + $start_file = \realpath($start_file); + + $descriptorspec = array( + STDIN, STDOUT, STDOUT + ); + + $pipes = array(); + $process = \proc_open("php \"$start_file\" -q", $descriptorspec, $pipes); + + if (empty(static::$globalEvent)) { + static::$globalEvent = new Select(); + Timer::init(static::$globalEvent); + } + + // 保存子进程句柄 + static::$_processForWindows[$start_file] = array($process, $start_file); + } + + /** + * check worker status for windows. + * @return void + */ + public static function checkWorkerStatusForWindows() + { + foreach(static::$_processForWindows as $process_data) + { + $process = $process_data[0]; + $start_file = $process_data[1]; + $status = \proc_get_status($process); + if(isset($status['running'])) + { + if(!$status['running']) + { + static::safeEcho("process $start_file terminated and try to restart\n"); + \proc_close($process); + static::forkOneWorkerForWindows($start_file); + } + } + else + { + static::safeEcho("proc_get_status fail\n"); + } + } + } + + + /** + * Fork one worker process. + * + * @param self $worker + * @throws Exception + */ + protected static function forkOneWorkerForLinux(self $worker) + { + // Get available worker id. + $id = static::getId($worker->workerId, 0); + if ($id === false) { + return; + } + $pid = \pcntl_fork(); + // For master process. + if ($pid > 0) { + static::$_pidMap[$worker->workerId][$pid] = $pid; + static::$_idMap[$worker->workerId][$id] = $pid; + } // For child processes. + elseif (0 === $pid) { + \srand(); + \mt_srand(); + static::$_gracefulStop = false; + if (static::$_status === static::STATUS_STARTING) { + static::resetStd(); + } + static::$_pidMap = array(); + // Remove other listener. + foreach(static::$_workers as $key => $one_worker) { + if ($one_worker->workerId !== $worker->workerId) { + $one_worker->unlisten(); + unset(static::$_workers[$key]); + } + } + Timer::delAll(); + //Update process state. + static::$_status = static::STATUS_RUNNING; + + // Register shutdown function for checking errors. + \register_shutdown_function(array("\\Workerman\\Worker", 'checkErrors')); + + // Create a global event loop. + if (!static::$globalEvent) { + $event_loop_class = static::getEventLoopName(); + static::$globalEvent = new $event_loop_class; + } + + // Reinstall signal. + static::reinstallSignal(); + + // Init Timer. + Timer::init(static::$globalEvent); + + \restore_error_handler(); + + static::setProcessTitle(self::$processTitle . ': worker process ' . $worker->name . ' ' . $worker->getSocketName()); + $worker->setUserAndGroup(); + $worker->id = $id; + $worker->run(); + // Main loop. + static::$globalEvent->loop(); + if (strpos(static::$eventLoopClass, 'Workerman\Events\Swoole') !== false) { + exit(0); + } + $err = new Exception('event-loop exited'); + static::log($err); + exit(250); + } else { + throw new Exception("forkOneWorker fail"); + } + } + + /** + * Get worker id. + * + * @param string $worker_id + * @param int $pid + * + * @return integer + */ + protected static function getId($worker_id, $pid) + { + return \array_search($pid, static::$_idMap[$worker_id]); + } + + /** + * Set unix user and group for current process. + * + * @return void + */ + public function setUserAndGroup() + { + // Get uid. + $user_info = \posix_getpwnam($this->user); + if (!$user_info) { + static::log("Warning: User {$this->user} not exists"); + return; + } + $uid = $user_info['uid']; + // Get gid. + if ($this->group) { + $group_info = \posix_getgrnam($this->group); + if (!$group_info) { + static::log("Warning: Group {$this->group} not exists"); + return; + } + $gid = $group_info['gid']; + } else { + $gid = $user_info['gid']; + } + + // Set uid and gid. + if ($uid !== \posix_getuid() || $gid !== \posix_getgid()) { + if (!\posix_setgid($gid) || !\posix_initgroups($user_info['name'], $gid) || !\posix_setuid($uid)) { + static::log("Warning: change gid or uid fail."); + } + } + } + + /** + * Set process name. + * + * @param string $title + * @return void + */ + protected static function setProcessTitle($title) + { + \set_error_handler(function(){}); + // >=php 5.5 + if (\function_exists('cli_set_process_title')) { + \cli_set_process_title($title); + } // Need proctitle when php<=5.5 . + elseif (\extension_loaded('proctitle') && \function_exists('setproctitle')) { + \setproctitle($title); + } + \restore_error_handler(); + } + + /** + * Monitor all child processes. + * + * @return void + */ + protected static function monitorWorkers() + { + if (static::$_OS === \OS_TYPE_LINUX) { + static::monitorWorkersForLinux(); + } else { + static::monitorWorkersForWindows(); + } + } + + /** + * Monitor all child processes. + * + * @return void + */ + protected static function monitorWorkersForLinux() + { + static::$_status = static::STATUS_RUNNING; + while (1) { + // Calls signal handlers for pending signals. + \pcntl_signal_dispatch(); + // Suspends execution of the current process until a child has exited, or until a signal is delivered + $status = 0; + $pid = \pcntl_wait($status, \WUNTRACED); + // Calls signal handlers for pending signals again. + \pcntl_signal_dispatch(); + // If a child has already exited. + if ($pid > 0) { + // Find out which worker process exited. + foreach (static::$_pidMap as $worker_id => $worker_pid_array) { + if (isset($worker_pid_array[$pid])) { + $worker = static::$_workers[$worker_id]; + // Fix exit with status 2 for php8.2 + if ($status === \SIGINT && static::$_status === static::STATUS_SHUTDOWN) { + $status = 0; + } + // Exit status. + if ($status !== 0) { + static::log("worker[{$worker->name}:$pid] exit with status $status"); + } + + // onWorkerExit + if ($worker->onWorkerExit) { + try { + ($worker->onWorkerExit)($worker, $status, $pid); + } catch (\Throwable $exception) { + static::log("worker[{$worker->name}] onWorkerExit $exception"); + } + } + + // For Statistics. + if (!isset(static::$_globalStatistics['worker_exit_info'][$worker_id][$status])) { + static::$_globalStatistics['worker_exit_info'][$worker_id][$status] = 0; + } + ++static::$_globalStatistics['worker_exit_info'][$worker_id][$status]; + + // Clear process data. + unset(static::$_pidMap[$worker_id][$pid]); + + // Mark id is available. + $id = static::getId($worker_id, $pid); + static::$_idMap[$worker_id][$id] = 0; + + break; + } + } + // Is still running state then fork a new worker process. + if (static::$_status !== static::STATUS_SHUTDOWN) { + static::forkWorkers(); + // If reloading continue. + if (isset(static::$_pidsToRestart[$pid])) { + unset(static::$_pidsToRestart[$pid]); + static::reload(); + } + } + } + + // If shutdown state and all child processes exited then master process exit. + if (static::$_status === static::STATUS_SHUTDOWN && !static::getAllWorkerPids()) { + static::exitAndClearAll(); + } + } + } + + /** + * Monitor all child processes. + * + * @return void + */ + protected static function monitorWorkersForWindows() + { + Timer::add(1, "\\Workerman\\Worker::checkWorkerStatusForWindows"); + + static::$globalEvent->loop(); + } + + /** + * Exit current process. + * + * @return void + */ + protected static function exitAndClearAll() + { + foreach (static::$_workers as $worker) { + $socket_name = $worker->getSocketName(); + if ($worker->transport === 'unix' && $socket_name) { + list(, $address) = \explode(':', $socket_name, 2); + $address = substr($address, strpos($address, '/') + 2); + @\unlink($address); + } + } + @\unlink(static::$pidFile); + static::log("Workerman[" . \basename(static::$_startFile) . "] has been stopped"); + if (static::$onMasterStop) { + \call_user_func(static::$onMasterStop); + } + exit(0); + } + + /** + * Execute reload. + * + * @return void + */ + protected static function reload() + { + // For master process. + if (static::$_masterPid === \posix_getpid()) { + if (static::$_gracefulStop) { + $sig = \SIGUSR2; + } else { + $sig = \SIGUSR1; + } + // Set reloading state. + if (static::$_status !== static::STATUS_RELOADING && static::$_status !== static::STATUS_SHUTDOWN) { + static::log("Workerman[" . \basename(static::$_startFile) . "] reloading"); + static::$_status = static::STATUS_RELOADING; + // Try to emit onMasterReload callback. + if (static::$onMasterReload) { + try { + \call_user_func(static::$onMasterReload); + } catch (\Exception $e) { + static::stopAll(250, $e); + } catch (\Error $e) { + static::stopAll(250, $e); + } + static::initId(); + } + + // Send reload signal to all child processes. + $reloadable_pid_array = array(); + foreach (static::$_pidMap as $worker_id => $worker_pid_array) { + $worker = static::$_workers[$worker_id]; + if ($worker->reloadable) { + foreach ($worker_pid_array as $pid) { + $reloadable_pid_array[$pid] = $pid; + } + } else { + foreach ($worker_pid_array as $pid) { + // Send reload signal to a worker process which reloadable is false. + \posix_kill($pid, $sig); + } + } + } + + // Get all pids that are waiting reload. + static::$_pidsToRestart = \array_intersect(static::$_pidsToRestart, $reloadable_pid_array); + + } + + // Reload complete. + if (empty(static::$_pidsToRestart)) { + if (static::$_status !== static::STATUS_SHUTDOWN) { + static::$_status = static::STATUS_RUNNING; + } + return; + } + // Continue reload. + $one_worker_pid = \current(static::$_pidsToRestart); + // Send reload signal to a worker process. + \posix_kill($one_worker_pid, $sig); + // If the process does not exit after static::$stopTimeout seconds try to kill it. + if(!static::$_gracefulStop){ + Timer::add(static::$stopTimeout, '\posix_kill', array($one_worker_pid, \SIGKILL), false); + } + } // For child processes. + else { + \reset(static::$_workers); + $worker = \current(static::$_workers); + // Try to emit onWorkerReload callback. + if ($worker->onWorkerReload) { + try { + \call_user_func($worker->onWorkerReload, $worker); + } catch (\Exception $e) { + static::stopAll(250, $e); + } catch (\Error $e) { + static::stopAll(250, $e); + } + } + + if ($worker->reloadable) { + static::stopAll(); + } + } + } + + /** + * Stop all. + * + * @param int $code + * @param string $log + */ + public static function stopAll($code = 0, $log = '') + { + if ($log) { + static::log($log); + } + + static::$_status = static::STATUS_SHUTDOWN; + // For master process. + if (\DIRECTORY_SEPARATOR === '/' && static::$_masterPid === \posix_getpid()) { + static::log("Workerman[" . \basename(static::$_startFile) . "] stopping ..."); + $worker_pid_array = static::getAllWorkerPids(); + // Send stop signal to all child processes. + if (static::$_gracefulStop) { + $sig = \SIGQUIT; + } else { + $sig = \SIGINT; + } + foreach ($worker_pid_array as $worker_pid) { + if (static::$daemonize) { + \posix_kill($worker_pid, $sig); + } else { + Timer::add(1, '\posix_kill', array($worker_pid, $sig), false); + } + if(!static::$_gracefulStop){ + Timer::add(static::$stopTimeout, '\posix_kill', array($worker_pid, \SIGKILL), false); + } + } + Timer::add(1, "\\Workerman\\Worker::checkIfChildRunning"); + // Remove statistics file. + if (\is_file(static::$_statisticsFile)) { + @\unlink(static::$_statisticsFile); + } + } // For child processes. + else { + // Execute exit. + $workers = array_reverse(static::$_workers); + foreach ($workers as $worker) { + if(!$worker->stopping){ + $worker->stop(); + $worker->stopping = true; + } + } + if (!static::$_gracefulStop || ConnectionInterface::$statistics['connection_count'] <= 0) { + static::$_workers = array(); + if (static::$globalEvent) { + static::$globalEvent->destroy(); + } + + try { + exit($code); + } catch (Exception $e) { + + } + } + } + } + + /** + * check if child processes is really running + */ + public static function checkIfChildRunning() + { + foreach (static::$_pidMap as $worker_id => $worker_pid_array) { + foreach ($worker_pid_array as $pid => $worker_pid) { + if (!\posix_kill($pid, 0)) { + unset(static::$_pidMap[$worker_id][$pid]); + } + } + } + } + + /** + * Get process status. + * + * @return number + */ + public static function getStatus() + { + return static::$_status; + } + + /** + * If stop gracefully. + * + * @return bool + */ + public static function getGracefulStop() + { + return static::$_gracefulStop; + } + + /** + * Write statistics data to disk. + * + * @return void + */ + protected static function writeStatisticsToStatusFile() + { + // For master process. + if (static::$_masterPid === \posix_getpid()) { + $all_worker_info = array(); + foreach(static::$_pidMap as $worker_id => $pid_array) { + /** @var /Workerman/Worker $worker */ + $worker = static::$_workers[$worker_id]; + foreach($pid_array as $pid) { + $all_worker_info[$pid] = array('name' => $worker->name, 'listen' => $worker->getSocketName()); + } + } + + \file_put_contents(static::$_statisticsFile, \serialize($all_worker_info)."\n", \FILE_APPEND); + $loadavg = \function_exists('sys_getloadavg') ? \array_map('round', \sys_getloadavg(), array(2,2,2)) : array('-', '-', '-'); + \file_put_contents(static::$_statisticsFile, + "----------------------------------------------GLOBAL STATUS----------------------------------------------------\n", \FILE_APPEND); + \file_put_contents(static::$_statisticsFile, + 'Workerman version:' . static::VERSION . " PHP version:" . \PHP_VERSION . "\n", \FILE_APPEND); + \file_put_contents(static::$_statisticsFile, 'start time:' . \date('Y-m-d H:i:s', + static::$_globalStatistics['start_timestamp']) . ' run ' . \floor((\time() - static::$_globalStatistics['start_timestamp']) / (24 * 60 * 60)) . ' days ' . \floor(((\time() - static::$_globalStatistics['start_timestamp']) % (24 * 60 * 60)) / (60 * 60)) . " hours \n", + FILE_APPEND); + $load_str = 'load average: ' . \implode(", ", $loadavg); + \file_put_contents(static::$_statisticsFile, + \str_pad($load_str, 33) . 'event-loop:' . static::getEventLoopName() . "\n", \FILE_APPEND); + \file_put_contents(static::$_statisticsFile, + \count(static::$_pidMap) . ' workers ' . \count(static::getAllWorkerPids()) . " processes\n", + \FILE_APPEND); + \file_put_contents(static::$_statisticsFile, + \str_pad('worker_name', static::$_maxWorkerNameLength) . " exit_status exit_count\n", \FILE_APPEND); + foreach (static::$_pidMap as $worker_id => $worker_pid_array) { + $worker = static::$_workers[$worker_id]; + if (isset(static::$_globalStatistics['worker_exit_info'][$worker_id])) { + foreach (static::$_globalStatistics['worker_exit_info'][$worker_id] as $worker_exit_status => $worker_exit_count) { + \file_put_contents(static::$_statisticsFile, + \str_pad($worker->name, static::$_maxWorkerNameLength) . " " . \str_pad($worker_exit_status, + 16) . " $worker_exit_count\n", \FILE_APPEND); + } + } else { + \file_put_contents(static::$_statisticsFile, + \str_pad($worker->name, static::$_maxWorkerNameLength) . " " . \str_pad(0, 16) . " 0\n", + \FILE_APPEND); + } + } + \file_put_contents(static::$_statisticsFile, + "----------------------------------------------PROCESS STATUS---------------------------------------------------\n", + \FILE_APPEND); + \file_put_contents(static::$_statisticsFile, + "pid\tmemory " . \str_pad('listening', static::$_maxSocketNameLength) . " " . \str_pad('worker_name', + static::$_maxWorkerNameLength) . " connections " . \str_pad('send_fail', 9) . " " + . \str_pad('timers', 8) . \str_pad('total_request', 13) ." qps status\n", \FILE_APPEND); + + \chmod(static::$_statisticsFile, 0722); + + foreach (static::getAllWorkerPids() as $worker_pid) { + \posix_kill($worker_pid, \SIGIOT); + } + return; + } + + // For child processes. + \gc_collect_cycles(); + if (\function_exists('gc_mem_caches')) { + \gc_mem_caches(); + } + \reset(static::$_workers); + /** @var \Workerman\Worker $worker */ + $worker = current(static::$_workers); + $worker_status_str = \posix_getpid() . "\t" . \str_pad(round(memory_get_usage(false) / (1024 * 1024), 2) . "M", 7) + . " " . \str_pad($worker->getSocketName(), static::$_maxSocketNameLength) . " " + . \str_pad(($worker->name === $worker->getSocketName() ? 'none' : $worker->name), static::$_maxWorkerNameLength) + . " "; + $worker_status_str .= \str_pad(ConnectionInterface::$statistics['connection_count'], 11) + . " " . \str_pad(ConnectionInterface::$statistics['send_fail'], 9) + . " " . \str_pad(static::$globalEvent->getTimerCount(), 7) + . " " . \str_pad(ConnectionInterface::$statistics['total_request'], 13) . "\n"; + \file_put_contents(static::$_statisticsFile, $worker_status_str, \FILE_APPEND); + } + + /** + * Write statistics data to disk. + * + * @return void + */ + protected static function writeConnectionsStatisticsToStatusFile() + { + // For master process. + if (static::$_masterPid === \posix_getpid()) { + \file_put_contents(static::$_statisticsFile, "--------------------------------------------------------------------- WORKERMAN CONNECTION STATUS --------------------------------------------------------------------------------\n", \FILE_APPEND); + \file_put_contents(static::$_statisticsFile, "PID Worker CID Trans Protocol ipv4 ipv6 Recv-Q Send-Q Bytes-R Bytes-W Status Local Address Foreign Address\n", \FILE_APPEND); + \chmod(static::$_statisticsFile, 0722); + foreach (static::getAllWorkerPids() as $worker_pid) { + \posix_kill($worker_pid, \SIGIO); + } + return; + } + + // For child processes. + $bytes_format = function($bytes) + { + if($bytes > 1024*1024*1024*1024) { + return round($bytes/(1024*1024*1024*1024), 1)."TB"; + } + if($bytes > 1024*1024*1024) { + return round($bytes/(1024*1024*1024), 1)."GB"; + } + if($bytes > 1024*1024) { + return round($bytes/(1024*1024), 1)."MB"; + } + if($bytes > 1024) { + return round($bytes/(1024), 1)."KB"; + } + return $bytes."B"; + }; + + $pid = \posix_getpid(); + $str = ''; + \reset(static::$_workers); + $current_worker = current(static::$_workers); + $default_worker_name = $current_worker->name; + + /** @var \Workerman\Worker $worker */ + foreach(TcpConnection::$connections as $connection) { + /** @var \Workerman\Connection\TcpConnection $connection */ + $transport = $connection->transport; + $ipv4 = $connection->isIpV4() ? ' 1' : ' 0'; + $ipv6 = $connection->isIpV6() ? ' 1' : ' 0'; + $recv_q = $bytes_format($connection->getRecvBufferQueueSize()); + $send_q = $bytes_format($connection->getSendBufferQueueSize()); + $local_address = \trim($connection->getLocalAddress()); + $remote_address = \trim($connection->getRemoteAddress()); + $state = $connection->getStatus(false); + $bytes_read = $bytes_format($connection->bytesRead); + $bytes_written = $bytes_format($connection->bytesWritten); + $id = $connection->id; + $protocol = $connection->protocol ? $connection->protocol : $connection->transport; + $pos = \strrpos($protocol, '\\'); + if ($pos) { + $protocol = \substr($protocol, $pos+1); + } + if (\strlen($protocol) > 15) { + $protocol = \substr($protocol, 0, 13) . '..'; + } + $worker_name = isset($connection->worker) ? $connection->worker->name : $default_worker_name; + if (\strlen($worker_name) > 14) { + $worker_name = \substr($worker_name, 0, 12) . '..'; + } + $str .= \str_pad($pid, 9) . \str_pad($worker_name, 16) . \str_pad($id, 10) . \str_pad($transport, 8) + . \str_pad($protocol, 16) . \str_pad($ipv4, 7) . \str_pad($ipv6, 7) . \str_pad($recv_q, 13) + . \str_pad($send_q, 13) . \str_pad($bytes_read, 13) . \str_pad($bytes_written, 13) . ' ' + . \str_pad($state, 14) . ' ' . \str_pad($local_address, 22) . ' ' . \str_pad($remote_address, 22) ."\n"; + } + if ($str) { + \file_put_contents(static::$_statisticsFile, $str, \FILE_APPEND); + } + } + + /** + * Check errors when current process exited. + * + * @return void + */ + public static function checkErrors() + { + if (static::STATUS_SHUTDOWN !== static::$_status) { + $error_msg = static::$_OS === \OS_TYPE_LINUX ? 'Worker['. \posix_getpid() .'] process terminated' : 'Worker process terminated'; + $errors = error_get_last(); + if ($errors && ($errors['type'] === \E_ERROR || + $errors['type'] === \E_PARSE || + $errors['type'] === \E_CORE_ERROR || + $errors['type'] === \E_COMPILE_ERROR || + $errors['type'] === \E_RECOVERABLE_ERROR) + ) { + $error_msg .= ' with ERROR: ' . static::getErrorType($errors['type']) . " \"{$errors['message']} in {$errors['file']} on line {$errors['line']}\""; + } + static::log($error_msg); + } + } + + /** + * Get error message by error code. + * + * @param integer $type + * @return string + */ + protected static function getErrorType($type) + { + if(isset(self::$_errorType[$type])) { + return self::$_errorType[$type]; + } + + return ''; + } + + /** + * Log. + * + * @param string $msg + * @return void + */ + public static function log($msg) + { + $msg = $msg . "\n"; + if (!static::$daemonize) { + static::safeEcho($msg); + } + \file_put_contents((string)static::$logFile, \date('Y-m-d H:i:s') . ' ' . 'pid:' + . (static::$_OS === \OS_TYPE_LINUX ? \posix_getpid() : 1) . ' ' . $msg, \FILE_APPEND | \LOCK_EX); + } + + /** + * Safe Echo. + * @param string $msg + * @param bool $decorated + * @return bool + */ + public static function safeEcho($msg, $decorated = false) + { + $stream = static::outputStream(); + if (!$stream) { + return false; + } + if (!$decorated) { + $line = $white = $green = $end = ''; + if (static::$_outputDecorated) { + $line = "\033[1A\n\033[K"; + $white = "\033[47;30m"; + $green = "\033[32;40m"; + $end = "\033[0m"; + } + $msg = \str_replace(array('', '', ''), array($line, $white, $green), $msg); + $msg = \str_replace(array('', '', ''), $end, $msg); + } elseif (!static::$_outputDecorated) { + return false; + } + \fwrite($stream, $msg); + \fflush($stream); + return true; + } + + /** + * @param resource|null $stream + * @return bool|resource + */ + private static function outputStream($stream = null) + { + if (!$stream) { + $stream = static::$_outputStream ? static::$_outputStream : \STDOUT; + } + if (!$stream || !\is_resource($stream) || 'stream' !== \get_resource_type($stream)) { + return false; + } + $stat = \fstat($stream); + if (!$stat) { + return false; + } + if (($stat['mode'] & 0170000) === 0100000) { + // file + static::$_outputDecorated = false; + } else { + static::$_outputDecorated = + static::$_OS === \OS_TYPE_LINUX && + \function_exists('posix_isatty') && + \posix_isatty($stream); + } + return static::$_outputStream = $stream; + } + + /** + * Construct. + * + * @param string $socket_name + * @param array $context_option + */ + public function __construct($socket_name = '', array $context_option = array()) + { + // Save all worker instances. + $this->workerId = \spl_object_hash($this); + static::$_workers[$this->workerId] = $this; + static::$_pidMap[$this->workerId] = array(); + + // Get autoload root path. + $backtrace = \debug_backtrace(); + $this->_autoloadRootPath = \dirname($backtrace[0]['file']); + Autoloader::setRootPath($this->_autoloadRootPath); + + // Context for socket. + if ($socket_name) { + $this->_socketName = $socket_name; + if (!isset($context_option['socket']['backlog'])) { + $context_option['socket']['backlog'] = static::DEFAULT_BACKLOG; + } + $this->_context = \stream_context_create($context_option); + } + + // Turn reusePort on. + /*if (static::$_OS === \OS_TYPE_LINUX // if linux + && \version_compare(\PHP_VERSION,'7.0.0', 'ge') // if php >= 7.0.0 + && \version_compare(php_uname('r'), '3.9', 'ge') // if kernel >=3.9 + && \strtolower(\php_uname('s')) !== 'darwin' // if not Mac OS + && strpos($socket_name,'unix') !== 0) { // if not unix socket + + $this->reusePort = true; + }*/ + } + + + /** + * Listen. + * + * @throws Exception + */ + public function listen() + { + if (!$this->_socketName) { + return; + } + + // Autoload. + Autoloader::setRootPath($this->_autoloadRootPath); + + if (!$this->_mainSocket) { + + $local_socket = $this->parseSocketAddress(); + + // Flag. + $flags = $this->transport === 'udp' ? \STREAM_SERVER_BIND : \STREAM_SERVER_BIND | \STREAM_SERVER_LISTEN; + $errno = 0; + $errmsg = ''; + // SO_REUSEPORT. + if ($this->reusePort) { + \stream_context_set_option($this->_context, 'socket', 'so_reuseport', 1); + } + + // Create an Internet or Unix domain server socket. + $this->_mainSocket = \stream_socket_server($local_socket, $errno, $errmsg, $flags, $this->_context); + if (!$this->_mainSocket) { + throw new Exception($errmsg); + } + + if ($this->transport === 'ssl') { + \stream_socket_enable_crypto($this->_mainSocket, false); + } elseif ($this->transport === 'unix') { + $socket_file = \substr($local_socket, 7); + if ($this->user) { + \chown($socket_file, $this->user); + } + if ($this->group) { + \chgrp($socket_file, $this->group); + } + } + + // Try to open keepalive for tcp and disable Nagle algorithm. + if (\function_exists('socket_import_stream') && static::$_builtinTransports[$this->transport] === 'tcp') { + \set_error_handler(function(){}); + $socket = \socket_import_stream($this->_mainSocket); + \socket_set_option($socket, \SOL_SOCKET, \SO_KEEPALIVE, 1); + \socket_set_option($socket, \SOL_TCP, \TCP_NODELAY, 1); + \restore_error_handler(); + } + + // Non blocking. + \stream_set_blocking($this->_mainSocket, false); + } + + $this->resumeAccept(); + } + + /** + * Unlisten. + * + * @return void + */ + public function unlisten() { + $this->pauseAccept(); + if ($this->_mainSocket) { + \set_error_handler(function(){}); + \fclose($this->_mainSocket); + \restore_error_handler(); + $this->_mainSocket = null; + } + } + + /** + * Parse local socket address. + * + * @throws Exception + */ + protected function parseSocketAddress() { + if (!$this->_socketName) { + return; + } + // Get the application layer communication protocol and listening address. + list($scheme, $address) = \explode(':', $this->_socketName, 2); + // Check application layer protocol class. + if (!isset(static::$_builtinTransports[$scheme])) { + $scheme = \ucfirst($scheme); + $this->protocol = \substr($scheme,0,1)==='\\' ? $scheme : 'Protocols\\' . $scheme; + if (!\class_exists($this->protocol)) { + $this->protocol = "Workerman\\Protocols\\$scheme"; + if (!\class_exists($this->protocol)) { + throw new Exception("class \\Protocols\\$scheme not exist"); + } + } + + if (!isset(static::$_builtinTransports[$this->transport])) { + throw new Exception('Bad worker->transport ' . \var_export($this->transport, true)); + } + } else { + $this->transport = $scheme; + } + //local socket + return static::$_builtinTransports[$this->transport] . ":" . $address; + } + + /** + * Pause accept new connections. + * + * @return void + */ + public function pauseAccept() + { + if (static::$globalEvent && false === $this->_pauseAccept && $this->_mainSocket) { + static::$globalEvent->del($this->_mainSocket, EventInterface::EV_READ); + $this->_pauseAccept = true; + } + } + + /** + * Resume accept new connections. + * + * @return void + */ + public function resumeAccept() + { + // Register a listener to be notified when server socket is ready to read. + if (static::$globalEvent && true === $this->_pauseAccept && $this->_mainSocket) { + if ($this->transport !== 'udp') { + static::$globalEvent->add($this->_mainSocket, EventInterface::EV_READ, array($this, 'acceptConnection')); + } else { + static::$globalEvent->add($this->_mainSocket, EventInterface::EV_READ, array($this, 'acceptUdpConnection')); + } + $this->_pauseAccept = false; + } + } + + /** + * Get socket name. + * + * @return string + */ + public function getSocketName() + { + return $this->_socketName ? \lcfirst($this->_socketName) : 'none'; + } + + /** + * Run worker instance. + * + * @return void + * @throws Exception + */ + public function run() + { + $this->listen(); + + // Try to emit onWorkerStart callback. + if ($this->onWorkerStart) { + try { + \call_user_func($this->onWorkerStart, $this); + } catch (\Exception $e) { + // Avoid rapid infinite loop exit. + sleep(1); + static::stopAll(250, $e); + } catch (\Error $e) { + // Avoid rapid infinite loop exit. + sleep(1); + static::stopAll(250, $e); + } + } + } + + /** + * Stop current worker instance. + * + * @return void + */ + public function stop() + { + // Try to emit onWorkerStop callback. + if ($this->onWorkerStop) { + try { + \call_user_func($this->onWorkerStop, $this); + } catch (\Exception $e) { + static::stopAll(250, $e); + } catch (\Error $e) { + static::stopAll(250, $e); + } + } + // Remove listener for server socket. + $this->unlisten(); + // Close all connections for the worker. + if (!static::$_gracefulStop) { + foreach ($this->connections as $connection) { + $connection->close(); + } + } + // Remove worker. + foreach(static::$_workers as $key => $one_worker) { + if ($one_worker->workerId === $this->workerId) { + unset(static::$_workers[$key]); + } + } + + // Clear callback. + $this->onMessage = $this->onClose = $this->onError = $this->onBufferDrain = $this->onBufferFull = null; + } + + /** + * Accept a connection. + * + * @param resource $socket + * @return void + */ + public function acceptConnection($socket) + { + // Accept a connection on server socket. + \set_error_handler(function(){}); + $new_socket = \stream_socket_accept($socket, 0, $remote_address); + \restore_error_handler(); + + // Thundering herd. + if (!$new_socket) { + return; + } + + // TcpConnection. + $connection = new TcpConnection($new_socket, $remote_address); + $this->connections[$connection->id] = $connection; + $connection->worker = $this; + $connection->protocol = $this->protocol; + $connection->transport = $this->transport; + $connection->onMessage = $this->onMessage; + $connection->onClose = $this->onClose; + $connection->onError = $this->onError; + $connection->onBufferDrain = $this->onBufferDrain; + $connection->onBufferFull = $this->onBufferFull; + + // Try to emit onConnect callback. + if ($this->onConnect) { + try { + \call_user_func($this->onConnect, $connection); + } catch (\Exception $e) { + static::stopAll(250, $e); + } catch (\Error $e) { + static::stopAll(250, $e); + } + } + } + + /** + * For udp package. + * + * @param resource $socket + * @return bool + */ + public function acceptUdpConnection($socket) + { + \set_error_handler(function(){}); + $recv_buffer = \stream_socket_recvfrom($socket, static::MAX_UDP_PACKAGE_SIZE, 0, $remote_address); + \restore_error_handler(); + if (false === $recv_buffer || empty($remote_address)) { + return false; + } + // UdpConnection. + $connection = new UdpConnection($socket, $remote_address); + $connection->protocol = $this->protocol; + if ($this->onMessage) { + try { + if ($this->protocol !== null) { + /** @var \Workerman\Protocols\ProtocolInterface $parser */ + $parser = $this->protocol; + if ($parser && \method_exists($parser, 'input')) { + while ($recv_buffer !== '') { + $len = $parser::input($recv_buffer, $connection); + if ($len === 0) + return true; + $package = \substr($recv_buffer, 0, $len); + $recv_buffer = \substr($recv_buffer, $len); + $data = $parser::decode($package, $connection); + if ($data === false) + continue; + \call_user_func($this->onMessage, $connection, $data); + } + } else { + $data = $parser::decode($recv_buffer, $connection); + // Discard bad packets. + if ($data === false) + return true; + \call_user_func($this->onMessage, $connection, $data); + } + } else { + \call_user_func($this->onMessage, $connection, $recv_buffer); + } + ++ConnectionInterface::$statistics['total_request']; + } catch (\Exception $e) { + static::stopAll(250, $e); + } catch (\Error $e) { + static::stopAll(250, $e); + } + } + return true; + } + + /** + * Check master process is alive + * + * @param int $master_pid + * @return bool + */ + protected static function checkMasterIsAlive($master_pid) + { + if (empty($master_pid)) { + return false; + } + + $master_is_alive = $master_pid && \posix_kill((int) $master_pid, 0) && \posix_getpid() !== $master_pid; + if (!$master_is_alive) { + return false; + } + + $cmdline = "/proc/{$master_pid}/cmdline"; + if (!is_readable($cmdline) || empty(static::$processTitle)) { + return true; + } + + $content = file_get_contents($cmdline); + if (empty($content)) { + return true; + } + + return stripos($content, static::$processTitle) !== false || stripos($content, 'php') !== false; + } +} +